Redis分布式锁奥义

分布式锁
  • 分布式系统进行逻辑处理的时候,经常会遇到并发问题,例如直播场景中,用户需要连麦主播,当多个用户在同一个时刻一起连麦时候,应该保证只有一个用户能连麦成功,我们改怎么保证这种业务场景下保证数据的正确性。
  • 当我们连麦的时候,其实设计到两个步骤操作,首先读取房间座位信息,当发现座位没有被占用,修改作为状态,之后存储。但是这几个步骤并非原子操作,也就是可能存在两个线程同时读取,一起修改,这样肯定会造成数据错乱。怎样来保证读取,修改,存储,这三个步骤的原子操作就是分布式锁需要解决的问题。
分布式锁的奥义
  • 分布式锁本质上实现就是在Redis中占用一个key值,当别的进程也要来占用这个key时候,发现已经存在这个key,就只能放弃,或者等待key消失。我们一般使用setnx(set if not exists) 命令,只允许被一个客户占用,先到先得,在用del删除key,释放key占用给别的线程
  • 存在的问题,比如逻辑执行期间,出现异常,导致del指令无法执行,占用会陷入死锁,永远不能得到释放,这个问题可以通过过期时间解决,我们在设置key时候添加一个过期时间,这样中介出现异常,5s后也会自动释放如下:
新docker-redis:0>setnx lock 3
"1"
新docker-redis:0>expire lock 5
"1"
  • 新的问题, setnx 与expire两个指令并非原子操作,在两个指令执行间隔期间挂机,那么还是会死锁。如果可以一起执行就不会出现这种问题,可能Redis事务来解决问题,但是这种业务场景下不下,因为expire必须在前面setnx成功的前提条件才能够执行,当setnx没有获取锁,expire是不能执行的,此时同一个事务中这两个命令会出现一个成功一个失败,与预期不符合。
  • 为解决此问题,Redis2.8 版本后引入了一个新的指令,是的setnx和expire指令能一起执行,彻底解决分布式锁的问题,从此后,所有第三方锁都可以舍弃了,如下:
新docker-redis:0>set lock true ex 5 nx
"OK"
新docker-redis:0>set lock true ex 5 nx
null
  • 以上指令setnx和expire的组合一起的原子指令,他就是分布式锁解决的奥义。
还有超时问题
  • Redis分布式锁还是解决不了某种超时,比如,我设置5s,但是业务太垃圾了执行了6秒,以至于超过了锁的限制,那么问题出现了。锁以释放,剩余逻辑无法得到保护,第二个线程重新持有锁,然后第一个还没执行完的线程现在执行完了,给你吧key删了,这样是不是很刺激。
  • 为避免这个问题,Redis分布式锁不要用着长时间的任务,如果偶尔出现问题,造成的数据小错误就人肉解决吧。
  • 另外还有一个小的技巧来规避这种删除问题,我们利用value造一个乐观锁,我们在set value的时候设置本轮次逻辑的一个特殊的value值,当我们执行完逻辑后,在删除之前先确认一下value是否本轮次的value,如果不是表示非本轮次,不执行删除,如果是当前value,则删除。
  • 问题又来了,匹配value,删除key,又不是原子操作,Redis也没有类似的delifequals的指令,但是Redis4.0 后提供了lua脚本的支持,因为lua脚本可以保证连续多个指令的原子性执行
  • 这并非完美解决方案,还是有部分逻辑在裸奔,那么我们尽量规避吧,还有就是这么长逻辑,应该想到的是优化。
Redis分布式锁失效情况
  • 在Redis集群中,我们分布式锁是有一定缺陷的,例如在哨兵模式集群中,主节点挂掉,从节点会取而代之,这对客户端是无感知的,比如,客户端A主节点上申请lock,但是这把锁还没同步到Slave节点,主节点挂掉,之后从节点变主节点,B客户端在申请lock,同样成功,这样就存在两把锁,违背了分布式锁的初衷。不安全性由此产生。
    在这里插入图片描述
  • 以上情况在理论上是完全有可能发生的,但是也仅仅只是在主从发生failover的情况下才产生,而且持续时间比较短,业务系统多试情况下是可以容忍的。所以我吗业务中一般会忽略这种极端的情况。
Redlock算法
  • 遇到问题,解决问题,以上出现的极端情况也是有解决方案的,她流产比较复杂,但是有很多开源的libray已经做了良好的封装,用户之间用即可,例如Python中redlock-py
import redlock
addres=[{
	"host":"localhost",
	"port":6789,
	"db":0
},{
	"host":"localhost",
	"port":6789,
	"db":0
},{
	"host":"localhost",
	"port":6789,
	"db":0
}]
dlm = redlock.Redlock(addrs)
success = dlm.lock("lock-liaojiamin", 5000)
if success:
	print 'lock success'
	dlm.unlock('lock-liaojiamin')
else:
	print 'ock failed'
  • 为了使用Redlock,需要提供多个Redis实例,这些实例之间相互独立,没有主从关系。通很多分布式算法一样,Redlock用大多数机制。
    • 加锁时候向过半节点发送set(key, value, nx=True, ex=xxx)指令,只要过半节点set成功,就认为加锁成功
    • 释放锁需要想所有节点发送del指令
    • Redlock需要想多个节点进行读写,意味着相比单实例Redis的性能会下降。
Redlock使用场景
  • 业务室对高可用性有苛刻的要求,希望一台Redis挂了也完全不受影响,就应该考虑用Redlock
  • 代价是需要更多的Redis实例,性能也下降,代码删还需引入额外的library,运维上也需要特殊对待,这些都是成本。

上一篇:Redis流量控制策略
下一篇:LBS解决方案

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值