文章目录
- 实现思路:
- 问题:
- 死锁
- 错位解锁
- 业务并发执行问题
实现思路:
- redis setIfAbsent 加锁
- 逻辑执行完,finally执行remove,释放锁
问题:
死锁
加锁后宕机导致无法释放锁;
解决方案: 设置锁过期时间,且需要保证setNx和设置过期时间操作的原子性
- 过执行一个Lua脚本文件来实现
- RedisConnection命令连用
(Boolean)redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(timeout), RedisStringCommands.SetOption.ifAbsent());
}
});
- 使用高版本Redis支持方法连用
错位解锁
业务时间大于锁超时时间
线程a获取锁,执行业务,过程中所超时失效;线程b此时开始执行,获取到锁;线程a执行完成,释放锁。此时出现线程b的锁被线程a释放的问题。
解决方案:
加锁时,记录当前线程id; 解锁时,比对当前线程id与锁记录的线程id,一致才允许操作。
解锁时,包含两个操作:查询redis中保存的锁记录的线程id;删除redis中锁记录;这两个操作可以通过lua脚本保证原子性。
业务并发执行问题
业务时间大于锁超时时间,在锁失效时,可能会导致多个线程同时执行业务逻辑的情况,若需要避免这种情况,可以为分布式锁续时
;
eg:
//设置锁
luaResult = luaScript(lockName,currentValue,expire);
if(luaResult){
//获取锁成功,设置失效时间
System.out.println("Lock success,execute business,current time:" + System.currentTimeMillis());
//开启守护线程 定期检测 续锁
ExpandLockExpireTask expandLockExpireTask = new ExpandLockExpireTask(lockName,currentValue,expire,this);
Thread thread = new Thread(expandLockExpireTask);
thread.setDaemon(true);
thread.start();
Thread.sleep(600 * 1000);
}
/**
* 锁续时任务
* @author hzk
* @date 2019/7/4
*/
public class ExpandLockExpireTask implements Runnable {
private String key;
private String value;
private long expire;
private boolean isRunning;
private LuaClusterLockJob2 luaClusterLockJob2;
public ExpandLockExpireTask(String key, String value, long expire, LuaClusterLockJob2 luaClusterLockJob2) {
this.key = key;
this.value = value;
this.expire = expire;
this.luaClusterLockJob2 = luaClusterLockJob2;
this.isRunning = true;
}
@Override
public void run() {
//任务执行周期
long waitTime = expire * 1000 * 2 / 3;
while (isRunning){
try {
Thread.sleep(waitTime);
if(luaClusterLockJob2.luaScriptExpandLockExpire(key,value,expire)){
System.out.println("Lock expand expire success! " + value);
}else{
stopTask();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void stopTask(){
isRunning = false;
}
}
加锁成功时,开启守护线程,此线程在锁过期时间还剩1/3(时间可以自己根据业务定义)时,开始为锁续时;重复此过程,直到业务逻辑执行完毕;
这样既能防止死锁,又能防止业务时间过长锁失效导致的并发问题。