redis分布式锁实现,带看门狗功能【java】

       本篇主要是介绍基于spring-data 内置的Lettuce开发包,实现的基于redis分布式锁工具类,并且实现了看门狗功能,看门狗实现稍显蹩脚,待后续优化。主要是提供一种思路。

  • 依赖引入,REDIS-VERSION 6.0.6
   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <version>2.6.3</version>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
spring:
    redis:
      client-type: lettuce
      # 连接0数据库
      database: 0
      # 超时时间
      timeout: 6000
      # redis验证密码,对应哨兵配置文件 sentinel auth-pass myredis 123456
      password: 123456
      lettuce:
        pool:
          max-active: 200
          max-wait: -1
          max-idle: 5
          min-idle: -1
      sentinel:
        # 集群的名称, 哨兵配置文件sentinel monitor myredis ip port 1
        master: myredis
        #哨兵集群
        nodes:
          - 192.168.132.130:26379
          - 192.168.132.130:26380
  • 创建RedisTemplate对象实例
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    private final RedisProperties properties;

    public RedisConfig(RedisProperties properties) {
        this.properties = properties;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(getConnectionFactory());
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer); // key
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); //value

        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisConnectionFactory getConnectionFactory() {
        //哨兵模式
        RedisSentinelConfiguration configuration = new RedisSentinelConfiguration();
        configuration.setMaster(properties.getSentinel().getMaster());
        configuration.setPassword(properties.getPassword());
        configuration.setDatabase(properties.getDatabase());
        List<String> nodes = properties.getSentinel().getNodes();
        nodes.forEach(node -> {
            String[] str = node.split(":");
            RedisNode redisServer = new RedisServer(str[0], Integer.parseInt(str[1]));
            configuration.sentinel(redisServer);
        });
        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration, getPool());
        
        factory.setValidateConnection(true);
        return factory;
    }

    @Bean
    public LettuceClientConfiguration getPool() {
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        //redis客户端配置:超时时间默认
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder
                builder = LettucePoolingClientConfiguration.builder().
                commandTimeout(Duration.ofMillis(60000));
        //连接池配置
        RedisProperties.Pool pool = properties.getLettuce().getPool();
        genericObjectPoolConfig.setMaxIdle(pool.getMaxIdle());
        genericObjectPoolConfig.setMinIdle(pool.getMinIdle());
        genericObjectPoolConfig.setMaxTotal(pool.getMaxActive());
        genericObjectPoolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
        builder.shutdownTimeout(Duration.ofMillis(4000));
        builder.poolConfig(genericObjectPoolConfig);
        return builder.build();
    }
}
  • 分布式锁具体代码实现逻辑(参考网上其他方案,做了修改):
    • 定义接口
// 分布式锁的接口类
public interface DistributedLock {

    default boolean acquire(String lockKey) throws InterruptedException {
        return acquire(lockKey, ConstantConfig.REDIS_LOCK_KEY_TIMEOUT);
    }

    default boolean acquire(String lockKey, int expireTime) throws InterruptedException {
        return acquireWithWait(lockKey, expireTime, 1);
    }

    boolean acquireWithWait(String lockKey, int expireTime, int waitTime) throws InterruptedException;

    boolean release(String lockKey);

}

实现接口的抽象类

import java.util.concurrent.TimeUnit;

public abstract class AbstractDistributedLock implements DistributedLock {
    @Override
    public boolean acquireWithWait(String lockKey, int expireTime, int waitTime) throws InterruptedException {
        boolean lock = false;
        long endTime = System.currentTimeMillis() + waitTime * 1000;
        while (System.currentTimeMillis() < endTime) {
            if ((lock = doAcquire(lockKey, expireTime)) == true) {
                break;
            }
            TimeUnit.MILLISECONDS.sleep(100);
        }
        return lock;
    }

    protected abstract boolean doAcquire(String lockKey, int expireTime);

}

// redis分布式锁实现,可以扩展成各种形式,本例是基于lettuce, 也可是reddision、jedis
public abstract class RedisLock extends AbstractDistributedLock{
}

具体的实现方式

import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Component
public class LettuceRedisLock extends RedisLock {

    @Resource
    private RedisTemplate redisTemplate;

    private ThreadLocal<Thread> ownerThreadLocal = new ThreadLocal<>();

    private ThreadLocal<WatchDogThread> watchDogThreadLocal = new ThreadLocal<>();

    @Override
    protected boolean doAcquire(String lockKey, int expireTime) {
        boolean success;
        if(Thread.currentThread() != getExclusiveThread()) {
            success = redisTemplate.opsForValue().setIfAbsent(lockKey, Thread.currentThread().hashCode(), Duration.ofSeconds(expireTime));
            if(success) {
                ownerThreadLocal.set(Thread.currentThread());
                WatchDogThread watchDog = new WatchDogThread(redisTemplate, lockKey);
                watchDogThreadLocal.set(watchDog);
                watchDog.setDaemon(true);
                watchDog.start();

            }
        } else {
            success = true;
        }
        return success;
    }

    @Override
    public boolean release(String lockKey) {
        boolean success = false;
        Object keyValue = redisTemplate.opsForValue().get(lockKey);
        Thread currentThread = getExclusiveThread();
        if(keyValue != null && currentThread != null && keyValue.equals(currentThread.hashCode())) {
            success = redisTemplate.delete(lockKey);
            ownerThreadLocal.remove();

            WatchDogThread watchDogThread = watchDogThreadLocal.get();
            if(watchDogThread != null) {
                watchDogThread.interrupt();
            }

            watchDogThreadLocal.remove();

        }
        return success;
    }

    public Thread getExclusiveThread() {
        return ownerThreadLocal.get();
    }

    static class WatchDogThread extends Thread {

        private String watchKey;
        private RedisTemplate redisTemplate;

        WatchDogThread(RedisTemplate redisTemplate, String key) {
            this.redisTemplate = redisTemplate;
            this.watchKey = key;
        }
        @Override
        public void run() {
            while (!Thread.interrupted()) {
                try {
                    long expire = redisTemplate.getExpire(watchKey, TimeUnit.MILLISECONDS);

                    if (expire > expire && expire < 5000) {
                        redisTemplate.expire(watchKey, Duration.ofSeconds(1));
                    }
                } catch (RedisSystemException e) {
                }

            }
        }
    }
}
  • 测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = CacheApplication.class)
@WebAppConfiguration
public class LettuceRedisLockTest {

    @Resource
    LettuceRedisLock lock;

    @Test
    public void test() throws Exception {
        String key = "xph";

        int threadCount = 10;
        Thread[] threads = new Thread[threadCount];
        for(int i = 0; i < threadCount; i ++) {
            threads[i] = new Thread(() -> {
                try {
                    boolean success = lock.acquire(key, 5);
                    if(success) {
                        System.out.println(Thread.currentThread() + "获取了锁资源!");
                    } else {
                        System.out.println(Thread.currentThread() + "噶0!");
                    }
                    Thread.sleep(new Random().nextInt(1000));
                    if(lock.release(key)) {
                        System.out.println(Thread.currentThread() + "释放了!");
                    } else {
                        System.out.println(Thread.currentThread() + "噶1!");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }, "T" + i);
        }

        for(int i = 0; i < threadCount; i ++) {
            Thread.sleep(new Random().nextInt(500));
            threads[i].start();
        }

        Thread.sleep(5000);
    }

}

### Redis分布式锁看门狗续租实现方式 在使用Redis作为分布式锁的场景中,为了防止因网络延迟或其他异常情况导致提前过期而引发数据不一致问题,通常会引入一种称为“看门狗”的机制来动态延长的有效时间。以下是关于如何在Redis分布式锁实现看门狗续租的具体说明: #### 1. 的初始化与设置初始有效时间 当客户端成功获取到时,会在Redis中存储一个键值对,其中键表示的名字,值则可能包含一些额外的信息(如加者的唯一标识)。同时,需要为这个键设置一个有限的生命周期(TTL),以确保即使发生意外崩溃也能最终释放。 ```python import redis from time import sleep client = redis.StrictRedis(host='localhost', port=6379, db=0) def acquire_lock(lock_name, client_id, timeout): end_time = time.time() + timeout while time.time() < end_time: result = client.set(lock_name, client_id, nx=True, ex=int(timeout)) if result: return True return False ``` 上述代码片段展示了如何尝试获取一个有指定超时期限的[^1]。 #### 2. 启动看门狗定时器进行自动续期 一旦获得之后,在业务逻辑处理期间,可以通过启动一个后台线程或者调度任务定期调用`renewExpiration()`函数刷新的时间戳,从而避免其因为超过设定的最大持有期限而被误删除。 ```java private ScheduledFuture<?> schedule; // 设置续约时间为当前剩余有效期的一半 long initialDelay = Math.max(leaseTime / 3L, 1); schedule = scheduler.scheduleAtFixedRate(() -> { try { RLock rLock = redissonClient.getLock(lockName); ((RedissonLock) rLock).forceRenewLock(); } catch (Exception e) {} }, initialDelay, leaseTime / 3L, TimeUnit.MILLISECONDS); ``` 此部分Java代码体现了基于Redisson库实现续费过程[^4]。这里值得注意的是,默认情况下每次都会将重新设定了一个新的固定长度的生存期而不是简单叠加原有值之上。 #### 3. 解阶段清理资源并停止看门狗 最后一步是在完成工作负载后正常解之前记得终止任何正在进行中的轮询活动以及清除关联的数据结构比如EXPIRATION_RENEWAL_MAP条目等动作以便彻底结束整个流程控制链路恢复正常状态不再占用不必要的计算能力消耗。 ```java public boolean unlock() { Boolean res = connection.sync(new UnlockCommand(...)); EXPIRATION_RENEWAL_MAP.remove(getEntryName()); return res != null && res; } ``` 上面摘录自官方文档的例子清楚地表明了解开一把由Redisson管理着的RLock实例之时所做的内部操作之一就是从映射表里剔除对应记录项的动作[^3]。 ### 总结 综上所述,利用Redis构建支持看守犬功能的分布型定解决方案不仅能够保障系统的稳定性还能提升用户体验满意度水平。它主要依赖于合理配置参数配合高效算法达成目标效果的同时兼顾性能考量因素做出最佳权衡决策方案设计思路清晰明了易于理解和维护升级迭代版本兼容性强适应范围广泛适用多种实际应用场景需求变化灵活调整优化空间大潜力无限值得深入研究探讨学习借鉴推广普及应用价值极高前景光明未来可期!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

x_pengcheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值