Redis 分布式锁

Redis分布式锁最简单的实现

分布式锁使用场景:

  • 客户端1 申请加锁,加锁成功
  • 客户端2 申请加锁,因为它后到达,加锁失败
  • 客户端1 释放锁
  • 客户端2 申请加锁,并加锁成功

SETNX

  想要实现分布式锁,必须要求Redis有「互斥」的能力,这里我们就要提到SETNX 命令,这个命令表示SET if Not Exists,即如果key不存在,才会设置它的值,否则什么也不做。

两个客户端进程可以执行这个命令,就达到互斥的效果,实现了分布式锁。
image

实现场景的加锁与释放锁

加锁 setnx lock 1
释放锁 del lock
image.png

问题1 死锁

问题:

  当客户端1 拿到锁后,如果发生下面的场景,就会造成「死锁」:
  1. 程序处理业务逻辑异常,没及时释放锁
  2. 进程挂了,没机会释放锁

解决方式:

  给lock 设置过期时间。例如,设置 10s 过期。这样永远不会死锁

SETNX lock 1    // 加锁
EXPIRE lock 10  // 10s后自动过期

  加锁、设置过期是 2 条命令,不能保证是原子操作(一起成功),存在潜在风险。所以,在 Redis 2.6.12 之后,Redis 扩展了 SET 命令的参数,用这一条命令就可以了:

// 设置 lock 值为 1,存活时间为 10秒。
// ex 为秒表示,px 为毫秒标识
// nx 键值不存在执行,xx 键值存在执行
SET lock 1 EX 10 NX

image

问题2 锁被别人释放

解决办法

  客户端在加锁时,设置一个只有自己知道的「唯一标识」进去,作为value存储。

// UUID
SET lock $uuid EX 20 NX

  之后,在释放锁时,要先判断这把锁是否还归自己持有,伪代码可以这么写:

if redis.get("lock") == $uuid:
    redis.del("lock")

  这里释放锁使用的是 GET + DEL 两条命令,这时,又会遇到我们前面讲的原子性问题了。这里可以使用lua脚本来解决。

// 安全释放锁的 Lua 脚本
if redis.call("GET",KEYS[1]) == ARGV[1]
then
    return redis.call("DEL",KEYS[1])
else
    return 0
end

小结

基于 Redis 实现的分布式锁,一个严谨的的流程如下:

1、加锁

SET lock_key $uuid EX $expire_time NX

2、操作共享资源
3、释放锁:Lua 脚本,先 GET 判断锁是否归属自己,再DEL 释放锁

posted @ 2023-11-02 16:46  之士咖啡  阅读(30)  评论(0编辑  收藏  举报