Redisson分布式锁(超时释放及锁续期)

发布于:2025-03-22 ⋅ 阅读:(25) ⋅ 点赞:(0)

🍓 简介:java系列技术分享(👉持续更新中…🔥)
🍓 初衷:一起学习、一起进步、坚持不懈
🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏
🍓 希望这篇文章对你有所帮助,欢迎点赞 👍 收藏 ⭐留言 📝

🍓 更多文章请点击
在这里插入图片描述在这里插入图片描述

一、分布式锁简介

分布式锁 是一种用于分布式系统中协调多个节点对共享资源进行互斥访问的机制。它确保在同一时间只有一个节点可以执行某个操作或访问某个资源,从而避免数据不一致或冲突。

概述 :在分布式系统中,多个线程访问共享数据就会出现数据安全性的问题。而由于jdk中的锁要求多个线程在同一个jvm中,因此在分布式系统中无法使用jdk中的锁保证数据的安全性,那么此时就需要使用分布式锁。

二、为什么用分布式锁?

作用 :可以保证在分布式系统中多个线程访问共享数据时数据的安全性

  1. 资源互斥访问:确保多个节点或进程在访问共享资源时不会发生冲突。
  2. 避免重复操作:防止同一任务被多个节点重复执行。
  3. 数据一致性:在分布式系统中,确保数据的一致性和完整性。
  4. 任务调度:在分布式任务调度系统中,确保任务不会被多个节点同时执行(例如:定时任务)。

三、分布式锁应该具备哪些条件

  1. 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  2. 高可用的获取锁与释放锁
  3. 高性能的获取锁与释放锁
  4. 具备可重入特性
  5. 具备锁失效机制,防止死锁

四、Redis实现分布式锁如何防止死锁?

产生死锁的原因:如果一个客户端持有锁的期间突然崩溃了,就会导致无法解锁,最后导致出现死锁的现象。
所以要有个超时的机制,在设置key的值时,需要加上有效时间,如果有效时间过期了,就会自动失效,就不会出现死锁。

五、Redis实现分布式锁如何合理的控制锁的有效时长?

有效时间设置多长,假如我的业务操作比有效时间长?我的业务代码还没执行完就自动给我解锁了,不就废废了吗。

锁续期实现思路: 当加锁成功后,同时开启守护线程,默认有效期是用户所设置的,然后默认每隔10秒就会给锁续期到用户所设置的有效期(可以修改),只要持有锁的客户端没有宕机,就能保证一直持有锁,直到业务代码执行完毕自己解锁,如果宕机了自然就在有效期失效后自动解锁。

这些都可以通过以使用redis官方所提供的Redisson进行实现。

六、Redisson使用步骤

6.1 引入依赖

<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.34.1</version>
     <exclusions>
         <exclusion>
             <groupId>org.redisson</groupId>
             <artifactId>redisson-spring-data-33</artifactId>
         </exclusion>
     </exclusions>
 </dependency>

 <dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-data-23</artifactId>
     <version>3.34.1</version>
 </dependency>

6.2 定义配置类

6.2.1 单机redis

spring:
  redis:
    host: 192.168.1.0
    port: 6379
    password: abcdefghijk
    database: 1
@Configuration
public class RedissonConfig {

    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.database}")
    private Integer database;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        //这边采用了【单节点模式】 - redis地址
        config.useSingleServer().setAddress("redis://"+host+":"+port).setPassword(password).setDatabase(database);
        //根据config创建出RedissonClient的实列
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

6.2.2 集群redis

spring:
  redis:
    timeout: 6000ms
    database: 0
    password: abcdefghijk           #密码,若没有,不填写
    cluster:
      nodes: #集群ip+端口号
        - 192.168.1.1:6379
        - 192.168.1.2:6380
        - 192.168.1.3:6381
        - 192.168.1.4:6382
        - 192.168.1.5:6383
        - 192.168.1.6:6384
        - 192.168.1.7:6385
@Configuration
public class RedissonConfig {

    @Autowired
    private RedisProperties redisProperties;


    @Bean
    public RedissonClient redissonClient() throws IOException {
        Config config = new Config();
        if (redisProperties.getCluster() != null) {
            //集群模式配置
            List<String> urls = redisProperties.getCluster().getNodes();

            String[] clusterNodes  = urls.toArray(new String[urls.size()]);
            for (int i = 0; i < clusterNodes.length; i++) {
                clusterNodes[i] = "redis://" + clusterNodes[i];
            }
            ClusterServersConfig clusterServersConfig = config.useClusterServers()
                    .addNodeAddress(clusterNodes);
            if (StrUtil.isNotBlank(redisProperties.getPassword())) {
                clusterServersConfig.setPassword(redisProperties.getPassword());
            }
        } else {
            //单节点配置
            String address = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort();
            SingleServerConfig serverConfig = config.useSingleServer();
            serverConfig.setAddress(address);
            if (StrUtil.isNotBlank(redisProperties.getPassword())) {
                serverConfig.setPassword(redisProperties.getPassword());
            }
            serverConfig.setDatabase(redisProperties.getDatabase());
        }
        config.setLockWatchdogTimeout(15000);
        return Redisson.create(config);
    }
}

6.3 分布式锁工具类(两种获取锁方式)

@Component
@Slf4j
public class RedissonLockUtil {
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 获取分布式锁(如果业务逻辑执行时间不确定,或者可能较长,建议启用看门狗机制。)
     *
     * @param name      锁的key值
     * @param waitTime  获取锁的最大等待时间
     * @param unit      时间单位
     * @return true: 获取到锁, false: 没有获取到锁
     */
    public boolean getLock(String name, long waitTime,  TimeUnit unit) throws InterruptedException {
        RLock lock = redissonClient.getLock(name);
        //没有设置锁释放时间 看门狗机制会自动续期锁的租约时间,避免锁提前释放。锁会在租约时间后自动释放,避免死锁。
        boolean succeed = lock.tryLock(waitTime, unit);
        return succeed;
    }

    /**
     * 获取分布式锁 (如果业务逻辑执行时间较短且可控,可以显式设置 leaseTime,避免看门狗机制的开销)
     * @param name      锁的key值
     * @param waitTime  获取锁的最大等待时间
     * @param leaseTime 锁的自动释放时间(超时释放)
     * @param unit      时间单位
     * @return true: 获取到锁, false: 没有获取到锁
     */
    public boolean getLock(String name, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        RLock lock = redissonClient.getLock(name);
        boolean succeed = lock.tryLock(waitTime, leaseTime, unit);
        return succeed;
    }

    /**
     *  解锁分布式锁
     * @param name     锁key值
     */
    public void unLock(String name){
        RLock lock = redissonClient.getLock(name);
        lock.unlock();
    }
}

6.4 业务代码加入分布式锁

public void executeInternal(JobExecutionContext jobExecutionContext) {
    boolean lock = false;
    try {
        lock = redissonLockUtil .getLock(ZNJ_COCKPIT_TECH_TRAINING_JOB, 5, 10, TimeUnit.SECONDS);
        if (lock){
            log.info("=====================[获取锁,正式开始业务逻辑]============");
            //赋能模块表同步
            //业务逻辑
            log.info("====================[释放锁,业务逻辑执行结束]============");
        }
    } catch (Exception e) {
        log.error("获取锁失败:e:{}",e.getMessage());
    } finally {
        redisUtils.unLock(ZNJ_COCKPIT_TECH_TRAINING_JOB);
    }
    log.info("=====================[end]================= lock:{}",lock);
}

在这里插入图片描述在这里插入图片描述