记一次 Redission 的 Lock.tryLock(500, TimeUnit.MillSecond) 导致的线上Bug。
程序员文章站
2022-06-05 15:56:24
...
前不久产品给提了一个Bug,说他在说feed 流的时候,有时候会突然刷到空内容,而且之后无论如何刷新,总也刷不出内容。我心里一想,卧槽,这么严重,立马提升bug为高优,并着手去修复。
业务背景介绍: 业务背景大概这这样, 在一个 关注Tab 页面下,用户也可查看用户关注人的一些动态信息Feed流,。前端只是去请求固定的接口,后端需要调用另一个系统去阿里云TimeLine 引擎分页查询用户的Feed 流,查询结果进行返回。当然 线上是多台机器部署,并使用SLB 进行负载均衡。
大致了解接口逻辑之后,便着手进行查询,我首先查询线上日志信息是否有报错,从Info 日志开始查询,得益于项目该打的日志打的很全,所以一会的功夫,搜索了一遍并没有发现有任何的异常信息(当然找不到,因为本来就没有收到什么报警通知),然后只能我去模拟这个用户 进行多次的尝试, 发现偶尔确实可以复现,查看前端请求后端接口 返回的数据确实为空,但是后端我单独去查,确实是有数据的呀,这让我非常郁闷,没办法,只能看代码了,看了很久,感觉也没有什么收获,一看表, 都九点多了,没办法,赶紧走吧, 这时我突然注意到 这个地方使用了 redis 的分布式锁,是不是这里有问题呢,伪代码大概如下。
RLock lock = redissonClient.getLock("distrubuted_lock" + UserId);
try {
if(lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// http请求 TimeLine 获取Feed 流数据
return Result;
}catch (RuntimeException e){
logger.warn("【服务异常】",e);
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
logger.error("【分布式锁服务异常】",e);
}
return SuccessResponseData.getSuccessResponseData(null);
看到这里,我隐隐约约觉得哪里好像不太对,但是又说不上来,哎。 突然脑子一闪,为什么获取不到 分布式锁就返回为空呢?
稍微思考了一下,首先 虽然一个请求只能被SLB 转发到一台机器上,但此处用了分布式锁,意义应该就是有一些操作同时只能有一个地方进行操作,是为了保证该地方的强一致性,所以添加了分布式锁,并使用了 有超时时间的tryLock 。
深入思考了一些,此处应该存在以下的问题:
- jvm 执行垃圾回收,导致 虚拟机暂停该线程,或者 更极端的导致 STW 情况出现, 从而导致无法正常获取锁。
- 由于 redis 是单线程的,如果在获取锁的时候,此时有一个需要执行时间颇长的任务,例如 拉取一个大对象,存储一个大对象,复杂逻辑操作等等, 都会导致无法及时的相应当前获取分布式锁的操作,从而导致获取失败,返回空数据列表。
- 考虑到极端情况,如果用户同时发出了两个请求,一个请求先到一台服务器,获取到了分布式锁,另一个请求到了另一台请求,尝试获取分布式锁,获取失败,返回空数据,并且此请求返回的速度要快于获取到分布式锁的请求,所以前端会接收到空数据,显示空白页。
考虑以上种种请求,解决方案有如下几种:
- 使用 Lock.lock() 阻塞式获取锁,此举可消灭因多提提交导致为获取分布式锁的请求先返回空数据的情况,但是该接口有可能长时间 (转圈)的可能, 用户体验不好
- 增大 tryLock() 的时间,此举可一定程度上 解决该 bug 出现的概率,但是并未从根本上解决问题。
- 可以给前端返回指定标示,表示因分布式锁故障导致未能成功查询,而不是没有数据,则前端在此发出请求。
考虑实现的难度以及成本,最后选择了第二条, 观察一段时间,如果还能出现的话,那么可能就会重视起来,要从根本是解决了。
观察中。。。。。。。。。。。。。。。。。。。。。。。