redis实现分布式锁

508 阅读3分钟

分布式锁

首先有一个先验知识:SETNX命令。它的全称是set if not exists。即如果这个key不存在的话,就可以给它set值;如果存在,则报错。

如图,ab作为key,它的value已经被设置为love了,如果再想把它设置为kk,则会报错。

所以分布式锁就用了这一个命令的思路:多个线程通过同时来设置这个命令,谁先能把它set上值,就代表抢到了锁。反之就得等待锁的释放。

第一种

首先声明,前面几种太简单就不赘述了,我想从具有过期时间的例子开始说起。

在这个例子中,我们将 获取锁 + 设置锁的过期时间 设置成一个事务(因为redis支持)

原理图如下:

那一行关键代码是这样的:

\

但是这种方法有一个缺陷:

假如有ABC三个线程同时执行某段业务代码,A线程先抢到了锁(设置值为123),然后执行业务,10s之后,锁自动被释放了,但是A的业务代码还没有执行完成! 此时B线程看锁被释放了。立马抢占了锁(也设置值为123),同样执行业务代码。此时A和B线程就产生了冲突。15s后,A线程释放了值为123的锁,但这个锁是B锁上的,又发生了冲突。

此时C看锁被释放了,立马又抢占了该锁(也设置了值为123),此时B和C在执行任务上又发生了冲突。

所以给了我们一个提醒:

  1. 线程处理任务的时间一定要小于锁过期的时间
  2. 为什么BC会打开A的锁,因为他们都把value设置为了同一个,所以就可以相互打开各自的锁,所以value值必须设置为不同的,可以用uuid来替换。

第二种

基于上一种方法的缺陷,我们改进了下一种方法,

  • 采用uuid来作为其value的值
  • 主动删除锁时,需要先判断锁的value值是否和设置的一样,一致的话才主动删除。

  1. 生成一个uuid
  2. 用生成的uuid加锁
  1. 如果加锁成功,则执行业务
  2. 执行完业务之后,获取当前锁的value值
  1. 如果该value值和一开始生成的uuid相同,则可以安全释放锁

你以为这个方法很完美了吗?但实际上并不是。

比如A、B两个线程 ,A先抢到了锁,A执行完业务后,去查询当前value的值,但这个查询特别耗时。

到达10s时,锁释放了,B线程抢占了锁,并开始执行业务。此时A查到其value值了,然后A就把锁释放了,这里要注意,A的锁其实已经过期了,但它释放的锁,其实是B的,但此时B还没有执行完业务呢。

在这就会造成冲突。

解决方案就是把4、5步设置为原子操作,即查询当前锁的value值+释放锁作为一个事务来处理即可。

第三种(Redisson)

啰嗦了这么多,redis早就给了我们一个完美的解决方案

他的使用方法就是:

\

\

\

\

这个锁是默认30s过期的。但它又做了新的优化。但redission实例没被关闭之前,会不断延长锁的过期时间,即10s后,如果redission没关闭,自动将过期时间再次设置为30s,以此往复。

即使服务器宕机了,30s后,锁依然会释放,不会造成死锁操作。