Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. 事故复盘
  3. 正文
                           

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

2024-03-09 2652点热度 0人点赞 0条评论

  • 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时,查询到了系统A update 之前的数据。

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

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(CpsOrderDO cpsOrderDO)这个接口,更新后发送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,我们内部也进行了讨论学习,对于数据一致性问题,总结了几条建议,分享给大家。

  1. 数据是否冗余值得思考,如果选择冗余来降低系统复杂性,就需要用程序保证数据一致性。
  2. 涉及到数据一致性的场景,对于重要的业务场景,最好有数据校对和预警,便于提前发现问题。
  3. 对于业务较复杂的场景,大事务+MQ结合的使用,代码一定要多review,否则可能存在隐患,要谨慎。
  4. 如果事务中发MQ,就需要用事务MQ,保证逻辑处理的结果和发送MQ的结果一致,否则就可能产生不一致(业务不一致或数据不一致)。比如这个场景中,系统A update后发出的MQ,发送成功,如果后续事务回滚了,消费方系统B就可能产生业务不一致问题。
  5. 对于大事务操作,比如用注解直接包住一个大方法这种方式,要慎用。

作者介绍

杨迎,转转商业后端开发工程师,目前负责商业B端相关业务系统开发(商机线索、客户运营、销售运营管理、广告发布等)。

本文仅供学习!所有权归属原作者。侵删!文章来源: 转转技术 -杨迎 :http://mp.weixin.qq.com/s/kHCs7tRlJKZjyInXpcCS6A

更多文章:

  1. Spring事务无法生效的11个场景
  2. 线上问题处理案例1:出乎意料的数据库连接池
  3. MySQL8.0驱动升级事故——之三
  4. 实战:一次疑似内存泄漏的问题排查
  5. 一次访问Redis延时高问题排查与总结
  6. 干货!有些bug,跨年才有机会见
  7. Log4j框架疯狂写日志,导致磁盘打满问题排查
  8. 生产环境JVM崩溃问题排查解决
  9. Vim 一下日志文件,Java 进程没了?
  10. 事务异常:Transaction rolled back because it has been marked as rollback-only
标签: 线上事故 生产事故 线上问题 Java 事务 转转 MQ 并发
最后更新:2024-03-09

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 1 结论
  • 2 问题背景及现象
  • 3 排查过程
    • 3.1 初次分析
    • 3.2 问题再次出现
    • 3.3 解决方案
  • 4 总结与反思
最新 热点 推荐
最新 热点 推荐
视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务? 视频笔记:什么是AI 智能体? 视频笔记:什么是Flink?
Elasticsearch 使用误区之六——富文本内容写入前不清洗基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性你可以不用RxJava,但必须得领悟它的思想!如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具视频笔记:什么是Flink?
Elasticsearch 使用误区之四——不合理的使用 track_total_hits 系统设计 | 业务编号生成 浅析设计模式2 —— 策略模式 log4j2同步日志引发的性能问题 ParNew+CMS 实践案例 : HiveMetastore FullGC诊断优化 微服务架构VS单体架构,为什么要选择微服务 系统设计 | 应用系统缓存 ChatGLM:ChatGPT的替代方案

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (19) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2

x