一,调度中心
业务流程优化
1,JOB 地址解析/库存校验/承运商分配
导致问题:日志表量多大,高德调用量过大,频繁调用第三方接口,接口处理慢
处理方案:降量,处理历史数据从时间维度,近三个月;多线程+分批,一个线程处理20条数据
2,针对库存匹配失败,状态29,单独拉出来JOB,降低JOB执行频率1h一次
3,延迟出库问题,导致dps到达状态没有更新成功
原因:正常先出库后发运,但是由于一批车统一做出库,就先发运了,导致后续到达无法更新成功
bug处理
1,dps偶发无法分配问题
20一批,一个线程处理一批,其中部门订单无法分配
原因:开始怀疑库存问题,实际不是。单个线程处理一批数据时,有个查询逻辑selectOne,但是匹配到多条数据,导致异常,该线程的后续数据,则无法进行分配处理,关键时,没有打印异常日志
处理方案
1,降量一批处理50,改成20
2,selectOne 改成limit
3,捕获异常
skywalking优化
根据订单履历,更新订单告警信息
订单报警巡检定时器
orderAlarmWithParamsJobHandler
/** * 报警业务逻辑 * 1,分配成功1小时后仍未收到承运商的调度信息 30/33 * 2,调度生效后48小时未反馈出库 6020/6050 * 3,库存匹配失败(出发地仓库与实车所在仓库不一致)29 * 4,地址解析失败 26 * 5,分配失败(39):履历31 没有匹配到有效的分配规则 * 6,分配失败(39):履历170 订单分配失败,VIN在途 * 7,地址解析成功后,长时间未分配承运商。履历中22,主表中未记录状态。待添加 */
问题所在
1,全量获取订单数据
2,在for循环中,查询订单履历
方案
1,查询最近一个月订单
2,根据订单ID查询履历,避免for循环中查询
问题
分配失败
分配规则没有维护好,需要在某个维度增加车型,仓库信息
二,运单中心
死锁:
原因:一个地方,根据主键更新运单里程;另外一个地方进行范围更新
网络异常:更新承运商失败,调用用户中心失败
详细:另外一个地方进行范围更新
DpTaskPanicBuyingPoolCreate 做了几件事 1,运单池扫描运单定时器
2,关闭超时自动抢单运单
3,关闭不符合条件的已扫描任务(补偿操作,运单包含配件单,配件单未下发被扫描)
<select id="selectIdsByGrabbingStatus" resultType="java.lang.Long"> SELECT id FROM tt_task where grabbing_status = '79501010' and relation_parts_count > 0 </select>
//3.关闭不符合条件的已扫描任务(补偿操作,运单包含配件单,配件单未下发被扫描) List<Long> ids = ttTaskService.selectIdsByGrabbingStatus(); if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(ids)) { ttTaskService.batchUpdateGrabbingStatus(ids, GrabbingStatusEnum.unScanned.getStatusCode()); }
public enum GrabbingStatusEnum { // unScanned("0","未扫描"), endScanned("79501010","已扫描"), inFleet("79501020","进入车队抢单池"), inPublic("79501030","进入公共抢单池");
解决方案
1,缩短大事务,把这个范围更新逻辑拉出来,单独job跑
2,改成ID更新
大表
车联网
执行时间15min
问题:之前取全量,取了多余的数据,JOB频率
方案:取最近3个月数据,只需地跑,10min一次
三,订单中心
1,运单重复生成问题,调度中心重复出库
问题:运单重复生成问题,调度中心重复出库
如何发现的:忘记了,安吉反馈重新出库了吗?
表现:taskmq 消费记录有两条,order发送两次
原因:order没控制住,通过查询订单表,根据外部订单号,并发情况下,拦不住短时间重复下发
解决方法:order接收订单时,加分布式锁,维度外部订单号,过期时间1min
根本原因:调用中心,job重新下发了,为什么会重新下发?-》中间表更新失败
老的记录:
// 先查询,没有再写入 // 实际结果 还没有执行新增订单,就查到了 // 后续都执行完成了,订单创建了,运单也异步创建了,这个再抛出一个异常,导致dps中间表状态更新失败,是2 // 然后再重试,这时候订单是真有了,再重新下发,重试9次,依然失败
2,调度中心偶发调度失败问题
原因1:订单中心,调用调度中心,调用失败
解决方案:增加重试机制,从500ms增加到3s,还是依然存在
最终方案:把查询订单方法上的注解去掉
为什么生效了
解决方案解释1:
移除事务注解:通过移除 @Transactional 注解,taskStatusChangeMessagePush 方法不再创建新的事务上下文,而是直接读取数据库中的最新数据,避免了事务隔离带来的延迟问题。
确保一致性:确保 commonCreatOrder 方法在写入订单时已经提交事务,使得后续的 taskStatusChangeMessagePush 方法能够立即读取到最新的订单数据
解释2:
删除 @Transactional(rollbackFor = Throwable.class) 注解后,taskStatusChangeMessagePush 方法不再参与事务管理,从而解决了偶发查询不到订单的问题。原因如下:
事务隔离问题:当方法被标记为事务时,数据库操作会在事务提交之前处于未提交状态。如果 commonCreatOrder 和 taskStatusChangeMessagePush 不在同一个事务中,但 taskStatusChangeMessagePush 自身是一个事务,则它会创建一个新的事务上下文,导致无法看到其他事务尚未提交的数据。
解释3
为什么查询方法上注解@Transactional(rollbackFor = Throwable.class) 去掉,问题解决了,可以查询到订单了
如果去掉查询方法上的 @Transactional(rollbackFor = Throwable.class)
注解后,问题解决了(可以查询到订单了),这说明问题与 事务的传播行为 或 事务的隔离级别 有关。以下是可能的原因分析和解决方案:
1. 事务传播行为的影响
-
原因:
@Transactional
注解默认的传播行为是Propagation.REQUIRED
,即如果当前存在事务,则加入该事务;如果不存在事务,则创建一个新事务。-
如果查询方法上加了
@Transactional
,而调用查询方法的地方已经在一个事务中,那么查询方法会加入该事务。 -
如果该事务尚未提交(例如,新增订单的事务还未提交),那么查询方法会读取到未提交的数据(取决于隔离级别),或者被阻塞(如果隔离级别较高)。
-
-
解决方案:
-
如果查询方法不需要事务,可以去掉
@Transactional
注解。 -
如果查询方法需要事务,但希望独立于其他事务,可以将传播行为设置为
Propagation.REQUIRES_NEW
。 -
示例代码:
复制
@Transactional(propagation = Propagation.REQUIRES_NEW) public Order queryOrder(Long orderId) { return orderRepository.findById(orderId); }
-
2. 事务隔离级别的影响
-
原因:
@Transactional
注解默认的隔离级别是数据库的默认隔离级别(通常是READ COMMITTED
)。-
如果查询方法上加了
@Transactional
,并且隔离级别较高(如REPEATABLE READ
或SERIALIZABLE
),可能会导致查询方法无法立即看到其他事务提交的数据。 -
如果去掉
@Transactional
注解,查询方法会以非事务方式执行,直接读取数据库的最新数据。
-
-
解决方案:
-
如果查询方法需要事务,但希望读取已提交的数据,可以将隔离级别设置为
READ COMMITTED
。 -
示例代码:
复制
@Transactional(isolation = Isolation.READ_COMMITTED) public Order queryOrder(Long orderId) { return orderRepository.findById(orderId); }
-
3. 事务超时或锁等待
-
原因:如果查询方法上加了
@Transactional
,并且当前事务持有锁(例如,新增订单的事务锁定了某些行),查询方法可能会被阻塞,直到锁释放。-
如果去掉
@Transactional
注解,查询方法会以非事务方式执行,不会被阻塞。
-
-
解决方案:
-
检查新增订单的事务是否持有锁,并确保事务尽快提交。
-
如果查询方法需要事务,可以设置较短的事务超时时间。
-
示例代码:
复制
@Transactional(timeout = 5) // 设置事务超时时间为5秒 public Order queryOrder(Long orderId) { return orderRepository.findById(orderId); }
-
4. Spring 事务管理的上下文问题
-
原因:如果查询方法上加了
@Transactional
,Spring 会为该方法的执行创建一个事务上下文。如果调用查询方法的地方已经在一个事务中,可能会导致事务上下文冲突。-
去掉
@Transactional
注解后,查询方法会以非事务方式执行,避免了上下文冲突。
-
-
解决方案:
-
确保事务的传播行为和上下文管理符合业务需求。
-
如果查询方法不需要事务,可以去掉
@Transactional
注解。
-
5. 事务的只读属性
-
原因:如果查询方法上加了
@Transactional
,但没有设置readOnly = true
,Spring 会将其视为一个读写事务,可能会导致额外的锁或事务管理开销。-
去掉
@Transactional
注解后,查询方法会以非事务方式执行,避免了这些问题。
-
-
解决方案:
-
如果查询方法需要事务,可以将其设置为只读事务。
-
示例代码:
复制
@Transactional(readOnly = true) public Order queryOrder(Long orderId) { return orderRepository.findById(orderId); }
-
总结
去掉查询方法上的 @Transactional
注解后问题解决,说明查询方法不需要事务,或者事务的传播行为、隔离级别等配置导致了问题。以下是建议的解决方案:
-
如果查询方法不需要事务:
-
直接去掉
@Transactional
注解。
-
-
如果查询方法需要事务:
-
设置合适的传播行为(如
Propagation.REQUIRES_NEW
)。 -
设置合适的隔离级别(如
Isolation.READ_COMMITTED
)。 -
设置
readOnly = true
,避免不必要的锁开销。
-
通过调整事务配置,可以确保查询方法既能正确执行,又能避免与其他事务的冲突。如果问题仍然存在,可以提供更多上下文信息,我可以进一步帮助你分析!