方案一:SETNX 命令
Redis 提供了一个获取分布式锁的命令 SETNX
setnx key value如果获取锁成功,redis 返回 1,获取锁失败 redis 返回 0
文章插图
客户端使用伪代码
if (setnx(k1,v1) == 1) { try{ // 执行逻辑 .... }catch() { }finally{ // 执行完成后释放锁 del k1; } }这个命令看似可以达到我们的目的,但是不符合分布式锁的特性,如果客户端在执行业务逻辑过程中,服务器宕机了,finally 中代码还没来得及执行,锁没有释放,也就意味其他客户端永远无法获取到这个锁
方案二:SETNX + EXPIRE
该方案获取锁之后,立即给锁加上一个过期时间,这样即使客户端没有手动释放锁,锁到期后也会自动释放
文章插图
我们来看下伪代码
if (setnx(k1, v1) == 1){ expire(key, 10); try { //.... 你的业务逻辑 } finally { del(key); } }这个方案很完美,既可以获取到,又不用担心客户端宕机 。等等,这里面真的没有问题吗?再仔细瞅瞅,一瞅就瞅出问题来了
if (setnx(k1, v1) == 1){ // 再刚获取锁之后,想要给锁设置过期时间,此时服务器挂了 expire(key, 10); // 这条命令没有执行 try { //.... 你的业务逻辑 } finally { del(key); } }这里的 setnx 命令和 expire 命令不是原子性的,他们之间执行需要一定的等等时间,虽然这个时间很短,但是依然有极小概率出现问题
使用 Lua 脚本
既然 setnx 和 expire 两个命令非原子性,那么我们让其符合原子性即可,通过 Lua 脚本即可实现 。Redis 使用单个 Lua 解释器去运行所有脚本,并且,Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行
具体实现如下:
if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) else return 0 end;这样应该没问题了吧,看似上面的几个问题都很好解决了 。不对,再想想,肯定还有没考虑到的
我们再来看一段伪代码
// 执行 lua 脚本 // 获取 k1 锁,过期时间 10 s if (execlua()==1){ try { buyGoods(); } finally { del(key); } }文章插图
从图中我们可以很清晰发现问题所在
- 客户端 A 还未执行完毕,客户端 B 就获取到了锁,这样就可能导致并发问题
- 客户端 A 执行完毕,开始删除锁 。但此时的锁为 B 所有,相当于删除了属于客户端 B 的锁,这样肯定会发生问题
方案四:SET EX PX NX + 校验唯一随机值,再删除
既然锁有可能被别的客户端删除,那么在删除锁的时候我们加上一层校验,判断释放锁是当前客户端持有的,如果是当前客户端,则允许删除,否则不允许删除 。
- EX second :设置键的过期时间为 second 秒 。SET key value EX second 效果等同于 SETEX key second value。
- PX millisecond :设置键的过期时间为 millisecond 毫秒 。SET key value PX millisecond 效果等同于 PSETEX key millisecond value。
- NX :只在键不存在时,才对键进行设置操作 。SET key value NX 效果等同于 SETNX key value。
- XX :只在键已经存在时,才对键进行设置操作 。
- 嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去!
使用示例:
if(jedis.set(resource_name, random_value, "NX", "EX", 100s) == 1){ //加锁, value 传入一个随机数 try { do something //业务处理 }catch(){ } finally { // 判断 value 是否相等, 相等才释放锁, 这里判断和删除是非原子性, 真实场景下可以将这两步放入 Lua 脚本中执行 if (random_value.equals(jedis.get(resource_name))) { jedis.del(lockKey); //释放锁 } } }
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 谢霆锋|谢霆锋发文示爱王菲:我爱了你20年,打脸两子,渣男属性彻底暴露
- 谢霆锋|“锋菲恋”沸沸扬扬二十年,这次是否真的画上句号?
- 吉克隽逸|从淳朴大山妹到目中无人耍大牌,吉克隽逸彻底垮台!
- 唐莲|《少年歌行》两位主角结局被改,唐莲彻底下线,赵玉真重伤退隐
- 地下室怎么防潮除湿更彻底? 地下室如何防水防潮
- 乙肝能否彻底治愈? 治疗乙肝
- 叶童|靳东这次的“清醒”,让多少实力派演员“大梦初醒”?
- 李思思|李思思带儿子游玩,自拍大变样不敢认,尖下巴彻底没了一姐气派
- 家里的蚂蚁怎么彻底消除? 家里有蚂蚁怎么办
- 订阅号消息怎么彻底删除—手机微信上的订阅号消息怎么关闭?
