Erlang/OTP 完全指南 / 01 - Erlang 简介
第 01 章:Erlang 简介
“Erlang 不是一门语言,它是一种构建可靠系统的方式。” — Joe Armstrong
1.1 历史背景
1.1.1 诞生故事
Erlang 诞生于 1986 年,由瑞典爱立信(Ericsson)公司的 Joe Armstrong、Robert Virding 和 Mike Williams 创造。
| 时间 | 事件 |
|---|---|
| 1986 | Joe Armstrong 开始设计 Erlang |
| 1987 | 首个原型实现完成 |
| 1991 | Erlang 首次公开发布 |
| 1996 | OTP(Open Telecom Platform)框架发布 |
| 1998 | Erlang/OTP 开源 |
| 2006 | Erlang 在并发编程领域重新受到关注 |
| 2008 | WhatsApp、RabbitMQ 等项目兴起 |
| 2017 | Erlang/OTP 20 引入 Maps 和新特性 |
| 2024+ | Erlang/OTP 26+ 持续演进 |
1.1.2 设计目标
Erlang 最初为电信系统设计,核心要求:
- 高并发(Concurrency):同时处理大量呼叫
- 高可用(Availability):99.9999999%(九个九)的正常运行时间
- 软实时(Soft Real-time):低延迟响应
- 热代码升级(Hot Code Upgrade):不停机更新
- 容错(Fault Tolerance):局部故障不影响整体
1.1.3 “九个九"的含义
| 可用性等级 | 每年停机时间 | 典型场景 |
|---|---|---|
| 99% (两个九) | 3.65 天 | 普通 Web 应用 |
| 99.9% (三个九) | 8.76 小时 | 电商平台 |
| 99.99% (四个九) | 52.6 分钟 | 金融系统 |
| 99.999% (五个九) | 5.26 分钟 | 电信核心网 |
| 99.9999999% (九个九) | 31 毫秒 | 爱立信 AXD301 ATM 交换机 |
1.2 Erlang 的特性
1.2.1 函数式编程
Erlang 是一门函数式编程(Functional Programming)语言:
%% 变量不可变(Single Assignment)
X = 1.
%% X = 2. % 错误!变量已绑定
%% 函数是一等公民
Double = fun(X) -> X * 2 end.
Double(5). %% => 10
%% 无副作用(理想情况)
%% 不修改输入数据,而是返回新的数据
1.2.2 轻量级进程
Erlang 的"进程"不是操作系统进程,也不是线程:
| 特性 | OS 进程 | OS 线程 | Erlang 进程 |
|---|---|---|---|
| 内存占用 | 数 MB | 数百 KB | 约 2 KB |
| 创建时间 | 数毫秒 | 数十微秒 | 约 1 微秒 |
| 数量上限 | 数千 | 数万 | 数百万 |
| 调度方式 | OS 内核 | OS 内核 | BEAM 虚拟机 |
| 通信方式 | IPC | 共享内存 | 消息传递 |
%% 创建 100 万个进程
Pids = [spawn(fun() -> receive stop -> ok end end)
|| _ <- lists:seq(1, 1000000)].
length(Pids). %% => 1000000
1.2.3 消息传递
进程间通信的唯一方式是消息传递(Message Passing):
%% 进程 A 发送消息给进程 B
Pid ! {hello, "world"}.
%% 进程 B 接收消息
receive
{hello, Msg} -> io:format("收到: ~p~n", [Msg])
end.
1.2.4 容错设计
Erlang 的容错基于 “Let it crash” 哲学:
%% 不要这样写(防御性编程)
handle(Data) ->
if
is_list(Data) -> process_list(Data);
is_tuple(Data) -> process_tuple(Data);
true -> error %% 覆盖所有情况?
end.
%% Erlang 方式(Let it crash)
handle(Data) ->
process(Data). %% 让它崩溃,由 Supervisor 处理
1.3 OTP 框架
1.3.1 什么是 OTP?
OTP(Open Telecom Platform)是一个用于构建并发、容错应用程序的框架和库集合:
┌─────────────────────────────────────────┐
│ 你的应用程序 │
├─────────────────────────────────────────┤
│ GenServer GenStateMachine GenEvent │ ← 行为模式(Behaviours)
├─────────────────────────────────────────┤
│ Supervisor Application │ ← 监督树
├─────────────────────────────────────────┤
│ ETS Mnesia Logger Crypto │ ← 标准库
├─────────────────────────────────────────┤
│ BEAM 虚拟机 │
└─────────────────────────────────────────┘
1.3.2 OTP 三大支柱
| 组件 | 英文名 | 作用 |
|---|---|---|
| 通用服务器 | GenServer | 封装有状态的并发服务 |
| 监督者 | Supervisor | 监控子进程,崩溃时自动重启 |
| 应用 | Application | 组织和管理应用生命周期 |
%% 一个简单的 OTP 应用结构
%% 应用(Application)
%% └── 监督者(Supervisor)
%% ├── 服务器(GenServer)1
%% ├── 服务器(GenServer)2
%% └── 服务器(GenServer)3
1.3.3 监督树示例
[Application]
│
[Supervisor]
/ | \
[GS1] [GS2] [GS3]
│
[Supervisor2]
/ \
[Worker1] [Worker2]
1.4 适用场景
1.4.1 电信领域
Erlang 的"老家”:
- 交换机控制:爱立信 AXD301 ATM 交换机
- 信令网关:SS7/SIP 协议处理
- 移动核心网:4G/5G 核心网元
1.4.2 消息队列
| 项目 | 描述 | 使用 Erlang 的部分 |
|---|---|---|
| RabbitMQ | 最流行的开源消息代理 | 核心 broker 全部用 Erlang |
| EMQX | 大规模 MQTT 消息服务器 | 核心引擎 |
| VerneMQ | 分布式 MQTT broker | 全部用 Erlang |
1.4.3 即时通讯
| 项目 | 峰值规模 |
|---|---|
| 9 亿用户,50 名工程师 | |
| 微信 | 部分后端服务使用 Erlang |
1.4.4 数据库与存储
| 项目 | 描述 |
|---|---|
| CouchDB | 文档数据库,HTTP REST API |
| Riak | 分布式 KV 数据库 |
| Mnesia | Erlang 内置分布式数据库 |
1.4.5 实时系统
- 游戏服务器:高并发玩家连接
- 物联网(IoT):海量设备连接管理
- 金融交易:低延迟订单处理
- CDN 边缘计算:内容分发节点
1.4.6 何时不适合使用 Erlang?
| 场景 | 原因 | 替代方案 |
|---|---|---|
| CPU 密集计算 | BEAM 调度器开销 | C/C++/Rust |
| 科学计算 | 缺乏数值库 | Python/Julia |
| 桌面 GUI | 无成熟 GUI 框架 | C#/Swift |
| 移动端开发 | 不支持 | Kotlin/Swift |
| 简单 CRUD Web | 生态不如主流语言 | Go/Node.js |
1.5 BEAM 虚拟机
1.5.1 架构概览
源代码 (.erl)
↓
编译器 (erlc)
↓
字节码 (.beam)
↓
BEAM 虚拟机 (erl)
↓
操作系统
1.5.2 BEAM vs 其他虚拟机
| 特性 | BEAM | JVM | .NET CLR |
|---|---|---|---|
| 并发模型 | Actor | 线程 | Task/TPL |
| 进程开销 | ~2KB | ~1MB 栈 | ~1MB |
| GC 策略 | 每进程独立 GC | 全局 GC | 分代 GC |
| 热更新 | 原生支持 | 需要框架 | 需要框架 |
| 调度 | 抢占式 | 抢占式 | 抢占式 |
| 延迟保证 | 软实时 | 一般无 | 一般无 |
1.5.3 Erlang 运行时系统
%% 查看运行时信息
erlang:system_info(otp_release). %% OTP 版本
erlang:system_info(schedulers). %% 调度器数量(通常等于 CPU 核心数)
erlang:system_info(process_count). %% 当前进程数
erlang:memory(). %% 内存使用情况
1.6 Erlang 生态系统
1.6.1 核心工具
| 工具 | 用途 |
|---|---|
| erlc | Erlang 编译器 |
| erl | 交互式 Shell(REPL) |
| rebar3 | 构建工具(类似 Maven/npm) |
| observer | 可视化监控工具 |
| dialyzer | 静态类型分析 |
1.6.2 知名 Erlang 项目
WhatsApp ── 即时通讯(9 亿用户)
RabbitMQ ── 消息队列(最流行 AMQP broker)
CouchDB ── 文档数据库
Riak ── 分布式 KV 存储
EMQX ── MQTT broker(百万连接)
Discord ── 部分后端服务
Klarna ── 支付系统
Ericsson ── 电信设备
Nintendo ── 部分在线服务
1.7 快速上手预览
1.7.1 Hello World
%% hello.erl
-module(hello).
-export([world/0]).
world() ->
io:format("Hello, Erlang!~n").
1.7.2 简单并发
%% 创建一个打印消息的进程
Pid = spawn(fun() ->
receive
{greet, Name} ->
io:format("Hello, ~s!~n", [Name])
end
end).
Pid ! {greet, "Erlang"}.
1.7.3 简单监督树
%% Supervisor 启动两个 worker
init(_) ->
Children = [
#{id => worker1, start => {my_worker, start_link, []}},
#{id => worker2, start => {my_worker, start_link, []}}
],
{ok, {#{strategy => one_for_one, intensity => 5, period => 10}, Children}}.
1.8 Erlang vs 其他语言
| 维度 | Erlang | Go | Java | Elixir |
|---|---|---|---|---|
| 类型 | 动态强类型 | 静态强类型 | 静态强类型 | 动态强类型 |
| 并发模型 | Actor | CSP (goroutine) | 线程/虚拟线程 | Actor (运行在 BEAM 上) |
| 容错 | 原生监督树 | 手动处理 | 需要框架 | 原生监督树 |
| 热更新 | 原生支持 | 不支持 | 需要框架 | 原生支持 |
| 学习曲线 | 中等 | 低 | 中等 | 中等 |
| 生态规模 | 小 | 大 | 极大 | 中等 |
| 运行平台 | BEAM | 原生 | JVM | BEAM |
1.9 注意事项
⚠️ 常见误区
- Erlang 不是银弹:不适合 CPU 密集型任务,数值计算性能远低于 C/Rust
- 动态类型的代价:Dialyzer 可以做静态分析,但无法替代编译期类型检查
- 生态较小:第三方库数量远少于 Java/Python/Go,需要自己造轮子
- 学习曲线:函数式思维 + OTP 设计模式需要时间适应
- 调试困难:并发 bug 难以复现,需要依赖 tracing 工具
💡 为什么学 Erlang?
- 理解 Actor 模型和并发编程的最佳范本
- OTP 的设计模式可以迁移到其他语言(Akka、Vert.x 等)
- Elixir 运行在 BEAM 上,学会 Erlang 更容易上手 Elixir
- 电信、消息队列、IoT 等领域仍有大量 Erlang 岗位
1.10 扩展阅读
- 📖 Programming Erlang (2nd Edition) — Joe Armstrong 著
- 📖 Learn You Some Erlang — 免费在线教程
- 📖 Erlang/OTP 官方文档
- 📖 Erlang Design Principles — OTP 设计原则
- 🎥 Erlang Master Class — YouTube 课程
- 📰 The Erlang/OTP Blog — 官方博客
下一章:02 - 环境搭建