redis实现分布式锁的探索

锁:解决多个线程争抢资源的情况,保证任何时候有且只有一个线程能持有资源,并且避免死锁。

关注问题:分布式、过期、宕机、代码原子性、GC、重入(lock次数)



方案1

简单粗暴的想法,缺少锁就给他加锁

使用synchronized、队列

锁住方法、锁住代码块、使用队列使其串行

优点:简单粗暴,可以解决单机并发问题

缺点:锁粒度较高,面对单机高并发和分布式系统就无力了


方案2

数据库锁表

将资源(order_id)存储数据库,并设置UNIQUE KEY

加锁:insert into lock_table (order_id) values (orderId1)

解锁:delete from lock_table where order_id=' orderId1'

缺点:没有过期时间,如果P1获取资源后异常宕机,就会死锁,还需要定时任务解锁,

锁失效时间由程序判断,这样会额外增加应用的执行时间,不可控


方案3

缓存锁

redis锁定的原理是利用setnx命令,set if not exists,即key不存在时才能set成功。1-设置成功, 0-设置不成功

优点:性能出色,效率高

 

实现1:

SETNX lock_key value
del lock_key

P1通过setnx返回1获得锁

P2通过setnx返回0未获得锁

P1执行完del lock_key释放锁

P2通过setnx返回1获得锁

如果P1挂掉,未能释放锁,其他线程setnx返回0永远不能获取锁

 

实现2:

SETNX lock_key <current timestamp + lock timeout>
del lock_key
GETSET lock_key < current Unix time + lock timeout +  1 >


P1通过 SETNX lock_key <current timestamp + lock timeout>获得锁,value为当前时间戳+锁的时间

P2通过setnx返回0,然后通过GET key 检查value是否超时

如果没超时则等待或重试

如果已超时则 GETSET lock_key < current Unix time + lock timeout + 1>,如果拿到的时间戳是超时的,则认为P2获得锁

如果有个P3在P2之前执行了上面的操作,那么P2拿到的是未超时的值,重试或等待

问题:尽管P2没能拿到锁,但是改写了P3的过期时间,不过是微小的误差。锁失效时间有程序判断,这样会额外增加应用的执行时间。如果各节点时间不一致容易导致死锁。


实现3:

SETNX lock_key value 
expire lock_key seconds 
del lock_key


P1执行setnx返回1后获得锁,然后设置过期时间,开始执行业务代码,执行完成后del lock_key释放锁

P2执行setnx返回0然后重试或等待

如果P1挂掉没能正确释放锁,超过有效期后expire命令会自动删除key,其他线程开始竞争锁

问题:setnx和expire不是原子操作,如果setnx成功但是在expire之前程序挂掉,就可能导致死锁

 

实现4:

SETNX lock_key value 
expire lock_key seconds 
del lock_key
ttl lock_key


ttl:当 key 不存在时,返回 -2 。当 key 存在但没有设置剩余生存时间时,返回 -1 。否则,以秒为单位,返回 key 的剩余生存时间。

P1执行setnx成功但是在expire之前程序挂掉

P2执行setnx返回0,然后执行ttl命令返回-1,则执行expire lock_key timeout设置失效时间

如果仅P1一个节点

批次1:P1执行setnx成功,但是在expire之前程序挂掉

批次2:执行setnx返回0,然后执行ttl命令返回-1,则执行expire lock_key timeout设置失效时间

问题:虽然避免了实现3中的死锁,但是可能存在失效时间误差的情况,并且可能导致某一批次任务不会被执行

 

实现5:

基于Jedis实现

加锁:

/**
* 加锁:
* 存储数据到缓存中,并制定过期时间和当Key存在时是否覆盖。
* @param key
* @param value
* @param nxxx nxxx的值只能取NX或者XX,如果取NX,则只有当key不存在是才进行set,如果取XX,则只有当key已经存在时才进行set
* @param expx expx的值只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒。
* @param time 过期时间,单位是expx所代表的单位。
*/
Jedis.set( final  String key,  final  String value,  final  String nxxx,  final  String expx,  final  int  time);
if  (LOCK_SUCCESS.equals(result)) {
     return  true ;
}
return  false ;
 
/**
* 解锁:
*/
String script =  "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" ;
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if  (RELEASE_SUCCESS.equals(result)) {
     return  true ;
}
return  false ;

 

 

实现6:

使用Redisson实现分布式锁

包含可重入锁(Reentrant Lock)公平锁(Fair Lock)联锁(MultiLock)红锁(RedLock)读写锁(ReadWriteLock)信号量(Semaphore)可过期性信号量(PermitExpirableSemaphore)闭锁(CountDownLatch)

猜你喜欢

转载自blog.csdn.net/zhsh5395/article/details/80622721