上周三晚上十点,我正准备睡觉,手机突然响了。是运维同事打来的,说线上AI代码补全工具的错误率突然飙升到了30%,用户反馈用不了。
我瞬间清醒了,打开电脑开始排查。这一查就是一整夜,直到第二天早上七点才找到根因。这篇文章记录一下这次排查的完整过程。
故障现象
先说说故障现象。我们的AI代码补全工具是一个前端VS Code插件,用户输入代码时会调用后端AI接口返回补全建议。那天晚上突然有大量用户反馈补全没反应,或者返回乱码。
监控面板显示错误率从平时的不到1%飙升到了30%,而且还在持续上升。错误日志里大量出现"Unexpected token"和"Response parse failed"的报错。
最诡异的是,这个错误不是所有用户都有,大概三分之一的用户受影响,而且集中在Windows平台。Mac和Linux用户基本正常。
初步排查
第一步先看后端日志。后端接口返回正常,状态码都是200,返回的数据格式也没问题。那问题应该出在前端。
我在本地复现了一下,发现确实有问题。补全接口返回的数据到了前端,但是解析的时候报错了。我把返回的原始数据打出来看,发现数据本身是正常的JSON,但前端解析的时候出了问题。
一开始我以为是编码问题。Windows平台的编码和Mac/Linux不一样,会不会是UTF-8 BOM的问题?我检查了一下,返回的数据确实有BOM头,但之前一直都有,为什么今天突然出问题了?
我把BOM去掉之后测试,问题依然存在。看来不是BOM的问题。
深入排查
既然不是编码问题,那是什么?我开始逐行对比正常和异常的返回数据。
对比了半天终于发现了异常。正常用户的返回数据里,AI生成的代码补全内容是正常的。但异常用户的返回数据里,代码内容里多了一些奇怪的字符,看起来像是零宽字符或者不可见字符。
这些字符在日志里看不到,因为它们是不可见的。但我用十六进制编辑器打开原始数据,发现了大量的U+200B(零宽空格)和U+FEFF(零宽不换行空格)。
这些字符哪来的?后端返回的数据里没有,那就是前端在处理数据的时候加进去的。
我开始追踪前端的数据处理流程。数据从后端返回后,经过了几个处理步骤:先解密,再解压,然后JSON解析,最后提取代码内容。
找到根因
排查到解压这一步的时候,我终于找到了问题。
我们用的是pako这个库来做gzip解压。最近升级了一个版本,新版本在Windows平台上有个bug:解压的时候会在某些边界条件下插入零宽字符。
为什么只有Windows平台有问题?因为pako新版本用了TextDecoder来处理解压后的字符串,而Windows平台的Node.js版本对TextDecoder的实现和其他平台不一样,在处理某些特定字节序列的时候会插入零宽字符。
为什么之前没发现?因为这个bug只在特定的输入数据下才会触发,而AI返回的代码内容刚好包含了这些特定的字节序列。那天晚上AI模型更新了,生成的代码风格变了,刚好触发了这个边界条件。
找到根因之后,解决方案就简单了:把pako回退到上一个稳定版本,或者在解压后手动去除零宽字符。
修复上线
找到根因的时候已经是早上六点了。我赶紧做了修复,在解压后加了一步零宽字符清理,然后打包测试。
测试通过后,七点半发布了紧急修复版本。错误率很快降回了正常水平,用户反馈也恢复了正常。
虽然修好了,但这次故障影响了大概九个小时,受影响用户有几千人。事后复盘的时候,我总结了几个教训。
经验教训
第一个教训是依赖升级要谨慎。pako这个库我们用了很久,一直很稳定,所以升级的时候没有仔细看changelog,也没有在Windows平台上做充分测试。以后升级核心依赖,必须在所有目标平台上跑一遍完整的测试用例。
第二个教训是监控要更细粒度。我们的错误监控只统计了错误率,没有按平台、按版本、按错误类型细分。如果一开始就能看到错误集中在Windows平台,排查速度会快很多。
第三个教训是数据处理要加防御性校验。从后端返回的数据,经过解密、解压、解析等多个步骤,每一步都可能出问题。应该在每一步之后都加数据校验,发现异常及时告警,而不是等到最后解析失败了才发现。
第四个教训是线上故障要有完善的应急流程。这次故障从发现到修复花了九个小时,其中有一半时间是在找复现路径。如果有完善的故障应急流程,比如快速回滚机制、灰度发布机制,影响范围会小很多。
写在最后
排查了一夜,虽然很累,但收获也很大。每一次线上故障都是一次学习的机会,让你对系统有更深的理解。
做前端开发久了,会遇到各种各样奇怪的bug。有时候bug不在你的代码里,而在你依赖的第三方库里;有时候只在特定平台、特定数据下才会出现。这种bug最难排查,但也最能锻炼能力。
希望这篇文章能给大家一些启发。线上故障不可怕,可怕的是故障之后没有总结,下次还犯同样的错误。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录