背景:
项目中有一个短信群发任务(例如1次要发送1W条短信),系统会获取任务中每一条短信的MQ并发发送短信。任务默认状态是未发送(状态码:0),需要在这一批任务发送第一条短信的时候,将任务状态修改为发送中(状态码:1),在任务发送结束将状态修改为发送完成(状态码:2)。
代码处理逻辑:
伪代码如下,通过redis记录当前任务已发送了多少条,如果是第一条,则将任务状态更新为发送中,如果已发送条数等于任务总条数,将状态更新为发送完成
Long sendCount = redisTemplate.opsForValue().increment(taskId);
if(sendCount == 1){
// 更新状态为发送中 update task set status = 1 where task_id = #{taskId}
}
if(sendCount >= totalCount){
// 更新状态为发送完成 update task set status = 2 where task_id = #{taskId}
}
代码这么写,简单测试了两个任务,发现没有问题
问题:
实际线上运行的时候,发现部分任务始终是发送中状态,错误日志也没有异常,后来查日志才发现这种情况在并发比较高的时候会有问题,假设本次任务有2条短信,如下情况会导致任务一直处在发送中状态:
步骤 | 第一条短信 | 第二条短信 | 任务状态 |
Long sendCount = redisTemplate.opsForValue().increment(taskId); | 执行 | 未发送 | |
Long sendCount = redisTemplate.opsForValue().increment(taskId); | 执行 | 未发送 | |
sendCount == 1 | false | 未发送 | |
sendCount >= totalCount | true | 发送完成 | |
sendCount == 1 | true | 发送中 | |
sendCount >= totalCount | false | 不更新 |
即,第二条短信先判断if(sendCount >= totalCount)为true,将任务状态更改为发送完成,第一条短信再判断if(sendCount == 1)为true,将任务状态由发送完成变成发送中,但是第一条短信判断if(sendCount >= totalCount)为false,不会将任务状态变成发送完成,所以任务状态会一直是发送中。
优化:
修改其实很简单,只需要将短信状态更新为发送中的时候,加一个判断,在任务状态不为2的时候才能更新。
Long sendCount = redisTemplate.opsForValue().increment(taskId);
if(sendCount == 1){
// 更新状态为发送中 update task set status = 1 where task_id = #{taskId} and status <> 2
}
if(sendCount >= totalCount){
// 更新状态为发送完成 update task set status = 2 where task_id = #{taskId}
}