从悲观锁、乐观锁到分布式锁

从悲观锁、乐观锁到分布式锁

技术杂谈小彩虹2021-07-11 19:17:41100A+A-

从悲观锁、乐观锁到分布式锁

前言

我们在设计商品秒杀模块时为了防止“库存”超卖的情况,我们常常会使用一个锁的机制,解决多线程下数据一致性问题,但是在分布式集群下单节点的锁往往是无法满足业务的需求,本篇博客从悲观锁、乐观锁开始,逐步介绍分布式数据一致性的利器——分布式锁。

正文

悲观锁与乐观锁

悲观锁与乐观锁的差异:

  • 悲观锁:一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放sychronized提供的是悲观锁
  • 乐观锁:一段执行逻辑加上乐观锁,不同线程同时执行时,可以同时进入执行,在最后更新数据的时候要检查这些数据是否被其他线程修改了(版本和执行初是否相同),没有修改则进行更新,否则放弃本次操作.

常见实现悲观锁与乐观锁的方案:

Java

  • 悲观锁:synchronizedReentrantLock
  • 乐观锁:java.util.concurrent.atomic原子变量

Mysql

  • 悲观锁:for update
  • 乐观锁:时间戳或者version版本号

Redis

  • 悲观锁:无
  • 乐观锁:使用watch监控对象变化实现乐观锁

Zookeeper

  • 悲观锁:无
  • 乐观锁:使用version版本号实现乐观锁

分布式锁

有的时候,我们需要保证一个方法在同 一时间内只能被同一个线程执行。在单机模式下,可以通过sychronized、锁等方式来实现。

分布式锁的三个动作:

  • 加锁
  • 解锁
  • 锁过期

实现分布式锁的解决方案

数据库锁方案

  • 通过一个一张表的一条记录,来判断资源的占用情况
  • 使用基于数据库的排它锁 (即select * from tb_User for update
  • 使用乐观锁的方式,即CAS操作(或version字段)

Redis方案

Redis分布式锁的三种行为:

  • 加锁:使用setnx来抢夺锁,将锁的标识符设置为1,表示锁已被占用。
  • 解锁:使用setnx来释放锁,将锁的标识符设置为0,表示锁已被释放。
  • 锁过期:用 expire 给锁加一个过期时间防止锁忘记了释放,expire时间过期将返回0。

setnxexpire都是原子操作,实际应用中使用lua脚本来确保操作的原子性。

Redis某些极端情况下会导致数据不一致的情况:

情况一:master宕机

  1. redis cluster集群环境下,假如现在A客户端想要加锁,它会根据路由规则选择一台master节点写入key mylock,在加锁成功后,master节点会把key异步复制给对应的slave节点。
  2. 如果此时redis master节点宕机,为保证集群可用性,会进行主备切换,slave变为了redis master。B客户端在新的master节点上加锁成功,而A客户端也以为自己还是成功加了锁的。
  3. 此时就会导致同一时间内多个客户端对一个分布式锁完成了加锁,导致各种脏数据的产生。

情况二:锁过期了,业务还没做完

某种特殊的场景导致业务处理比较慢,但是锁过期了,数据被其他线程修改,导致脏数据的产生。

情况三:长时间获取不到锁,导致事务超时

事务超时用于控制事务执行的超时,执行时间是事务内所有代码执行总和,单位为秒。默认值为-1表示永不超时,如果部分业务设置了超时时间,此时如果长时间获取不到锁,就会导致事务超时自动回滚。

情况四:B的锁被A给释放了

模拟场景:

  • A、B两个线程来尝试给key myLock加锁,A线程先拿到锁(假如锁3秒后过期),B线程就在等待尝试获取锁。
  • 那如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时A线程的锁自动释放(删除key),B线程检测到myLock这个key不存在,执行
    SETNX命令也拿到了锁。
  • 但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导致B线程的锁被A线程给释放了。

解决方案:每个线程加锁时要带上自己独有的value值来标识,只释放指定valuekey,否则就会出现释放锁混乱的场景。

Redisson方案

Redisson是一个企业级的开源Redis Client,也提供了分布式锁的支持:

  • Redisson所有指令都通过lua脚本执行,而lua脚本支持原子性执行;
  • 假设Redisson设置一个key的默认过期时间为30s,Redisson中的“看门狗”机制,它会在你获取锁之后,每隔10秒帮你把key的超时时间设为30s这样的话,就算一直持有锁也不会出现key过期了,其他线程获取到锁的问题了;
  • Redisson的“看门狗”逻辑保证了没有死锁发生:如果机器宕机了,看门狗也就没了。此时就不会延长key的过期时间,到了30s之后就会自动过期了,其他线程可以获取到锁;
    在这里插入图片描述

RedLock方案

RedLock是多节点redis实现的分布式锁算法,它可以解决redis哨兵模式下异步的主从复制下master宕机时数据不一致导致锁的互斥失效。

RedLock的算法的特点:

  • 同步RedLock由于每个redis的时间流速相差不多,所以RedLock可以认为是同步算法;
  • 互斥:任何时刻只能有一个client获取锁;
  • 释放死锁:即使锁定资源的服务崩溃或者分区,仍然能释放锁;
  • 容错性:只要多数redis节点(一半以上)在使用,client就可以获取和释放锁;

假设有5个完全独立的redis主服务器:

  1. 获取当前时间戳;
  2. client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁;
  3. client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功;
  4. 如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL是5s,获取所有锁用了2s,再减去时钟漂移(但实际上可以忽略),则真正锁有效时间为3s;
  5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁;

TTL: redis key 的过期时间或有效生存时间

时钟漂移:指两个电脑间时间流速基本相同的情况下,两个电脑(或两个进程间)时间的差值;如果电脑距离过远会造成时钟漂移值 过大
在这里插入图片描述

Zookeeper方案

Zookeeper分布式锁的特点:

  • 基于zookeeper临时有序节点可以实现的分布式锁。大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。
  • 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。
  • Curator是一个zookeeper的开源客户端,也提供了分布式锁的实现;

使用zookeeper实现分布式锁的算法流程,大致如下:

  1. 如果锁空间的根节点不存在,首先创建Znode根节点。这里假设为“/test/lock”。这个根节点,代表了一把分布式锁。
  2. 客户端如果需要占用锁,则在“/test/lock”下创建临时的且有序的子节点。
  3. 客户端如果需要占用锁,还需要判断,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点。如果是则认为获得锁,否则监听前一个Znode子节点变更消息,获得子节点变更通知后重复此步骤直至获得锁;
  4. 获取锁后,开始处理业务流程。完成业务流程后,删除对应的子节点,完成释放锁的工作。以便后面的节点获得分布式锁。

Redis分布式锁和Zookeeper分布式锁的区别

  • redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能;
  • zookeeper分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小;
  • redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁;
  • redis选择虽然使用了Raft算法但是它本身采用的是异步复制,所以它的数据并不是强一致性,某些极端情况下会导致数据不一致的情况,但是可以采用Redisson方案、RedLock方案减少这些极端情况带来的影响;
  • zookeeper使用了基于Paxos算法的zab协议保证了数据的强一致性;

在这里插入图片描述

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们