分布式锁方案

Redis原子操作保证并发安全:
  • 多个操作在redis中实现成一个操作,即单命令操作
  • 把多个操作写到一个lua脚本,以原子性方式执行单个lua脚本
单个节点实现分布式锁:setnx key value:当且仅当key不存在时,set成功并返回结果为1,如果key存在,什么都不做返回0 expire key timeout:给key设置超时时间,单位是s。超过这个时间锁会自动释放,避免死锁 delete key:删除key
风险及处理:
  • setnx加锁后,如果操作数据发生了异常,导致一直没有执行最后的del命令,会导致锁被当前客户端一直持有,其他客户端无法拿到锁,所以要给锁设置一个过期时间,过期后就要自动删除,保证不出现无法加锁的问题
  • 如果客户端A执行setnx加锁,客户端B执行del释放锁,会导致客户端A的加锁被误释放。如果有其他客户端在申请加锁,就可以获取到锁并操作数据,导致A和C同时操作数据,产生错误。所以要对锁设置客户端的唯一标识,释放锁时保证不误释放
  • 利用lua脚本实现释放锁操作过程,因为释放锁的操作包括了读取锁变量、判断值、删除锁变量多个操作,redis执行lua脚本,可以以原子性方式执行,从而保证锁释放操作的原子性。
lua脚本处理重入锁:
  • 加锁:判断锁是否存在,不存在获得锁并记录重入层数。存在说明有人获得锁,判断是不是自己的锁,是自己的重入数+1,不是就获取失败
  • 解锁:判断锁是否存在,存在就判断是不是自己的锁,是就重入数-1,直到重入数为0,去删除key。不是自己的锁或锁不存在旧直接返回。
  用单个redis实例保存锁变量,如果该实例宕机,锁变量就没有了,就算有主从集群模式,master宕机,由于主从复制是异步的,加锁操作命令还未到slave,此时主从切换,新master节点依旧会丢失该锁。因此分布式锁实现时,还需要考虑可靠性,基于多个redis节点实现分布式锁。
Redlock算法:让客户端和多个独立的redis实例依次请求加锁,如果客户端能够和半数以上的实例成功完成加锁,就认为客户端获得锁,这样锁变量会在多个实例存在,单个实例宕机不影响分布式锁。执行步骤:
1、客户端获取当前时间
2、客户端按顺序依次向N个redis实例执行加锁操作,同样是在每个实例执行setnx命令,并加过期时间,和客户端唯一标识,还有给加锁操作设置超时时间,防止向一个实例请求加锁时,没拿到锁一直等待。通过加锁超时时间,超时后,客户端会和下一个实例继续请求加锁。加锁操作的超时时间要远远小于锁的有效时间,一般就是几十毫秒。
3、一旦客户端完成和所有redis实例的加锁操作,客户端要计算整个加锁过程的耗时;客户端认为加锁成功的条件:从超过半数的实例上成功获取到锁、获取锁的总耗时没有超过锁的有效时间;计算结果是锁的最初有效时间-客户端获取锁的总耗时,如果锁的有效时间来不及完成共享数据的操作,就释放锁,避免出现没完成数据操作,锁就过期的情况。如果加锁未成功,那么客户端向所有实例发起释放锁操作,也是在每个节点执行lua脚本就行。
风险:释放锁需要向所有节点发送请求,即使某个节点的加锁请求时失败的。因为如果某个节点的加锁请求时成功了,执行了set操作,但是返回给客户端响应包丢失,客户端认定加锁失败,实际redis认为加锁成功了,所以在释放锁时,也要对加锁失败的节点发起请求,使用Redlock要避免机器时钟发送跳跃。否则可能导致Redlock失效。假如3个节点,A操作2个加锁成功,1个节点时钟跳跃,锁提前过期。线程B操作另外2个节点加锁成功了,此时Redlock就失效了。
Redission:


Zookeeper实现
 
简单模式:某节点尝试创建临时znode,创建成功代表获取了锁,这个时候其他客户端来创建锁会失败,只能注册监听器监听这个锁,释放锁就是删除这个znode,一旦释放掉就通知客户端,然后有一个等待着的客户端就可以再次重新加锁。
临时顺序节点:一把锁被多个人竞争,第一个拿到锁的人执行加锁,然后释放锁。后面每个人都会去监听排在自己前面的那个人创建的node,一旦某个人释放了锁,排在后面的人会被zk通知,被通知后后面一个人就可以获取到锁。
Curator框架实现分布式锁过程:

多个客户端争取一个zk锁,原理都类似:
  • 创建锁节点下的下一个临时顺序节点
  • 当自己的节点不是第一个节点就对上一节点加监听器
  • 上一个节点释放锁,自己节点向前排队
采用临时顺序节点的原因是:某个客户端创建临时顺序节点后,宕机了,zk感知到客户端宕机会自动删除对应临时顺序节点,保证自动释放锁。
zk分布式锁的羊群效应:临时节点不带顺序会造成羊群效应
  • 如果几十个客户端同时争取一个锁,此时会导致任务一个客户端释放锁时,zk要反向通知几十个客户端,几十个客户端又要发送请求到zk去尝试创建锁,会对zk服务造成很大压力。利用临时顺序节点,做到加锁请求排队,每次只通知下一个节点,降低请求量。
  • 遇到类似脑裂对分布式锁的影响:
  • 客户端与zk服务之间网络异常,导致zk以为客户端已死,删除了客户端创建的临时节点。但是客户端任务自己加锁成功仍在执行逻辑,此时其他客户端发现没有锁了去创建了锁,也开始执行。导致分布式锁失效,造成问题。
posted @ 2025-04-16 18:18  难得  阅读(35)  评论(0)    收藏  举报