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锁只在单机上有效,如果是多台服务器上的并发访问,则需要使用分布式锁, ...
随机推荐
- 第一次打开pycharm运行python文件报错”No Python interpreter selected“问题的解决办法
前面没有细讲,这里细述一下安装pycharm后,第一次打开pycharm运行python文件报错"No Python interpreter selected"问题的解决办法. 出 ...
- 解决gpg failed to sign the data fatal: failed to write commit object解决方案
今天有位新同事在comit代码的时候一直报这个错误: gpg failed to sign the data fatal: failed to write commit object. 看到网上说gp ...
- C# 方法里面的默认参数
最近有很多地方都用到了方法的默认参数,遂总结之. (一)先从原理说起 在C#中,一旦为某个参数分配了一个默认值,编译器就会向内部该参数应用定制一个attribute 即是(OptionalAttrib ...
- 系统资源监控——联用awk与grep文本处理工具,截取磁盘使用量字段
一.使用到的命令行 1.df : df -h #将磁盘使用量用表的形式呈现. 2.awk: awk '{print $5}' #默认分隔符是空格,$后的数字是指定从第几列开始截取. awk -F [] ...
- python 中的迭代器和生成器简单介绍
可迭代对象和迭代器 迭代(iterate)意味着重复,就像 for 循环迭代序列和字典那样,但实际上也可使用 for 循环迭代其他对象:实现了方法 __iter__ 的对象(迭代器协议的基础). __ ...
- 报错:net::err_unknown_url_scheme的解决办法
在项目中设置了api请求和web页面请求的地址,如下图: 控制台报错,如下图: 问题是:没有加入"http://"这个头,因此访问不到. 解决办法: 再次访问正常
- YUV相关积累
关于yuv 格式-Semi Planar和Planar https://www.cnblogs.com/welen/articles/5454315.html
- tf源码中的object_detection_tutorial.ipynb文件
今天看到原来下载的tf源码的目标检测源码中test的代码不知道跑哪儿去了,这里记录一下... Imports import numpy as np import os import six.moves ...
- (第一章第三部分)TensorFlow框架之会话
系列博客链接: (一)TensorFlow框架介绍:https://www.cnblogs.com/kongweisi/p/11038395.html (二)TensorFlow框架之图与Tensor ...
- MyBatis 使用(XML版本)
一.MyBatis相关概念 对象 / 关系数据库映射(ORM) ORM全称Object/Relation Mapping:表示对象-关系映射的缩写 ORM完成⾯向对象的编程语⾔到关系数据库的映射.当O ...