找回密码
 会员注册
查看: 36|回复: 0

记一次事务里发普通消息的线上问题排查过程

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64080
发表于 2024-9-20 04:35:33 | 显示全部楼层 |阅读模式
1结论2问题背景及现象3排查过程3.1初次分析3.2问题再次出现3.3解决方案4总结与反思1结论结论先行:事务+MQ的使用场景,使用方式一定得正确,稍有不慎,可能就会带来数据不一致问题。2问题背景及现象商业退款业务,每周都会有几笔退款订单自动处理失败,究其直接原因,是因为数据表里的一个字段cost更新失败导致。抽象一下,业务场景大概是这样的:系统A处理完一笔订单后,会发送MQ,系统B消费系统A的MQ,消费过程中,会去系统A拉取信息,然后更新系统B对应的表信息。3排查过程3.1初次分析小伙伴B在review系统B代码后,发现消费MQ时,没有加锁,可能造成系统B数据更新失败。原因是系统A在处理业务过程中,同一笔业务,发送了两个MQ,topic一致,tag分别为create和update,系统A的cost字段,是update时更新的。系统B在消费系统A的两个MQ时,如果处理tag=update时事务先提交,tag=create时后提交,就会将cost字段覆盖,造成系统B的cost字段更新失败。发现问题,及时修复,加完锁上线,以为问题修复了,结果高兴太早了。3.2问题再次出现第二周校对数据时,又发现有几笔退款订单自动处理失败,问题再次出现。感觉不对啊,继续定位问题,小伙伴B说我这边逻辑当前看应该没有问题了,按照逻辑分析,应该拿回来的cost就为0,所以没有更新。小伙伴A说系统A中也没看出啥问题,执行流程和日志显示,吐出去的数据是对的。两人都觉得自己的程序没有啥问题,然后继续加日志,捕捉到底是系统B接到的数据就出问题了,还是其他地方有没考虑到的点。没过两天,就出现了监控日志预警,系统B在消费tag=update的MQ过程中,查询系统A,返回的信息中cost=0,update_time=170377168510(系统B的log记录下来的)。系统A发送MQ前的日志记录cost=3048,update_time=170377168557,此时数据库表中的最新update_time=170377168557,和系统A日志一致。于是问题变得清晰了,按照之前确认的结论(不一定对),系统B消费tag=update的MQ时,查询到了系统Aupdate之前的数据。3.2.1分析思路一我们考虑会不会是数据库主从问题,系统A对应的主库A更新成功了,但是主库A数据同步到从库B还没完成,系统B查询走的从库B,此时可能出现数据不一致,查询到了update_time=510的数据。后经过确认,系统A集群对应数据库没有从库,排除该可能。3.2.2分析思路二考虑会不会是数据库事务级别带来的幻读,查询了下配置,系统A的数据库隔离级别是repeatable-read,这个可重复读隔离级别,跨事务的话,A事务读取数据,只能感知到B事务提交后的结果,也不对,557时系统A更新cost后,给的结论是事务提交成功了,排除了该可能。3.2.3分析思路三小伙伴们分析搞不定了,叫了架构大佬一起看,同步了上下文,大佬C说,你们不会用了大事务吧,我们说没有啊,每步操作事务提交都是成功的。大家都蒙圈了,感觉不应该啊,太违背直觉了。过程中,DBA同事把当时时间节点前后的更新binlog全部导出来了,我们一起一条条对照,也没发现啥异常。然后到了午饭点,大家说下午接着一起分析,吃完饭,小伙伴A又开始review代码,最终发现了端倪,确实是大事务的原因导致。a.updateCharge(CpsOrderDOcpsOrderDO)这个接口,更新后发送MQ,但是方法被多层包裹,上层在一个大事务里,导致消息发送成功后,可能事务还未提交成功。b.updateCostSuccess接口里,其实包含了三步操作,执行完后,还处于大事务中,事务并未提交,后面还有处理流程。c.具体过程如下3.3解决方案找到问题,测试环境验证一下猜想,updateCostSuccess后,延迟事务提交,基本可以稳定复现问题。解决方案就清晰了,发送MQ移步到事务提交成功后即可,修复上线后,继续观察了一段时间预警,没有再次出现该问题了,到此,该问题得到彻底解决。完整流程如下:4总结与反思对于有经验的工程师来说,可能一眼就知道了问题在哪里,比如我们架构的同事们,但是对于没经验,或者没有考虑到这个点上的工程师来说,可能就比较难发现原因,属于典型但不复杂的场景。大家可以想一想,这个问题中,哪些环节有问题,可能带来当前的数据不一致结果。数据不一致,有两个大方向的原因:a.数据冗余导致;b.并发控制不好导致。这个问题,这两个都有一定的关联,对于cost字段,系统A里已经有一份原始数据,是否有必要在系统B的业务订单表biz_business_order中再存一份,值得思考。对于并发控制,系统B消费系统A同topic,不同tag=create||update的MQ时,如果不做并发锁控制,也可能导致系统B中cost字段产生不一致。基于本次出现的这个case,我们内部也进行了讨论学习,对于数据一致性问题,总结了几条建议,分享给大家。数据是否冗余值得思考,如果选择冗余来降低系统复杂性,就需要用程序保证数据一致性。涉及到数据一致性的场景,对于重要的业务场景,最好有数据校对和预警,便于提前发现问题。对于业务较复杂的场景,大事务+MQ结合的使用,代码一定要多review,否则可能存在隐患,要谨慎。如果事务中发MQ,就需要用事务MQ,保证逻辑处理的结果和发送MQ的结果一致,否则就可能产生不一致(业务不一致或数据不一致)。比如这个场景中,系统Aupdate后发出的MQ,发送成功,如果后续事务回滚了,消费方系统B就可能产生业务不一致问题。对于大事务操作,比如用注解直接包住一个大方法这种方式,要慎用。作者介绍杨迎,转转商业后端开发工程师,目前负责商业B端相关业务系统开发(商机线索、客户运营、销售运营管理、广告发布等)。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 11:34 , Processed in 0.327675 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表