【Redis】加锁自动续期-Redissions实现

Redission实现自动续期

Redisson是一个功能强大的Redis Java客户端,它不仅提供了对Redis的基本操作支持,还内置了多种分布式数据结构和分布式锁的实现。
使用Redisson可以大大简化Redis分布式锁的实现,并且Redisson提供了更高级的功能,如 自动续期、可重入锁和公平锁等

基本原理示意图:

img

导入依赖

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

配置文件

spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=
spring.redis.database=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=500

RedissionClient配置类–注入容器配置

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://localhost:6379"); 
        return Redisson.create(config);
    }
}

编写redission工具类

注意:tryLock时指定过期时间不会启动看门狗线程进行锁续期。

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;

@Service
public class DistributedLockService {

    @Autowired
    private RedissonClient redissonClient;

    private static final String LOCK_KEY = "resource_1_lock";

    /**
    尝试获取锁  (当明确指定了过去时间leaseTime时(-1不算),不会启动看门狗线程,会在指定时间后自动释放锁)
    @param lockKey 锁的键名
    @param waitTime 等待锁的最大时间(毫秒)
    @param leaseTime 锁的过期时间(毫秒)
     @return 是否成功获取锁
    **/
    public boolean tryLock(String lockKey, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    /**
     尝试获取锁,且自动续期锁  (leaseTime为-1或不设置时,会启动看门狗线程)
     @param lockKey 锁的键名
     @param waitTime 等待锁的最大时间(毫秒)
     @param leaseTime 锁的过期时间(毫秒)
     @return 是否成功获取锁
     **/
    public boolean tryLockWithAutoRenewal(String lockKey, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, -1, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    /**
    释放锁
    @param lockKey 锁的键名
     **/
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }
}

使用redission执行业务

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class LockUsageExample {

    @Autowired
    private DistributedLockService lockService;

    public void executeWithLock(String lockKey) {
        long waitTime = 2000;  // 等待锁的最大时间(5秒)
        long leaseTime = 5000;  // 锁的过期时间(10秒)
        // 尝试获取锁
        if (lockService.tryLockWithAutoRenewal(lockKey, waitTime, leaseTime)) {
            try {
                // 执行业务逻辑
                System.out.println(Thread.currentThread().getName() + "成功获取锁,开始执行任务...");
                // 模拟长时间任务
                try {
                    Thread.sleep(8000);  // 任务耗时8秒
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

            } finally {
                // 释放锁
                lockService.unlock(lockKey);
                System.out.println(Thread.currentThread().getName() +"任务完成,锁已释放...");
            }
        } else {
            System.out.println(Thread.currentThread().getName() +"无法获取锁,任务被其他客户端占用...");
        }
    }
}

模拟并发执行任务

package com.zw.service.redission;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
public class LockTest implements CommandLineRunner {

    @Autowired
    private LockUsageExample usageExample;

    @Override
    public void run(String... args) throws Exception {
        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        // 启动多个线程尝试获取锁
        for (int i = 0; i < 5; i++) {
            executorService.submit(() -> {
                usageExample.executeWithLock("kkey1");
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

验证结果

img

Redission注意点:

  1. 自动续期机制:Redisson的自动续期机制通过看门狗定期检查锁的状态,并在锁即将过期时自动延长锁的过期时间,确保锁不会因过期而被误释放。看门狗线程会每隔一段时间(通常是internalLockLeaseTime / 3,其中internalLockLeaseTime 默认为30)检查锁的状态并自动续期,续期后过期时间又变成了internalLockLeaseTime,即30秒。(实现:在lock或tryLock方法时,不指定过期时间或指定为-1时会启动看门狗)

  2. 容错性:即使启用了自动续期机制,Redisson仍然会为锁设置一个合理的过期时间(默认为 30 秒)。这意味着如果程序崩溃,线程被中断或看门狗无法继续续期锁时,锁将在过期时间到达后自动被Redis删除。其他客户端可以在此之后重新获取锁,从而避免死锁。

  3. 避免死锁的最佳实践:设置合理的过期时间、使用tryLock而不是lock、确保unlock的调用、以及监控锁的使用情况,都是避免死锁的有效手段。

  4. 确保unlock的调用:在任务完成后,务必调用unlock方法来释放锁。建议使用try-finally块来确保即使发生异常,锁也能被正确释放。

posted @ 2025-04-23 14:23  明小子@  阅读(277)  评论(0)    收藏  举报