开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情
Redis实现分布式锁
实现1:SETNX实现的分布式锁
SETNX key value
将key设置为value,如果key不存s在,这种情况下等同于SET命令。当key存在时,什么也不做。
SETNX是“SET if Not Exists”
返回值:
1设置key成功
0设置key失败
加锁步骤:
1.SETNX lock.foo<current Unix time + lock timeout + 1>如果客户端获得锁,SETNX返回1,
加锁成功。如果SETNX返回0,那么该键已经被其他客户端锁定。
2.接上一步,SETNX返回0加锁失败,此时,调用GET lock.foo获取时间戳检查锁是否已经过期;
3.若旧的时间戳已过期,则表明加锁成功。
4.若旧的时间戳还未过期(说明被其他客户端抢去并设置了时间戳),代表了加锁失败,需要等待重试。
5.如果没有过期,则休眠一会重试。
6.如果已经过期,则可以获取该锁。具体的:调用GETSET lock.foo<current Unix timestamp + lock
timout +1>基于当前时间重新设置新的过期时间。注意:这里设置的时候因为在SETNX与GETSET之间有个窗口期,在这期间锁可能已被其他客户端抢去,所以这里需要判断GETSET的返回值,他的返回值是SET之前旧的时间戳。
这种是针对单个redis master实例,既不是有互为备份的slave节点也不是多master集群,如果是redis集
群,每个redis master节点都是独立存储的,这种场景使用单个redis master实例是不安全的。比如以下场景:
客户端1从Master获取了锁。
Master宕机了,存储锁的key还没来得及同步到Slave上。
Slave升级为Master。
客户端2从新的Master获取到了对应的同一个资源的锁。
于是,客户端1和客户端2同时持有了同一个资源的锁。锁的安全性被打破。
加锁步骤:
集群加锁的总体思想是想尝试锁住所有节点,当有一半以上的节点被锁住就代表成功。集群部署你的数据可
能保存在任何一个redis服务节点上,一旦加锁必须确保集群内任意节点被锁住,否则也就失去了加锁的意义。
具体的:
假设有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获取锁。
解锁步骤:
客户端向所有Redis节点发起释放锁的操作,不管这些节点在当时获取锁的时候成功与否。
删除原因:保证服务器资源的高利用效率,不用等到自动过期才删
除。
删除方法:最好使用LUA脚本删除(redis保证执行此脚本时不执
行其他操作,保证操作的原子性)代码如下:逻辑是先获取key,如果存在并且值是自己设置的就删除此key;否则就跳过。
实现2:
Redisson框架,会启动一个watch dog(看门狗),它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间,因此,Redisson解决了【锁过期释放,业务没执行完】的问题。
多机实现的分布式锁:RedLock+Redssion 分布式锁主要是用于秒杀下单、抢购商品等等业务场景的,主要是为了防止库存超卖,都需要用到分布式锁。