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

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

  • 依赖引入,REDIS-VERSION 6.0.6
   
        org.springframework.boot
        spring-boot-starter-data-redis
        2.6.3
    

    
        org.apache.commons
        commons-pool2
    
  • 配置文件信息,本例是基于哨兵集群方式创建的,集群搭建可以参考redis集群搭建:
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 redisTemplate() {

        RedisTemplate 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 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 ownerThreadLocal = new ThreadLocal<>();

    private ThreadLocal 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,分布式,java)