首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
SAAS
ToB门户
了解全球最新的ToB事件
论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
微博
Follow
记录
Doing
博客
Blog
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
排行榜
Ranklist
相册
Album
应用中心
qidao123.com ToB IT社区-企服评测·应用市场
»
论坛
›
软件与程序人生
›
后端开发
›
Java
›
记一次诡异的线上异常赋值排查:代码没错,结果不对 ...
返回列表
发新帖
记一次诡异的线上异常赋值排查:代码没错,结果不对
[复制链接]
发表于 2025-6-6 21:24:26
|
显示全部楼层
|
阅读模式
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要
登录
才可以下载或查看,没有账号?
立即注册
×
这篇文章重要是记载和分享一次线上故障排查的履历 —— 可以说是目前为止我遇到过的最抓狂的排查之一。明明
代码
看上去没有任何问题,我也反复检查过每一处逻辑,理论上它就不应该出错,但现实执行的结果却始终不符合预期。一边怀疑人生,一边硬着头皮往下查,折磨,真的折磨……
背景
在我们的一套比较老的高可用系统中,有一张用于记载订单信息的数据表。此中有一个标识字段(以下称为 flag),该字段会在颠末一系列处置惩罚后被赋予差别的标识值,供后续业务逻辑使用。
我把与此次问题相关的一段赋值逻辑简化如下:
如果订单包含多个明细且包含多个sku(品类 > 1),则 flag 标记为 2;
否则标记为 1。
赋值
代码
if (orderSize == 1 && orderDetails.size() > 1 && skus > 1) {
return 2; // flag = 2
}
if (orderSize == 1) {
return 1; // flag = 1
}
复制
代码
按预期,凡是订单同时满意“多明细”和“多品类”两个条件时,flag 字段就应被赋值为 2。事实上,这个小
功能
也确实在线上稳定运行有3个多月了。
然而,近期客户反馈了一个异常工单:部分同时满意“多明细”和“多品类”的订单,其标识字段存在不一致的环境,有的标记为 2,有的却标记为 1。
这不是一个阻断性故障,也不是一个核心
功能
模块,但也一定程度给后续业务处置惩罚带来了麻烦,至少在问题解决之前,都是由线上实行实时监测并手动在
数据库
中去修改这个标识字段的值的……
排查分析
1、代码逻辑分析
我的第一反应是判断逻辑是否写错了,反复考核了代码,然后去仿真上验证,结果固然也没有复现。
详细询问后,得知客户已运营的多个堆栈中,只有一个堆栈出现了这样的异常征象,其他的都是正常运行,且所有堆栈运营的 tag 都是一样的。
2、binlog分析
我确认代码没有问题,又一个第一反应,就是去看下落表的时候是不是正常的,有可能这又是一起值被更新覆盖的变乱。
截取一段时间内的 binlog 后,我发现了一个诡异的征象:
所有新增(insert)记载的 flag 字段值都是 1;
而那些最终标识字段值符合预期的票据,其实是在insert 之后紧接着来了一次 update,将其更新为 2;
更希奇的是,insert 操作总是发生在 A
服务器
,update 则总是来自 B
服务器
;
如果某次 update 应用行为发生在 A 服务,那 flag 字段值就始终为1,从没有变成预期的 2;
固然我们的系统里有些定时任务会做一些兜底处置惩罚,但 flag 本该在 insert 的时候就写入正确值,为什么会在 B
服务器
才被补刀?这显然是有问题的。
然后我又拉下来客户称没有问题的其他几个运营堆栈的binlog 做对比,发现它们是没有问题的,insert 的时候 flag 字段值就是 2,没有发生额外的 update。
岂非是这个堆栈 A、B 两台服务器的代码不一样?
于是我写了一个 groovy 脚本,分别在 A 服务器 和 B 服务器 上执行此业务方法,并且传入相同的订单数据,发现返回值:
A 服务器: 返回 2;
B 服务器: 返回 2;
同时也从两台服务器上拉下了核心的业务jar包,对比后也确实是一样的。
更诡异了……
3、缓存&并发分析
不是代码问题,也不是摆设包不一致,我又猜,会不会是并发大概缓存问题导致的,由于 insert 时的数据是独立赋值的,而 update 时的数据来源于
数据库
。
说不定 A 服务器有缓存,导致 insert 的时候使用的是旧值而不是新值。
但是翻了相关的上下文业务代码后,发现这里并没有使用任何 本地缓存 大概 redis。
我琢磨,唯一可能的并发点,可能就是代码中通过 parallelStream 来处置惩罚的订单明细,方法中有对同一个 order 连用了两次并行流操作,可能会影响对 skus 的计算?我之前听大哥们复盘的时候提过,有大哥由于在 CompleteFuture 里用了并行流,结果踩了并发
安全
的坑……
我真的反复看了很多遍代码(唉,历史遗留问题太多,我们的代码复杂度堪比徒步翻山),很确定这块儿就是个串行业务,没有异步,也没有多线程,按理说不应该是并发问题。
排查到这里,我总觉得可能是环境问题了。按以往的经验,这种代码没毛病、又只有它这独个堆栈有问题,重启一下 A 服务器的 tomcat 解决所有!但我也不敢真的这样建议……敬畏生产环境是基本修养!
4、类加载分析
颠末以上排查后,我也整不出新思绪了,然后小组里讨论了一下,有同事提到,会不会是 A 服务器的类加载有问题?
当时我对类加载机制还没有太多实战经验,完全没往这处儿想,但越琢磨越觉得有戏。固然我之前已经从服务器上拉取了对应的 jar 包并细致检查里面的代码,但万一 tomcat 现实运行的并不是 jar 包中的那份代码呢?
于是我们立刻用 Arthas 对线上环境做分析,分别在 A、B 两台服务器用 jad 命令反编译目标类,果然,在 A 服务器的 JVM 中根本没有这段新加的 flag 的赋值代码!
这时,我突然想起,之前我的确在两台服务器上都跑过 groovy 脚本,如果 A 服务器的 JVM 中压根儿没有这段赋值代码,那为什么脚本还能输出一个正确结果?
……
很快我就想明白了,我们的服务摆设在 tomcat 上,一共包含 3 个 war 包。简单来说,insert 操作发生在 war 包1中,update 操作发生在 war 包2中,而我测试用的 groovy 脚本是在 war包2 所属的应用上执行的,也就是说,当时脚本跑得没问题,其实是由于正好在有关逻辑的那一边执行的,而我一时忘记了这点……
解决
当我们排查到 JVM 中并没有那段关键的业务逻辑代码时,基本就可以收工了。背面就是关照实行切换 master ,然后删除 tomcat 下的 work/webapp 目录 和 temp 目录,然后重启。
至于为啥会出现这种“灵异变乱”,我们猜可能是 class 被缓存了、tomcat 没重新加载,又大概有大哥悄悄热更新了某台服务器……
跋文
这次问题看起来只是一个简单的字段值异常,结果越查越离谱,面临这个结果我也挺不测的。固然最终写出来的排查过程看起来就这么些字儿,但是期间履历了对 binlog 的分析、对代码一遍又一遍的“灵魂拷问”、在分布式服务器之间来回查验,最后借助 Arthas 逐层深入……折磨,真的折磨……
最后,也感谢这一通“折腾”,我也算实打实地体会到了 JVM 类加载与热摆设的相关机制,对 Java 的类加载行为有了进一步的认知。Arthas 的命令,比如 jad、sc、mc、redefine 等也被我练的“炉火纯青”了……
过程固然令人抓狂,好在结局还不错。
回复
使用道具
举报
返回列表
浏览过的版块
分布式数据库
物联网
SAAS
移动端开发
前端开发
DevOps与敏捷开发
程序人生
SAP
SQL-Server
怀念夏天
+ 我要发帖
登录后关闭弹窗
登录参与点评抽奖 加入IT实名职场社区
去登录
微信订阅号
微信服务号
微信客服(加群)
H5
小程序
快速回复
返回顶部
返回列表