Redis分布式锁的基础是Redis的单进程单线程,用来控制分布式系统之间同步访问共享资源。
实现的原理是CAP。
分布式锁的目的是对资源的保护,确保业务逻辑与预想的一致性、正确性。实现原理是每一个线程争夺对Redis的写操作的权限,从而获得操作业务代码的权限。
网上有非常多不同版本的实现,但是总觉得都有一定的问题,所以自己写了一个实现。
@Slf4j
public class RedisLockHelper {
private RedisTemplate<String, Object> redisTemplate;
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 默认超时时间 100毫秒
*/
private int defaultLockTimeout = 100;
/**
* 默认等待时间 50毫秒
* 超时后重试获取锁时间
*/
private int defaultLockTimeoutWait = 50;
/**
* 锁名
*/
private String lockKeyName;
/**
* 锁状态
*/
private volatile boolean locked = false;
/**
* 当前锁的超时时间
*/
private String timeOutTime = "";
public RedisLockHelper() {
}
/**
* <p>@Description: <p>
* <p>@param lockName 锁名<p>
**/
public boolean lock(String lockName) throws InterruptedException {
this.lockKeyName = lockName + "_LOCK";
return getLock();
}
/**
* <p>@Description: <p>
* <p>@param lockName 锁名<p>
* <p>@param timeOut 超时时间<p>
**/
public boolean lock(String lockName, int timeOut) throws InterruptedException {
this.lockKeyName = lockName + "_LOCK";
this.defaultLockTimeout = timeOut;
return getLock();
}
/**
* <p>@Description: <p>
* <p>@param lockName 锁名<p>
* <p>@param timeOut 超时时间<p>
* <p>@param timeWait 等待时间 <p>
**/
public boolean lock(String lockName, int timeOut, int timeWait) throws InterruptedException {
this.lockKeyName = lockName + "_LOCK";
this.defaultLockTimeout = timeOut;
this.defaultLockTimeoutWait = timeWait;
return getLock();
}
private boolean setNx(final String key, final String value) {
try {
Object obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
connection.close();
return success;
}
});
return obj != null ? (Boolean) obj : false;
} catch (Exception var4) {
log.error("普通缓存放入原子操作异常:{}", var4.getMessage(), var4);
return false;
}
}
private Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
private String getSet(String key, String value) {
return (String) redisTemplate.opsForValue().getAndSet(key, value);
}
/**
* 获取锁方法
* <p>
* 线程会循环直到获取到锁为止 业务代码是一定会执行一遍才能返回
*/
private boolean getLock() throws InterruptedException {
//锁名为空或超时时间为空时 防止死锁 就不能获取锁
while (!StringUtils.isBlank(lockKeyName) && defaultLockTimeout > 0) {
String newTimeOutTime = String.valueOf(System.currentTimeMillis() + defaultLockTimeout);
//直接去获取锁 setNx (SET if Not exists)
if (this.setNx(lockKeyName, newTimeOutTime)) {
log.info("获取锁名为:{},超时时间为:{} 的锁成功", lockKeyName, defaultLockTimeout);
locked = true;
this.timeOutTime = newTimeOutTime;
return true;
}
//获取锁内的时间 检查是否超时
Object oldTimeOutTime = get(lockKeyName);
//如果获取旧的超时时间是null 可能是其它线程在上锁
//锁里的时间小于当前时间 则说明锁已过期 可以尝试获取
if (oldTimeOutTime != null && Long.parseLong((String) oldTimeOutTime) < System.currentTimeMillis()) {
String currentTimeOutTime = this.getSet(lockKeyName, newTimeOutTime);
//保存后取出来的时间和之前获取的时间相等 则获取锁成功 否则锁已被其它线程获取
if (StringUtils.equals(currentTimeOutTime, String.valueOf(oldTimeOutTime))) {
log.info("获取锁名为:{},超时时间为:{} 的锁成功2", lockKeyName, defaultLockTimeout);
this.locked = true;
this.timeOutTime = newTimeOutTime;
return true;
}
}
Thread.sleep(defaultLockTimeoutWait);
}
log.info("获取锁名为:{},超时时间为:{} 的锁失败", lockKeyName, defaultLockTimeout);
return false;
}
/**
* 解锁方法
* <p>
* 正常情况下都需要主动的解锁 这样锁的效率才最高的
* 如果经常性的等超时时间然后被替换 就需要优化代码 优化超时时间
*/
public void unLock() {
String oldTimeOutTime = String.valueOf(this.get(lockKeyName));
//只有是自己的锁才去解锁 否则就不进行操作
if (locked && timeOutTime.equals(oldTimeOutTime)) {
redisTemplate.delete(lockKeyName);
log.info("释放了锁名为:{}的锁", lockKeyName);
}
}
}
某晚突然想到集群部署时 每台机器的系统时间一定要保持一致,否则就会造成混乱
当然写的肯定还是有很多的问题 欢迎指错与交流