首页 › programming › Erlang的灵魂:把崩溃关进笼子

Erlang的灵魂:把崩溃关进笼子——一堂BEAM神课深读

导语

2026年,程序员圈子里最卷的两件事:一是让AI推理跑得更快,二是让服务在百万并发下不崩。多数人在第一条路上狂奔,却很少有人回头问一句:有没有一套运行时,天生就是为"大量任务同时跑、其中几个发疯也不连累别人"设计的?

有,而且快四十年了。1980年代末,爱立信为了给电话交换机写软件,养了一支小团队专门造语言,造出来的东西叫Erlang,配套的虚拟机叫BEAM。电话交换机是什么概念?七乘二十四小时不能停、成千上万路呼叫同时进行、任何一路出问题都不能影响别人——这几乎是对软件最苛刻的命题。2019年,Elixir社区的老兵Saša Jurić(也是《Elixir in Action》的作者)在GOTO大会上做了一场约45分钟的演讲,标题叫《The Soul of Erlang and Elixir》,把BEAM的灵魂掰开讲透:它不是靠代码写得完美来保证可靠,而是靠"假设每个人都会崩",把每个崩溃关进独立的笼子。

对通信行业的读者,这场演讲还有一层额外的亲切:你每天维护的电信网,正是这门语言出生的理由。这篇解读,带你把这堂课拆开揉碎。

视频背景卡片

项目 内容
视频标题 The Soul of Erlang and Elixir
演讲者 Saša Jurić(《Elixir in Action》作者)
频道 GOTO Conferences(官方频道)
场合 GOTO 2019 大会
时长 约45分钟
播放量 数十万级(GOTO频道常青热门,撰稿时未逐日核对)
核心主题 BEAM虚拟机的设计哲学:隔离、抢占、独立GC与"放任崩溃"

说明:如链接失效,在YouTube搜索标题「The Soul of Erlang and Elixir」即可直达官方版本;播放量等动态数据以频道页面为准。

核心内容拆解

1. 出身即命题:给电话交换机写软件

(视频原话大意)演讲从历史讲起:1980年代,爱立信实验室的Joe Armstrong、Robert Virding、Mike Williams接到的任务,不是"再写一门语言",而是一个业务命题——交换机软件要能扛住海量并发呼叫、故障时不能整机停摆、升级时不能中断服务。语言只是答案,问题才是灵魂。

后来的故事广为流传:Erlang一度在爱立信内部失宠,团队出走,1998年语言开源;而运行Erlang的AXD301交换机,据爱立信宣传口径达到过99.9999999%的可用性——著名的"九个9"。这个数字业内一直有争议,但有一点没人反驳:它是唯一一门把"电信级可用性"写进基因的主流语言。

(我的延伸)注意这个叙事顺序——先有极端场景,后有设计取舍。我们平时评价技术栈,习惯问"性能如何、生态如何",却很少问第三个问题:"它假设世界是善意的,还是恶意的?"BEAM的所有设计,都建立在"世界是恶意的"这个假设上。

2. 反直觉的卖点:它不快,但它从不突然卡死

(视频原话大意)Saša全场最核心的一个反转是:BEAM的原始计算速度并不出色——纯数值计算它跑不过C,甚至跑不过很多带JIT的现代运行时。但演讲里他反复演示一类场景:一个进程陷入死循环疯狂烧CPU,或者成百上千个进程同时产生垃圾,系统其他部分的响应延迟几乎纹丝不动。

他的总结(大意):大多数运行时优化的是"单个任务跑多快",BEAM优化的是"一万个任务里有一个发疯时,其他九千九百九十九个的延迟"。前者的指标是吞吐,后者的指标是可预测性。

(我的延伸)这个区分在今天的AI基础设施里极其值钱。推理服务的瓶颈常常不是算力不够,而是长尾延迟:某一个批处理任务卡住垃圾回收,所有排队请求的P99一起爆炸。BEAM把"可预测延迟"做成默认属性,这正是它被WhatsApp、Discord这类海量连接业务选中的原因——据其工程博客和媒体广泛报道,WhatsApp曾用少量工程师维护数亿用户,单机并发连接做到百万级。

3. 隔离是第一性原理:进程就是沙箱

(视频原话大意)BEAM怎么做到上一条?答案是整个体系的地基:进程隔离。BEAM进程不是操作系统的进程,而是虚拟机里的轻量执行单元,初始只占几KB内存,一台机器开百万个都不吃力。关键在于:每个进程拥有自己独立的堆(heap),进程之间不共享任何可变内存。

这一条设计带来一串连锁反应。第一,崩溃不传染:一个进程的内存坏掉,烂掉的是它自己的堆,别人碰都碰不到。第二,不需要锁:没有共享可变状态,就没有数据竞争,也就不需要用锁把并发度锁死。第三,生命周期互不牵连:进程随时生灭,互不留烂摊子。

(我的延伸)对比一下主流路线:Java的线程共享一个堆,于是整个语言二十年的进化史,一半是在为"共享内存"擦屁股——锁、volatile、内存屏障、并发容器。Go用channel鼓励"不要用共享内存通信",但共享内存依然合法,擦屁股的事一件没少。BEAM是直接在语言层面取消了犯错的可能。这不是API设计,这是宪法设计。

4. 每个进程自己管垃圾:没有全局暂停这回事

(视频原话大意)隔离的第二个红利是垃圾回收。主流运行时(典型如JVM)的GC演进史,就是一部与"全局暂停"(stop-the-world)斗争史:GC要整理堆,就得让所有线程站住,于是总有一段"全世界一起卡住"的时间。收集器一代代改良,但全局暂停的阴影始终在。

BEAM根本没有全局堆。每个进程GC自己的堆,你回收你的,我回收我的,互不等待。更妙的是:BEAM进程大多短命(处理一个请求、转 发一条消息就结束),进程一死,它整个堆直接整体回收——最好的垃圾回收,是压根不用回收。

(我的延伸)这解释了第2点那个"死循环不连累别人"的演示:GC的成本被切成了百万份小颗粒,每份都小到可以忽略。这其实和硬件设计的思路同构——把大粒度的共享资源拆成小粒度的私有资源,是工程师对抗"一个部件卡住全系统"的万能解法,CPU的核内缓存、5GC的服务化拆分,走的都是这条路。

5. 抢占式调度:失控进程抢不走别人的CPU

(视频原话大意)第三个机制是调度器。很多语言采用协作式调度:任务自己跑,跑到"让出点"(比如IO等待)才交出CPU。这个模型的命门是——一个不配合的任务能把整个调度器饿死。Node.js的事件循环被一个同步大循环堵死,是无数事故的经典剧本。

BEAM是抢占式的:每个进程领到一小段执行预算(按"归约次数"计,每个时间片大约几千次归约),用完强制让位,下一个进程上台。没人能多占一纳秒。所以演讲里那个死循环进程才那么安详——它发它的疯,调度器每过一小片就把它拎下去,别人的活照干。

(我的延伸)两点公道话必须说。其一,BEAM的抢占也有例外:调用C扩展(NIF)时调度器可能被阻塞,所以Discord当年要用Rust写NIF做热路径时,社区专门讨论过这个问题——世上没有免费的完美。其二,这个思路已经在反哺主流:Go在1.14版本(2020年)加入了基于信号的异步抢占,解决的正是"无让出点的死循环饿死调度器"这同一个病。BEAM不是另类,它是把别人后来才补的课,1980年代就当默认值。

6. 消息传值与位置透明:分布式几乎免费

(视频原话大意)进程间不共享内存,那怎么交换数据?拷贝式消息传递:发送方把数据复制一份塞进接收方的信箱。听上去"浪费",但复制买来的是完全隔离——两边的堆彻底独立,谁也污染不了谁(大块二进制数据另有共享优化,此处不展开)。

更深远的是,本地进程和远程节点之间用的也是同一套消息语义。代码不用改,进程就能从"同一台机器"搬到"另一个机房",这就是位置透明:分布式在BEAM里不是外挂框架,而是语言内建的延伸。

(我的延伸)这条设计的当代回声,是微服务时代我们花大价钱买的东西:RPC框架、服务网格、序列化协议——本质上都是在给"本没有隔离语义的运行时"补拷贝边界。BEAM出生就有这个边界,代价是单次拷贝的开销,收益是无需框架的一致心智模型。快与慢的账,各人自己算,但它至少让你看清:微服务的复杂度里,有多少是运行时欠的债。

7. Let it crash:不防御,让监督者收拾残局

(视频原话大意)最后一块拼图是错误处理哲学。常规思路是防御式编程:预判每一种失败,写捕获、写重试、写降级,结果错误处理的代码往往比业务还多,还总有防不住的。Erlang的哲学反过来——let it crash:进程遇到搞不定的状态,别挣扎,直接崩。收拾残局是"别人"的事:监督者(supervisor)盯着下属进程,谁崩了就按预定策略重启它,重启后的进程回到干净的初始状态,带着坏的旧状态继续跑反而更危险。

由此整个系统形成一棵监督树:最核心的少量代码(error kernel)必须绝对正确,外层业务进程随便崩、随时修,系统的健壮性不来自每个零件永不损坏,而来自损坏可以被局限、被替换。

(我的延伸)把它翻译成通信人熟悉的语言:这就是网管系统的自动重启策略 + 进程级容错粒度。区别在于,网元的自愈是运维系统外挂的,而OTP把它做成了程序结构本身。看完这场演讲再看我们自己的网元告警日志,你会忍不住想:那些凌晨三点的"进程僵死",有多少是当年就该关进笼子里的。

知识结构图

flowchart TD
    A["电信级可用性命题"] --> B["设计哲学:隔离"]
    B --> C["进程沙箱:独立堆"]
    B --> D["消息传值:拷贝通信"]
    B --> E["抢占式调度"]
    B --> F["监督树:放任崩溃"]
    C --> C1["崩溃不传染"]
    C --> C2["无锁免竞争"]
    C --> C3["无全局GC暂停"]
    D --> D1["位置透明:分布式免费"]
    E --> E1["失控进程抢不走CPU"]
    F --> F1["重启回到已知好状态"]
    C1 --> G["可预测的低延迟"]
    C3 --> G
    E1 --> G
    F1 --> H["九个9的可用性传说"]

实战启示

看完这堂课,即使你一行Erlang都不会写,也有四件立刻能用上的事。

第一,把"隔离"当设计审查项。下次评审方案时加一个问题:这个模块崩了,会不会把别人带走?共享内存、全局单例、公共线程池,都是"崩溃传染"的高发区。在Go、Java里做不到BEAM式的绝对隔离,但"每个请求独立goroutine/任务 + 独立超时 + panic recover边界"这类廉价隔离,值得成为默认习惯。

第二,盯住你的GC暂停。长尾延迟的元凶常常不是慢代码,而是全局暂停。给延迟敏感的服务建立GC指标监控(暂停次数、最大暂停时长),比单纯堆机器更有效。BEAM给的启发是结构性的:能用"短命对象 + 小堆"就别造"长寿大对象",让垃圾生得快、死得早。

第三,区分吞吐型任务和延迟型任务。把发疯的批处理和在线请求塞进同一个调度域,等于让一辆失控卡车和救护车抢车道。物理隔离(独立实例、独立队列、独立线程池)永远优于事后调参。

第四,重新理解"重启"。别把重启当耻辱,把它当设计:确保每个服务都能在崩溃后回到干净初始状态,配上合理的重启策略和告警,比防御式地写一万行catch更接近可靠。OTP用了四十年证明:可靠性不是不犯错,而是每个错都有人接。

延伸学习路径

  1. Joe Armstrong博士论文《Making reliable distributed systems in the presence of software errors》(2003)——Erlang设计哲学的"圣经",把这篇演讲背后的每个决策论证了一遍,免费公开。
  2. 《The Beam Book》(Erik Stenman,GitHub开源)——想下到BEAM内部看调度器、堆与GC实现细节,读这本。
  3. 《Elixir in Action》(Saša Jurić,Manning)——主讲人自己的书,把演讲里的哲学落到可上手的代码。
  4. Elixir官方教程(elixir-lang.org)——想亲手试试"发疯进程不连累别人",半小时就能在本机复现演讲里的演示。
  5. Discord工程博客《Using Rust to Scale Elixir for 11 Million Concurrent Users》——看BEAM哲学在超大规模生产环境里如何与工程现实妥协共存。

一句话总结

BEAM用四十年证明:可靠性不是靠人人完美,而是靠给每个崩溃准备一个笼子——隔离做得越彻底,系统越敢出事。

参考来源

  1. The Soul of Erlang and Elixir — Saša Jurić, GOTO 2019 — 本文分析的核心视频
  2. Joe Armstrong博士论文:Making Reliable Distributed Systems — Erlang/OTP设计哲学的原始论证
  3. The Beam Book(GitHub开源) — BEAM虚拟机内部实现参考
  4. Elixir语言官方网站 — 语言入门与官方教程
  5. Elixir in Action(Manning出版页) — 主讲人Saša Jurić的著作

本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。 ```

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入