Redis++:Redis做分布式锁真的靠谱吗
Redis做分布式锁真的靠谱吗
Redis的分布式锁可以通过Lua进行实现,通过setnx和expire命令连用的方式 || 也可以使用高版本的方法同时设置失效时间,但是假如在以下情况下,就会造成无锁的现象。
注:分布式锁能不用就不用,尤其是在高并发的情况下。
释放了不该释放的锁:
有时候直接执行 del mylock 是有问题的,我们不能直接执行 del mylock 为什么?—— 会导致 “信号错误”,释放了不该释放的锁 。
假设如下场景:
| 时间线 | 线程1 | 线程2 | 线程3 |
|---|---|---|---|
| 时刻1 | 执行 setnx mylock val1 加锁 | 执行 setnx mylock val2 加锁 | 执行 setnx mylock val2 加锁 |
| 时刻2 | 加锁成功 | 加锁失败 | 加锁失败 |
| 时刻3 | 执行任务... | 尝试加锁... | 尝试加锁... |
| 时刻4 | 任务继续(锁超时,自动释放了) | setnx 获得了锁(因为线程1的锁超时释放了) | 仍然尝试加锁... |
| 时刻5 | 任务完毕,del mylock 释放锁 | 执行任务中... | 获得了锁(因为线程1释放了线程2的) |
| ... |
上面的表格中,有两个维度,一个是纵向的时间线,一个是横线的线程并发竞争。
我们可以发现线程 1 在开始的时候比较幸运,获得了锁,最先开始执行任务,但是,由于他比较耗时,最后锁超时自动释放了他都还没执行完。
因此,线程 2 和线程3 的机会来了。
而这一轮,线程2 比较幸运,得到了锁。
可是,当线程2正在执行任务期间,线程1 执行完了,还把线程2的锁给释放了。
这就相当于,本来你锁着门在厕所里边尿尿,进行到一半的时候,别人进来了,因为他配了一把和你一模一样的钥匙!这就乱套了啊
因此,我们需要安全的释放锁——“不是我的锁,我不能瞎释放”。
所以,我们在加锁的时候,就需要标记“这是我的锁”,在释放的时候在判断 “ 这是不是我的锁?”。
这里就需要在释放锁的时候加上逻辑判断,合理的逻辑应该是这样的:
1. 线程1 准备释放锁 , 锁的key 为 mylock 锁的 value 为 thread1_magic_num
2. 查询当前锁 current_value = get mylock
3. 判断 if current_value == thread1_magic_num -- > 是 我(线程1)的锁
else -- >不是 我(线程1)的锁
4. 是我的锁就释放,否则不能释放(而是执行自己的其他逻辑)。
为了实现上面这个逻辑,我们是无法通过 redis 自带的命令直接完成的。
如果,再写复杂的代码去控制释放锁,则会让整体代码太过于复杂了。
所以,我们引入了lua脚本。
结合Lua 脚本实现释放锁的功能,更简单,redis 执行lua脚本也是原子的,所以更合适,让合适的人干合适的事,岂不更好。
Lua实现分布式锁 :
加锁:
--[[
思路:
1.用2个局部变量接受参数
2.由于redis内置lua解析器,执行加锁命令
3.如果加锁成功,则设置超时时间
4.返回加锁命令的执行结果
]]
local key = KEYS[1]
local value = KEYS[2] local rs1 = redis.call('SETNX',key,value)
if rs1 == true
then
redis.call('SETEX', key,3600, value)
end return rs1
解锁:
--[[
思路:
1.接受redis传来的参数
2.判断是否是自己的锁,是则删掉
3.返回结果值
]]
local key = KEYS[1]
local value = KEYS[2] if redis.call('get',key) == value
then
return redis.call('del',key)
else
return false
end
如此,我们便实现了锁的安全释放。
同时,我们还需要结合业务逻辑,进行具体健壮性的保证,比如如果结束了一定不能忘记释放锁,异常了也要释放锁,某种情况下是否需要回滚事务等。
总结这个分布式锁使用的过程便是:
- 加锁时 key 同,value 不同。
- 释放锁时,根据value判断,是不是我的锁,不能释放别人的锁。
- 及时释放锁,而不是利用自动超时。
- 锁超时时间一定要结合业务情况权衡,过长,过短都不行。
- 程序异常之处,要捕获,并释放锁。如果需要回滚的,主动做回滚、补偿。保证整体的健壮性,一致性。
用redis做分布式锁真的靠谱吗???
上面的文字中,我们讨论如何使用redis作为分布式锁,并讨论了一些细节问题,如锁超时的问题、安全释放锁的问题。
目前为止,似乎很完美的解决的我们想要的分布式锁功能。然而事情并没有这么简单,用redis做分布式锁并不“靠谱”。
不靠谱的情况:
比如在 Sentinel 集群中,主节点挂掉时,从节点会取而代之,客户端上却并没有明显感知。
原先第一个客户端在主节点中申请成功了一把锁,但是这把锁还没有来得及同步到从节点,主节点突然挂掉了。
然后从节点变成了主节点,这个新的节点内部没有这个锁,所以当另一个客户端过来请求加锁时,立即就批准了。
这样就会导致系统中同样一把锁被两个客户端同时持有,不安全性由此产生。
不过这种不安全也仅仅是在主从发生 failover 的情况下才会产生,而且持续时间极短,业务系统多数情况下可以容忍。

redlock:
为了解决故障转移情况下的缺陷,Antirez 发明了 Redlock 算法,使用redlock算法,需要多个redis实例,加锁的时候,它会想多半节点发送 setex mykey myvalue 命令,只要过半节点成功了,那么就算加锁成功了。
释放锁的时候需要想所有节点发送del命令。
这是一种基于【大多数都同意】的一种机制。
感兴趣的可以查询相关资料。在实际工作中使用的时候,我们可以选择已有的开源实现,python有redlock-py,java 中有Redisson redlock。
为了使用 Redlock,需要提供多个 Redis 实例,这些实例之前相互独立没有主从关系。
同很多分布式算法一样,redlock 也使用「大多数机制」。
加锁时,它会向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要过半节点 set成功,那就认为加锁成功。
释放锁时,需要向所有节点发送 del 指令。
不过 Redlock 算法还 需要考虑出错重试、时钟漂移等很多细节问题,同时因为 Redlock 需要向多个节点进行读写,意味着相比单实例 Redis 性能会下降一些。
Redlock 使用场景:
如果你很在乎高可用性,希望挂了一台 redis 完全不受影响,那就应该考虑 redlock。
不过代价也是有的,需要更多的 redis 实例,性能也下降了,代码上还需要引入额外的library,运维上也需要特殊对待,这些都是需要考虑的成本,使用前请再三斟酌。
人生无常大肠包小肠
引文:https://www.cnblogs.com/dalianpai/p/12709408.html
Redis++:Redis做分布式锁真的靠谱吗的更多相关文章
- 基于Redis的分布式锁真的安全吗?
说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...
- 程序员修神之路--redis做分布式锁可能不那么简单
菜菜哥,复联四上映了,要不要一起去看看? 又想骗我电影票,对不对? 呵呵,想去看了叫我呀 看来你工作不饱和呀 哪有,这两天我刚基于redis写了一个分布式锁,很简单 不管你基于什么做分布式锁,你觉得很 ...
- 基于 Redis 做分布式锁
基于 REDIS 的 SETNX().EXPIRE() 方法做分布式锁 setnx() setnx 的含义就是 SET if Not Exists,其主要有两个参数 setnx(key, value) ...
- Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流
1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...
- Redis高并发分布式锁详解
为什么需要分布式锁 1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock. 2.但是随 ...
- 基于Redis的简单分布式锁的原理
参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...
- redis客户端、分布式锁及数据一致性
Redis Java客户端有很多的开源产品比如Redission.Jedis.lettuce等. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持:Redis ...
- 如何用redis正确实现分布式锁?
先把结论抛出来:redis无法正确实现分布式锁!即使是redis单节点也不行!redis的所谓分布式锁无法用在对锁要求严格的场景下,比如:同一个时间点只能有一个客户端获取锁. 首先来看下单节点下一般r ...
- spring boot:用redis+redisson实现分布式锁(redisson3.11.1/spring boot 2.2)
一,为什么要使用分布式锁? 如果在并发时锁定代码的执行,java中用synchronized锁保证了线程的原子性和可见性 但java锁只在单机上有效,如果是多台服务器上的并发访问,则需要使用分布式锁, ...
随机推荐
- C#CancellationToken/CancellationTokenSource-取消令牌/取消令牌源 CT/CTS
详细情况:https://www.cnblogs.com/wucy/p/15128365.html 背景 为什么引入取消令牌? Thread.abort()方法会破坏同步锁中代码的原子逻辑,破坏锁的作 ...
- PostgreSQL-PL/pgSQL控制结构
PL/pgSQL的控制结构是最重要及最有用的一部分了,在实际工作场景都离不开业务处理逻辑,在写PL/pgSQL时,利用控制结构来操作数据.PL/pgSQL支持的控制结构与其他语言几乎差不多,比如:条件 ...
- GNS3与抓包工具Wireshark的关联
转至:https://blog.51cto.com/xpleaf/1615145 (一)前言 本博文分享GNS3与Wireshark关联的方法. 显然现在网络上已经有类似的文章分享,而本博文旨在提供更 ...
- 路径修改后cmd命令行窗口仍然没有变化的原因
修改环境变量后,要重启cmd再输入才有用
- Mysql引擎、隔离机制、存储结构、索引
目录 数据库常用的两种引擎 两种引擎差异对比 如何选择引擎 两个引擎索引结构 查找mysql数据存储位置方式 MyISAM InnoDB 1. 非独立表空间 2. 独立表空间 3. idb文件存的哪些 ...
- 面试官:我们来聊一聊Redis吧,你了解多少就答多少
哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新,建议收藏关注 一.前言 作为一名Java程 ...
- 4. Java流程控制
4.Java流程控制 4.1.用户交互Scanner 之前我们学的基本语法中我们并没有实现程序和人的交互,但是Java给我们提供了这样一个工具类,我们可以获取用户的输入.java.util.Scann ...
- DDos攻击竟然这么恐怖,它的原理是什么?
DDOS的定义 分布式拒绝服务(DDoS:Distributed Denial of Service)攻击指借助于客户/服务器技术,将多个计算机联合起来作为攻击平台,对一个或多个目标发动DDoS攻击, ...
- redirect route 路由传参
return redirect()->route('exams.sign',['token'=>$token,'id'=>$result['id']]); // 签到页面 Route ...
- 10 Java的方法 可变参数
5.可变参数 JDK 1.5开始,Java支持传递同类型的可变参数给一个方法. 在方法声明中,在制定参数类型后加一个省略号(-). 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数.任何普通 ...