记第一次比较大的失误
2021年11月24日,基本解决了壮壮写的bug后,壮壮决定记录下这第一次工作写的大bug。
背景:上周五下班前,壮壮接到一个任务,写一个定时任务,定时检索推送失败的消息,周末加班是不可能滴,所以,这周一我才开始搞这个事,在我一通敲之后,方法完成了,语句优美,考虑周到,平均俩行一个注释,作为一个新人,我觉得很不错。问了问大哥,大哥说那你就测吧,不过其它服务在集群上,你把你的部署上去测一下。我就部署上去,然后看到快55分了,就把定时设置在了55分。数着秒到了55,日志开始疯狂的出现,我一惊,完蛋,我就整了3条带壮壮名字的假数据啊,然后快速的删掉了我的服务,整个过程不到1分钟。删掉之后,我想仔细排查问题,然后突然发现消息服务疯狂被访问,问了下大哥,大哥找了找,通道服务一直在进行请求,他找了很久发现我那个定时任务不到1分钟居然往消息队列里塞了20w数据,每一条都是壮壮消息重推测试,我脑门瞬间冒冷汗,啊,这。。。
代码:
public void resendMessage(String id) {
//这里while(true)是因为每次只处理100条,
//所以处理完就再去查100条进行处理,最后查不到就证明全部处理完了
while (true) {
//通过Id获取需要重新推送的消息,limit 100
List<Message> messageList = messageRepository.findByAppId(appId);
//判断需要重推的消息列表是否为空
if (CollectionUtils.isEmpty(messageList)) {
//无待处理的消息,跳出循环
break;
}
//遍历需重推的消息列表
for (Message message : messageList) {
//获取重试次数
int tries = Optional.ofNullable(notice.getTries()).orElse(0);
try {
//同步发送消息,失败抛出异常
producer.syncSend(message);
log.info(message.getMessageId() + " resend ok.");
//更新message信息(问题出现的地方)
messageRepository.updateProcessResult(result);
} catch (ExecutionException|InterruptedException e) {
//发送失败的处理
//设置处理状态为待处理,无关代码,删掉
}
}
}
}
问题
-
最初找问题的时候,发现是重推成功后,处理状态不发生改变造成的。定位后,发现更新的接口有个id必传,但是我没有传(这个接口的调用是从原消息推送方法里copy过来的,我没想到它有bug,还漏传参数,也怪我不够细心,都没看一眼接口就调用)
-
修改方案: 必然是传上那个缺失的id
-
因为后果比较严重,还是我毕业后遇到的第一次重大失误,所以今天我进行复盘,我本来以为原因是,更新失败异常,但是下面的catch 指定了发送消息的俩个异常,所以没有捕获更新失败的异常,但是因为在 try 里,所以它没有抛出异常。写了个小例子试了试,是我无知了,不捕获是可以正常抛出的
-
那么问题在哪里呢
-
我再次进入那个更新查看,发现它的传参是一个对象,对象里有那个id,所以 mybatis 会拼接一个 null 上去,然后我就去执行了这个 sql ,更新成功,更新了0行,原来如此,排查结束。
UPDATE message SET process_status = 0 WHERE app_id = NULL AND message_id = 1 > Affected rows: 0 > 时间: 0.004s
总结
- 年轻人不要不知天高地厚,不经测试就 while(true) ,还上服务器是不行的,昨天改完再测的时候,我是从循环3次到10次,最后才改成true的,希望我能牢记教训,以后都先 限制循环次数,测试没问题再 while(true)
- 调用接口,一定要看看接口的要求,哪些参数是必传
- 随手粘来的代码一定要研究透,不然出问题都不知道去哪里找(这还是在本项目里复制的,要是百度的可能更容易出问题)