一、Erlang分布式系统的故障特点

Erlang作为一门专为分布式和高并发设计的语言,天生适合构建容错系统。但分布式环境下,网络分区、节点宕机、消息丢失等问题依然常见。比如下面这个典型场景:

% 技术栈:Erlang/OTP 25
% 场景:节点A向节点B发送消息,但B可能因网络问题未收到
send_msg(TargetNode, Msg) ->
    case net_adm:ping(TargetNode) of  % 检查节点连通性
        pong -> 
            {ok, _} = gen_server:call({server, TargetNode}, Msg), % RPC调用
            io:format("Message sent successfully~n");
        pang ->
            io:format("Node ~p unreachable~n", [TargetNode]),
            {error, node_down}  % 处理节点不可达
    end.

注释

  • net_adm:ping/1 是Erlang自带的节点探测函数
  • gen_server:call/2 可能因超时失败(默认5秒)

这类问题往往表现为:消息延迟状态不一致进程孤立

二、核心解决策略

2.1 超时与重试机制

Erlang的gen_server:call默认超时为5秒,但分布式场景需要更灵活的控制:

% 自定义超时和重试
retry_call(Node, Msg, Retries) when Retries > 0 ->
    case gen_server:call({server, Node}, Msg, 3000) of % 3秒超时
        {error, timeout} -> 
            timer:sleep(1000),
            retry_call(Node, Msg, Retries - 1); % 指数退避更佳
        Reply -> 
            Reply
    end;
retry_call(_, _, 0) -> {error, max_retries}.

2.2 进程监控与接管

通过linkmonitor实现进程级容错:

start_worker() ->
    Pid = spawn_link(fun worker_loop/0), % 建立双向链接
    erlang:monitor(process, Pid),       % 额外监控
    {ok, Pid}.

worker_loop() ->
    receive
        {work, Data} -> process_data(Data);
        shutdown -> exit(normal)
    after 5000 -> exit(no_work) % 5秒无消息自毁
    end.

关键点

  • spawn_link 在子进程崩溃时通知父进程
  • monitor 提供更详细的退出原因(通过DOWN消息)

三、实战:分布式数据库同步

假设我们实现一个多节点的KV存储:

% 技术栈:Mnesia + Erlang
init_db(Nodes) ->
    mnesia:create_schema(Nodes),  % 初始化集群
    mnesia:create_table(user, [
        {disc_copies, Nodes},     % 数据持久化节点
        {attributes, [id, name]}  % 表结构
    ]).

write_data(Key, Value) ->
    Trans = fun() ->
        case mnesia:write({user, Key, Value}) of
            ok -> ok;
            {aborted, Reason} -> 
                % 自动切换到其他节点写入
                mnesia:activity(transaction, mnesia:write, [{user, Key, Value}])
        end
    end,
    mnesia:sync_transaction(Trans). % 同步事务

注意

  • Mnesia在节点失效时会自动重定向操作
  • sync_transaction 比异步更安全但性能较低

四、高级技巧与陷阱规避

4.1 脑裂处理

网络分区可能导致"双主"问题。解决方案:

% 使用Erlang的全局锁(global模块)
acquire_lock(Resource) ->
    case global:set_lock({Resource, self()}) of
        true -> 
            {ok, LockId};
        false -> 
            {error, conflict}
    after 10000 ->  % 10秒后强制释放
        global:del_lock({Resource, self()})
    end.

4.2 消息堆积防护

使用erlang:system_info(message_queue_len)监控:

check_mailbox() ->
    case erlang:process_info(self(), message_queue_len) of
        {_, Len} when Len > 1000 -> 
            % 触发背压机制或告警
            {warning, mailbox_full};
        _ -> 
            ok
    end.

五、总结与选型建议

适用场景

  • 电信级可靠性要求的系统
  • 需要软实时响应的场景(如游戏服务器)

优缺点

  • ✅ 轻量级进程模型支持百万级并发
  • ❌ 学习曲线陡峭,生态不如Java/Python丰富

最后建议

  1. 始终用-export([init/1])显式定义函数可见性
  2. sys.config中配置kernel参数调节分布式行为
  3. 使用reconobserver_cli进行运行时诊断