一、前言
因为我的一个需求需要请求一个耗时比较长的接口(耗时长其实是对接方的锅),该接口交给了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(),导致上述异常,而数据又回滚,就会产生好像执行了但实际上啥也没有的情景。
那么我们的解决思路有两种:
- 第一种延长锁的持续时间,但是这个我之前说过这个超时是因为对接方的锅,如果使用其他对接方请求这个接口还是很快的。那么这个想法就先pass
- 那第二种解决办法也是最后采取的解决方式,我们知道其实这个bug引起的最不好的体验就是整个请求产生的数据因为发生Bug,被Spring事务回滚了,导致我们想知道发生了啥,去db中找也找不到,而且更为重要的是,如果采用上一种延长锁的持续时间,可能会导致锁的竞争更加激烈,会影响系统的性能,并且其实大部分请求并不需要那么久,只是个别请求产生的延迟导致的Bug.那么最终采取的解决方式也比较简单:就是在finally块处,对lock.unlock进行try/catch捕获,如果捕获到异常,什么也不处理,这样就算有时候出现超时,超过了锁的持续时间,就算锁已被释放,这个时候去执行finally块被捕获,也不会被Spring事务回滚。
@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
但是这个并不会产生什么大问题,不去管它就好,如果大家有什么更好的解决方案,可以评论区一起讨论。