❓背景
lua
临近下班的时候收到客户反馈!!说有用户投诉被短信轰炸了,轰炸了一天,啊不是一个,是一群,其中有一个最惨,
被轰炸的频率比其他人都多好几倍!!---再次对被轰炸的用户表示同情(毕竟咱也是刚接手这项目)
这个项目和上篇文章 # 又是一个BUG--记一个事务注解和异步线程导致数据异常的故事-上、又是一个BUG--记一个事务注解和异步线程导致数据异常的故事-下是一个项目都属于基本没看过代码和业务的。
通过一波日志分析找到了日志打印的地方然后让我们来浅看一下代码
less
@Slf4j
@Component
@EnableScheduling
public class ProjectSchedule {
@Scheduled(cron = "0/30 * * * * ? ")
void doSomethingWith() {
// 查询所有待执行的任务
List<ProjectTask> projectTaskList = projectTaskService.list(Wrappers.lambdaQuery(ProjectTask.class).eq(ProjectTask::getEffective, 1));
String currentLock;
for (ProjectTask projectTask : projectTaskList) {
if ((new Date()).before(projectTask.getExecutionTime())) {
// 当前时间还未到定时任务所要执行的时间,则跳过
continue;
}
currentLock = LOCK_COMMENT_NAME + projectTask.getId();
boolean lock = false;
try {
lock = RedisLockUtils.lock(currentLock, currentLock);
if (!lock) {
// 如果没有获取到redis锁,则证明有服务在执行,跳过本次扫描任务
log.info(StrUtil.format("没有获取到锁,{} 未执行", projectTask));
continue;
}
ProjectTask checkProjectTask = projectTaskService.getById(projectTask.getId());
if (checkProjectTask.getEffective().intValue() != projectTask.getEffective().intValue()){
// 运行定时任务前最后检验是否其他服务器的定时任务已经跑过了此定时任务
log.error(StrUtil.format("已有服务器执行完此定时任务{},此服务器跳出本次执行!",checkProjectTask));
continue;
}
log.info(StrUtil.format("执行任务id:{}", projectTask.getId()));
Project project = projectService.getByNo(projectTask.getProjectNo());
switch (projectTask.getTaskType()) {
//...这里省略其他状态的处理,让我们只关注下这个case 7 的处理
case 7:
project.setProjectStatus(ProjectStatusEnum.FINISHED.getCode());
projectService.updateById(project);
// 项目完成发公告
CompletableFuture.runAsync(() -> {
noticeService.publishNotice(project, 7,null);
});
log.info(StrUtil.format(">>> 执行定时任务:项目【{}】现在的状态为:{}", project.getProjectNo(), ProjectStatusEnum.FINISHED.getMessage()));
// 开始发放对应的劳动时长
projectService.awardToStudent(project.getProjectNo(),null);
break;
}
//下边这个是switch结束后的逻辑
// 改任务状态为完成:0
projectTask.setEffective(0);
projectTaskService.updateById(projectTask);
}
其实我是根据日志里的执行定时任务:...
才找到这个的,经过多方打听得知,这个的业务大概就是定时任务会去扫描所有的任务,根据状态出进行不同的处理,例如本次的就是当项目完成时会去发送短信,通知报名了这个项目的用户对本项目进行评价,类似你在某东买了个手机,确认收货了,系统给你发条短信催你去评价,想象一下一天收到好几页的催评短信,好像都得挺崩溃的,好像又跑题了=-=,这个case 7
之前的代码也粘贴上了,是当时看到定时任务开始之前还有个锁,心想"哇,不赖,有锁"。
直接看case 7
的处理
- 开启异步线程去处理项目公告--其中包含发送短信的逻辑
- 执行后续的定制化逻辑
- 修改定时任务的状态为完成,让这个任务之后不会再被扫到。
流程就下边这样
根据日志得知(中间换了个电脑之前的线上日志找不到了,贴不了截图了)是第2点执行定制化逻辑报错了,导致定时任务的状态一直没被更改,还在池子里,每三十秒被拿出来处理一次,发一次短信
至此我们知道为啥用户被轰炸了,只需要去关注下报错就好了,也就是projectService.awardToStudent(project.getProjectNo(),null);
方法,看过前两篇帖子的同志们有没有觉得这个方法很眼熟,反正就上次的bug里这个方法也复用到了,也很关键,不过这次是另一处有问题,代码就不贴了,还是多少贴点
perl
SignDetail signDetail = signDetailService.getOne(new LambdaQueryWrapper<SignDetail>()
.eq(SignDetail::getStudentId, Long.parseLong(studentId))
.eq(SignDetail::getProjectDetailId, Long.parseLong(projectDetailId))
.ne(SignDetail::getSignType, SignTypeEnum.USER_DELETE.getCode()));
这里边SignDetail
类是之前sign
的拓展,每个项目每个人只会有一条记录,但本次这BUG就是这里,同一个人同一个项目查到了六条记录,哇六条,这也就是为啥开头我说的其中有个人被轰炸的次数是别人的好多倍了
但其实以我对这个项目的理解所有能生成sign
类的地方基本都有对用户重复报名的限制,咋就突然有个大佬有六条数据出来了,这个问题我就先写了个接口去把库里重复人员都删了,暂时解决轰炸的问题,然后这个生产事故在当时,写到这就算结束了,删除重复人员保障这代码不报错了, 也就不会有被轰炸的了,至于重复数据,看过一圈代码的我当时特别自信的跟项目经理说,代码我看过了,咱都加了重复报名校验了,绝对没问题了
我都这么写了,后来肯定是被打脸的,关于后来咋定位重复报名也就是sign
咋个多条数据的事让我们下次继续
(都看到这了,各位客官点个赞支持下呀)