REDIS 学习(10)流程图解使用redis实现分布式锁

redis作为集中式缓存,可以通过它来实现分布式锁。

首先用到的redis操作有:

setnx key value:      当key不存在的时候生效并返回1,当已经有此key的时候返回0

getset key value:     设置新值返回旧值,如果之前不存在也设置新值并返回nil

get key:     返回对应的值,没有则返回nil

del key,key1,key2:      删除,返回删除成功的个数

方案1,使用setnx key value来获得锁,del来释放锁

REDIS 学习(10)流程图解使用redis实现分布式锁_第1张图片

                                           图1

方案1有个问题是,如果获得锁的线程或者进程崩溃了,这个锁将得不到释放

方案2我们增加将值设置为当前时间,引入超时判断

REDIS 学习(10)流程图解使用redis实现分布式锁_第2张图片

图2

方案2依然有个问题,并发情况下,同时判断锁超时,n0,n1,n2同时del并通过setnx获取锁可能,会n2把n1获取的锁给删除掉。

方案3增加getset 获取旧的时间设置新的时间,这样删除某个超时锁的操作只有一起,其他的获取锁判断会接下来判断锁没有超时。

REDIS 学习(10)流程图解使用redis实现分布式锁_第3张图片

图3

注意虚线部分,这是之前其他某个博主的做法,就是谁设置了有效时间戳后就认为获取了这个锁。而实线部分我的做法是获取超时锁后就释放掉,然后重新获取锁。两者应该区别不是太大,都是通过getset解决超时锁只进行一次删除的问题。

 

最后,依然会有个悬而未决的问题,最初获取这个锁的线程或进程,如果在超时后,锁被其他线程或者进程重新获取后,这个线程或者进程如果复活了,也会再次触发释放锁(del)?

所以我认为,del操作应该增加类似版本号的判断,本文例子用它的时间戳即可

 

2017年2月7日更正:

由于setnx不能设置过期时间,所以通过setnx结合expire的方式在图1中就能实现锁的过期释放策略:

if(setnx(a,'tag') =ok){

   expire a 1;

}

不过set a  tag ex 1 nx就已经实现了setnx+expire这两步操作,设值返回ok,已经存在返回null。由于是一个redis命令所以是原子操作。而且未来的redis版本可能去掉setnx和setex, 因此推荐用set ex nx的方式。当然为了防止假死的线程复活后删除锁,在set 的时候依然可以通过增加本版本号或者使用随机数的方式来处理。

2019年10月13日补充:

总结了设置锁的三个要素,set nx ex 保证没有值才能设置成功1️⃣,过期时间2️⃣, 其中key是锁的名字值要带上版本号3️⃣。

但是删除的过程 怎么保证自己的线程能够删除自己的版本号的锁?

思路:还得是CAS

方案一  redis提供脚本做这件事,删除值的时候可以比较expect value一致才删除

方案2 使用redis 的事务 watch + multi + exec来实现CAS乐观锁修改,watch后,mulit到exe之间对此键的值修改,如果值期间被别人改过会提交失败

127.0.0.1:6379> get a

"102"

127.0.0.1:6379> watch a

OK

127.0.0.1:6379> get a

"102"

------------->>这里别的redis客户端修改了值>>------

127.0.0.1:6379> multi 

OK

127.0.0.1:6379> set a 1

QUEUED

127.0.0.1:6379> exec

(nil)

127.0.0.1:6379> get a

"103"

127.0.0.1:6379> 

 

你可能感兴趣的:(网络编程,集群,redis,分布式,缓存,redis)