第 07 站展开探险地图
00装好工具前置准备01起跑线前置准备02进程与信箱R1Elixir 数据流水线可选复习R2读懂 Erlang可选复习03消息与超时04OTP 消息章法05会重启的监督树06给并发设上限07BEAM 节点失联X1两种语言对暗号双语扩展X2双语搭档双语扩展08可靠任务调度器
首页/BEAM 主线/第 07 站
07
联网联网挑战BEAMOTP

BEAM 节点失联

节点连上只是故事的开始;断线、迟到和旧数据也要有安排。

4小关卡
约 6 小时 · 建议分 4 次可以分几次
QUESTION · 本站只追这一问

远程 worker 在请求执行中途断线时,调用者得到什么结果,系统怎样标记 stale、重试并避免重复执行?

先看现场

节点已经失联,仪表盘仍显示绿色

远程节点在请求中途断开,最后一次指标仍被当作健康数据展示。调用者重试后,原任务又可能已经完成,外部动作因此执行两遍。

能够观察到
  • nodeup、nodedown 与请求 reference 的时间线
  • 最后一次样本的时间戳与 stale 标记
  • 重试、幂等记录,以及重连后的重新注册日志
为什么学这一站

它要解决什么

分布式 Erlang 让远程发送写起来很自然,网络却仍会延迟或断开。节点名和 cookie 只帮助连接;一致性、投递、容量和安全仍要设计。

走完这一站

你会做到

  • 能启动两个有名字的 BEAM 节点,让它们互相找到并发送消息

  • 能说清 nodedown 表示什么,也能分清远程 PID 与只在某个节点有效的注册名

  • 能用带上下文的日志、Telemetry 指标和 Observer 追踪 mailbox 与延迟

出发前
  • 知道进程怎样通信、监督树怎样恢复,以及背压怎样表示忙不过来
  • 能够启动一个基本 OTP Application;还没做过 release 也可以继续
同一件事,两种写法

先看做什么,再看怎么写

节点连断会变成消息。收到 nodedown 后如何降级或拒绝,要提前约好。

当前代码
Elixir
# 让当前进程接收节点连上与断开的消息
:net_kernel.monitor_nodes(true)

# 节点事件会像普通 BEAM 消息一样到达
receive do
  {:nodeup, node} ->
    # 记录刚刚连上的节点
    Logger.info("node connected", node: node)

  {:nodedown, node} ->
    # 断连后由应用决定降级或重试
    Logger.warning("node disconnected", node: node)
end
设计选择

为什么这样写

为什么节点能互发消息,还要写断线协议?

这次选择

相近的发送写法只解决连接后的表达。网络仍会延迟、分区,旧数据也会假装新鲜。

代价与边界

重试提高成功机会,也会重复副作用;需要 stale、幂等和次数上限。

换一把尺子

从 Java、Python、JavaScript 看过来

Java

熟悉的起点
RPC、消息代理或集群框架。
BEAM 在意什么
远程 PID 发送写起来接近本地发送,但失败语义仍是网络语义。
别带错直觉
方便的调用形式不提供恰好一次。

Python

熟悉的起点
HTTP/RPC、Celery 等任务队列。
BEAM 在意什么
节点事件也以 BEAM 消息进入应用。
别带错直觉
看到 nodedown 不等于知道断线原因。

JavaScript

熟悉的起点
fetch、WebSocket 或队列消费者。
BEAM 在意什么
同一 VM 模型能延伸到节点间,但边界仍要有 timeout 与幂等。
别带错直觉
网络分区不会因为语法短而消失。
LAB
动手

让一个节点离线

约 15–25 分钟

启动两个本地节点,连通后停掉一个。观察事件、远程进程和未完成请求。

  1. 01

    使用相同 cookie 启动 a@127.0.0.1 和 b@127.0.0.1

  2. 02

    从 A ping B,并打开 monitor_nodes

  3. 03

    停止 B,记录 A 收到的事件,也看看未完成请求怎样结束

复制到终端,按回车
# 尝试连接节点 b;成功返回 :pong,失败返回 :pang
Node.ping(:"b@127.0.0.1")
你会看到
  • 连接成功时,Node.ping/1 返回 :pong
  • B 停止后,A 会收到 nodedown
  • BEAM 会报告断连,但应用仍要自己决定请求是失败、稍后重试还是换一条路
故意弄坏

给节点设置不同 cookie。确认失败发生在连接阶段,而非业务 handler。

这次能看清

节点的连通和断开可以被观察,并转成应用能够处理的事件。

这次还不能说明

本机停节点不能模拟丢包、长延迟、半开连接和脑裂。

先认词

这段代码里的关键词

01

node

参与分布式通信的 BEAM 实例,名字通常是 name@host。远程 PID 也含节点身份。

02

cookie

节点建立连接时使用的共享秘密。它不是完整安全方案,不能代替加密、隔离和访问控制。

03

Telemetry

Elixir 常用的事件测量工具。业务代码发事件,handler 负责统计或导出。

从代码里认出章法

这里用了哪些设计模式

01

Circuit Breaker

节点不可用时停止继续路由,并用探测决定何时恢复。

02

Lease

让健康与注册信息按时间失效,不把旧数据当作当前事实。

03

Idempotent Consumer

让重试或重复投递不会重复产生外部效果。

想一想

分布式 Erlang 让远程 PID 收消息写起来像本地发送,它自动提供了什么?

轮到你

画一张节点状态图

定期上报 scheduler、内存和关键 mailbox。断连后保留最后值,并标记 stale。

提示 1先迈一步

每个指标都带上采样时间,数字离开时间就容易骗人。

提示 2再缩小一点

断开后保留最后一次真实数据,同时明确标出 stale。

提示 3离答案很近了

观察 mailbox 连续变长的趋势,不要只盯着某一个瞬间的数字。

提示 4从终点往回想

先挑一条“过关信号”,为它写一个最小测试。如果电脑看不出结果,就把这句话改成一个真正能观察到的现象。

过关条件
  • 节点断开时不会显示成“所有指标都是健康的 0”

  • 日志能看出 node、process 和 request/reference 属于谁

  • 节点重连后能恢复上报,也不会把同一服务重复注册

带走

记住三句话

  1. 1

    远程发送写起来像本地发送,只是方便了表达,并没有让网络消失。

  2. 2

    断连、timeout 和 stale 数据都要写进双方约定,而不是等出事再猜。

  3. 3

    除了“进程还活着”,mailbox、延迟、重启次数和拒绝率更能说明系统是否真的健康。

再读一点

去看原版资料

Distributed ErlangElixir LoggerTelemetry
本站结束实验做过,答案也想过,就把这一站收好。