Erlang OTP 框架:构建工业级并发应用

深入讲解 Erlang OTP 框架的核心行为(Gen_server、Supervisor、Application),掌握如何构建可容错、可扩展、可维护的工业级并发系统。

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 构建的系统进行交互。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「erlang」更多文章

  1. Erlang/OTP 生产案例与性能调优
  2. Erlang 分布式编程:节点互联与集群部署
  3. Elixir 入门与 Phoenix Web 框架实战