Redlock原理简介和实现过程

前言

上篇文章介绍了通过

SET key_name my_random_value NX PX 30000                  
NX 表示if not exist 就设置并返回True,否则不设置并返回False   
PX 表示过期时间用毫秒级, 30000 表示这些毫秒时间后此key过期

方式实现的redis分布锁

但有缺点:

只作用在一个Redis节点上,即使Redis通过sentinel保证高可用,如果这个master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:

在Redis的master节点上拿到了锁;

但是这个加锁的key还没有同步到slave节点;

master故障,发生故障转移,slave节点升级为master节点;

导致锁丢失。

由此 redis官方推荐 redlock 来解决这个问题

使用场景

多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)

优点

  • 防止了 单节点故障造成整个服务停止运行的情况;

  • 在多节点中锁的设计,及多节点同时崩溃等各种意外情况有自己独特的设计方法

概念说明

  • 1.TTL:Time To Live;只 redis key 的过期时间或有效生存时间

  • 2.clock drift:时钟漂移;指两个电脑间时间流速基本相同的情况下,两个电脑(或两个进程间)时间的差值;如果电脑距离过远会造成时钟漂移值 过大

最低保证分布式锁的有效性及安全性的要求

  • 1.互斥;任何时刻只能有一个client获取锁

  • 2.释放死锁;即使锁定资源的服务崩溃或者分区,仍然能释放锁

  • 3.容错性;只要多数redis节点(一半以上)在使用,client就可以获取和释放锁

使用redession实现分布锁的过程

假设有5个完全独立的redis主服务器
  • 1.获取当前时间戳

  • 2.client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

    比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁

  • 3.client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功

  • 4.如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移);

  • 5.如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁

代码实现

maven依赖

<dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.4.3</version>
</dependency>

获取redession客户端连接

业务处理接口

具体的业务逻辑实现都需要实现该接口

封装锁管理接口

锁管理接口实现类

调用加锁

redessoin获取锁和释放锁源码分析

获取锁

跟踪RLock类的lock.tryLock(100, lockTime, TimeUnit.SECONDS);方法

可以找到方法

现在来分析下其中的lua脚本

if (redis.call('exists', KEYS[1]) == 0) 

首先分布式锁的KEY不能存在,,

then redis.call('hset', KEYS[1], ARGV[2], 1); 

如果确实不存在,那么执行hset命令(hset REDLOCK_KEY uuid+threadId 1)

redis.call('pexpire', KEYS[1], ARGV[1]); 

并通过pexpire设置失效时间(也是锁的租约时间)

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) 

如果分布式锁的KEY已经存在,并且value也匹配,表示是当前线程持有的锁,

then redis.call('hincrby', KEYS[1], ARGV[2], 1); 
redis.call('pexpire', KEYS[1], ARGV[1]); 

那么重入次数加1,并且设置失效时间

return redis.call('pttl', KEYS[1])

获取分布式锁的KEY的失效时间毫秒数

释放锁

if (redis.call('exists', KEYS[1]) == 0)
then redis.call('publish', KEYS[2], ARGV[1])

如果分布式锁KEY不存在,那么向channel发布一条消息

if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) 
then return nil

如果分布式锁存在,但是value不匹配,表示锁已经被占用,那么直接返回

local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);

如果就是当前线程占有分布式锁,那么将重入次数减1

if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); 

重入次数减1后的值如果大于0,表示分布式锁有重入过,那么只设置失效时间,还不能删除

return 0; else redis.call('del', KEYS[1]); 
redis.call('publish', KEYS[2], ARGV[1]); 

重入次数减1后的值如果为0,表示分布式锁只获取过1次,那么删除这个KEY,并发布解锁消息

DEMO源码

https://gitee.com/pingfanrenbiji/redis-demo

参考文章

https://www.cnblogs.com/rgcLOVEyaya/p/RGC_LOVE_YAYA_1003days.html
https://yq.aliyun.com/articles/674394

本文使用 mdnice 排版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值