getset原子性 redis_一文看透 Redis 分布式锁进化史(解读 + 缺陷分析)

  • 各个版本的Redis分布式锁
    • V1.0
    • V1.1 基于[GETSET]
    • V2.0 基于[SETNX]
    • V3.0
    • V3.1
  • 分布式Redis锁:Redlock
  • 总结

  • 《Netty 实现原理与源码解析 —— 精品合集》
  • 《Spring 实现原理与源码解析 —— 精品合集》
  • 《MyBatis 实现原理与源码解析 —— 精品合集》
  • 《Spring MVC 实现原理与源码解析 —— 精品合集》
  • 《Spring Boot 实现原理与源码解析 —— 精品合集》
  • 《数据库实体设计合集》
  • 《Java 面试题 —— 精品合集》
  • 《Java 学习指南 —— 精品合集》

近两年来微服务变得越来越热门,越来越多的应用部署在分布式环境中,在分布式环境中,数据一致性是一直以来需要关注并且去解决的问题,分布式锁也就成为了一种广泛使用的技术,常用的分布式实现方式为Redis,Zookeeper,其中基于Redis的分布式锁的使用更加广泛。

但是在工作和网络上看到过各个版本的Redis分布式锁实现,每种实现都有一些不严谨的地方,甚至有可能是错误的实现,包括在代码中,如果不能正确的使用分布式锁,可能造成严重的生产环境故障,本文主要对目前遇到的各种分布式锁以及其缺陷做了一个整理,并对如何选择合适的Redis分布式锁给出建议。

各个版本的Redis分布式锁

V1.0

tryLock(){  SETNX Key 1EXPIRE Key Seconds
}
release(){  DELETE Key
}

这个版本应该是最简单的版本,也是出现频率很高的一个版本,首先给锁加一个过期时间操作是为了避免应用在服务重启或者异常导致锁无法释放后,不会出现锁一直无法被释放的情况。

这个方案的一个问题在于每次提交一个Redis请求,如果执行完第一条命令后应用异常或者重启,锁将无法过期,一种改善方案就是使用Lua脚本(包含SETNX和EXPIRE两条命令),但是如果Redis仅执行了一条命令后crash或者发生主从切换,依然会出现锁没有过期时间,最终导致无法释放。

另外一个问题在于,很多同学在释放分布式锁的过程中,无论锁是否获取成功,都在finally中释放锁,这样是一个锁的错误使用,这个问题将在后续的V3.0版本中解决。

针对锁无法释放问题的一个解决方案基于GETSET命令来实现

V1.1 基于GETSET

tryLock(){  NewExpireTime=CurrentTimestamp+ExpireSecondsif(SETNX Key NewExpireTime Seconds){oldExpireTime = GET(Key)if( oldExpireTime < CurrentTimestamp){NewExpireTime=CurrentTimestamp+ExpireSecondsCurrentExpireTime=GETSET(Key,NewExpireTime)if(CurrentExpireTime == oldExpireTime){return 1;}else{return 0;}}}
}
release(){  DELETE key}

思路:

  1. SETNX(Key,ExpireTime)获取锁
  2. 如果获取锁失败,通过GET(Key)返回的时间戳检查锁是否已经过期
  3. GETSET(Key,ExpireTime)修改Value为NewExpireTime
  4. 检查GETSET返回的旧值,如果等于GET返回的值,则认为获取锁成功
注意:这个版本去掉了EXPIRE命令,改为通过Value时间戳值来判断过期

问题:

  1. 在锁竞争较高的情况下,会出现Value不断被覆盖,但是没有一个Client获取到锁
  2. 在获取锁的过程中不断的修改原有锁的数据,设想一种场景C1,C2竞争锁,C1获取到了锁,C2锁执行了GETSET操作修改了C1锁的过期时间,如果C1没有正确释放锁,锁的过期时间被延长,其它Client需要等待更久的时间

V2.0 基于SETNX

tryLock(){  SETNX Key 1 Seconds
}
release(){  DELETE Key
}

Redis 2.6.12版本后SETNX增加过期时间参数,这样就解决了两条命令无法保证原子性的问题。但是设想下面一个场景:

  1. C1成功获取到了锁,之后C1因为GC进入等待或者未知原因导致任务执行过长,最后在锁失效前C1没有主动释放锁
  2. C2在C1的锁超时后获取到锁,并且开始执行,这个时候C1和C2都同时在执行,会因重复执行造成数据不一致等未知情况
  3. C1如果先执行完毕,则会释放C2的锁,此时可能导致另外一个C3进程获取到了锁

大致的流程图

f195de355fa1f4d84c0f6239d9378734.png

存在问题:

  1. 由于C1的停顿导致C1 和C2同都获得了锁并且同时在执行,在业务实现间接要求必须保证幂等性
  2. C1释放了不属于C1的锁

V3.0

tryLock(){  SETNX Key UnixTimestamp Seconds
}
release(){  EVAL(//LuaScriptif redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])elsereturn 0end)
}

这个方案通过指定Value为时间戳,并在释放锁的时候检查锁的Value是否为获取锁的Value,避免了V2.0版本中提到的C1释放了C2持有的锁的问题;另外在释放锁的时候因为涉及到多个Redis操作,并且考虑到Check And Set 模型的并发问题,所以使用Lua脚本来避免并发问题。

存在问题:

如果在并发极高的场景下,比如抢红包场景,可能存在UnixTimestamp重复问题,另外由于不能保证分布式环境下的物理时钟一致性,也可能存在UnixTimestamp重复问题,只不过极少情况下会遇到。

V3.1

tryLock(){  SET Key UniqId Seconds
}
release(){  EVAL(//LuaScriptif redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])elsereturn 0end)
}

Redis 2.6.12后SET同样提供了一个NX参数,等同于SETNX命令,官方文档上提醒后面的版本有可能去掉SETNX, SETEX, PSETEX,并用SET命令代替,另外一个优化是使用一个自增的唯一UniqId代替时间戳来规避V3.0提到的时钟问题。

这个方案是目前最优的分布式锁方案,但是如果在Redis集群环境下依然存在问题:

由于Redis集群数据同步为异步,假设在Master节点获取到锁后未完成数据同步情况下Master节点crash,此时在新的Master节点依然可以获取锁,所以多个Client同时获取到了锁

分布式Redis锁:Redlock

V3.1的版本仅在单实例的场景下是安全的,针对如何实现分布式Redis的锁,国外的分布式专家有过激烈的讨论, antirez提出了分布式锁算法Redlock,在distlock话题下可以看到对Redlock的详细说明,下面是Redlock算法的一个中文说明(引用)

假设有N个独立的Redis节点

  1. 获取当前时间(毫秒数)。
  2. 按顺序依次向N个Redis节点执行获取锁的操作。这个获取操作跟前面基于单Redis节点的获取锁的过程相同,包含随机字符串my_random_value,也包含过期时间(比如PX 30000,即锁的有效时间)。为了保证在某个Redis节点不可用的时候算法能够继续运行,这个获取锁的操作还有一个超时时间(time out),它要远小于锁的有效时间(几十毫秒量级)。客户端在向某个Redis节点获取锁失败以后,应该立即尝试下一个Redis节点。这里的失败,应该包含任何类型的失败,比如该Redis节点不可用,或者该Redis节点上的锁已经被其它客户端持有(注:Redlock原文中这里只提到了Redis节点不可用的情况,但也应该包含其它的失败情况)。
  3. 计算整个获取锁的过程总共消耗了多长时间,计算方法是用当前时间减去第1步记录的时间。如果客户端从大多数Redis节点(>= N/2+1)成功获取到了锁,并且获取锁总共消耗的时间没有超过锁的有效时间(lock validity time),那么这时客户端才认为最终获取锁成功;否则,认为最终获取锁失败。
  4. 如果最终获取锁成功了,那么这个锁的有效时间应该重新计算,它等于最初的锁的有效时间减去第3步计算出来的获取锁消耗的时间。
  5. 如果最终获取锁失败了(可能由于获取到锁的Redis节点个数少于N/2+1,或者整个获取锁的过程消耗的时间超过了锁的最初有效时间),那么客户端应该立即向所有Redis节点发起释放锁的操作(即前面介绍的Redis Lua脚本)。
  6. 释放锁:对所有的Redis节点发起释放锁操作

然而Martin Kleppmann针对这个算法提出了质疑,提出应该基于fencing token机制(每次对资源进行操作都需要进行token验证)

  1. Redlock在系统模型上尤其是在分布式时钟一致性问题上提出了假设,实际场景下存在时钟不一致和时钟跳跃问题,而Redlock恰恰是基于timing的分布式锁
  2. 另外Redlock由于是基于自动过期机制,依然没有解决长时间的gc pause等问题带来的锁自动失效,从而带来的安全性问题。

接着antirez又回复了Martin Kleppmann的质疑,给出了过期机制的合理性,以及实际场景中如果出现停顿问题导致多个Client同时访问资源的情况下如何处理。

针对Redlock的问题,基于Redis的分布式锁到底安全吗给出了详细的中文说明,并对Redlock算法存在的问题提出了分析。

总结

不论是基于SETNX版本的Redis单实例分布式锁,还是Redlock分布式锁,都是为了保证下特性

  1. 安全性:在同一时间不允许多个Client同时持有锁
  2. 活性
    死锁:锁最终应该能够被释放,即使Client端crash或者出现网络分区(通常基于超时机制)
    容错性:只要超过半数Redis节点可用,锁都能被正确获取和释放

所以在开发或者使用分布式锁的过程中要保证安全性和活性,避免出现不可预测的结果。

另外每个版本的分布式锁都存在一些问题,在锁的使用上要针对锁的实用场景选择合适的锁,通常情况下锁的使用场景包括:

Efficiency(效率):只需要一个Client来完成操作,不需要重复执行,这是一个对宽松的分布式锁,只需要保证锁的活性即可;

Correctness(正确性):多个Client保证严格的互斥性,不允许出现同时持有锁或者对同时操作同一资源,这种场景下需要在锁的选择和使用上更加严格,同时在业务代码上尽量做到幂等

在Redis分布式锁的实现上还有很多问题等待解决,我们需要认识到这些问题并清楚如何正确实现一个Redis 分布式锁,然后在工作中合理的选择和正确的使用分布式锁。

来源:http://t.cn/Rmayeve

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/475372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 702. 搜索长度未知的有序数组(二分查找)

文章目录1. 题目2. 解题1. 题目 给定一个升序整数数组&#xff0c;写一个函数搜索 nums 中数字 target。 如果 target 存在&#xff0c;返回它的下标&#xff0c;否则返回 -1。注意&#xff0c;这个数组的大小是未知的。 你只可以通过 ArrayReader 接口访问这个数组&#xff0…

11个非常漂亮动物为主题的高品质图标集

今天发布一些令人振奋的动物为主题的图标集。这里收集了世界各地的设计师和艺术家的图标集杰作&#xff0c;如果你在做卡通设计网站&#xff0c;这个是你合适的选择 Birdie Adium Dock Icons Animals Tweeties: A Free Twitter Icon Set Birdies Zoom-eyed Creatures FREE Plus…

ae中心点重置工具_7步学习AE 入门篇 第2步 初学乍练

【1】做动画需要什么&#xff1f;如果动画的制作是因果关系&#xff0c;那么需要的成因具备了 结果自然就出现了。在AE中做动画一共需要3个工具&#xff0c;合成、关键帧和图层。在体验动画制作之前 我们对一些名词术语先来解释一下。合成&#xff1a;AE中用来承载视频的容器&a…

LeetCode 359. 日志速率限制器(哈希map)

文章目录1. 题目2. 解题1. 题目 请你设计一个日志系统&#xff0c;可以流式接收日志以及它的时间戳。 该日志会被打印出来&#xff0c;需要满足一个条件&#xff1a;当且仅当日志内容 在过去的 10 秒钟内没有被打印过。 给你一条日志的内容和它的时间戳&#xff08;粒度为秒…

linux 内存溢出排查_【开发者成长】JAVA 线上故障排查完整套路!

云栖号资讯&#xff1a;【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯&#xff0c;还在等什么&#xff0c;快来&#xff01;线上故障主要会包括 CPU、磁盘、内存以及网络问题&#xff0c;而大多数故障可能会包含不止一个层面的问题&#xff0c;所以进行…

C#托管代码与C++非托管代码互相调用一(C#调用C++代码.net 代码安全)

在最近的项目中&#xff0c;牵涉到项目源代码保密问题&#xff0c;由于代码是C#写的&#xff0c;容易被反编译&#xff0c;因此决定抽取核心算法部分使用C编写&#xff0c;C到目前为止好像还不能被很好的反编译&#xff0c;当然如果你是反汇编高手的话&#xff0c;也许还是有可…

LeetCode 249. 移位字符串分组(哈希)

文章目录1. 题目2. 解题1. 题目 给定一个字符串&#xff0c;对该字符串可以进行 “移位” 的操作&#xff0c;也就是将字符串中每个字母都变为其在字母表中后续的字母&#xff0c;比如&#xff1a;“abc” -> “bcd”。这样&#xff0c;我们可以持续进行 “移位” 操作&…

如何扩大缓存区_艾莱依首个自动化仓落地,库宝解决线边缓存难题

中国拥有着世界上最大的服装生产和消费市场&#xff0c;随着新零售的发展和消费习惯的改变&#xff0c;消费个性化浪潮、提倡消费体验等为服装领域带来诸多挑战&#xff0c;如何打造更为柔性的、高效的服装供应链成为企业提升竞争力的一大核心。针对工厂自动化环节&#xff0c;…

LeetCode 170. 两数之和 III - 数据结构设计(哈希map)

文章目录1. 题目2. 解题1. 题目 设计并实现一个 TwoSum 的类&#xff0c;使该类需要支持 add 和 find 的操作。 add 操作 - 对内部数据结构增加一个数。 find 操作 - 寻找内部数据结构中是否存在一对整数&#xff0c;使得两数之和与给定的数相等。 示例 1: add(1); add(3); …

PowerBI 报表平台首页报表列表加载慢

Power BI 加载时会进行用户身份验证&#xff0c;如果没有部署域控制器&#xff0c;验证就会超时。因此部署域控制器就会解决首页加载慢的问题

天不知道地知道你不知道我知道谜底_温州这里有个7000平方米的“寻宝”地,你不知道就亏大了!...

468㎡的中国百名工艺美术大师联展100㎡的非遗技艺表演区藏在贵州深山里的苗绣……一场自然与手工的文化盛宴即将拉开序幕3月21-25日&#xff0c;2019温州国际时尚文博会&#xff0c;让我们走进3号工艺美术馆~一起来寻宝这一次&#xff0c;全方位领略中国工艺之美&#xff0c;捕…

LeetCode 288. 单词的唯一缩写(哈希)

文章目录1. 题目2. 解题1. 题目 一个单词的缩写需要遵循 <起始字母><中间字母数><结尾字母> 这样的格式。 以下是一些单词缩写的范例&#xff1a; a) it --> it (没有缩写)1↓ b) d|o|g --> d1g1 1 …

delphi64位 char数组转换string中文乱码_使用位运算、值交换等方式反转java字符串-共四种方法...

在本文中&#xff0c;我们将向您展示几种在Java中将String类型的字符串字母倒序的几种方法。 StringBuilder(str).reverse()char[]循环与值交换byte循环与值交换apache-commons-lang3如果是为了进行开发&#xff0c;请选择StringBuilder(str).reverse()API。出于学习的目的&…

LeetCode 1119. 删去字符串中的元音

文章目录1. 题目2. 解题1. 题目 给你一个字符串 S&#xff0c;请你删去其中的所有元音字母&#xff08; ‘a’&#xff0c;‘e’&#xff0c;‘i’&#xff0c;‘o’&#xff0c;‘u’&#xff09;&#xff0c;并返回这个新字符串。 示例 1&#xff1a; 输入&#xff1a;&quo…

LeetCode 760. 找出变位映射(哈希)

文章目录1. 题目2. 解题1. 题目 给定两个列表 A and B&#xff0c;并且 B 是 A 的变位&#xff08;即 B 是由 A 中的元素随机排列后组成的新列表&#xff09;。 我们希望找出一个从 A 到 B 的索引映射 P 。 一个映射 P[i] j 指的是列表 A 中的第 i 个元素出现于列表 B 中的第…

hystrix 页面_微服务 | 使用Hystrix实现Spring Cloud的熔断机制

1. 熔断机制介绍在介绍熔断机制之前&#xff0c;我们需要了解微服务的雪崩效应。在微服务架构中&#xff0c;微服务是完成一个单一的业务功能&#xff0c;这样做的好处是可以做到解耦&#xff0c;每个微服务可以独立演进。但是&#xff0c;一个应用可能会有多个微服务组成&…

LeetCode 1165. 单行键盘(哈希)

文章目录1. 题目2. 解题1. 题目 我们定制了一款特殊的力扣键盘&#xff0c;所有的键都排列在一行上。 我们可以按从左到右的顺序&#xff0c;用一个长度为 26 的字符串 keyboard &#xff08;索引从 0 开始&#xff0c;到 25 结束&#xff09;来表示该键盘的键位布局。 现在…

看了新闻,思科研发中心 没有成都的。。。

思科中国研发中心又添三处 //上海是&#xff1a;交大。。 合肥是 中科大。。至于苏州&#xff1f; 为何不到成都&#xff0c;只给个销售中心 查看评论 发表评论2011年11月08日 17:51分 作者&#xff1a;网界网 佚名 来源&#xff1a;网界网 摘要&#xff1a;思科宣…

去重 指定区域数据_大数据分析常用去重算法分析

去重分析在企业日常分析中的使用频率非常高&#xff0c;如何在大数据场景下快速地进行去重分析一直是一大难点。在近期的 Apache Kylin 沙龙上&#xff0c; Kyligence 大数据研发工程师陶加涛为大家揭开了大数据分析常用去重算法的神秘面纱。首先&#xff0c;请大家思考一个问题…

LeetCode 1469. 寻找所有的独生节点

文章目录1. 题目2. 解题1. 题目 二叉树中&#xff0c;如果一个节点是其父节点的唯一子节点&#xff0c;则称这样的节点为 “独生节点” 。 二叉树的根节点不会是独生节点&#xff0c;因为它没有父节点。 给定一棵二叉树的根节点 root &#xff0c;返回树中 所有的独生节点的值…