面试问Redis锁,我脸都绿了......
脚本之家
你与百万开发者在一起
本文经掘金作者 Vt 授权转载
作者:Vt
出处:https://juejin.im/post/5e61a454e51d4526f071e1df
在家办公的第 N 周,也不知道笔者工位上的键盘和显示器有没有想我,不知道会不会落灰太严重,被保洁阿姨扔掉了。
笔者今天带来一篇关于 Redis 锁的文章,连敲带画码出此文,有一些细节,对 Redis 锁不清晰的盆友不妨瞧一瞧。
如果是有经验的盆友,挑挑毛病,那笔者是更感谢了!闲话不多,马上发车。
谈起 Redis 锁,下面三个,算是出现最多的高频词汇:
Setnx
RedLock
Redisson
Setnx
一般代指 Redis 中对 Set 命令加上 NX 参数进行使用,Set 这个命令,目前已经支持这么多参数可选:
SET key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]
当然了,就不在文章中默写 API 了,基础参数还有不清晰的,可以蹦到官网。
那么为什么要使用 PX 30000 去设置一个超时时间?是怕进程 A 不讲道理啊,锁没等释放呢,万一崩了,直接原地把锁带走了,导致系统中谁也拿不到锁。
就算这样,还是不能保证万无一失。如果进程 A 又不讲道理,操作锁内资源超过笔者设置的超时时间,那么就会导致其他进程拿到锁,等进程 A 回来了,回手就是把其他进程的锁删了,如图:
当进程 B 操作完成,去释放锁的时候(图中 T8 时刻):
当解锁的时候,先获取 Value 判断是否是当前进程加的锁,再去删除。伪代码:
String uuid = xxxx;
// 伪代码,具体实现看项目中用的连接工具
// 有的提供的方法名为set 有的叫setIfAbsent
set Test uuid NX PX 3000
try{
// biz handle....
} finally {
// unlock
if(uuid.equals(redisTool.get('Test')){
redisTool.del('Test');
}
}
这回看起来是不是稳了?相反,这回的问题更明显了,在 Finally 代码块中,Get 和 Del 并非原子操作,还是有进程安全问题。
搞清劣势所在,才能更好的完善。
上文中最后这段代码,还是有很多公司在用的。
大公司实现规范,但是小司小项目虽然存在不严谨,可并发倒也不高,出问题的概率和大公司一样低。
-- 鲁迅
那么删除锁的正确姿势之一,就是可以使用 Lua 脚本,通过 Redis 的 eval/evalsha 命令来运行:
-- lua删除锁:
-- KEYS和ARGV分别是以集合方式传入的参数,对应上文的Test和uuid。
-- 如果对应的value等于传入的uuid。
if redis.call('get', KEYS[1]) == ARGV[1]
then
-- 执行删除操作
return redis.call('del', KEYS[1])
else
-- 不成功,返回0
return 0
end
1. setnx Test uuid
2. expire Test 30
如果有缘你也阅读过这篇文章,并且学到了这个套路,作为本文的笔者我要加一句提醒:请注意你的工作年限!首先回答官网表明即将废弃的命令,再引出 Set 命令七年前的“新特性”,如果是刚毕业不久的人这么说,面试官会以为自己穿越了。
你套路面试官,面试官也会套路你。
-- vt・沃兹基硕德
Redisson
但是 Redisson 这个客户端可有点厉害,笔者在官网截了仅仅是一部分的图:
笔者也非常严谨的思考了一下:这么厉害的东西哪能写废代码?
其实笔者仔细看了一下,加锁解锁的 Lua 脚本考虑的非常全面,其中就包括锁的重入性,这点可以说是考虑非常周全,我也随手写了代码测试一下:
RedLock
笔者大概画了一下对红锁的理解:
顺序向五个节点请求加锁
根据一定的超时时间来推断是不是跳过该节点
三个节点加锁成功并且花费时间小于锁的有效期
认定加锁成功
回头看看 Redis 官网关于红锁的描述,就在这篇描述页面的最下面,你能看到著名的关于红锁的神仙打架事件。
Martin Kleppmann 的质疑贴
Antirez 的反击贴
总结
更多精彩
在公众号后台对话框输入以下关键词
查看更多优质内容!
女朋友 | 大数据 | 运维 | 书单 | 算法
大数据 | JavaScript | Python | 黑客
AI | 人工智能 | 5G | 区块链
机器学习 | 数学 | 送书
●
● SpringBoot项目:RedisTemplate实现轻量级消息队列