Redis分布式锁如何提高可用性

在编程中我们时常考虑高并发带来的数据访问不安全问题,那么我们在redis中是否也要考虑呢?答案是肯定的,有人会问:redis不是单线程的吗?对它是单线程,但是在某些情况他会出现信息更新,用户没有拿到最新数据,然后导致操作有误,看下图

我们可以看到张三和李四同时请求这个number,但是李四执行set后张三拿到的数据是没有更新的,而后执行了set命令,这样这个number应该是30才对。

Redis分布式锁简介

redis给我们提供了分布式锁,开启锁的指令是setnx(set if not exists),释放锁指令del,下面我们来尝试

> setnx lookname mango(integer) 1> get lookname"mango"> del lookname(integer) 1> get mango(nil)

死锁

这里我们思考一个问题,如果我们在执行逻辑的时候出现异常了,那么这个锁就会一直得不到释放,导致死锁,所以我们想办法让它自己释放,添加一个过期时间这个指令是expire key seconds,即使我们不del,他它过了这个时间会自动释放

> setnx lookname mango(integer) 1> expire lookname 5(integer) 1> get lookname(nil)

还有一个问题,如果我在执行setnx和expire的过程中出现了异常怎么办?我们很快想到了用事务,但是我们这里注意expire依赖setnx,expire这个是当setnx抢到了资源的时候我们的expire才会执行,否则是执行不成功的,事务里面没有if else语句。

为了解决这个问题,redis开源社区开发出很多分布式锁的lib库,也就是说我们每次使用分布式锁的时候都要引入这个第三方的库,关于这个问题作者在Redis2.8版本中加入了set扩展,把这两个命令放在一起执行。

> set lookname mango ex 5 nxOK> get lookname"mango"> get lookname    # 5s 后(nil)

超时问题

如果我们在加锁后到这个锁被释放的时间内的业务逻辑还没有执行完,那么这个时候出现了新的问题就是超时,比如说我只想的逻辑需要30秒,而我的锁过期时间只有10秒,此时我的逻辑还没有处理完,问题就来了。

不使用过期时间

解决超时问题的快捷办法就是不使用过期时间,我们需要在key后面添加一个比较隐私的随机数,例如name1564835、name6879425,达到减少key的碰撞,我们对这个key做上标记,等执行逻辑处理完后程序回收这个内存。

延迟回收

如果10秒钟不够那么我们可以给这个key延长回收时间,我们在key回收前判断客户端是否还在使用这个key,如果没有使用这个key我们就什么都不做,如果在使用,我们就增加回收时长,如何做?我们可以在调用端重开一个线程,监测快过期的key,客户端可以给redis服务实例发送一个Lua脚本检测这个key的值有没有改变,如果没有改变让redis服务端延长锁的时间。

RedLock算法

我们在一个集群中,当主节点挂掉时,从节点会取而代之,原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了,然后从节点变成了主节点这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,此时两个客户端都持有这个资源,问题又出现了。

但是这个问题是极小概率事件并且是非常短时间造成的,从分布式系统角度上我们是可以容忍这个问题的。但是我们希望redis完全不受影响,可以考虑 redlock。Redlock算法是由Antirez发明的,它的流程比较复杂,不过已经有了很多开源的 library 做了良好的封装,用户可以拿来即用,比如 redlock-py。

redlock.Redlock()

RedLock算法原理:

1.获取当前时间(单位是毫秒)。

2.轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。

3.客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁((N/2) +1),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。

4.如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。

5.如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

注意:加锁时,它会向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要过半节点 set 成功,那就认为加锁成功。释放锁时,需要向所有节点发送 del 指令。不过 Redlock 算法还需要考虑出错重试、时钟漂移等很多细节问题,同时因为 Redlock 需要向多个节点进行读写,意味着相比单实例 Redis性能会下降一些,代码额外引入第三方lib。

有兴趣的童鞋可以参考:Redlock(redis分布式锁)原理分析​

 

一名正在抢救的coder

笔名:mangolove

CSDN地址:https://blog.csdn.net/mango_love

GitHub地址:https://github.com/mangoloveYu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值