因为我的一个需求需要请求一个耗时比较长的接口(耗时长其实是对接方的锅),该接口交给了Spring事务管理,并且使用了分布式锁,但是在请求的时候,出现error,看日志发现是unlock的时候没有锁可以去解锁,才爆出的异常。然后又由于使用了事务,导致整个请求的数据被回滚。

然后就通过日志看请求时间和这次异常的时间,发现整整相差了1分钟,而分布式锁设置的时间只有5s
@Override
@PostMapping(path = "xxxxx")
@Transactional(rollbackFor = Exception.class)
public Result create(@RequestBody Request request) {
// 通过缓存防止重复申请
String lockDate = new StringBuilder().append(request.getId()).toString();
DistributedLock lock = distributedLockService.getLock(LOCK_PREFIX + lockDate);
if (!lock.tryLock(0, 5, TimeUnit.SECONDS)) {
return Result.error(100, "请勿重复请求");
}
try{
//业务代码
}catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
logger.warn(e.getMessage());
return Result.error(100, "证书申请失败");
} finally {
lock.unlock();
}
这就导致tryLock设置的时间已经超过,锁此时已自动释放,那么走到finally块时,又执行lock.unlock(),导致上述异常,而数据又回滚,就会产生好像执行了但实际上啥也没有的情景。
那么我们的解决思路有两种:
@Override
@PostMapping(path = "xxxxx")
@Transactional(rollbackFor = Exception.class)
public Result create(@RequestBody Request request) {
// 通过缓存防止重复申请
String lockDate = new StringBuilder().append(request.getId()).toString();
DistributedLock lock = distributedLockService.getLock(LOCK_PREFIX + lockDate);
if (!lock.tryLock(0, 5, TimeUnit.SECONDS)) {
return Result.error(100, "请勿重复请求");
}
try{
//业务代码
}catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
logger.warn(e.getMessage());
return Result.error(100, "证书申请失败");
} finally {
try{
lock.unlock();
}catch(Exception e){
}
}
当然,唯一不好的,就是如果你的公司如果使用了sonarLint,会爆
Unlock this lock along all executions paths of this method
但是这个并不会产生什么大问题,不去管它就好,如果大家有什么更好的解决方案,可以评论区一起讨论。