做技术的人,最怕的就是半夜接到线上故障的电话。上个月我就经历了一次,一个和AR眼镜相关的线上Bug,让我整整排查了一夜。
这篇文章记录一下这次故障的完整排查过程,从现象发现、定位根因到最终修复,分享一下调试经验和踩坑记录。希望能给做类似开发的朋友一些参考,也提醒大家在日常开发中注意一些容易忽略的细节。
为了不泄露公司信息,文中的具体数据和架构做了脱敏处理,但故障的现象、原因和处理过程都是真实的。
故障发生
那天是周五,本来想着终于可以过个轻松的周末了。晚上十一点多,我正准备睡觉,手机突然响了,是运维同事打来的。
他说线上AR眼镜的服务出问题了,大量用户反馈眼镜连不上服务器,App一直显示"连接中",然后超时失败。监控面板上显示,AR眼镜服务的错误率已经超过了30%,而且还在上升。
我一下子就清醒了。AR眼镜是我们公司今年的重点项目,用户量增长很快,这个服务出问题影响很大。我立刻打开电脑,登录VPN,开始排查。
先看了一下监控,确实如运维所说,AR眼镜服务的错误率从晚上十点半开始飙升,从平时的不到1%涨到了30%多。响应时间也从几百毫秒涨到了好几秒。用户的投诉在社区里已经堆了几十条,都是说眼镜连不上。
奇怪的是,其他服务都正常,只有AR眼镜的连接服务出了问题。而且错误率不是100%,大概30%左右,说明不是服务完全挂了,而是某些请求失败了。
初步排查
我先看了服务的日志,发现大量的连接超时错误。客户端发起连接请求,服务端没有在规定时间内响应,导致超时。
一开始我以为是服务端的性能问题,比如CPU或者内存不够用了。但看了一下服务器的资源使用率,CPU才40%,内存也才60%,完全在正常范围内。网络带宽也没问题,没有打满。
那会不会是数据库的问题?我查了一下数据库的慢查询,发现有一些查询确实比较慢,但还不至于导致30%的错误率。而且数据库的连接数也正常,没有耗尽。
会不会是第三方依赖的问题?我们的AR眼镜服务依赖了几个第三方服务,比如用户认证、设备管理、消息推送。我逐一检查了这些依赖的状态,发现都正常,响应时间也在正常范围内。
这就奇怪了。服务端资源正常,数据库正常,第三方依赖正常,那为什么会有大量的连接超时呢?
发现线索
排查了一个多小时,没有找到明显的原因。我决定换个思路,仔细分析一下失败的请求有什么共同特征。
我从日志里捞了几百条失败的请求,对比成功的请求,看看有什么不同。对比了用户ID、设备型号、网络类型、地理位置、请求时间等维度,终于发现了一个规律:失败的请求几乎都来自某几个特定型号的AR眼镜。
我们支持的AR眼镜有好几个品牌和型号,失败的请求集中在其中两个型号上,这两个型号占了失败请求的90%以上。而其他型号的眼镜,连接基本正常。
这个发现很关键。说明问题不是出在服务端,而是出在特定型号的设备上。或者说,是服务端和这些特定型号的设备之间有兼容性问题。
但奇怪的是,这两个型号的眼镜之前一直用得好好的,为什么突然就出问题了呢?最近也没有发布新的版本,服务端和客户端都没有更新。
深入分析
既然问题出在特定型号的设备上,我就开始研究这两个型号的眼镜有什么特别之处。
我找了一台这两个型号的测试机,在测试环境里复现问题。刚开始复现不出来,测试环境里连接一切正常。这让我更困惑了,为什么线上有问题,测试环境没问题呢?
我又仔细对比了线上和测试环境的差异。配置基本一样,代码版本一样,唯一的区别是线上的用户量更大,请求更多。
难道是并发的问题?只有在高并发下才会出现?我在测试环境里用压测工具模拟了大量的连接请求,果然,当并发量达到一定程度的时候,这两个型号的眼镜开始出现连接超时了。
找到了复现方法,问题就好定位了。我开始抓包分析,看看失败的请求和成功的请求在网络层面有什么不同。
抓包分析之后,我发现了一个奇怪的现象:这两个型号的眼镜在建立连接的时候,会发送一个特定的握手包,这个包的大小比其他型号的大。在低并发的时候,服务端能正常处理这个包;但在高并发的时候,服务端处理这个大包的时间变长,导致超时。
为什么这个包会更大呢?我仔细分析了包的内容,发现这两个型号的眼镜在握手包里携带了大量的设备信息,包括传感器数据、电池状态、网络状态等,比其他型号多了好几倍的数据量。
找到根因
知道了问题出在握手包太大,接下来就要找为什么服务端处理大包会慢。
我看了一下服务端处理握手包的代码,发现了一个问题:服务端在解析握手包的时候,用的是一个固定大小的缓冲区。如果包的大小超过了缓冲区,就会触发内存重新分配和数据拷贝。
在低并发的时候,这个额外的开销不明显。但在高并发的时候,大量的请求同时触发内存重新分配,导致CPU的上下文切换增多,处理速度变慢,最终导致超时。
但这还不能完全解释问题。因为缓冲区的大小虽然不大,但也不至于差这么多。我继续深挖,发现了更根本的原因。
原来,服务端在解析握手包的时候,有一个循环是逐个字节读取的,每读一个字节就调用一次系统调用。对于小包来说,这个开销还能接受;但对于大包来说,系统调用的次数成倍增加,性能急剧下降。
这个代码是早期写的,那时候支持的设备少,握手包都很小,所以没什么问题。后来新出的眼镜型号携带了更多的设备信息,握手包变大了,这个性能问题就暴露出来了。
而最近用户量增长很快,并发量上来了,这个问题就从偶发变成了普遍现象,最终导致了线上故障。
紧急修复
找到根因之后,已经是凌晨四点多了。我赶紧开始修复。
修复方案很直接:把逐个字节读取改成批量读取,一次读取整个包到内存里,然后再解析。这样可以大大减少系统调用的次数,提升处理性能。
同时,我也把固定大小的缓冲区改成了动态分配,根据包的大小来分配内存,避免不必要的内存重新分配。
改完代码之后,我在测试环境里做了压测,确认问题解决了。同样的并发量下,这两个型号的眼镜连接成功率从60%提升到了99%以上,响应时间也恢复了正常。
但这时候我不敢直接上线,因为已经是凌晨了,万一修复引入了新的问题,更麻烦。我决定先做一个临时的缓解措施,把这两个型号的握手包大小限制一下,超过一定大小就截断非必要的信息。这样虽然不是最优解,但能快速缓解线上的问题。
我把临时修复上线之后,监控显示错误率开始下降,从30%降到了5%以下。用户的投诉也慢慢少了。这时候已经是早上六点多了,天快亮了。
彻底解决
临时缓解之后,我睡了几个小时,下午继续做彻底的修复。
我把批量读取的优化代码做了更完善的测试,包括单元测试、集成测试、性能测试。确认没有问题之后,在下午的低峰期上线了正式的修复。
上线之后,监控显示AR眼镜服务的错误率降到了0.5%以下,响应时间也恢复了正常。那两个型号的眼镜连接完全正常了,用户的投诉也清零了。
这次故障总算彻底解决了。从晚上十一点到第二天下午,前后花了将近二十个小时,其中大部分时间都在定位问题。真正写修复代码的时间其实只有半个多小时。
复盘总结
故障解决之后,我们做了一次详细的复盘。
首先是做得好的地方。第一,发现及时,监控告警比较灵敏,故障发生后很快就发现了。第二,响应及时,运维和开发都很快投入了排查。第三,修复稳妥,先用临时方案缓解,再做彻底修复,没有造成更大的影响。
然后是做得不好的地方。第一,测试覆盖不够,没有在高并发下测试不同型号设备的兼容性。第二,代码质量有问题,逐个字节读取的写法明显有性能隐患,Code Review的时候没有发现。第三,容量规划不足,用户量增长之后没有及时做性能压测和优化。第四,监控粒度不够,没有按设备型号做细分监控,导致问题发生后不能快速定位到特定型号。
基于复盘的结果,我们制定了一系列改进措施。第一,完善测试用例,增加不同设备型号和高并发场景的测试。第二,对所有网络处理的代码做一次性能审查,改掉类似的低效写法。第三,定期做全链路压测,提前发现性能瓶颈。第四,细化监控维度,按设备型号、网络类型、地理位置等维度做细分监控。第五,建立设备兼容性测试库,新设备上线之前做充分的兼容性测试。
经验和教训
这次故障给了我很多经验和教训。
第一个教训是,不要忽视老代码。很多线上故障都是老代码在新的场景下暴露出来的问题。代码写的时候可能是合理的,但随着业务发展和环境变化,原来合理的代码可能就变成了隐患。要定期审查老代码,特别是核心链路的代码。
第二个教训是,性能问题往往在高并发下才会暴露。测试环境如果不做高并发压测,很多性能问题发现不了。要把性能测试作为日常开发的一部分,而不是上线前才做。
第三个教训是,监控要细粒度。只看整体的错误率和响应时间是不够的,要按多个维度做细分监控。这样出了问题才能快速定位,不用像大海捞针一样去排查。
第四个教训是,兼容性测试很重要。特别是做硬件相关的开发,不同型号的设备可能有不同的行为。要建立完善的设备兼容性测试体系,不能只在少数几种设备上测试。
第五个教训是,排查问题要找规律。遇到复杂的故障,不要盲目地猜,要从数据中找规律。失败的请求有什么共同特征?成功的请求有什么不同?找到规律之后,定位问题就快了。
第六个教训是,临时缓解和彻底修复要分开。线上出了问题,先想办法快速缓解,让用户恢复正常使用,然后再慢慢做彻底的修复。不要为了做一个完美的修复而让用户长时间受影响。
写在最后
这次通宵排查的经历虽然辛苦,但也让我学到了很多。做技术就是这样,每次故障都是一次成长的机会。从故障中学习,比从书本上学到的东西更深刻。
AR眼镜是一个新兴的领域,很多技术还不够成熟,遇到的问题也比较新奇。但不管是什么领域的技术,排查问题的思路和方法都是相通的:冷静分析、找规律、定位根因、稳妥修复、复盘改进。
希望这篇文章能给做类似开发的朋友一些参考。也希望大家在日常开发中多注意代码质量和测试覆盖,少踩一些坑,少熬一些夜。
最后用一句话来结束这篇文章:线上故障不可怕,可怕的是不从故障中学习。
愿每一个开发者,都能在一次次的故障排查中成长,写出更健壮的代码。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录