What
具体场景:工单表(Ticket)存在接单人、状态、客户信息等字段,但存在转单(即接单人更改)后,转单记录表存在记录(TransferRecord),但实际转单人未更改成功的情况。
Bug表现:出现表数据部分字段未更改成功?(偶发,转单发生率 ≈ 1/1000)
可否复现:测试环境未能复现 😂
Why
诡异点:转单API的日志打印,工单表(Ticket)接单人字段的更新真真实实成功了,而且也不存在啥并发
(⊙o⊙)… ,莫非是哪个 👻 程序改了这个表?
思路有了,该业务存在定时服务,走查代码,初看并没有对 Order 表接单人字段人的更改 … …
甩锅失败,但个人感觉还是这个定时服务出了问题,继续细看,发现一段 Ticket 表的保存,其业务逻辑(每隔 10s 定时)如下:
- 获取创建时间 10 分钟内且未处理过的 Ticket (company_id =0 表示未置过该值)
- 调用某个(其他系统的) API,获取客户信息,并根据返回的 res 保存对应 Ticket 表里面的客户信息(其他系统的记录ID & 客户等级)
- 保存 Ticket 表
这段业务背景:调用的某个API为旁支业务(线上响应较慢且有可能失败,需要重复调用 …),创建工单( Order)时调用恐影响主要逻辑
真相只有一个 👇 (Golang Gin 实现,看不懂没关系,仅看执行逻辑即可)
哪一步做了 Save?🙌 DB.Save(&t)
这边的 t 指针数据为调用 api.GetCompanyInfoByEmail
之前的 t 整个 Ticket 单的数据!!!
所以,如果API调用及处理(时间差)期间,发生了转单,也就是接单人 t.ToEmail 变了(API 服务调用),但是这边的 t.ToEmail 还是老数据的覆盖,就产生了类似「字段未更改成功」的现象。
该 Bug 偶发的原因:
- ”其他系统 API“ 调用时长的时间差是否与转单发生时间交错(测试环境该 API 很快,所以未复现)
- 实际业务创建 10s 内的情况下也不常发生转单 🙃
How
问题提定位后,解决就不难了。确定 CompanyID 以及 VipLevel 只在该定时业务处理后,该定时业务代码 Fix 为只更新这两个字段(+更新时间 🙃)即可。
Bug Fixed。但是,有啥值得反思的呢?
- 核心问题:业务逻辑未理清,旁支处理更新了全表 =》应处理为只更新需要更新的字段
- 异步处理放在定时服务处理,有点奇怪但也不失为一种方式,但会造成排错成本的增加。如果把该异步处理放在转单API 里面,加个协程处理更佳(即使协程处理也须注意只更新需要的字段)。