Python高级用法之分布式锁

1,数据一致性

当多个进程/线程对同一个共享资源读写,会因为资源的争夺而出现混乱,导致数据不一致。

如下图:

在数据库的原始数据是 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 对象来处理。

  • 多机多进程部署的情况,就得依赖一个第三方组件(存储锁对象)来实现一个分布式的同步锁了。

2,分布式锁实现方式

目前主流的分布式锁实现方式有以下几种:

  • 基于数据库来实现,如 mysql

  • 基于缓存来实现,如 redis

  • 基于 zookeeper 来实现

下面我们简单介绍下这几种锁的实现。

2.1,基于数据库的锁:

基于数据库的锁实现也有两种方式,一是基于数据库表,另一种是基于数据库排他锁。

基于数据库表的增删:

基于数据库表增删是最简单的方式,首先创建一张锁的表主要包含下列字段:方法名,时间戳等字段。

具体使用的方法,当需要锁住某个方法时,往该表中插入一条相关的记录。这边需要注意,方法名是有唯一性约束的,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。执行完毕,需要delete该记录。

对于上述方案可以进行优化,如应用主从数据库,数据之间双向同步。一旦挂掉快速切换到备库上;做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍;使用while循环,直到insert成功再返回成功,虽然并不推荐这样做;还可以记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了,实现可重入锁。

数据库的排他锁:

基于MySql的InnoDB引擎,可以使用以下方法来实现加锁操作。

在查询语句后面增加for update,数据库会在查询过程中给数据库表增加排他锁。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。其他没有获取到锁的就会阻塞在上述select语句上,可能的结果有2种,在超时之前获取到了锁,在超时之前仍未获取到锁。

获得排它锁的线程即可获得分布式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,释放锁 connection.commit() 。

存在的问题主要是性能不高和sql超时的异常。

2.2,基于zookeeper实现

基于zookeeper临时有序节点可以实现的分布式锁。每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

提供的第三方库有 curator ,具体使用读者可以自行去看一下。Curator提供的InterProcessMutex是分布式锁的实现。acquire方法获取锁,release方法释放锁。另外,锁释放、阻塞锁、可重入锁等问题都可以有有效解决。讲下阻塞锁的实现,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是就获取到锁,便可以执行业务逻辑。

最后,Zookeeper实现的分布式锁其实存在一个缺点,那就是性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。并发问题,可能存在网络抖动,客户端和ZK集群的session连接断了,zk集群以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。

用法参考:https://yunjianfei.iteye.com/blog/2164888

2.3,基于缓存redis实现

相对于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点,存取速度快很多。而且很多缓存是可以集群部署的,可以解决单点问题。

使用redis的SETNX实现分布式锁,多个进程执行以下Redis命令:

SETNX lock.id <current Unix time + lock timeout + 1>
  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值