
今天测试的同学报了个很严重的问题。
我当时正在调试新一版的语音唤醒算法,车机模拟器连着我的电脑,屏幕上滚动着一行行日志。群里突然弹出一条消息,@了我,语气很急:“座舱中控屏在导航过程中黑屏重启了,你们赶紧看看。”
看到这条消息的时候,我心里“咯噔”一下。智能座舱的车机,跟普通App不一样。App崩了用户顶多骂两句,车机在导航的时候黑屏,那是真会让人在高速上心慌的。赶紧放下手头的事,连上测试车的远程日志。
日志翻了一圈,定位到一个很隐蔽的问题:多媒体服务在后台播放音乐的时候,内存占用缓慢增长,大概一个多小时之后就会触顶,然后系统把整个中控进程给杀了。测试同学刚好在开长途模拟路试,跑到一个小时出头的时候,屏幕就黑了。
问题不算复杂,内存泄漏,修一修就能好。但让我后怕的是另一件事:这个泄漏,居然是被人工跑出来的。我们的内存监控呢?那个我自认为配得很全的实时看板呢?为什么没有提前发出任何警告?
我开始往回翻监控数据。发现内存其实从第一次路测开始就在慢慢涨,但因为涨得特别平滑,没有突然的尖峰,我设的那个“内存突增”告警阈值根本没触发。而“内存持续增长”这个模式,我压根就没做哨兵。
也就是说,这个bug在车机里藏了好几周,陪着测试同学跑过了上千公里,直到今天才被肉眼发现。
这件事让我想起以前在上一家公司带我的一个老工程师,我们都叫他老韩。老韩做车载系统有个习惯,刚开始我觉得有点神经质。每次软件烧到车里之前,他一定要拉着测试和系统一起,把三个问题过一遍:
1、关键变量能不能实时看到?
2、异常状态能不能一秒内搞出来?
3、如果车机彻底死掉,黑匣子里最后那几秒的日志存没存下来?
那时候年轻,觉得他小题大做。代码我都跑过了,功能也正常,搞那么多监控干嘛?后来有一次,他负责的座舱域控在客户路试的时候出了个偶发的花屏。全车人找了三天没找到原因,最后是老韩提前埋的一个寄存器快照哨兵,在花屏发生的那一瞬间把显示控制器的状态全部拍了下来。工程师根据那张“照片”,反推出是某个时钟配置在极低温下漂移了。那次如果没有那个哨兵,问题可能永远都解不掉。
从那以后我才明白,老韩不是在搞形式主义,他是在给车机布哨兵。
智能座舱这个行当,说起来挺特别。我们写的代码跑在车上,不是跑在用户的手机里。手机死机了可以重启,车机在行驶中死机,那不是体验问题,是安全问题。用户可以把手机扔一边,但不能把方向盘扔一边。所以我们做座舱研发的,对“上线之后怎么办”这件事,要比普通软件想得多得多。
但说实话,想得多不代表做得好。
我见过太多项目,功能堆得飞快,屏幕越做越大,芯片越换越强,但底层的可观测性一塌糊涂。日志要么不打,要么打得太碎,出了事故根本拼不出完整的时间线。监控指标全是系统自带的CPU、内存,跟业务逻辑没有任何关系。告警阈值拍脑袋设,今天响个不停,明天鸦雀无声。
哨兵是什么?在我们座舱研发这一行,哨兵可以有它自己的样子。
它可以是一个关键业务的健康探针。比如语音引擎的心跳,比如导航渲染的帧率,比如蓝牙电话的连接状态。这些功能如果默默挂了,用户可能不会立刻发现,但一旦要用的时候才反应过来,那体验就是断崖式的。
它可以是一个状态快照。像老韩做的那样,在怀疑有问题的模块里埋一个“摄像头”,平时不干活,一旦异常触发,就把所有相关寄存器的值、内存里的关键变量、任务调度的情况全部拍下来。事后复盘的时候,这张照片比任何日志都管用。
它还可以是一个渐进式的告警策略。不是等到内存爆了才报,而是在它涨到70%的时候就提醒“趋势异常”,涨到80%的时候警告“风险增高”,涨到90%的时候才紧急告警。这样给研发留出反应时间,而不是直接被用户骂醒。
但做哨兵这件事,在座舱团队里往往不太受待见。因为它不写进产品需求里,不算功能点。你花三天时间设计一套状态监控方案,产品经理不会给你鼓掌。你给系统增加了5%的开销去跑探针,性能测试那边还可能找你麻烦。但如果没有哨兵,出了事故,责任全是你的。
这就很尴尬。哨兵工作,做好了没人知道,做不好全锅你背。
我经历过一次比较惨的事故,就是因为哨兵没设到位。那次是仪表盘的告警音逻辑出了问题。在某些特定工况下,该响的报警不响。因为报警音是叠加在其他音频上的,平时很难注意。直到有一台试驾车在客户手里,客户发现开了十分钟的低胎压竟然没有提示音,投诉到了质量部。我们拉日志才发现,那个报警音的调用链路里,有一个状态位被异常清掉了。如果当初在那个状态位旁边加一个“被清空前最后一次是谁干的”哨兵,根本不用排查三天。
那次之后,我们团队立了一个规矩:每一个新功能上车之前,负责开发的工程师必须回答三个问题——
1、它挂了你能第一时间知道吗?
2、你知道它为什么挂的吗?
3、它恢复的时候你能确认恢复好了吗?
答不上来,代码不许合入。
这三个问题,问的就是哨兵。
回到今天测试同学报的那个内存泄漏。修完bug之后,我干了一件事:在内存管理模块里加了一个趋势哨兵。不是等内存爆了才告警,而是每隔五分钟算一次内存增长率。如果连续三个周期增长率都为正,就发一条预警到监控群。这样下次再有缓慢泄漏,我们不是在路试跑了一千公里之后才发现,而是在第一个小时就能收到风声。
我跟那个测试同学说谢谢,他摆摆手说,没事,反正我本来就要跑长途。但我知道,要不是他今天点出这个问题,这个bug还会继续藏着,跟着下一轮路试、再下一轮,直到某个用户在路上遇到黑屏。
哨兵就是这样。大多数时候,它沉默地蹲在系统深处,不吃资源,不打扰人,看起来很多余。但当暗流涌动的时候,它是第一个发出声音的那个。
我们做座舱的,天天跟屏幕、语音、芯片打交道,总想把功能做得更酷、响应更快、界面更炫。但有时候,慢下来,想一想那些藏在代码角落里的哨兵,可能比什么都重要。
因为座舱里的最后一道门,外面是高速行驶的车,里面是活生生的人。哨兵守着的,就是那道门。
夜雨聆风