跳到主要内容
电台没死,先死的是我的告警

电台没死,先死的是我的告警

阿舟
阿舟

· 阅读约 8 分钟

上周三下午翻手机,翻到一条三天前的告警。服务凌晨两点挂过,五分钟后自愈。不是它没发,是我压根没收到。那条“看起来很完整”的通知链路,断在了它自己的第一环。

同一天晚上我刷到《纽约客》十月那篇写阿根廷农村电台的稿子,乔丹·萨拉马写的,讲 AM 电台替偏远居民播读私人留言。两条线就这么撞上了。一个阿根廷牧羊人,离最近的村子五十英里,没有手机信号,靠一台信号飘忽的 AM 收音机等家里的消息;我坐在千兆光纤的工位上,告警挂了三天浑然不觉。

画外音:我们这行天天把“链路”“闭环”挂在嘴上,真论对链路的理解,不如一个放羊的。

先说说这电台到底是什么,免得有人以为我在讲什么复古情怀。阿根廷几家主要的 AM 电台每天固定时段播 Mensajero Rural,翻译过来大概就是“给乡下人的留言”。留言怎么送进去?打电话、发 WhatsApp,或者写张纸条——纸条是最有意思的一种,常常写在随手撕下来的碎纸上,字迹潦草,拼写也不规整。上了年纪、识字不多的老人,会把纸条托下一个进城的人捎到电台门口的信箱里。

主持人拿到之后,用第三人称念出来:“告诉胡安,他妈妈的药买到了,让他别急。”就这种。内容日常得不能再日常——报平安、找走失的牲口、留个联系方式。

这种土得掉渣的东西,救过命。

2013 年夏天,马拉圭。公共卫生官员比利亚尔通过电台通知巴兰卡斯河上游的牧民:医疗队哪天到、在哪个营地扎营。布里希达和费利克斯这对牧民夫妻听到了——用一块快没电的电池听的。他们六岁的儿子加斯东发烧,肚子疼得厉害。

听完通知,一家人骑马走了六英里多到营地。比利亚尔随后开车八十五英里,把他们送到公路上的救护车。孩子住院,阑尾切了,救回来了。事后夫妻俩又通过电台给家里报了平安。

我反复看这段。不是因为惨,是因为这条链路每一环都丑得不行:电池快没电、骑马六英里、开车八十五英里,最后还要靠电台再报一次平安。没有一环是“优雅”的。但每一环都在。

还有一段更邪门的。1982 年 5 月,马岛战争已经打起来了。里奥加耶戈斯一个播音员念了条寻人信息,找塞尔希奥·戈麦斯·罗卡,让他去德塞阿多港。这不是什么民间寻人——是军方在用 Mensajero Rural 给一艘海军拖船传信,那船被英军炸过,通信系统全毁了。

问题在于,戈麦斯·罗卡本人已经在袭击里死了,跟七个船员一起。但另一个幸存船员听到了这条广播,转告了副手巴桑,巴桑带着船开到了德塞阿多港。船保住了。

这段我看了两遍才反应过来它说明了什么:广播是公开信道,理论上发给一个人的话,全阿根廷都能听见。这在常规系统设计里叫“信息泄露”,是 bug。但在通信全断、只知道“对方可能还开着收音机”的场景下,“谁都能听见”恰恰是唯一能把信息送进去的方式。

代价当然有。正因为谁都能听见,当地人发留言时会主动省略细节、用模糊说法,牵扯到钱的尤其谨慎,有的干脆用事先约好的暗语。你看,没人给他们发过什么《公开信道隐私指南》,他们自己就演化出了一套草根的信息安全实践。

主持人这个角色,我一直觉得被低估了。马拉圭的主持人哈克斯说过,念错一个人名或地名,后果可能很严重——医疗队通知、洪水暴雪时的求救信号,念错一个字可能就有人白跑一趟,甚至送命。所以“念”这件事本身是有人工审查在里面的,轻度编辑,但要准确传达原意。

LU20 的卡里娜·洛佩斯在那家电台主持早间 Mensajero 快二十年。1980 年,她爸在野外,是通过 LU20 知道她出生的。三十三年后,她爸去世,她写好了那条消息,但没自己念,让同事代读——她念不下去。

这个细节我卡了很久。我们现在做 agent、做自动化,“human in the loop”是个被说烂的词,多数时候指的是“AI 干完人点个头”。但洛佩斯这件事提醒我,人在环里还有一种作用:有些话机器转述不了,不是语法问题,是那个人当时开不了口。这个能力没法写进 prompt,也没法做成 API。

扯远了,回正题。

说回我那条断了三天的告警。原来的代码大概长这样:

def notify(msg):
    requests.post(SLACK_WEBHOOK, json={"text": msg})

一个函数,一个 webhook,发出去就完事。我当时觉得这叫“跑通”——本地一测,消息确实到了 Slack,收工。至于 webhook 本身挂了怎么办、Slack 那天抽风怎么办、token 过期了怎么办,我一条都没想。因为“它刚才明明跑通了啊”。

后来那个 webhook 就静默失效了。我不确定是过期还是被谁删的,反正消息发出去没有任何报错。而我的监控恰恰只监控了“服务本身”,没监控“通知链路”。所以服务挂了,通知发不出去,而“通知发不出去”这件事,没有任何机制会告诉我。

画外音:我把“通知链路”当成了“监控链路”,这俩根本不是一回事。

这个 bug 是在我毫无防备的情况下被一次真实的服务抖动发现的,谈不上什么技术含量,纯属缴智商税。

改完之后大概这样:

def notify(msg):
    results = []
    for ch in (slack, sms, email):
        try:
            ch.send(msg)
            results.append((ch.name, True))
        except Exception as e:
            results.append((ch.name, str(e)))
    if not any(ok for _, ok in results):
        raise RuntimeError(f"全链路失败: {results}")

多通道不是重点,重点是最后那行——一条链路都不通的时候,得有人知道。然后我还加了个心跳,每天固定时间故意发一条测试消息,收不到就说明链路又断了:

0 9 * * * curl -fsS -X POST "$SLACK_WEBHOOK" -d '{"text":"[心跳] 通知链路自检"}'

土吧?土。但土的东西才活得久。

我原本以为阿根廷这个故事到这儿就该结束了——卫星互联网一来,这种 AM 电台迟早凉。2023 年底阿根廷放开外国互联网服务商竞争,Starlink 进来了,大概两百万人接上了卫星网,主要在农村。逻辑上,网一通,谁还听电台。

结果并没有。

萨帕塔兄弟家厨房里,LU20 还是整下午开着。格拉迪斯·门多萨的弟弟用 Starlink 都一年了,她还是习惯通过 Mensajero 给弟弟发消息。

更妙的是丘布特高原上形成了一种新的通信层级——没网的人,会请有网的邻居帮忙代发 Mensajero 信息。六十八岁的牧民德尔加多丢了头公牛,自己没网没电话,开车半小时到镇上,请人用 WhatsApp 把消息发给 LU20,当天就播了。

我看到这段的时候第一个反应是想笑,第二个反应是有点脸红。我把“新技术出现→旧技术淘汰”当成了默认假设,这跟我嘲笑过的“AI 一来程序员就没饭吃”是一个思维惰性。真相是新技术给能负担的人加了一层,旧链路服务的是剩下的人——以及所有人,在主链路断掉的那一刻。

受访的牧民自己看得很清楚:双向无线电、电话、手机,哪一样都没把这电台干掉,凭什么互联网能。他们猜以后个人留言可能会少,更多用在生日、慰问这类“公共性通知”上。这个判断比很多行业分析都准。

开头我提的那个牧羊人,佩雷拉,其实有个后续。今年早些时候作者重返巴塔哥尼亚,请洛佩斯帮忙在电台里播一条找他的信息。几天后一个司机听到了,辗转找到了他的联系方式。

佩雷拉早就离开牧羊生活了,搬到特雷利乌附近,在肉类加工厂上班,结了婚,三个孩子,主要用 WhatsApp 和 Facebook。

他没听到那条找他的留言。他离开农村之后就不再有收音机了。

但这事不算失败。埃尔米拉索尔有个八十二岁的莫利纳,独居几十年,不识字,每天早上七点听 Mensajero,几乎没落下过一次。学校甚至把老人的加餐时间调到下午四点半——就为了赶在节目播出的时段。他说他不是在等自己的消息,他是替所有人听。

那套链路到底该不该留?我觉得这问题问反了。该问的是:你主链路修得越快,兜底那条是不是丢得越干净。

我给自己定了一条:以后每搭一条自动链路,先问一句——它断了,谁会知道,多久会知道。答不上来,就别急着上线。

阿舟
阿舟

写代码写到一半开始怀疑人生,靠 AI 工具续命,顺手把踩过的坑都记下来。

查看主页 →