本篇主要是介绍基于spring-data 内置的Lettuce开发包,实现的基于redis分布式锁工具类,并且实现了看门狗功能,看门狗实现稍显蹩脚,待后续优化。主要是提供一种思路。
org.springframework.boot
spring-boot-starter-data-redis
2.6.3
org.apache.commons
commons-pool2
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
@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);
}
}