当前位置:网站首页>redis 分布式锁

redis 分布式锁

2022-04-23 20:06:00 链滴

1.简单锁

redis 的 setnx 命令可以提供互斥,可以实现一个简单分布式锁

1.1 加锁

127.0.0.1:6379> SETNX lock 1(integer) 1 // 客户端1,加锁成功
127.0.0.1:6379> SETNX lock 1(integer) 0 // 客户端2,加锁失败

1.2 释放锁

del 命令可以释放锁,

127.0.0.1:6379> DEL lock // 释放锁(integer) 1

1.3 存在问题

死锁问题: 线程挂掉或程序异常,锁无法释放

2.简单锁 + 超时

setnx+expire 可以为分布式锁加一个超时时间,这样死锁问题可以解决

2.1 实现

127.0.0.1:6379> SETNX lock 1 // 加锁(integer) 1127.0.0.1:6379> EXPIRE lock 10 // 10s后自动过期(integer) 1

2.2 问题

  1. 锁过期: 线程 1 持有锁时,由于程序执行时间过长,导致锁过期,此时如果被其他线程获取到锁,易产生并发问题
  2. 释放其他线程的锁: 在 1 的情况下, 当线程 1 执行完程序后,很可能会把线程二的锁释放掉

3 简单锁 + 超时 + 线程 id

3.1 实现

在加锁的时候 key 为加锁资源, value 为线程 id, 解锁时校验线程,可以保证锁不会被其他线程释放

// 锁的VALUE设置为UUID127.0.0.1:6379> SET lock $uuid EX 20 NXOK
// 锁是自己的,才释放if redis.get("lock") == $uuid: redis.del("lock")

3.2 问题

  1. 释放锁时非原子操作, 先 get 再 del, (问题不大,一般程序中并发请求都是 setnx, 如果用 set 则会产生问题)
  2. 锁过期问题依旧存在

3.3 优化

使用 lua 脚本释放锁可以实现原子化

// 判断锁是自己的,才释放if redis.call("GET",KEYS[1]) == ARGV[1]then return redis.call("DEL",KEYS[1])else return 0end

4. 锁过期问题

综上所述,使用 setnx + 超时 + 线程 id+lua 脚本 基本可以实现一个严谨的分布式锁

38a5368788885b9c96809edab024e9fb.png

唯一问题似乎只有锁超时的问题了,锁超时问题可以这么处理:

加锁时,先设置一个过期时间,然后我们开启一个「守护线程」,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行「续期」,重新设置过期时间。

b71ca6eefed9b8f397a353f3acc8d076.png

业界成熟方案有 redission,可以提供

  • 可重入锁
  • 乐观锁
  • 公平锁
  • 读写锁
  • Redlock(红锁,下面会详细讲)

具体使用方法参考

0. 项目介绍 - 《Redisson 使用手册》 - 书栈网 · BookStack

5. redlock

版权声明
本文为[链滴]所创,转载请带上原文链接,感谢
https://ld246.com/article/1650706623701