无码a片

Redis 散播式锁的正确已毕旨趣演化历程与 Redisson 实战回来

发布日期:2022-06-18 17:09    点击次数:159

Redis 散播式锁的正确已毕旨趣演化历程与 Redisson 实战回来

Redis 散播式锁使用 SET 辅导就不错已毕了么?在散播式规模 CAP 表面一直存在。

散播式锁的蹊径可没那么通俗,咱们在网上看到的散播式锁决策可能是有问题的。

「码哥」一步步带你长远散播式锁是如何一步步完善,在高并发坐褥环境中如何正确使用散播式锁。

在参加正文之前,咱们先带着问题去思考:

什么时候需要散播式锁? 加、解锁的代码位置有崇拜么? 如何幸免出现锁再也无法删除? 超时期间开荒几许合适呢? 如何幸免锁被其他线程开释 如何已毕重入锁? 主从架构会带来什么安全问题? 什么是 Redlock Redisson 散播式锁最好实战 看门狗已毕旨趣 …… 什么时候用散播式锁?

码哥,说个庸俗的例子陶冶下什么时候需要散播式锁呢?

诊所惟有一个大夫,许多患者前来就诊。

大夫在并吞本事只可给一个患者提供就诊处事。

若是不是这样的话,就会出现大夫在就诊肾亏的「肖菜鸡」准备开药时候患者切换成了脚臭的「谢霸哥」,这时候药就被谢霸哥取走了。

治肾亏的药被有脚臭的拿去了。

当并发去读写一个【分享资源】的时候,咱们为了保证数据的正确,需要限度并吞本事惟有一个线程窥察。

散播式锁即是用来限度并吞本事,惟有一个 JVM 程度中的一个线程不错窥察被保护的资源。

散播式锁初学

65 哥:散播式锁应该知足哪些特点?

互斥:在职何给定本事,惟有一个客户端不错持有锁;

无死锁:任何本事都有可能取得锁,即使获取锁的客户端崩溃;

容错:只须大宽广 Redis的节点都照旧启动,客户端就不错获取和开释锁。

码哥,我不错使用 SETNX key value 呐喊是已毕「互斥」特点。

这个呐喊来自于SET if Not eXists的缩写,真义是:若是 key 不存在,则开荒 value 给这个key,不然啥都不做。Redis 官方地址说的:

呐喊的复返值:

1:开荒顺利; 0:key 莫得开荒顺利。

如下场景:

敲代码一天累了,想去安靖推拿下肩颈。

168 号技师最抢手,大众可爱点,是以并发量大,需要散播式锁限度。

并吞本事只允许一个「客户」预约 168 技师。

肖菜鸡肯求 168 技师顺利:

> SETNX lock:168 1 (integer) 1 # 获取 168 技师顺利 

谢霸哥后头到,肯求失败:

> SETNX lock 2  (integer) 0 # 客户谢霸哥 2 获取失败 

此刻,肯求顺利的客户就不错享受 168 技师的肩颈安靖处事「分享资源」。

享受终局后,要实时开释锁,给自后者享受 168 技师的处事契机。

肖菜鸡,码哥考考你如何开释锁呢?

很通俗,使用 DEL 删除这个 key 就行。

> DEL lock:168 (integer) 1 

码哥,你见过「龙」么?我见过,因为我被一条龙处事过。

肖菜鸡,事情可没这样通俗。

这个决策存在一个存在形成锁无法开释的问题,形成该问题的场景如下:

客户端方位节点崩溃,无法正确开释锁;

业务逻辑格外,无法实行 DEL辅导。

这样,这个锁就会一直占用,锁在我手里,我挂了,这样其他客户端再也拿不到这个锁了。

超时开荒

码哥,我不错在获取锁顺利的时候开荒一个「超时期间」

比如设定推拿处事一次 60 分钟,那么在给这个 key 加锁的时候开荒 60 分钟落伍即可:

> SETNX lock:168 1 // 获取锁  (integer) 1  > EXPIRE lock:168 60 // 60s 自动删除  (integer) 1 

这样,到点后锁自动开释,其他客户就不错连接享受 168 技师推拿处事了。

谁要这样写,就糟透了。

「加锁」、「开荒超时」是两个呐喊,他们不是原子操作。

若是出现只实行了第一条,第二条没契机实行就会出现「超时期间」开荒失败,依然出现锁无法开释。

码哥,那咋办,我想被一条龙处事,要惩处这个问题

Redis 2.6.X 之后,官方拓展了 SET 呐喊的参数,知足了当 key 不存在则开荒 value,同期开荒超时期间的语义,何况知足原子性。

SET resource_name random_value NX PX 30000 
NX:暗示惟有 resource_name 不存在的时候才气 SET 顺利,从而保证惟有一个客户端不错取得锁; PX 30000:暗示这个锁有一个 30 秒自动落伍时候。

这样写还不够,咱们还要留心不成开释不是我方加的锁。咱们不错在 value 上做著述。

连接往下看……

开释了不是我方加的锁

这样我能适当的享受一条龙处事了么?

No,还有一种场景会导致开释他人的锁:

客户 1 获取锁顺利并开荒开荒 30 秒超时;

客户 1 因为一些原因导致实行很慢(相聚问题、发生 FullGC……),过了 30 秒依然没实行完,然则锁落伍「自动开释了」;

客户 2 肯求加锁顺利;

客户 1 实行完成,实行 DEL 开释锁辅导,这个时候就把客户 2 的锁给开释了。

有个要道问题需要惩处:我方的锁只可我方来开释。

我要如何删除是我方加的锁呢?

在实行 DEL 辅导的时候,咱们要想主义检查下这个锁是不是我方加的锁再实行删除辅导。

解铃还须系铃人

码哥,我在加锁的时候开荒一个「惟一象征」手脚 value 代表加锁的客户端。SET resource_name random_value NX PX 30000

在开释锁的时候,客户端将我方的「惟一象征」与锁上的「象征」比拟是否终点,匹配上则删除,不然莫得权柄开释锁。

伪代码如下:

// 比对 value 与 惟一象征 if (redis.get("lock:168").equals(random_value)){    redis.del("lock:168"); //比对顺利则删除  } 

有莫得想过,这是 GET + DEL 辅导组合而成的,这里又会波及到原子性问题。

咱们不错通过 Lua 剧底本已毕,这样判断和删除的过程即是原子操作了。

// 获取锁的 value 与 ARGV[1] 是否匹配,匹配则实行 del if redis.call("get",KEYS[1]) == ARGV[1] then     return redis.call("del",KEYS[1]) else     return 0 end 

这样通过惟一值开荒成 value 象征加锁的客户端很进攻,仅使用 DEL 是不安全的,因为一个客户端可能会删除另一个客户端的锁。

使用上头的剧本,每个锁都用一个就地字符串“签名”,惟有当删除锁的客户端的“签名”与锁的 value 匹配的时候,才会删除它。

官方文档亦然这样说的:https://redis.io/topics/distlock

这个决策照旧相对无缺,咱们用的最多的可能即是这个决策了。

正确开荒锁超时

锁的超时期间如何计较合适呢?

这个时候不成瞎写,一般要凭据在测试环境屡次测试,然后压测多轮之后,比如计较出平均实行时候 200 ms。

那么锁的超时期间就放大为平均实行时候的 3~5 倍。

为啥要放放大呢?

因为若是锁的操作逻辑中有相聚 IO 操作、JVM FullGC 等,线上的相聚不会总一帆风顺,咱们要给相聚抖动留有缓冲时候。

那我开荒更大极少,比如开荒 1 小时不是更安全?

不要钻牛角,多大算大?

开荒时候过长,一朝发生宕机重启,就意味着 1 小时内,散播式锁的处事全部节点不可用。

你要让运维手动删除这个锁么?

只须运维确凿不会打你。

有莫得无缺的决策呢?不管时候如何开荒都不大合适。

咱们不错让取得锁的线程开启一个看护线程,用来给将近落伍的锁「续航」。

加锁的时候开荒一个落伍时候,同期客户端开启一个「看护线程」,定时去检测这个锁的失效时候。

若是将近落伍,然则业务逻辑还没实行完成,自动对这个锁进行续期,从新开荒落伍时候。

这个旨趣行得通,可我写不出。

别慌,照旧有一个库把这些责任都封装好了他叫 Redisson。

在使用散播式锁时,它就继承了「自动续期」的决策来幸免锁落伍,这个看护线程咱们一般也把它叫做「看门狗」线程。

一齐优化下来,决策似乎比拟「严谨」了,综合出对应的模子如下。

通过 SET lock_resource_name random_value NX PX expire_time,同期启动看护线程为将近落伍但还没实行完的客户端的锁续命; 客户端实行业务逻辑操作分享资源; 通过 Lua 剧本开释锁,先 get 判断锁是否是我方加的,再实行 DEL。

这个决策本色上照旧比拟无缺,能写到这一步照旧击败 90% 的秩序猿了。

然则对于追求极致的秩序员来说还远远不够:

可重入锁如何已毕? 主从架构崩溃归附导致锁丢失如何惩处? 客户端加锁的位置有蹊径么? 加解锁代码位置有崇拜

凭据前边的分析,咱们照旧有了一个「相对严谨」的散播式锁了。

于是「谢霸哥」就写了如下代码将散播式锁愚弄到神色中,以下是伪代码逻辑:

public void doSomething() {   redisLock.lock(); // 上锁     try {         // 处理业务         .....         redisLock.unlock(); // 开释锁     } catch (Exception e) {         e.printStackTrace();     } } 

有莫得想过:一朝实行业务逻辑过程中抛出格外,秩序就无法实行开释锁的经过。

是以开释锁的代码一定要放在 finally{} 块中。

加锁的位置也有问题,放在 try 外面的话,若是实行 redisLock.lock() 加锁格外,然则本色辅导照旧发送到处事端并实行,仅仅客户端读取反应超时,就会导致莫得契机实行解锁的代码。

是以 redisLock.lock() 应该写在 try 代码块,这样保证一定会实行解锁逻辑。

要而言之,正确代码位置如下 :

public void doSomething() {     try {         // 上锁         redisLock.lock();         // 处理业务         ...     } catch (Exception e) {         e.printStackTrace();     } finally {       // 开释锁       redisLock.unlock();     } } 
已毕可重入锁

65 哥:可重入锁要如何已毕呢?

当一个线程实行一段代码顺利获取锁之后,连接实行时,又遭遇加锁的代码,可重入性就就保证线程能连接实行, 小12萝8禁在线喷水观看而不可重入即是需要恭候锁开释之后,再次获取锁顺利,才气连接往下实行。

用一段代码讲明注解可重入:

public synchronized void a() {     b(); } public synchronized void b() {     // pass } 

假定 X 线程在 a 方法获取锁之后,连接实行 b 方法,若是此时不可重入,线程就必须恭候锁开释,再次争抢锁。

锁明明是被 X 线程领有,却还需要恭候我方开释锁,然后再去抢锁,这看起来就很奇怪,我开释我我方~

Redis Hash 可重入锁

Redisson 类库即是通过 Redis Hash 来已毕可重入锁

当线程领有锁之后,往后再遭遇加锁方法,径直将加锁次数加 1,然后再实行方法逻辑。

退出加锁方法之后,加锁次数再减 1,当加锁次数为 0 时,锁才被信得过的开释。

不错看到可重入锁最大特点即是计数,计较加锁的次数。

是以当可重入锁需要在散播式环境已毕时,咱们也就需要统计加锁次数。

加锁逻辑

咱们不错使用 Redis hash 结构已毕,key 暗示被锁的分享资源, hash 结构的 fieldKey 的 value 则保存加锁的次数。

通过 Lua 剧本已毕原子性,假定 KEYS1 = 「lock」, ARGV「1000,uuid」:

---- 1 代表 true ---- 0 代表 false if (redis.call('exists', KEYS[1]) == 0) then     redis.call('hincrby', KEYS[1], ARGV[2], 1);     redis.call('pexpire', KEYS[1], ARGV[1]);     return 1; end ; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then     redis.call('hincrby', KEYS[1], ARGV[2], 1);     redis.call('pexpire', KEYS[1], ARGV[1]);     return 1; end ; return 0; 

加锁代码领先使用 Redis exists 呐喊判断现时 lock 这个锁是否存在。

若是锁不存在的话,径直使用 hincrby创建一个键为 lock hash 表,何况为 Hash 表中键为 uuid 运行化为 0,然后再次加 1,临了再开荒落伍时候。

若是现时锁存在,则使用 hexists判断现时 lock 对应的 hash 表中是否存在 uuid 这个键,若是存在,再次使用 hincrby 加 1,临了再次开荒落伍时候。

临了若是上述两个逻辑都不允洽,径直复返。

解锁逻辑

-- 判断 hash set 可重入 key 的值是否等于 0

-- 若是为 0 代表 该可重入 key 不存在

-- 判断 hash set 可重入 key 的值是否等于 0 -- 若是为 0 代表 该可重入 key 不存在 if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then     return nil; end ; -- 计较现时可重入次数 local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1); -- 小于等于 0 代表不错解锁 if (counter > 0) then     return 0; else     redis.call('del', KEYS[1]);     return 1; end ; return nil; 

领先使用 hexists 判断 Redis Hash 表是否存给定的域。

若是 lock 对应 Hash 表不存在,或者 Hash 表不存在 uuid 这个 key,径直复返 nil。

若存在的情况下,代说明时锁被其持有,领先使用 hincrby使可重入次数减 1 ,然后判断计较之后可重入次数,若小于等于 0,被按摩师玩弄到潮喷在线播放则使用 del 删除这把锁。

解锁代码实行形势与加锁访佛,只不外解锁的实行赶走复返类型使用 Long。这里之是以莫得跟加锁相同使用 Boolean ,这是因为解锁 lua 剧本中,三个复返值含义如下:

1 代表解锁顺利,锁被开释 0 代表可重入次数被减 1 null 代表其他线程尝试解锁,解锁失败. 主从架构带来的问题

码哥,到这里散播式锁「很无缺了」吧,没猜测散播式锁这样多蹊径。

路还很远,之前分析的场景都是,锁在「单个」Redis 实例中可能产生的问题,并莫得波及到 Redis 主从花样导致的问题。

咱们常常使用「Cluster 集群」或者「哨兵集群」的花样部署保证高可用。

这两个花样都是基于「主从架构数据同步复制」已毕的数据同步,而 Redis 的主从复制默许是异步的。

以下内容来自于官方文档 https://redis.io/topics/distlock

咱们试想下如下场景会发生什么问题:

客户端 A 在 master 节点获取锁顺利。

还莫得把获取锁的信息同步到 slave 的时候,master 宕机。

slave 被选举为新 master,这时候莫得客户端 A 获取锁的数据。

客户端 B 就能顺利的取得客户端 A 持有的锁,叛逆了散播式锁界说的互斥。

诚然这个概率极低,然则咱们必须得承认这个风险的存在。

Redis 的作家漠视了一种惩处决策,叫 Redlock(红锁)

Redis 的作家为了协调散播式锁的表率,搞了一个 Redlock,算是 Redis 官方对于已毕散播式锁的指引范例,https://redis.io/topics/distlock,然则这个 Redlock 也被外洋的一些散播式大众给喷了。

因为它也不无缺,有“罅隙”。

什么是 Redlock

红锁是不是这个?

泡面吃多了你,Redlock 红锁是为了惩处主从架构中当出现主从切换导致多个客户端持有并吞个锁而漠视的一种算法。

大众不错看官方文档(https://redis.io/topics/distlock),以下来自官方文档的翻译。

想用使用 Redlock,官方建议在不同机器上部署 5 个 Redis 主节点,节点都是十足孤苦,也不使用主从复制,使用多个节点是为容错。

一个客户端要获取锁有 5 个门径:

客户端获取现常常候 T1(毫秒级别);

使用疏通的 key和 value规则尝试从 N个 Redis实例上获取锁。

每个请求都开荒一个超时期间(毫秒级别),该超时期间要远小于锁的灵验时候,这样便于快速尝试与下一个实例发送请求。

比如锁的自动开释时候 10s,则请求的超时期间不错开荒 5~50 毫秒内,这样不错留心客户端永劫候艰涩。

客户端获取现常常候 T2 并减去门径 1 的 T1 来计较出获取锁所用的时候(T3 = T2 -T1)。当且仅当客户端在大宽广实例(N/2 + 1)获取顺利,且获取锁所用的总时候 T3 小于锁的灵验时候,才合计加锁顺利,不然加锁失败。

若是第 3 步加锁顺利,则实行业务逻辑操作分享资源,key 的信得过灵验时候等于灵验时候减去获取锁所使用的时候(门径 3 计较的赶走)。

若是因为某些原因,获取锁失败(莫得在至少 N/2+1 个 Redis 实例取到锁或者取锁时候照旧跳动了灵验时候),客户端应该在扫数的 Redis 实例上进行解锁(即便某些 Redis 实例根底就莫得加锁顺利)。

另外部署实例的数目条目是奇数,为了能很好的知足过半原则,若是是 6 台则需要 4 台获取锁顺利才气合计顺利,是以奇数更合理

事情可没这样通俗,Redis 作家把这个决策漠视后,受到了业界驰名的散播式系统大众的质疑。

两人好比至人打架,两人一来一趟论据实足的对一个问题漠视许多论断……

Martin Kleppmann 漠视质疑的博客:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

Redlock 野心者的复兴:http://antirez.com/news/101

Redlock 是与非

Martin Kleppmann 合计锁定的方针是为了保护对分享资源的读写,而散播式锁应该「高效」和「正确」。

高效性:散播式锁应该要知足高效的性能,Redlock 算法向 5 个节点实行获取锁的逻辑性能不高,本钱加多,复杂度也高;

正确性:散播式锁应该留心并发程度在并吞本事只可有一个线程能对分享数据读写。

出于这两点,咱们没必要承担 Redlock 的本钱和复杂,运行 5 个 Redis 实例并判断加锁是否知足大宽广才算顺利。

主从架构崩溃归附极小可能发生,这没什么大不了的。使用单机版就够了,Redlock 太重了,没必要。

Martin 合计 Redlock 根底够不上安全性的条目,也依旧存在锁失效的问题!

Martin 的论断

Redlock 弄巧成拙反类犬:对于偏好效果来讲,Redlock 比拟重,没必要这样做,而对于偏好正确性来说,Redlock 是不够安全的。

时钟假定不对理:该算法对系统时钟做出了危急的假定(假定多个节点机器时钟都是一致的),若是不知足这些假定,锁就会失效。

无法保证正确性:Redlock 不成提供访佛 fencing token 的决策,是以惩处不了正确性的问题。为了正确性,请使用有「共鸣系统」的软件,举例 Zookeeper。

Redis 作家 Antirez 的反驳

在 Redis 作家的反驳著述中,有 3 个重心:

时钟问题:Redlock 并不需要十足一致的时钟,只需要大体一致就不错了,允许有「过错」,只须过错不要跳动锁的租期即可,这种对于时钟的精度条目并不是很高,而且这也允洽实践环境。

相聚延长、程度暂停问题:

客户端在拿到锁之前,岂论履历什么耗时长问题,Redlock 都简略在第 3 步检测出来

客户端在拿到锁之后,发生 NPC,那 Redlock、Zookeeper 都窝囊为力

质疑 fencing token 机制。

对于 Redlock 的争论咱们下期相逢,当今参加 Redisson 已毕散播式锁实战部分。

Redisson 散播式锁

基于 SpringBoot starter 形势,添加 starter。

<dependency>   <groupId>org.redisson</groupId>   <artifactId>redisson-spring-boot-starter</artifactId>   <version>3.16.4</version> </dependency

不外这里需要顾惜 springboot 与 redisson 的版块,因为官方保举 redisson 版块与 springboot 版块合作使用。

将 Redisson 与 Spring Boot 库集成,还取决于 Spring Data Redis 模块。

「码哥」使用 SpringBoot 2.5.x 版块, 是以需要添加 redisson-spring-data-25。

<dependency>   <groupId>org.redisson</groupId>   <!-- for Spring Data Redis v.2.5.x -->   <artifactId>redisson-spring-data-25</artifactId>   <version>3.16.4</version> </dependency

添加成就文献

spring:   redis:     database:     host:     port:     password:     ssl:     timeout:     # 凭据本色情况成就 cluster 或者哨兵     cluster:       nodes:     sentinel:       master:       nodes: 

就这样在 Spring 容器中咱们领有以下几个 Bean 不错使用:

RedissonClient RedissonRxClient RedissonReactiveClient RedisTemplate ReactiveRedisTemplate 失败无尽重试
RLock lock = redisson.getLock("码哥字节"); try {    // 1.最常用的第一种写法   lock.lock();    // 实行业务逻辑   .....  } finally {   lock.unlock(); } 

拿锁失败时会束缚的重试,具有 Watch Dog 自动宽限机制,默许续 30s 每隔 30/3=10 秒续到 30s。

失败超时重试,自动续命
// 尝试拿锁10s后罢手重试,获取失败复返false,具有Watch Dog 自动宽限机制, 默许续30s boolean flag = lock.tryLock(10, TimeUnit.SECONDS); 
超时自动开释锁
// 莫得Watch Dog ,10s后自动开释,不需要调用 unlock 开释锁。  lock.lock(10, TimeUnit.SECONDS); 
超时重试,自动解锁
// 尝试加锁,最多恭候100秒,上锁以后10秒自动解锁,莫得 Watch dog boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) {    try {      ...    } finally {        lock.unlock();    } } 
Watch Dog 自动延时

若是获取散播式锁的节点宕机,且这个锁还处于锁定现象,就会出现死锁。

为了幸免这个情况,咱们都会给锁开荒一个超时自动开释时候。

关联词,如故会存在一个问题。

假定线程获取锁顺利,并开荒了 30 s 超时,然则在 30s 内任务还没实行完,锁超时开释了,就会导致其他线程获取不该获取的锁。

是以,Redisson 提供了 watch dog 自动延时机制,提供了一个监控锁的看门狗,它的作用是在 Redisson 实例被关闭前,连续的延长锁的灵验期。

也即是说,若是一个拿到锁的线程一直莫得完成逻辑,那么看门狗会匡助线程连续的延长锁超时期间,锁不会因为超时而被开释。

默许情况下,看门狗的续期时候是 30s,也不错通过修改 Config.lockWatchdogTimeout 来另行指定。

另外 Redisson 还提供了不错指定 leaseTime 参数的加锁方法来指定加锁的时候。

跳动这个时候后锁便自动解开了,不会延长锁的灵验期。

旨趣如下图:

有两个点需要顾惜:

watchDog 惟有在未线路指定加锁超时期间(leaseTime)时才会成效。 lockWatchdogTimeout 设定的时候不要太小 ,比如开荒的是 100 毫秒,由于相聚径直导致加锁完后,watchdog 去宽限时,这个 key 在 redis 中照旧被删除了。 源码导读

在调用 lock 方法时,会最终调用到 tryAcquireAsync。

调用链为:lock()->tryAcquire->tryAcquireAsync,详确讲明注解如下:

private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {         RFuture<Long> ttlRemainingFuture;         //若是指定了加锁时候,会径直去加锁         if (leaseTime != -1) {             ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);         } else {             //莫得指定加锁时候 会先进行加锁,何况默许时候即是 LockWatchdogTimeout的时候             //这个是异步操作 复返RFuture 访佛netty中的future             ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,                     TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);         }          //这里亦然访佛netty Future 的addListener,在future内容实行完成后实行         ttlRemainingFuture.onComplete((ttlRemaining, e) -> {             if (e != null) {                 return;             }              // lock acquired             if (ttlRemaining == null) {                 // leaseTime不为-1时,不会自动宽限                 if (leaseTime != -1) {                     internalLockLeaseTime = unit.toMillis(leaseTime);                 } else {                     //这里是定时实行 现时锁自动宽限的动作,leaseTime为-1时,才会自动宽限                     scheduleExpirationRenewal(threadId);                 }             }         });         return ttlRemainingFuture;     } 

scheduleExpirationRenewal 中会调用 renewExpiration 启用了一个 timeout 定时,去实行宽限动作。

private void renewExpiration() {         ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());         if (ee == null) {             return;         }          Timeout task = commandExecutor.getConnectionManager()           .newTimeout(new TimerTask() {             @Override             public void run(Timeout timeout) throws Exception {                 // 概略部分代码                 ....                  RFuture<Boolean> future = renewExpirationAsync(threadId);                 future.onComplete((res, e) -> {                     ....                      if (res) {                         //若是 莫得报错,就再次定时宽限                         // reschedule itself                         renewExpiration();                     } else {                         cancelExpirationRenewal(null);                     }                 });             }             // 这里咱们不错看到定时任务 是 lockWatchdogTimeout 的1/3时候去实行 renewExpirationAsync         }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);          ee.setTimeout(task);     } 

scheduleExpirationRenewal 会调用到 renewExpirationAsync,实行底下这段 lua 剧本。

他主要判断即是 这个锁是否在 redis 中存在,若是存在就进行 pexpire 宽限。

protected RFuture<Boolean> renewExpirationAsync(long threadId) {         return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,                 "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +                         "redis.call('pexpire', KEYS[1], ARGV[1]); " +                         "return 1; " +                         "end; " +                         "return 0;",                 Collections.singletonList(getRawName()),                 internalLockLeaseTime, getLockName(threadId));     } 
watch dog 在现时节点还存活且任务未完成则每 10 s 给锁续期 30s。 秩序开释锁操作时因为格外莫得被实行,那么锁无法被开释,是以开释锁操作一定要放到 finally {} 中; 要使 watchLog 机制成效 ,lock 时 不要开荒 落伍时候。 watchlog 的延时期间 不错由 lockWatchdogTimeout 指定默许延时期间,然则不要开荒太小。 watchdog 会每 lockWatchdogTimeout/3 时候,去延时。 通过 lua 剧本已毕延长。 回来

完工,我建议你合上屏幕,我方在脑子里从新过一遍,每一步都在做什么,为什么要做,惩处什么问题。

咱们一齐从新到尾梳理了一遍 Redis 散播式锁中的各式蹊径,其实许多点是不管用什么做散播式锁都会存在的问题,进攻的是思考的过程。

对于系统的野心,每个人的起点都不相同,莫得无缺的架构,莫得普适的架构,然则在无缺和普适能均衡的很好的架构,即是好的架构。

本文转载自微信公众号「码哥字节」,不错通过以下二维码宽恕。转载本文请关联码哥字节公众号。

 



栏目分类



Powered by 无码a片 @2013-2022 RSS地图 HTML地图