【Redis】Redis实现分布式锁

分布式锁

分布式锁是一种在分布式系统中实现同步机制的技术。它允许多个进程或节点在访问共享资源时进行同步,以确保它们按照预期的顺序执行。

这篇文章使用Redis来分布式锁,通俗的来说,分布式锁本质上要实现的目标就是在Redis里面占一个"茅坑",当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后重试。

接下来我们来循序渐进的实现一个成功的分布式锁。

一、使用setnx指令

Redis分布式锁一般使用setnx(setifnotexists)指令,只允许被一个客户端占坑。先来先占,用完了,再调用del指令释放"茅坑"。

setnx lock:codehole true

...do something critical ...

del lock:codehole

简单的使用setnx指令肯定会出现一些问题

问题一:如果逻辑执行到中间出现异常了,可能会导致del指令没有被调用,这样就会陷入死锁,锁永远得不到释放。

我们首先想到的肯定是:“加一个过期时间就行了”。那我们接着往下看。

二、使用setnx+expire指令

给锁加上过期时间后,如果程序执行到中间出现异常或超时了,到了过期时间后就自动释放锁。

setnx lock:codehole true
expire lock:codehole 5

... do something critical ...
del lock:codehole

问题二:如果在setnx和expire之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人杀掉的,就会导致expire得不到执行,也会造成死锁。

解决方案: 使用Redis中setnx和expire组合原子命令。

三、使用setnx和expire组合原子命令

为了解决这个问题,Redis在2.8版本中加入了set指令的扩展参数,使得setnx和expire指令可以一起执行,解决了这个问题。

set lock:codehole true ex 5 nx

... do something crutical ...

del lock:codehole

问题三:释放锁错乱问题,当前锁可能释放的不是自己的锁。

如果在加锁和释放锁之间的逻辑执行的太长,以至于超出了锁的超时限制,就会出现问题。因为这时候锁过期了,第二个线程重新持有了这把锁,但是紧接着第一个线程执行完了业务逻辑,就把锁给释放了。

场景:如果业务逻辑的执行时间是7s。执行流程如下

  1. index1业务逻辑没执行完,3秒后锁被自动释放。

  2. index2获取到锁,执行业务逻辑,3秒后锁被自动释放。

  3. index3获取到锁,执行业务逻辑

  4. index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁,导致index3的业务只执行1s就被别人释放。

最终等于没锁的情况。

解决方案:获取锁时,设置一个指定的唯一值value(例如:雪花Id),释放前获取这个锁,判断是否是自己的锁。

四、指定唯一值value

获取锁时,设置一个自己的唯一值,释放锁之前先判断这个value唯一值是否是自己的锁,如果是自己的锁才可以释放。

set lock:codehole 唯一值 ex 5 nx

... do something crutical ...

释放之前判断唯一值是否是自己加锁前的唯一值
del lock:codehole

问题:判断加删除操作缺乏原子性,也会造成问题。

场景:

  1. index1执行删除时,查询到的lock值确实和uuid相等

  2. index1执行删除前,lock刚好过期时间已到,被redis自动释放,在redis中没有了lock,没有了锁。

  3. index2获取了lock,index2线程获取到了cpu的资源,开始执行方法

  4. index1执行删除,此时会把index2的lock删除。index1 因为已经在方法中了,所以不需要重新上锁。index1有执行的权限。index1已经比较完成了,这个时候,开始执行删除的index2的锁!

解决方案:使用lua脚本,lua脚本可以保证连续多个指令的原子性执行。

五、使用Redis+Lua脚本

使用 Lua 脚本来处理,Lua 脚本可以保证连续多个指令的原子性执行。

if redis.call("get",KEYS[1]) == ARGV[1] then
 return redis.call("del",KEYS[1])
else
 return 0

最终,分布式锁的最终版本就是Redis中setnx 和 Lua脚本了。

你可能感兴趣的:(Redis,redis,分布式,数据库)