如果上图的 Merge 是单线程操作,数据库里面的结果是正确的,但是如果变成了多线程,即有多个线程同时对上图的 Machine 数据进行读写操作,是必然会出现数据不一致问题的,如下图所示:
假设某台机器(图中的 machine )在数据库的原始数据是 d0,上图的处理流程如下:
t1 时刻,有两个数据源的数据 d1,d2 分别到达数据处理层,主进程分配线程 Merge1 处理 d1,Merge2 处理 d2,两者又同时(假设还是 t1 )从数据库获取原始数据 d0
t2 时刻,Merge1 合并完 d0 和 d1 的数据,并将合并后的数据存到数据库,数据库的数据变成 d0 + d1
t3 时刻,Merge2 合并完 d0 和 d2 的数据,并将合并后的数据存到数据库,数据库的数据变成 d0 + d2
t1 到 t3,数据库最终的数据变成了 d0 + d2,数据源 d1 的数据消失,出现数据不一致问题。
方案探索
上面所列的问题,是由于多线程同时对某一个共享数据进行读写导致,我们只要找到一种方案,使得对共享数据的访问是同步的,即可解决该问题。当有某个线程或者进程已经访问了该数据,其他进程或者线程就必须等待其访问结束,才可拥有该共享数据的访问权(进入临界区)。最简单的方式,就是加个同步锁。
锁的实现方式,按照应用的实现架构,可能会有以下几种类型:
如果处理程序是单进程多线程的,在 python下,就可以使用 threading 模块的 Lock 对象来限制对共享变量的同步访问,实现线程安全。
单机多进程的情况,在 python 下,可以使用 multiprocessing 的 Lock 对象来处理。
多机多进程部署的情况,就得依赖一个第三方组件(存储锁对象)来实现一个分布式的同步锁了。
CMDB 系统目前是多机多进程多线程的处理机制,所以符合第三种方式。
分布式锁实现方式
目前主流的分布式锁实现方式有以下几种:
基于数据库来实现,如 mysql
基于缓存来实现,如 redis
基于 zookeeper 来实现
每种实现方式各有千秋,综合考量,我们最终决定使用 redis,主要原因是:
redis 是基于内存来操作,存取速度比数据库快,在高并发下,加锁之后的性能不会下降太多
redis 可以设置键值的生存时间(TTL)
redis 的使用方式简单,总体实现开销小
假设某台机器(图中的 machine )在数据库的原始数据是 d0,上图的处理流程如下:
t1 时刻,有两个数据源的数据 d1,d2 分别到达数据处理层,主进程分配线程 Merge1 处理 d1,Merge2 处理 d2,两者又同时(假设还是 t1 )从数据库获取原始数据 d0
t2 时刻,Merge1 合并完 d0 和 d1 的数据,并将合并后的数据存到数据库,数据库的数据变成 d0 + d1
t3 时刻,Merge2 合并完 d0 和 d2 的数据,并将合并后的数据存到数据库,数据库的数据变成 d0 + d2
t1 到 t3,数据库最终的数据变成了 d0 + d2,数据源 d1 的数据消失,出现数据不一致问题。
方案探索
上面所列的问题,是由于多线程同时对某一个共享数据进行读写导致,我们只要找到一种方案,使得对共享数据的访问是同步的,即可解决该问题。当有某个线程或者进程已经访问了该数据,其他进程或者线程就必须等待其访问结束,才可拥有该共享数据的访问权(进入临界区)。最简单的方式,就是加个同步锁。
锁的实现方式,按照应用的实现架构,可能会有以下几种类型:
如果处理程序是单进程多线程的,在 python下,就可以使用 threading 模块的 Lock 对象来限制对共享变量的同步访问,实现线程安全。
单机多进程的情况,在 python 下,可以使用 multiprocessing 的 Lock 对象来处理。
多机多进程部署的情况,就得依赖一个第三方组件(存储锁对象)来实现一个分布式的同步锁了。
CMDB 系统目前是多机多进程多线程的处理机制,所以符合第三种方式。
分布式锁实现方式
目前主流的分布式锁实现方式有以下几种:
基于数据库来实现,如 mysql
基于缓存来实现,如 redis
基于 zookeeper 来实现
每种实现方式各有千秋,综合考量,我们最终决定使用 redis,主要原因是:
redis 是基于内存来操作,存取速度比数据库快,在高并发下,加锁之后的性能不会下降太多
redis 可以设置键值的生存时间(TTL)
redis 的使用方式简单,总体实现开销小