记人生第一次线上事故

第一次作为开发 owner 主持迭代会议,整个过程戴着耳机,昏昏欲睡地听同事一个接一个讲自己的技术方案。疫情期间,技术评审以线上形式进行,三十平的会议室里只有四个人,彼此隔开两米以上,勉力维持不能再低的安全感。评审即将结束的时候,mentor 突然问小组 leader 可以离开不,接着解释说线上出事了,是刚上线的那个服务。

没什么可怀疑的,就是我负责的服务。

leader 很淡定,问怎么了。mentor 已经托起笔记本朝外走,说业务有问题,几百单卡住了。我脑袋突然热了一下,有点懵,坐在椅子上没动弹。三月的深圳气温已经达到 20 度,每天来去匆匆没什么感觉,活在这个城市却像个一无所知的过路人。直到 mentor 说线上出事故了,这时候我才浑身发热,脑门发潮,重新对深圳的温度有了切身的感受。

leader 依然很淡定,让我也去看看,他来主持会议。我立即退出线上会议,产品 owner 不明所以,马上又发来会议邀请,被我干脆利落地拒绝了。抄起电脑跟着 mentor 出了会议室,在空荡荡的走廊上我才敢问,线上发生什么故障了。

mentor 是工作十多年的大佬,家里有个读小学一年级的儿子,平时没事就喜欢去海钓,只不过那时候也随身带着笔记本。大佬说现在还不清楚,坐回工位立马就登录生产机器看错误日志,一边让我先把服务脚本停了避免产生更多的错误数据,接着把旧服务重新装在线上启动起来,尽快恢复服务

没错,我负责的是旧服务的迁移重构。旧服务是 C++ 的守护进程,任务是从消息队列里面拉消息跑异步流程,我的任务是把它改造成 Java 的异步脚本,由 Saturn 调度。这个东西可以说就是小组里的业务核心,小组 leader 很重视,上午亲自配合完成灰度测试,逐步把旧服务完全停掉了。新服务测试了很久,开发完成就自测了一周,之后测试人员也测了一个礼拜,全量发布之前我还时不时调度脚本从消息队列里拉消息跑,没想到上线还没几个小时就出故障了。

这真是出师未捷身先死。

mentor 排查故障很快,错误日志显示是数据库执行 SQL 语句的异常,贴了日志去问 DBA ,很快反馈回来,数据更新唯一键冲突。

代码排查没有任何难度,日志里精确地打印了事故现场。MyBatis-plus 框架,选择目标状态的行记录,用 bean 对象来更新数据。代码一目了然,简单到没有给歧义留下容身之处。

LambdaUpdateWrapper<Request> updateWrapper = new UpdateWrapper<Request>().lambda()
                .eq(Request::getStatus, preStatus);
int ret = requestMapper.update(request, updateWrapper);

更新操作包裹在一个事务中,mentor 看了一会,松了口气,说还好,看上去数据不难修。大佬确实就是大佬,遇事不慌,碰到故障首先想的是怎么修数据,其他话一句没有。我全程保持静默,安静得像只竖起耳朵的兔子,在刚拉起的小群里一声不敢出。尽管如此,面对着电脑屏幕我额头一直冒汗,脑子里几乎没有成逻辑的思路。

大佬提完线上 SQL 单,leader 也开完技术评审会回来,我作为开发 owner 主持的第一个会议就这样无疾而终。数据修完,等待系统重试,mentor 和 leader 一起审查代码,终于发现了错误,更新操作没有指定唯一条件,导致带主键 ID 的 bean 对象去批量更新目标状态的表数据行,造成了唯一键覆盖的冲突

分析之后也就明白了这个 BUG 为什么没有测试出来。之前在测试环境的时候脚本都是部署一台机器,数据量比较小,一次更新时处于同一个目标状态的数据只有一条,更新也就没有问题。而线上环境脚本在多台机器上跑,这就会造成同一时间有多条数据处于同一个状态,根据状态值去选定数据更新必然会有多条数据同时更新,而采用带主键的 bean 对象去更新多行数据,自然就造成了唯一键冲突。修复代码很简单,只要指定一个唯一条件就可以了。

LambdaUpdateWrapper<Request> updateWrapper = new UpdateWrapper<Request>().lambda()
                .eq(Request::getStatus, preStatus);
                // 指定唯一键条件
                .eq(Request::getId, id)
int ret = requestMapper.update(request, updateWrapper);

人生中的第一次线上事故,没有想到来得如此简单直接劈头盖脸,让人都不知道如何自处。

幸好这个故障只导致了流程卡住的问题,对用户来说几乎没有感知,因此对业务的影响也相对较小。也幸好 mentor 和 leader 都是大佬,经验丰富头脑清晰,出了事故一句重话都没说,还在小群里安慰只是恶心了一下业务同事,让他们紧张地跑过来问怎么回事罢了。尤其是之后又补了一句,小场面,淡定淡定。这像剑客事了拂衣一般风轻云淡的态度不得不令人佩服,如果大佬嘴上叼了一支烟,此时我就会毫不犹豫地掏出打火机。

总结起来,遇到线上故障首个要点是冷静,要避免错误数据快速大量地堆积,同时尽快恢复正常服务,修复数据,最后才是排查问题源头,解决问题。多么痛的领悟,但愿踩过的坑能让人走更远的路。

发布了107 篇原创文章 · 获赞 99 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_45505313/article/details/104703317