Redis 分布式锁:使用Set+lua替代 setnx
2022/1/8 2:06:15
本文主要是介绍Redis 分布式锁:使用Set+lua替代 setnx,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
解锁 redis 锁的正确姿势
redis 是 php 的好朋友,在 php 写业务过程中,有时候会使用到锁的概念,同时只能有一个人可以操作某个行为。这个时候我们就要用到锁。锁的方式有好几种,php 不能在内存中用锁,不能使用 zookeeper 加锁,使用数据库做锁又消耗比较大,这个时候我们一般会选用 redis 做锁机制。
setnx
锁在 redis 中最简单的数据结构就是 string。最早的时候,上锁的操作一般使用 setnx,这个命令是当:lock 不存在的时候 set 一个 val,或许你还会记得使用 expire 来增加锁的过期,解锁操作就是使用 del 命令,伪代码如下:
if (Redis::setnx("my:lock", 1)) { Redis::expire("my:lock", 10); // ... do something Redis::del("my:lock") }
这里其实是有问题的,问题就在于 setnx 和 expire 中间如果遇到 crash 等行为,可能这个 lock 就不会被释放了。于是进一步的优化方案可能是在 lock 中存储 timestamp。判断 timestamp 的长短。
set
现在官方建议直接使用 set 来实现锁。我们可以使用 set 命令来替代 setnx,就是下面这个样子
if (Redis::set("my:lock", 1, "nx", "ex", 10)) { ... do something Redis::del("my:lock") }
上面的代码把 my:lock 设置为 1,当且仅当这个 lock 不存在的时候,设置完成之后设置过期时间为 10。
获取锁的机制是对了,但是删除锁的机制直接使用 del 是不对的。因为有可能导致误删别人的锁的情况。
比如,这个锁我上了 10s,但是我处理的时间比 10s 更长,到了 10s,这个锁自动过期了,被别人取走了,并且对它重新上锁了。那么这个时候,我再调用 Redis::del 就是删除别人建立的锁了。
官方对解锁的命令也有建议,建议使用 lua 脚本,先进行 get,再进行 del
程序变成:
$token = rand(1, 100000);//可替换为 UUID function lock() { return Redis::set("my:lock", $token, "nx", "ex", 10); } function unlock() { $script = ` if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end ` return Redis::eval($script, "my:lock", $token) } if (lock()) { // do something unlock(); }
博客园首页新随笔联系订阅管理随笔 - 440 文章 - 1 评论 - 101 阅读 - 160万
Redis 分布式锁:使用Set+lua替代 setnx
解锁 redis 锁的正确姿势
redis 是 php 的好朋友,在 php 写业务过程中,有时候会使用到锁的概念,同时只能有一个人可以操作某个行为。这个时候我们就要用到锁。锁的方式有好几种,php 不能在内存中用锁,不能使用 zookeeper 加锁,使用数据库做锁又消耗比较大,这个时候我们一般会选用 redis 做锁机制。
setnx
锁在 redis 中最简单的数据结构就是 string。最早的时候,上锁的操作一般使用 setnx,这个命令是当:lock 不存在的时候 set 一个 val,或许你还会记得使用 expire 来增加锁的过期,解锁操作就是使用 del 命令,伪代码如下:
if (Redis::setnx("my:lock", 1)) { Redis::expire("my:lock", 10); // ... do something Redis::del("my:lock") }
这里其实是有问题的,问题就在于 setnx 和 expire 中间如果遇到 crash 等行为,可能这个 lock 就不会被释放了。于是进一步的优化方案可能是在 lock 中存储 timestamp。判断 timestamp 的长短。
set
现在官方建议直接使用 set 来实现锁。我们可以使用 set 命令来替代 setnx,就是下面这个样子
if (Redis::set("my:lock", 1, "nx", "ex", 10)) { ... do something Redis::del("my:lock") }
上面的代码把 my:lock 设置为 1,当且仅当这个 lock 不存在的时候,设置完成之后设置过期时间为 10。
获取锁的机制是对了,但是删除锁的机制直接使用 del 是不对的。因为有可能导致误删别人的锁的情况。
比如,这个锁我上了 10s,但是我处理的时间比 10s 更长,到了 10s,这个锁自动过期了,被别人取走了,并且对它重新上锁了。那么这个时候,我再调用 Redis::del 就是删除别人建立的锁了。
官方对解锁的命令也有建议,建议使用 lua 脚本,先进行 get,再进行 del
程序变成:
$token = rand(1, 100000);//可替换为 UUID function lock() { return Redis::set("my:lock", $token, "nx", "ex", 10); } function unlock() { $script = ` if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end ` return Redis::eval($script, "my:lock", $token) } if (lock()) { // do something unlock(); }
这里的 token 是一个随机数,当 lock 的时候,往 redis 的 my:lock 中存的是这个 token,unlock 的时候,先 get 一下 lock 中的 token,如果和我要删除的 token 是一致的,说明这个锁是之前我 set 的,否则的话,说明这个锁已经过期,是别人 set 的,我就不应该对它进行任何操作。
所以:不要再使用 setnx,直接使用 set 进行锁实现。
redis新版本setnx已经支持直接设置过期时间了,所以lock的时候用set或者setnx都可以。而解锁的时候必须要用lua脚本实现 因为get和del不是原子性的,必须要打包成一套命令执行。
这篇关于Redis 分布式锁:使用Set+lua替代 setnx的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-24Redis资料:新手入门快速指南
- 2024-12-24Redis资料:新手入门教程与实践指南
- 2024-12-24Redis资料:新手入门教程与实践指南
- 2024-12-07Redis高并发入门详解
- 2024-12-07Redis缓存入门:新手必读指南
- 2024-12-07Redis缓存入门:新手必读教程
- 2024-12-07Redis入门:新手必备的简单教程
- 2024-12-07Redis入门:新手必读的简单教程
- 2024-12-06Redis入门教程:从安装到基本操作
- 2024-12-06Redis缓存入门教程:轻松掌握缓存技巧