OTP(Open Telecom Platform)是 Erlang 生态系统的核心框架,它提供了一组标准化的行为模式(Behaviours)和工具库,使开发者能够构建遵循最佳实践的可靠并发系统。OTP 并非 Erlang 的附属品,而是语言设计理念的工程化表达——通过抽象出通用的并发模式,让开发者专注于业务逻辑而非进程管理的细节。本文将深入 OTP 的三大核心行为:Gen_server、Supervisor 和 Application,展示它们如何协同工作以构建高可用的生产级系统。
一、OTP 设计哲学
1.1 行为模式的本质
OTP 的行为模式是一种设计模式的形式化封装。在面向对象语言中,设计模式常以文档和约定形式存在;而在 Erlang 中,OTP 行为通过回调函数接口将设计模式固化到语言运行时中。
| OTP 行为 | 设计模式 | 解决的问题 |
|---|---|---|
gen_server | 通用服务器 | 状态管理 + 同步/异步请求处理 |
gen_fsm | 有限状态机 | 复杂状态转换逻辑 |
gen_event | 事件处理器 | 多处理器订阅事件流 |
supervisor | 故障恢复树 | 进程崩溃时的自动重启策略 |
application | 模块化部署 | 应用生命周期管理与依赖注入 |
1.2 进程的树形组织
OTP 应用中的所有进程都被组织成监督树(Supervision Tree):
Application Root
└── Top Supervisor
├── Worker A (gen_server)
├── Worker B (gen_server)
├── Middle Supervisor
│ ├── Worker C (gen_server)
│ └── Worker D (gen_fsm)
└── Event Manager (gen_event)
├── Handler 1
└── Handler 2
监督树的核心保障是:任何一个叶子进程崩溃,其直属 Supervisor 会按照预设策略重启它。如果重启失败达到一定阈值,向上级 Supervisor 上报,最终可能导致整个子树重启。这种层级化的容错设计使得局部故障不会影响全局。
二、Gen_server 详解
gen_server 是 OTP 中使用最频繁的行为模式,它封装了一个通用的客户端-服务器循环:维护状态、处理同步和异步请求、管理超时和终止。
2.1 完整实现
-module(kv_server).
-behaviour(gen_server).
%% API
-export([start_link/0, get/1, put/2, delete/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
-record(state, {store = #{}}).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
get(Key) ->
gen_server:call(?SERVER, {get, Key}).
put(Key, Value) ->
gen_server:call(?SERVER, {put, Key, Value}).
delete(Key) ->
gen_server:call(?SERVER, {delete, Key}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
io:format("KV Server starting...~n"),
{ok, #state{store = #{}}}.
handle_call({get, Key}, _From, State) ->
Value = maps:get(Key, State#state.store, undefined),
{reply, Value, State};
handle_call({put, Key, Value}, _From, State) ->
NewStore = State#state.store#{Key => Value},
NewState = State#state{store = NewStore},
{reply, ok, NewState};
handle_call({delete, Key}, _From, State) ->
NewStore = maps:remove(Key, State#state.store),
NewState = State#state{store = NewStore},
{reply, ok, NewState};
handle_call(_Request, _From, State) ->
{reply, {error, unknown_call}, State}.
handle_cast({async_put, Key, Value}, State) ->
% 异步更新,不返回响应
NewStore = State#state.store#{Key => Value},
{noreply, State#state{store = NewStore}};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(timeout, State) ->
% 处理定时器超时
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(Reason, State) ->
io:format("KV Server terminating: ~p, State: ~p~n", [Reason, State]),
ok.
code_change(_OldVsn, State, _Extra) ->
% 热更新时调用,可在此转换状态格式
{ok, State}.
2.2 Callback 函数详解
| Callback | 触发条件 | 返回值 |
|---|---|---|
init/1 | 进程启动时 | {ok, State} / {ok, State, Timeout} / ignore / {stop, Reason} |
handle_call/3 | 收到 gen_server:call 同步请求 | {reply, Reply, NewState} / {noreply, NewState} / {stop, Reason, Reply, NewState} |
handle_cast/2 | 收到 gen_server:cast 异步请求 | {noreply, NewState} / {stop, Reason, NewState} |
handle_info/2 | 收到普通消息或超时 | {noreply, NewState} / {stop, Reason, NewState} |
terminate/2 | 进程终止前 | ok |
code_change/3 | 热更新时 | {ok, NewState} |
2.3 同步 vs 异步调用
% 同步调用(阻塞等待响应)
Result = gen_server:call(ServerPid, Request).
Result = gen_server:call(ServerPid, Request, 5000). % 5秒超时
% 异步调用(发送后立即返回)
gen_server:cast(ServerPid, {async_put, key, value}).
% 远程调用(跨节点)
Result = gen_server:call({ServerName, NodeName}, Request).
同步调用内部通过引用(Reference)确保回复消息与请求一一对应,即使多个客户端并发调用也不会混淆响应。
2.4 超时与优先级
handle_call(priority_task, _From, State) ->
% 处理完立即返回,但延迟过低优先级的回复
{reply, high_priority_result, State, 0}; % 0 毫秒超时 = 立即处理下一个消息
handle_info(timeout, State) ->
% 被上面的 0 超时触发
process_low_priority_tasks(State),
{noreply, State, infinity}.
三、Supervisor 监督策略
3.1 Supervisor 的核心职责
Supervisor 是 OTP 容错体系的核心。它自己不执行业务逻辑,只负责启动、监控和重启子进程。子进程可以是 Worker(业务进程)或其他 Supervisor(子监督树)。
-module(kv_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
SupFlags = #{
strategy => one_for_one, % 重启策略
intensity => 5, % 最大重启次数
period => 60 % 时间窗口(秒)
},
ChildSpecs = [
% 子进程规格
#{
id => kv_server, % 子进程标识
start => {kv_server, start_link, []},
restart => permanent, % 总是重启
shutdown => 5000, % 优雅关闭超时(毫秒)
type => worker, % worker / supervisor
modules => [kv_server]
},
#{
id => cache_worker,
start => {cache_worker, start_link, []},
restart => temporary, % 不重启(一次性任务)
shutdown => brutal_kill,
type => worker,
modules => [cache_worker]
}
],
{ok, {SupFlags, ChildSpecs}}.
3.2 重启策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
one_for_one | 只重启崩溃的子进程 | 进程相互独立 |
one_for_all | 一个崩溃,重启所有子进程 | 进程强依赖 |
rest_for_one | 重启崩溃进程及之后启动的进程 | 进程有启动顺序依赖 |
simple_one_for_one | 动态添加同类子进程 | 工作者池 |
% rest_for_one 示例:数据库连接依赖配置服务
init([]) ->
SupFlags = #{strategy => rest_for_one, intensity => 3, period => 10},
ChildSpecs = [
#{id => config_service, ...}, % 1. 配置服务
#{id => db_connection, ...}, % 2. 数据库连接(依赖配置)
#{id => api_server, ...} % 3. API 服务(依赖数据库)
],
% 如果 db_connection 崩溃,db_connection 和 api_server 都会重启
% 但 config_service 不受影响
3.3 重启类型
| 类型 | 说明 |
|---|---|
permanent | 进程总是会被重启,即使正常退出 |
transient | 仅在异常退出时重启,正常退出不重启 |
temporary | 从不重启 |
3.4 动态工作者池
-module(pool_sup).
-behaviour(supervisor).
-export([start_link/0, start_worker/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
start_worker() ->
supervisor:start_child(?MODULE, []). % 动态添加子进程
init([]) ->
SupFlags = #{
strategy => simple_one_for_one,
intensity => 100,
period => 60
},
% 只有一个模板规格,可以 spawn 多个实例
ChildSpec = #{
id => worker,
start => {worker, start_link, []},
restart => transient,
shutdown => 5000,
type => worker
},
{ok, {SupFlags, [ChildSpec]}}.
四、Application 应用管理
OTP Application 是 Erlang 程序的打包和生命周期管理单元,类似于其他语言中的「应用」或「服务」概念。
4.1 Application 的结构
my_app/
├── src/
│ ├── my_app.app.src % 应用配置
│ ├── my_app_app.erl % 应用回调模块
│ ├── my_app_sup.erl % 顶层 Supervisor
│ └── my_app_server.erl % 业务逻辑
├── priv/
├── include/
└── rebar.config
% src/my_app.app.src
{application, my_app, [
{description, "My OTP Application"},
{vsn, "1.0.0"},
{registered, [my_app_sup, my_app_server]},
{mod, {my_app_app, []}},
{applications, [
kernel,
stdlib,
sasl, % 系统架构支持日志
cowboy % 外部依赖
]},
{env, [
{port, 8080},
{db_host, "localhost"}
]},
{modules, []}, % 编译时自动填充
{licenses, ["Apache-2.0"]},
{links, []}
]}.
% src/my_app_app.erl
-module(my_app_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
% 启动时调用,返回顶层 Supervisor 的 PID
my_app_sup:start_link().
stop(_State) ->
% 停止时清理
ok.
4.2 应用生命周期
% 启动应用
> application:start(my_app).
ok
% 带依赖自动启动
> application:ensure_all_started(my_app).
{ok,[sasl,my_app]}
% 停止应用
> application:stop(my_app).
ok
% 查看运行中的应用
> application:which_applications().
[{my_app,"My OTP Application","1.0.0"},
{stdlib,"ERTS CXC 138 10","4.1"},
{kernel,"ERTS CXC 138 10","8.5.3"}]
4.3 环境配置
% 读取应用环境配置
Port = application:get_env(my_app, port, 8080).
DBHost = application:get_env(my_app, db_host, "localhost").
% 运行时动态设置
application:set_env(my_app, debug_mode, true).
五、Event Manager 事件处理
gen_event 提供了发布-订阅模式的事件处理框架:
-module(event_logger).
-behaviour(gen_event).
-export([init/1, handle_event/2, handle_call/2, handle_info/2,
terminate/2, code_change/3]).
init([]) ->
{ok, #{count => 0}}.
handle_event({user_login, UserId}, State) ->
io:format("[EVENT] User ~p logged in~n", [UserId]),
{ok, State#{count => maps:get(count, State, 0) + 1}};
handle_event({user_logout, UserId}, State) ->
io:format("[EVENT] User ~p logged out~n", [UserId]),
{ok, State};
handle_event(_Event, State) ->
{ok, State}.
handle_call(get_stats, State) ->
{ok, State, State};
handle_call(_Request, State) ->
{ok, {error, unknown}, State}.
% 其他 callbacks...
handle_info(_Info, State) -> {ok, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
% 使用事件管理器
> {ok, EventMgr} = gen_event:start_link().
> gen_event:add_handler(EventMgr, event_logger, []).
> gen_event:notify(EventMgr, {user_login, 42}).
[EVENT] User 42 logged in
ok
多个 Handler 可以同时订阅同一事件流,每个 Handler 独立维护自己的状态。这种解耦设计使得日志记录、指标上报、通知发送等功能可以独立演进。
六、总结
OTP 框架代表了 Erlang 社区数十年工程经验的结晶。Gen_server 提供了通用的请求-响应抽象,Supervisor 构建了层级化的容错机制,Application 实现了模块化的生命周期管理。三者协同工作,形成了一套完整的高可靠性系统开发框架。
掌握 OTP 的关键在于理解其背后的设计哲学:通过标准化的行为模式减少重复代码,通过监督树实现自愈系统,通过代码热更新实现零停机部署。这些理念不仅适用于电信系统,也深刻影响了现代微服务架构和云原生设计模式。当你使用 RabbitMQ、WhatsApp 或 VerneMQ 时,你正在与 OTP 构建的系统进行交互。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。