背景:
项目中有一个短信群发任务(例如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}
}