最新《Redis实战篇》五、分布式锁-redission(1)

if(redis.call(‘hexists’, key, threadId) == 1) then
– 存在, 获取锁,重入次数+1
redis.call(‘hincrby’, key, threadId, ‘1’);
– 设置有效期
redis.call(‘expire’, key, releaseTime);
return 1; – 返回结果
end;
return 0; – 代码走到这里,说明获取锁的不是自己,获取锁失败


* 释放锁的Lua脚本:



local key = KEYS[1]; – 锁的key
local threadId = ARGV[1]; – 线程唯一标识
local releaseTime = ARGV[2]; – 锁的自动释放时间
– 判断当前锁是否还是被自己持有
if (redis.call(‘HEXISTS’, key, threadId) == 0) then
return nil; – 如果已经不是自己,则直接返回
end;
– 是自己的锁,则重入次数-1
local count = redis.call(‘HINCRBY’, key, threadId, -1);
– 判断是否重入次数是否已经为0
if (count > 0) then
– 大于0说明不能释放锁,重置有效期然后返回
redis.call(‘EXPIRE’, key, releaseTime);
return nil;
else – 等于0说明可以释放锁,直接删除
redis.call(‘DEL’, key);
return nil;
end;


**2、测试Redission的分布式锁的可重入效果**



/**
* @author lxy
* @version 1.0
* @Description 测试Redisson的分布式锁的可重入性质
* @date 2022/12/21 16:01
*/
@Slf4j
@SpringBootTest
public class RedissonTest {

@Resource
private RedissonClient redissonClient;

private RLock lock;

@BeforeEach
void setUp(){
    lock = redissonClient.getLock("order");
}

@Test
void method1() throws InterruptedException {
    // 尝试获取锁
    boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);
    if (!isLock){
        log.error("获取锁失败....1");
        return;
    }
    try {
        log.info("获取锁成功....1");
        method2();
        log.info("开始执行业务....1");
    } finally {
        log.warn("开始释放锁....1");
        lock.unlock();
    }

}

void method2(){
    // 尝试获取锁
    boolean isLock = lock.tryLock();
    if(!isLock){
        log.error("获取锁失败....2");
        return;
    }
    try {
        log.info("获取锁成功....2");
        log.info("开始执行业务....2");
    } finally {
        log.warn("准备释放锁....2");
        lock.unlock();
    }
}

}


Debug测试:


![](https://img-blog.csdnimg.cn/img_convert/c65ad4e7e318bbfd27c866aa9a5ead62.png)


![](https://img-blog.csdnimg.cn/img_convert/1a6bd76a35f7215cbbe5828a59cf9120.png)


**3、接下来我们可以查看下Redisson中的分布式锁的实现:**


![image-20221221163708051](https://img-blog.csdnimg.cn/img_convert/510d4d790b0b55cbc959bef8ce19ecbc.png)



> 
> 注意源码中的KEYS[1]指外边的大Key,AVG[1]:大Key的过期时间,AVG[2]:当前的线程ID
> 
> 
> 


![image-20221221163825349](https://img-blog.csdnimg.cn/img_convert/3e22f9139fc91a4e7c4fb7aef7889fb9.png)



> 
> 源码中的KEYS[1]指外边的大Key,AVG[2]:大Key的过期时间,AVG[3]:当前的线程ID。KEYS[2]和ARGV[1]所代表的含义我们后面会讲解~
> 
> 
> 


#### 5.4 分布式锁-redission锁重试和WatchDog机制


关于锁可重试的原理见:<https://www.processon.com/view/link/63a86e6534446c6f609d3a3f>


关于锁超时续约 和 锁释放的原理见:<https://www.processon.com/view/link/63a891cece3d3c6150d7c2ac>


**Redission分布式锁原理**


![image-20221226021503722](https://img-blog.csdnimg.cn/img_convert/8a01f71e372a509084a734c43387d1a8.png)



> 
> 注意:只有leaseTime=-1,才会走WatchDog的逻辑
> 
> 
> 


**总结:Redisson分布式锁原理**


* 可重入:利用hash结构记录线程id和重入次数
* 可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制
* 超时续约:利用watchDog,每隔一段时间(releaseTime / 3),重置超时时间



> 
> **为什么需要超时续约呢?**  
>  因为我们某个线程获取到锁然后开始执行业务逻辑,但是业务执行时候出现了卡顿,从而导致锁超时后会被释放。之后我这业务执行完,再次执行unlock会出错。同时超时释放别的线程会拿到分布式锁而卡顿的业务还没执行完,从而就会产生线程安全问题~  
>  所以超时续约目的主要是 `当前线程获取锁后,只要没执行完就不会超时释放,会不断的超时续约`,直到业务逻辑执行完释放锁后,超时续约结束!
> 
> 
> 


#### 5.5 分布式锁-redission锁的MutiLock原理


为了提高redis的可用性,我们会搭建集群或者主从,现在以主从为例


此时我们去写命令,写在主机上, 主机会将数据同步给从机,但是假设在主机还没有来得及把数据写入到从机去的时候,此时主机宕机,哨兵会发现主机宕机,并且选举一个slave变成master,而此时新的master中实际上并没有锁信息,此时锁信息就已经丢掉了。


![image-20221226022637870](https://img-blog.csdnimg.cn/img_convert/c85c3272db0443291745a09d3edbe390.png)


为了解决这个问题,redission提出来了`MutiLock锁`,使用这把锁咱们就不使用主从了,每个节点的地位都是一样的, 这把锁`加锁的逻辑需要写入到每一个主丛节点上,只有所有的服务器都写入成功,此时才是加锁成功`,假设现在某个节点挂了,那么他去获得锁的时候,只要有一个节点拿不到,都不能算是加锁成功,就保证了加锁的可靠性。


![image-20221226022539880](https://img-blog.csdnimg.cn/img_convert/69100fa6371903d31f31bc17bd75c409.png)


**代码演示:**


①Linux下建立三个Redis节点(使用Docker)



docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf
docker run -p 6380:6379 --name redis2 -v /mydata/redis2/data:/data -v /mydata/redis2/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf
docker run -p 6381:6379 --name redis3 -v /mydata/redis3/data:/data -v /mydata/redis3/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf


②修改Redisson配置类



/**
* @author lxy
* @version 1.0
* @Description Redisson配置
* @date 2022/12/21 13:04
*/
@Configuration
public class RedissonConfig {

@Bean
public RedissonClient redissonClient(){
    // 配置
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.174.128:6379");
    // 创建RedissonClient对象
    return Redisson.create(config);
}

@Bean
public RedissonClient redissonClient2(){
    // 配置
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.174.128:6380");
    // 创建RedissonClient对象
    return Redisson.create(config);
}

@Bean
public RedissonClient redissonClient3(){
    // 配置
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.174.128:6381");
    // 创建RedissonClient对象
    return Redisson.create(config);
}

}


③编写测试代码



@Slf4j
@SpringBootTest
public class RedissonTest {

@Resource
private RedissonClient redissonClient;

@Resource
private RedissonClient redissonClient2;

@Resource
private RedissonClient redissonClient3;

private RLock lock;

@BeforeEach
void setUp(){
    RLock lock1 = redissonClient.getLock("order");
    RLock lock2 = redissonClient2.getLock("order");
    RLock lock3 = redissonClient3.getLock("order");


    // 创建联锁 multiLock (这里其实用哪个去掉方法都可以,通过观察源码发现,底层是new RedissonMultiLock(lock1,lock2,lick3))
    lock = redissonClient.getMultiLock(lock1, lock2, lock3);
    // lock = redissonClient.getLock("order");;
}
...
// 其他代码同上

}


④Debug观察结果


![](https://img-blog.csdnimg.cn/img_convert/a2df6aa6359b208e9f9234b76a14c669.png)


![](https://img-blog.csdnimg.cn/img_convert/0ee1a97e4a2cfe320d405f786422b324.png)


后续:第一次释放锁,重数都减一,第二次释放重数变为0,从而都被删掉~


**结论:** 联锁就是多个独立的锁,每一个独立的锁就是一个Redission~


**那么MutiLock 加锁原理是什么呢?我们来猜测一下~**



![img](https://img-blog.csdnimg.cn/img_convert/9e8a21b9e6b53e82d2639ace266079c0.png)
![img](https://img-blog.csdnimg.cn/img_convert/bf33d48f6a3bcbe4e5479f1edec26393.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

...(img-JXPf7d0z-1714821874065)]
[外链图片转存中...(img-NX8dZAlj-1714821874066)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值