物来顺应,未来不迎,当时不杂,既过|

万事胜意k

园龄:2年8个月粉丝:11关注:4

Redis学习之Redisson实现分布式锁

Redisson实现分布式锁

Redisson 是 Java 的 Redis 高级客户端,提供了各种现成的分布式工具类便于我们使用 Redis。

官网:https://github.com/redisson/redisson

中文文档:https://github.com/redisson/redisson/wiki/

使用方式:

1)引入独立的Redisson包:

不建议引入 springboot-starter,因为可能会和 springboot 内置的 redis 整合冲突

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

2)创建一个Redisson客户端,代码如下:

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

3)使用Redisson的Lock,代码如下:

@Resource
private RedissionClient redissonClient;
@Test
void testRedisson() throws Exception{
   //获取锁(可重入),指定锁的名称
   RLock lock = redissonClient.getLock("anyLock");
   //尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
   boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);
   //判断获取锁成功
   if(isLock){
       try{
           System.out.println("执行业务");          
      }finally{
           //释放锁
           lock.unlock();
      }
       
  }  
}

如何实现可重入锁

目的:保证同一个线程可以多次获取同一把锁

解决思路:在锁的 value 中额外保存当前线程获取锁的次数,每次获取锁 +1、释放锁 -1,当次数为 0 时才真正删除 key。

采用hash结构来存储锁信息,如图:

image-20231011212907569

流程如下:

image-20231011212925904

注意:

  1. 所有的判断和操作都需要使用Lua脚本来保证原子性

  2. 每次获取和释放锁时要重置锁的有效期。

获取锁的Lua脚本:

image-20231011213352946

释放锁的Lua脚本:

image-20231011213408607

如何重试获取锁

基于 Redis Pub / Sub 发布订阅机制。如果获取锁失败,则阻塞订阅释放锁的消息;当锁被释放时,会触发推送(告诉其他线程我释放锁啦),然后其他线程再重试获取;如此往复,直到超时。

如何防止锁提前超时释放

基于看门狗机制

总的来说就是默认锁过期时间是30s,而自动续期机制在源码当中就是开启了定时任务,定时间隔是看门狗时间的三分之一,也就是10s,所以就是在业务没有处理完的情况下锁默认每隔10s续期到30s;

需要思考两个问题:

  1. 如何保证同一个锁只注册一个定时任务?

  2. 如何防止无限续期?

要解决这些问题,使用全局 ConcurrentHashMap 来管理锁 => 任务信息,key 为锁的 id,从而保证唯一。当某个锁释放时,从全局 ConcurrentHashMap 中取出定时任务并取消掉,然后把锁的信息从 Map 中删掉即可。

最终,完整的分布式锁流程如下:

image-20231012165830290

如何解决主从一致性问题

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

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

image-20231012165932042

可以使用Redisson的MultiLock(联锁)来解决,和核心思想是开启多个Redis 主节点,设置锁时必须在所有主节点都写入成功,才算设置成功。如果出现有一个节点拿不到,都不能算是加锁成功,就保证了加锁的可靠性。

image-20231012170207816

MutiLock 加锁原理

当我们去设置了多个锁时,redission会将多个锁添加到一个集合中,然后用while循环去不停去尝试拿锁,但是会有一个总共的加锁时间,这个时间是用需要加锁的个数 * 1500ms ,假设有3个锁,那么时间就是4500ms,假设在这4500ms内,所有的锁都加锁成功, 那么此时才算是加锁成功,如果在4500ms有线程加锁失败,则会再次去进行重试.

image-20231012170345846

实现 MultiLock 的几个关键:

  1. 遍历所有节点,依次设置锁,并使用列表来记录所有主节点的锁是否设置成功。

  2. 只要有一个节点设置不成功,就要释放所有的锁,从头来过。

  3. 因为不同节点设置锁成功的时间不同,所以在所有锁设置成功后,要统一设置过期时间(但如果 leaseTime = -1 就不用了,因为开启了看门狗机制会自动续期)

  4. 锁释放时间(leaseTime)必须要大于抢锁最大等待时间(waitTime),否则可能出现第一个节点抢到锁,最后一个节点还没抢到锁,之前的锁就已经超时释放了。所以如果指定了 waitTime 和 leaseTime,默认 leaseTime = waitTime * 2。

MultiLock最安全,但成本也是最高的。

本文作者:万事胜意k

本文链接:https://www.cnblogs.com/ysk0904/p/17759963.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   万事胜意k  阅读(200)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起