RedisTemplete 实现分布式锁
redis 分布式锁
项目中设置了定时任务,而项目属于微服务架构,后续会采用多点部署。考虑到多个服务节点只需运行一次的特点,所以需要采用分布式锁在同一时间只运行一次代码。中间也踩过不少坑,不断的修复。
在使用的过程中需要注意两点:
1、redis的配置需要设置超时等待时间,否则会在获取不到redis资源的时候出现阻塞等待,导致方法无法退出。
2、由于在分布式锁中使用了redis的事务管理,而在redisTemplete中是不会主动释放资源的,这也会导致redis连接数不够,从而出现第一个问题。所以我们需要在释放锁的时候同时也释放redis连接。
3、在任务过程中。我们需要注意的是守护续约线程的及时关闭。在一些操作结束后我们需要在finally 中释放以及关闭。
RedisLock
package com.xxx.xxx.xxx.xxx.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author 豪猪不挡道
* @version 1.0
* @desc redis分布式锁事务实现
* @date 2021/1/20 20:04
*/
@Slf4j
@Component
public class RedisLock {
private static ThreadLocal<Thread> THREA_LOCAL = new ThreadLocal<>();
private final String lock_prefix = "lock_prefix:";
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* @param lock
* @param timeoutSeconds
* @return
* @throws InterruptedException
*/
public String lock(String lock, long timeoutSeconds) throws InterruptedException {
log.info(Thread.currentThread().getId() + " get lock begin...");
try {
String identification = UUID.randomUUID().toString();
String lockName = String.format("%s%s", lock_prefix, lock);
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockName, identification, timeoutSeconds, TimeUnit.SECONDS);
if (flag) {
// 续签
Thread renewal = new Thread(new RenewalLock(lock, identification, timeoutSeconds));
renewal.setDaemon(true);
THREA_LOCAL.set(renewal);
renewal.start();
return identification;
}
} finally {
log.info(Thread.currentThread().getId() + " get lock end...");
}
return "";
}
/**
* @param lock
* @param identification
*/
public void release(String lock, String identification) {
try {
redisTemplate.setEnableTransactionSupport(true);
String lockName = String.format("%s%s", lock_prefix, lock);
redisTemplate.watch(lockName);
String identificationDB = redisTemplate.opsForValue().get(lockName);
if (identificationDB != null && identification != null && identification.equals(identificationDB)) {
redisTemplate.multi();
redisTemplate.delete(lockName);
redisTemplate.exec();
}
redisTemplate.unwatch();
} finally {
RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
THREA_LOCAL.get().interrupt();
THREA_LOCAL.remove();
}
}
/**
* 续签lock
*/
private class RenewalLock implements Runnable {
private String lockName;
private String identification;
private Long timeout;
public RenewalLock(String lock, String identification, Long timeout) {
this.lockName = lock_prefix + lock;
this.identification = identification;
this.timeout = timeout;
}
@Override
public void run() {
int i = 0;
while (true) {
try {
if (Thread.currentThread().isInterrupted()) {
log.info("RenewalLock end.");
return;
}
TimeUnit.SECONDS.sleep(timeout / 2);
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.watch(lockName);
String identificationDB = redisTemplate.opsForValue().get(lockName);
if (identificationDB != null && identification != null && identificationDB.equals(identification)) {
redisTemplate.multi();
redisTemplate.expire(lockName, timeout, TimeUnit.SECONDS);
redisTemplate.exec();
log.debug(Thread.currentThread().getId() + ": reset expire time ok, " + ++i);
}
redisTemplate.unwatch();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("InterruptedException renewalLock end.");
} catch (Exception e) {
Thread.currentThread().interrupt();
log.error("Exception renewalLock end.");
log.info(e.getMessage(), e);
} finally {
RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
}
}
}
}
}