Redis实现分布式锁

佛系码
• 阅读 1119

一、redis分布式锁的简易实现

用redis实现分布式锁是一个老生常谈的问题了。因为redis单条命令执行的原子性和高性能,当多个客户端执行setnx(相同key)时,最多只有一个获得成功。因此在对可用性要求不是特别高的场景下,redis分布式锁方案不失为一个性价比高的实现。

  1. 多个客户端执行setnx lockid random px lock-duration
    其中lockid与被锁住资源唯一对应。random为随机值,用于客户端判定自身是否为该锁的owner。lock-duration为锁保持时长,由业务操作耗时决定。
  2. 对于客户端来说,执行命令后,如果redis返回1,则表示抢到锁;否则没抢到
  3. 对于抢到锁的客户端,完成业务操作之后,需主动删除该锁。

二、redis分布式锁的注意事项

1. 锁必须要设定一个过期时间

如果不设置过期时间,考虑如下时序:

  • 客户端A抢锁成功
  • 客户端A的进程异常退出,没来得及主动释放锁
  • 其他客户端试图抢锁(毫无疑问是失败的)

如上所示,客户端A抢到锁了,但是由于某些异常导致进程还没有来得及释放锁就退出了。这样其他客户端setnx的返回永远是0,即永远也抢不到锁。

相反,如果设置过期时间,即使客户端A没有主动释放锁,到了过期时间之后redis也会自动释放。

  • 客户端A抢锁成功
  • 客户端A的进程异常退出,没来得及主动释放锁
  • 其他客户端抢锁失败
  • 锁自动过期
  • 其他客户端抢锁成功

2. 获取锁的命令不能分为两步执行

如果实现为,

setnx lockid
expire lockid lock-duration 

除非使用lua script, 否则redis无法支持上述两个命令的原子性,当第一个命令执行完成后,抢到锁的客户端A异常退出了,那么其他客户端将永远抢到锁。

注:redis在2.6.12版本后已经支持setnx命令的TTL参数,这个问题不复存在

3. 锁的值必须设置为随机值

假设锁的值为固定值,考虑如下情况

  • 客户端A抢到锁,执行业务操作
  • 客户端A由于某些原因阻塞,超过了锁有效时间,导致锁自动被释放
  • 客户端B抢锁成功,执行操作
  • 客户端A从阻塞中恢复,主动释放锁,执行del lockid
  • 客户端B创建的锁被客户端A删除。此时客户端C抢锁成功,客户端B与C的业务操作产生竞态。

如果锁的值是随机值,并且每次成功加锁时,都记录该随机值的话,并且释放锁时,判断锁的值是否等于记录值,等于则del, 不等于则跳过。

4. 释放锁时,需使用lua script封装保证原子性

如果不使用lua封装释放锁的逻辑,考虑时序:

  • 客户端A抢到锁,执行业务操作
  • 客户端A完成业务操作,主动释放锁:首先get lockid,发现记录值和锁当前值相等,判定该锁为自己所加。
  • 客户端A由于某些原因阻塞(比如GC),超过锁有效时间,锁被redis自动释放
  • 客户端B成功抢锁
  • 客户端A从阻塞中恢复,执行下一步del lockid,客户端B加的锁被A释放
  • 客户端C抢锁成功,B与C产生竞态

而redis执行lua script的原子性能避免上述问题。

5. 多个redis节点保证高可用

如果只在一个redis节点上抢锁,如果该节点宕机,将导致所有的客户端都抢不到锁,无法保证服务的高可用。

三、redsync实现一览

redlock是一种基于redis的分布式锁算法。而redsync是redlock算法的golang实现,其暴露了三个API:加锁(Lock),解锁(Unlock),续锁(Extend)

1. Lock

  • 首先随机生成一个value
  • 针对所有redis连接,执行set lockid value NX PX lock-duration
  • 如果超过半数连接上的请求都正常返回,且now < start + (1 - factor) * expire,意味着抢锁成功
  • 否则先清理key, 然后重试,重试时间间隔可由用户自定义。

2. Unlock

针对所有redis实例,执行lua脚本。这里会判断key对应的value和Mutex在Lock时使用的value值是否一致,只有一致了执行del命令。此举是为了保证每个客户端不会释放别的客户端创建的锁。

 if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end 

如果有超过半数实例上的请求返回,则意味着释放锁成功。否则判定失败。

3. Extend

Extend操作是为了保证当客户端业务处理时长超过expire时间时,客户端可主动延长锁的过期时间,而无需二次抢锁。针对所有redis连接,执行lua脚本,重新设置过期时间

 if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("pexpire", KEYS[1], ARGV[2])
    else
        return 0
    end 

半数以上返回成功,则意味着Extend成功

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
5个月前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。languageJavaScript"name":"vuecliversion2","version":"1.0.0","desc
浅梦一笑 浅梦一笑
5个月前
初学 Python 需要安装哪些软件?超级实用,小白必看!
编程这个东西是真的奇妙。对于懂得的人来说,会觉得这个工具是多么的好用、有趣,而对于小白来说,就如同大山一样。其实这个都可以理解,大家都是这样过来的。那么接下来就说一下python相关的东西吧,并说一下我对编程的理解。本人也是小白一名,如有不对的地方,还请各位大神指出01名词解释:如果在编程方面接触的比较少,那么对于软件这一块,有几个名词一定要了解,比如开发环
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
技术小男生 技术小男生
5个月前
linux环境jdk环境变量配置
1:编辑系统配置文件vi/etc/profile2:按字母键i进入编辑模式,在最底部添加内容:JAVAHOME/opt/jdk1.8.0152CLASSPATH.:$JAVAHOME/lib/dt.jar:$JAVAHOME/lib/tools.jarPATH$JAVAHOME/bin:$PATH3:生效配置
光头强的博客 光头强的博客
5个月前
Java面向对象试题
1、请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
刚刚好 刚刚好
5个月前
css问题
1、在IOS中图片不显示(给图片加了圆角或者img没有父级)<div<imgsrc""/</divdiv{width:20px;height:20px;borderradius:20px;overflow:h
小森森 小森森
5个月前
校园表白墙微信小程序V1.0 SayLove -基于微信云开发-一键快速搭建,开箱即用
后续会继续更新,敬请期待2.0全新版本欢迎添加左边的微信一起探讨!项目地址:(https://www.aliyun.com/activity/daily/bestoffer?userCodesskuuw5n)\2.Bug修复更新日历2.情侣脸功能大家不要使用了,现在阿里云的接口已经要收费了(土豪请随意),\\和注意
晴空闲云 晴空闲云
5个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
Stella981 Stella981
1年前
Redis 分布式锁的实现以及存在的问题(Spring Cloud)
一.Redis分布式锁这里是列表文本锁是针对某个资源,保证其访问的互斥性,在实际使用当中,这个资源一般是一个字符串。使用Redis实现锁,主要是将资源放到Redis当中,利用其原子性,当其他线程访问时,如果Redis中已经存在这个资源,就不允许之后的一些操作。springboot使用Redis的操作主要
Easter79 Easter79
1年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
密钥管理系统-为你的天翼云资产上把“锁
本文关键词:数据安全,密码机,密钥管理一、你的云上资产真的安全么?1.2021年1月,巴西的一个数据库30TB数据被破坏,泄露的数据包含有1.04亿辆汽车和约4000万家公司的详细信息,受影响的人员数量可能有2.2亿;2.2021年2月,广受欢迎的音频聊天室应用Clubhouse的用户数据被恶意黑客或间谍窃取。据悉,一位身份不明的用户能够将Clubho
佛系码
佛系码
Lv1
码路无尽,回头是岸
4
文章
0
粉丝
1
获赞