同一车次里的部分发货单未发货 1.一个车次里可以添加多个发货单
2.一个发货单只能属于一个车次
3.一次可以向车次里添加多个发货单
4.发车前 车次状态=未发车, 发货单状态=待发货
5.发车后 车次状态=已发车, 发货单状态=已发货, 订货单状态=全部发货/部分发货
6.车次发车处理耗时大概需要 7-10 秒(根据实际单据数量而定)
7.创建车次之后, 会发送一个延时MQ消息 . 当发车时间一到, MQ消息被消费, 会将车次状态变成已发车, 车次里的所有发货单状态变成已发货, 对应的订货单状态变成全部发货或部分发货.
已发车的同一车次( 241119-01141 )里有一个发货单( FH-241119-004183 )状态是待发货, 而对应的订货单状态是已发货
车次发车时间 2024-11-19 23:28:28.000
发货单创建时间 2024-11-19 23:28:28.007 与 发车时间
同一秒 订货单最后更新时间 2024-11-19 23:28:28.059
车次发车逻辑伪代码如下
@Transactional(rollbackFor = Exception.class)
public void doTran() {
1.更新车次状态为已发车
2.查询该车次里待发货的所有发货单 send_order_list
3.更新发货单状态为已发货
4.循环处理每个发货单
for send_order in send_order_list {
4.1更新发货单明细
4.2更新订货单明细
4.3更新订货单状态为全部发货/部分发货
}
}
根据发货单号 FH-241119-004183 查询日志系统, 分析得知该发货单是通过 批量城配发货 功能被添加到车次( 241119-01141 )里.
创建发货单逻辑伪代码如下
@Transactional(rollbackFor = Exception.class)
public void doSend() {
send_order['send_order_status'] = "待发货"
if (当前时间 > 车次发车时间) { //
send_order['send_order_status'] = "已发货"
}
send_order['tms_id'] = 车次ID // 发货单存储车次ID
1.保存发货单 //
2.创建发货明细
3.更新订货明细
if (当前时间 > 车次发车时间) { //
4.更新订货单状态为已发货
}
}
根据以上业务逻辑, 它是考虑了车次已经发车但仍然向车次里添加发货单的场景, 会将发货单状态变成已发货, 订货单状态变成已发货.
然而在车次发车的临界时间点(2024-11-19 23:28:28.000), #1处不满足条件, 发货单状态是默认待发货, 2和3操作会访问数据库, 在执行#3处时满足条件, 订货单状态变成已发货. 在 #2 和 #3 之间, doTran 方法开始执行, 没有查询到向车次里添加的该发货单 (数据库隔离级别RC), 便发生了文章中所说的这种情况.
虽然一次向车次里添加单个发货单时,发生文章所说情况的概率极低, 但是当一次向车次里添加很多个发货单时,这个概率就会被放大.
虽然发货单的创建时间 2024-11-19 23:28:28.007 大于 车次发车时间 2024-11-19 23:28:28.000, 那是因为发货单的创建时间是在 #2处生成的, 而不是在#2之前生成.
一图概之
1.
车次发车逻辑(doTran) 和 向车次添加发货单逻辑(doSend) 采用同一个分布式锁, 可能会造成车次发车一直拿不到锁,造成车次迟迟未发车. 毕竟两个逻辑不对等, 不能使用分布式锁.
2.#1和#3两处的判断必须保持一致
3.默认延迟30秒再处理发车逻辑. 便会把添加到车次里的所有发货单进行处理. (并未根本解决)
4.如果向车次添加发货单逻辑(doSend)事务处理时长超过30秒,依然出现问题.