关于Redis分布式锁的思考

一个分布式锁,需要满足以下:
1.独享,即一个锁不能同时被两个客户端持有
2.无死锁,即当一个持有锁的客户端异常退出了,锁要自动释放,否则其他客户端永远都拿不到锁
3.单点问题,即若只用一个单实例来实现分布式锁,那么当这个节点挂了,也就导致分布式锁功能停止了。这其实并不是分布式锁的独有问题,单点问题是所有单实例Redis服务的共有问题。
对于单点问题,Redis主从的方案也无法完全解决,例如
客户端A拿到了锁;
主从异步复制是存在延迟的,假设从库还没复制到这个锁状态,主库就挂了;
当从库提升为主库时,由于没有主库挂掉之前的锁状态,所以客户端B也可以拿到锁,这时就造成了两个客户端同时拿到锁的情况;
以上情况小概率发生,所以解决单点问题需要采用多实例,但多实例中的主从同样会发生以上的情况,需要采取一定的措施来避免,我们后面会讨论到。

一、单实例实现分布式锁
Redis官方推荐的正确方式是:
SET resource_name my_random_value NX PX 3000
当然过期时间可以自定义。

我们来一步步地衍进说明是如何得到这种方法的。
我们会通过一个相同的key,例如klock,来确定是否拿到锁,一个客户端在klock上设置一个值,表示这个客户端在持有锁;
要实现锁的独享,那么我们需要保证在锁被持有期间,其他客户端不能对klock写入,那么我们使用到setnx的功能,释放锁时将klock删掉;
但当一个持有锁的客户端异常退出了,那么klock也就一直存在,其他客户端就无法拿到锁,那么我们需要对klock设置一个超时时间,这样即使拿锁的客户端挂了,在一定时间后,klock也就自动过期了,其他客户端可以拿锁。
官方还指出,客户端释放锁时将klock删掉,需要判断klock的值是否等于自己设置的值,防止误删了其他客户端所设置的klock。
其实这里的逻辑还是有问题的:设置超时时间可以防止客户端挂掉导致的死锁,但是如果一个程序逻辑,在拿到锁以后,在超时时间内无法完成响应的操作,那么锁就被其他客户端拿了,这也就违反了锁的独享性。
同时,如果锁是独享的,那么删掉klock的时候,又何必去判断这个锁是不是自己设置的呢?
再者,拿锁的客户端挂了,其他客户端最大的堵塞时间由klock的超时时间来决定,无法完美地实现拿锁的客户端挂了即马上释放锁。

所以SET resource_name my_random_value NX PX 3000是一种较为安全的实现方式,但并不是完美的,仍然有缺陷。

二、多实例来实现分布式锁
官方成为REDLOCK算法:
假设我们用5个Redis实例来实现分布式锁
1.获取当前时间;
2.同时向5个Redis实例发出SET resource_name my_random_value NX PX 3000,当然过期时间自定义;同时设置一个通信超时时间,例如50ms,防止网络异常或Redis挂了,客户端还在一直重连;
3.当某个实例SET resource_name my_random_value NX PX 3000成功,则用当前时间减去步骤1的开始时间,如果得到的时间值小于超时时间,才算是拿锁成功;当大多数实例(这个例子是3个以上)都判断为拿锁成功,锁才算获取成功;
4.如果拿锁失败,在所有实例上执行解锁操作,即把resource_name删掉,当然需要判断resource_name的值是否等于自己所要设置的值;
5.如果拿锁失败,会在随机延迟后重试,官方说是为了防止多个客户端同时抢锁,其实在高并发下,多个客户端同时抢锁是无法避免的。

但是,以下这种情况,算不算违反锁的独享性呢?
例如在某些情况下,会话1在3个实例(A,B,C)成功执行了SET resource_name my_random_value NX PX 3000,而有2个(D,E)没执行成功,那么可以判定会话1拿锁成功;
这时其他会话尝试拿锁,只会在D,E两个实例set成功,不满足大多数条件,所以拿锁失败;
当会话1处理完成了,去删除resource_name时,这肯定不能保证A,B,C三个实例上的resource_name都同时删除,总会有一定的时间差。那么假设拿锁的并发非常高,当A实例执行del resource_name成功,而B,C还没成功时,有一个会话2来申请锁,这时会话2就会在A,D,E上set resource_name成功,也就可以判定会话2拿到了锁;那么此时会话1还没结束拿锁,而会话2拿到了,所以此时就违反了锁的独享性。
但是既然会话1都已经开始del resource_name,可以认为此时会话1不再需要锁了,所以会话2拿到锁也不会有影响。

还存在一种高并发下脑裂的情况:
同时有三个会话来抢夺锁,会话1在A,B上set resource_name成功,会话2在C上set resource_name成功,会话3在D,E上set resource_name成功,那么没有会话可以拿到锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值