一.业务场景
消费mq订单消息,落订单中间表,成功后,生成订单跟踪表。
二.生产出现问题
- 中间表及订单跟踪表出现大量重复数据,进而又导致,后续流程出错,而且生产上跑了两天才发现问题,存在几万条脏数据。
- 新表加唯一索引后,捕获了DuplicateKeyException异常,生产依然出现大量唯一键冲突异常,影响其他错误问题定位。
三.原因
- 根本原因:消息重复下发了。且重复次数极多,有点重复下发几十次,且mq key不同。todo 未找到未什么重复下发。
- 自身原因:代码及数据库层没有控制好。像这种存在幂等性场景,本该考虑到,外部的消息是否重复下发,无法控制。
四.补救
- 新建中间表,加唯一索引:因为中间表和订单跟踪表脏数据太多,而且存在业务联系复杂,需要花时间定位脏数据,运维执行sql,挽救很难了,现在应保证后续数据正确。
- 捕获唯一键异常:以为是消费异常,导致一直重试,一直重复下发。想捕获这个异常,也就不会抛异常,重试了。其实不是,因为第一次消费是正常的,且就算重复也是有次数的,不会这么多。而且DuplicateKeyException异常并没有捕获到,断点测试根本就没有进入catch,然后尝试了代码中抛出的其他异常,断点测试可以进入catch,但是最终还是抛出了UnexpectedRollbackException异常,捕获这条路是走不通了,应该是有方法没有找到。
- 加redis分布式锁:针对单号加锁,存在一个问题,过期时间该设置多少?最终没有采用这种方案,1.过期时间无法确定,2.过期后再重复下发。3.四个场景都会都对单号加锁,会互相影响。
- 先查询处理:存在就不执行;最终采用这种方案。这里多个节点是否存在并发问题,应该不会,就算两个节点都没有查询到数据,进入了后续流程,写入订单跟踪表。先落中间表,再落订单跟踪;或者先落订单跟踪,再落中间表,因为在一个事务里面,只要中间表抛异常,后续流程就不会执行。
- 比较查询和用redis选择问题。个人还是觉得redis更好,因为redis比查询数据库快很多,且是分布式锁。而且redis存在问题也可以解决,1.重复也是基于业务,过期时间可以大概确认;2.有唯一主键兜底处理,过期再重复没关系;3.每种场景对key,单号设置前缀,不同的key。
五.启示
- 细心,再细心:不要出现低级bug,跟高中考试一样,哎呀,如果把不该错的都做对,分数高很多。高分的人为什么没有错,只能说明基础不牢,技术不照,写代码粗心,做其他事情一样粗心。
- 测试:不测试=有bug,无论新的东西,还是改动东西,都必须要测,看着简单,可能就把流程性bug给测试,把bug发到生产。
- 日志:把日志打清楚,要能区分开,尤其关键节点,方便问题排查。
- 上生产后看日志:1.有没有报错;2.关键日志有没有问题;3.正常流程是不是通的。有些问题,上了才知道,有些场景想不到。早发现,早治疗。
- 考虑量:生产上量大的多,要分批,写入更新查询分批进行。
- 幂等性:1.重复操作会不会有问题;2.加分布式锁;3.建表加唯一索引;4.乐观锁;5.前置状态。