redis实现分布式锁方式(redis分布式锁三个方法) 刘英 • 2023年12月18日 下午5:05 • 问答百科 为什么需要分布式锁 在聊分布式锁之前,有必要先解释一下,为什么需要分布式锁。 与分布式锁相对就的是单机锁,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个进程中。如果换做是多个进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。 Redis本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁。而且 Redis 的读写性能高,可以应对高并发的锁操作场景。本文主要探讨如何基于Redis实现分布式锁以及实现过程中可能面临的问题。 分布式锁如何实现 作为分布式锁实现过程中的共享存储系统,Redis可以使用键值对来保存锁变量,在接收和处理不同客户端发送的加锁和释放锁的操作请求。那么,键值对的键和值具体是怎么定的呢?我们要赋予锁变量一个变量名,把这个变量名作为键值对的键,而锁变量的值,则是键值对的值,这样一来,Redis就能保存锁变量了,客户端也就可以通过Redis的命令操作来实现锁操作。 想要实现分布式锁,必须要求Redis有互斥的能力。可以使用SETNX命令,其含义是SET IF NOT EXIST,即如果key不存在,才会设置它的值,否则什么也不做。两个客户端进程可以执行这个命令,达到互斥,就可以实现一个分布式锁。 以下展示了Redis使用key/value对保存锁变量,以及两个客户端同时请求加锁的操作过程。 // 加锁 SETNX lock_key 1 // 业务逻辑 DO THINGS // 释放锁 DEL lock_key 但是,以上实现存在一个很大的问题,当客户端1拿到锁后,如果发生下面的场景,就会造成死锁。 程序处理业务逻辑异常,没及时释放锁 进程挂了,没机会释放锁 以上情况会导致已经获得锁的客户端一直占用锁,其他客户端永远无法获取到锁。 如何避免死锁 为了解决以上死锁问题,最容易想到的方案是在申请锁时,在Redis中实现时,给锁设置一个过期时间,假设操作共享资源的时间不会超过10s,那么加锁时,给这个key设置10s过期即可。 但以上操作还是有问题,加锁、设置过期时间是2条命令,有可能只执行了第一条,第二条却执行失败,例如: SETNX执行成功,执行EXPIRE时由于网络问题,执行失败 SETNX执行成功,Redis异常宕机,EXPIRE没有机会执行 SETNX执行成功,客户端异常崩溃,EXPIRE没有机会执行 总之这两条命令如果不能保证是原子操作,就有潜在的风险导致过期时间设置失败,依旧有可能发生死锁问题。幸好在Redis 2.6.12之后,Redis扩展了SET命令的参数,可以在SET的同时指定EXPIRE时间,这条操作是原子的,例如以下命令是设置锁的过期时间为10秒。 SET lock_key 1 EX 10 NX 至此,解决了死锁的问题,但还是有其他问题。想像下面这个这样一种场景: 锁过期 释放了别人的锁 第1个问题是评估操作共享资源的时间不准确导致的,如果只是一味增大过期时间,只能缓解问题降低出现问题的概率,依旧无法彻底解决问题。原因在于客户端在拿到锁之后,在操作共享资源时,遇到的场景是很复杂的,既然是预估的时间,也只能是大致的计算,不可能覆盖所有导致耗时变长的场景。 第2个问题是释放了别人的锁,原因在于释放锁的操作是无脑操作,并没有检查这把锁的归属,这样解锁不严谨。如何解决呢? 锁被别人给释放了 解决办法是,客户端在加锁时,设置一个只有自己知道的唯一标识进去,例如可以是自己的线程ID,如果是redis实现,就是SET key unique_value EX 10 NX。之后在释放锁时,要先判断这把锁是否归自己持有,只有是自己的才能释放它。 //释放锁 比较unique_value是否相等,避免误释放 if redis.get("key") == unique_value then return redis.del("key") 这里释放锁使用的是GET + DEL两条命令,这时又会遇到原子性问题了。 客户端1执行GET,判断锁是自己的 客户端2执行了SET命令,强制获取到锁(虽然发生概率很低,但要严谨考虑锁的安全性) 客户端1执行DEL,却释放了客户端2的锁 由此可见,以上GET + DEL两个命令还是必须原子的执行才行。怎样原子执行两条命令呢?答案是Lua脚本,可以把以上逻辑写成Lua脚本,让Redis执行。因为Redis处理每个请求是单线程执行的,在执行一个Lua脚本时其它请求必须等待,直到这个Lua脚本处理完成,这样一来GET+DEL之间就不会有其他命令执行了。 以下是使用Lua脚本(unlock.script)实现的释放锁操作的伪代码,其中,KEYS[1]表示lock_key,ARGV[1]是当前客户端的唯一标识,这两个值都是我们在执行 Lua脚本时作为参数传入的。 //Lua脚本语言,释放锁 比较unique_value是否相等,避免误释放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end 最后我们执行以下命令,即可 redis-cli --eval unlock.script lock_key , unique_value 这样一路优先下来,整个加锁、解锁流程就更严谨了,先小结一下,基于Redis实现的分布式锁,一个严谨的流程如下: 加锁时要设置过期时间SET lock_key unique_value EX expire_time NX 操作共享资源 释放锁:Lua脚本,先GET判断锁是否归属自己,再DEL释放锁 有了这个严谨的锁模型,我们还需要重新思考之前的那个问题,锁的过期时间不好评估怎么办。 如何确定锁的过期时间 前面提到过,过期时间如果评估得不好,这个锁就会有提前过期的风险,一种妥协的解决方案是,尽量冗余过期时间,降低锁提前过期的概率,但这个方案并不能完美解决问题。是否可以设置这样的方案,加锁时,先设置一个预估的过期时间,然后开启一个守护线程,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行续期,重新设置过期时间。 这是一种比较好的方案,已经有一个库把这些工作都封装好了,它就是Redisson。Redisson是一个Java语言实现的Redis SDK客户端,在使用分布式锁时,它就采用了自动续期的方案来避免锁过期,这个守护线程我们一般叫它看门狗线程。这个SDK提供的API非常友好,它可以像操作本地锁一样操作分布式锁。客户端一旦加锁成功,就会启动一个watch dog看门狗线程,它是一个后台线程,会每隔一段时间(这段时间的长度与设置的锁的过期时间有关)检查一下,如果检查时客户端还持有锁key(也就是说还在操作共享资源),那么就会延长锁key的生存时间。 Redis的部署方式对锁的影响 上面讨论的情况,都是所在单个Redis 实例中可能产生的问题,并没有涉及到Redis的部署架构细节。 Redis发展到现在,几种常见的部署架构有: 单机模式; 主从模式; 哨兵(sentinel)模式; 集群模式; 我们使用Redis时,一般会采用主从集群+哨兵的模式部署,哨兵的作用就是监测redis节点的运行状态。普通的主从模式,当master崩溃时,需要手动切换让slave成为master,使用主从+哨兵结合的好处在于,当master异常宕机时,哨兵可以实现故障自动切换,把slave提升为新的master,继续提供服务,以此保证可用性。那么当主从发生切换时,分布式锁依旧安全吗? 客户端1在master上执行SET命令,加锁成功 此时,master异常宕机,SET命令还未同步到slave上(主从复制是异步的) 哨兵将slave提升为新的master,但这个锁在新的master上丢失了,导致客户端2来加锁成功了,两个客户端共同操作共享资源 可见,当引入Redis副本后,分布式锁还是可能受到影响。即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况。 集群模式+Redlock实现高可靠的分布式锁 为了避免Redis实例故障而导致的锁无法工作的问题,Redis的开发者 Antirez提出了分布式锁算法Redlock。Redlock算法的基本思路,是让客户端和多个独立的Redis实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。这样一来,即使有单个Redis实例发生故障,因为锁变量在其它实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。 来具体看下Redlock算法的执行步骤。Redlock算法的实现要求Redis采用集群部署模式,无哨兵节点,需要有N个独立的Redis实例(官方推荐至少5个实例)。接下来,我们可以分成3步来完成加锁操作。 第二步是,客户端按顺序依次向N个Redis实例执行加锁操作。 这里的加锁操作和在单实例上执行的加锁操作一样,使用SET命令,带上NX、EX/PX选项,以及带上客户端的唯一标识。当然,如果某个Redis实例发生故障了,为了保证在这种情况下,Redlock算法能够继续运行,我们需要给加锁操作设置一个超时时间。如果客户端在和一个Redis实例请求加锁时,一直到超时都没有成功,那么此时,客户端会和下一个Redis实例继续请求加锁。加锁操作的超时时间需要远远地小于锁的有效时间,一般也就是设置为几十毫秒。 第三步是,一旦客户端完成了和所有Redis实例的加锁操作,客户端就要计算整个加锁过程的总耗时。 客户端只有在满足两个条件时,才能认为是加锁成功,条件一是客户端从超过半数(大于等于 N/2+1)的Redis实例上成功获取到了锁;条件二是客户端获取锁的总耗时没有超过锁的有效时间。 为什么大多数实例加锁成功才能算成功呢?多个Redis实例一起来用,其实就组成了一个分布式系统。在分布式系统中总会出现异常节点,所以在谈论分布式系统时,需要考虑异常节点达到多少个,也依旧不影响整个系统的正确运行。这是一个分布式系统的容错问题,这个问题的结论是:如果只存在故障节点,只要大多数节点正常,那么整个系统依旧可以提供正确服务。 在满足了这两个条件后,我们需要重新计算这把锁的有效时间,计算的结果是锁的最初有效时间减去客户端为获取锁的总耗时。如果锁的有效时间已经来不及完成共享数据的操作了,我们可以释放锁,以免出现还没完成共享资源操作,锁就过期了的情况。 当然,如果客户端在和所有实例执行完加锁操作后,没能同时满足这两个条件,那么,客户端就要向所有Redis节点发起释放锁的操作。为什么释放锁,要操作所有的节点呢,不能只操作那些加锁成功的节点吗?因为在某一个Redis节点加锁时,可能因为网络原因导致加锁失败,例如一个客户端在一个Redis实例上加锁成功,但在读取响应结果时由于网络问题导致读取失败,那这把锁其实已经在Redis上加锁成功了。所以释放锁时,不管之前有没有加锁成功,需要释放所有节点上的锁以保证清理节点上的残留的锁。 在Redlock算法中,释放锁的操作和在单实例上释放锁的操作一样,只要执行释放锁的 Lua脚本就可以了。这样一来,只要N个Redis实例中的半数以上实例能正常工作,就能保证分布式锁的正常工作了。所以,在实际的业务应用中,如果你想要提升分布式锁的可靠性,就可以通过Redlock算法来实现。 版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 ivillcn@qq.com 举报,一经查实,本站将立刻删除。文章链接:https://www.shangraobbs.com/n/4619.html 赞 (0) 刘英普通用户 0 0 生成海报 黄金价格暴跌大降价:中国黄金(2022年03月29日)最新黄金金价表折叠纯度计量 上一篇 2023年12月18日 下午5:01 pdf转长图片软件(pdf转word免费的软件) 下一篇 2023年12月18日 相关推荐 黄渤最搞笑的电影推荐(这6部喜剧电影,让你从头笑到尾) 1.《斗牛》 《斗牛》是一出喜剧,却是那种笑着笑着,笑容会渐渐僵硬在脸上的喜剧。该影片的成功,在于它拍出了个体的善、群体的恶,拍出了镜头背后一个导演对于他主人公揶揄、怜悯又珍视的复... Feeling tone 2023年10月11日 • 问答百科 000 ps怎么让字体有弧度(教你6个PS实用小技巧) 这6个PS实用小技巧,十分好用,省时省力,很多人都知道,同样也有很多人不知道,特别是新手,现在手把手教你。 对PS不太熟悉的你,有必要get一下。 或许你全都知道,可是你不打算分享... 长城号SEO专员 2024年1月19日 • 问答百科 000 企业计划管理软件(管理软件功能介绍) 划分计划层次能够体现计划管理由宏观到微观,由战略到战术、由粗到细的深化过程。也能明确责任,不同层次计划的制订或实施有不同的管理层负责。 企业经营规划的目标,通常是以货币或金额来表达... 刘英 2023年12月29日 • 问答百科 000 问答百科 猜谜语儿童3-10岁(脑筋急转弯3-10岁) 孩子喜欢脑筋急转弯,但是买的几本书都很难猜,感觉答案很无厘头,对孩子没什么用处。网上下载的脑筋急转弯很多,但是需要筛选,因为很多都不适合孩子。所以我就自己给孩子筛选和编辑了这些脑筋... Feeling tone 2023年11月4日 000 虚假发货如何申诉成功(淘宝虚假发货维权步骤) 淘宝买家投诉的正确方法! 遇到这些情况的姐妹们千万不要嫌麻烦,忍气吞声啊! 1.如果商家承诺48h发货却没有发货可以按步骤投诉发货问题,会获得五元红包的赔偿。 2.如果商家发货却没... Feeling tone 2023年8月13日 • 问答百科 000 6000个奥特曼合体的奥特曼叫什么(杰德VS小金人) 这里盘点的合体奥特曼意思是指两个或者两个以上实体合体而成的奥特曼,不包含借力量一列的 一,超级泰罗 登场于奥特物语,泰罗使用奥特之角接受佐菲、奥特曼、赛文奥特曼、杰克奥特曼、艾斯奥... Feeling tone 2023年8月19日 • 问答百科 000 房地产项目进度计划表(施工进度计划表范本) 进度管理是项目管理过程的一个普通应用,很好掌握进度状况,可以对整个项目发展有一个全面掌握,对于任何事情的发展,都要有一个全面了解,特别是很重要的事情。 如果对进度没有很好掌握,那么... 刘英 2024年1月6日 • 问答百科 000 问答百科 100m带宽服务器多少钱(带宽百兆独立服务器价格表) 现在随着电商、视频和下载等网站规模越来越大,不但对服务器的性能有着较高的要求,同时对带宽要求也较高。今天主机侦探小编推荐的RAKsmart香港大带宽服务器刚好可以满足这类企业建站的... 刘英 2024年2月3日 000 问答百科 家用光纤接头接法图解(超详细的图解过程) 针对光纤入户如何布线以及如何接线,我结合自己的工作经验做个简单分享。 1、弱电箱选择弱电箱作为家庭的网络集中点,光猫和路由器等重要设备都是放在弱电箱内,弱电箱在选择的时候建议选择尺... 刘英 2023年12月10日 000 memcache安装教程(redis分布式缓存) 一、分布式部署设置: memcached 虽然称为 “ 分布式 ” 缓存服务器,但服务器端并没有 “ 分布式 ” 功能。每个服务器都是完全独立和隔离的服务。 memcached 的... 长城号SEO专员 2024年1月10日 • 问答百科 000 发表回复 您的电子邮箱地址不会被公开。 必填项已用*标注*昵称: *邮箱: 网址: 记住昵称、邮箱和网址,下次评论免输入 提交