模块 06查看课程目录
00起跑线:两门语言,一台机器01先建立 BEAM 心智模型02Elixir 基础:让数据流过函数03Erlang 基础:读懂 BEAM 的母语04两种语法,一套语义05从裸进程理解并发06从手写循环推导 OTP07监督树与应用08状态、吞吐与背压09分布式与可运维性10Erlang ↔ Elixir 互操作11综合项目:可靠任务调度器
06
OTP中级ElixirErlangOTP

从手写循环推导 OTP

GenServer 不是“放状态的盒子”,而是一套经过约束的协议。

5检查点
7 小时预计完成
为什么先学这个

先修正心智模型,再增加 API

OTP behaviour 把经过反复验证的消息循环、系统消息、调试与升级钩子统一起来。它减少样板,但不会替你决定 API、状态归属、背压和超时策略。

学完你能做到

可验证的学习目标

  • 能区分客户端 API 与 callback 实现
  • 能为一致性要求选择 call 或 cast
  • 能把长期进程正确放进监督树
前置知识
  • 完成裸进程章节
  • 写过 request/reply 协议
最小心智模型

先抓住三个词

01

behaviour

一组 callback 合约。实现模块遵守合约,通用运行框架负责循环、系统消息与调试。

02

call

同步请求。调用者等待 reply,天然形成等待,但并不等同于完整背压。

03

cast

异步消息。调用者不知道是否已处理,过量 cast 可能让 mailbox 无界增长。

双语代码桥

先对齐协议,再看标点

客户端函数隐藏消息形状;callback 只处理协议。两边的返回 tuple 语义一一对应。

Elixir
defmodule KV do
  use GenServer

  def start_link(opts), do: GenServer.start_link(__MODULE__, %{}, opts)
  def get(server, key), do: GenServer.call(server, {:get, key})
  def put(server, key, value), do: GenServer.call(server, {:put, key, value})

  @impl true
  def init(state), do: {:ok, state}

  @impl true
  def handle_call({:get, key}, _from, state),
    do: {:reply, Map.fetch(state, key), state}

  def handle_call({:put, key, value}, _from, state),
    do: {:reply, :ok, Map.put(state, key, value)}
end
Erlang
-module(kv).
-behaviour(gen_server).
-export([start_link/0, get/2, put/3]).
-export([init/1, handle_call/3]).

start_link() -> gen_server:start_link(?MODULE, #{}, []).
get(Server, Key) -> gen_server:call(Server, {get, Key}).
put(Server, Key, Value) ->
  gen_server:call(Server, {put, Key, Value}).

init(State) -> {ok, State}.
handle_call({get, Key}, _From, State) ->
  {reply, maps:find(Key, State), State};
handle_call({put, Key, Value}, _From, State) ->
  {reply, ok, State#{Key => Value}, State}.
LAB
可运行实验

cast 为什么不是免费吞吐

约 15–25 分钟

把 put 改为 cast,然后快速发送十万条更新。观察 mailbox 与响应性,而不是只看发送循环有多快。

  1. 01

    暴露 `put_async/3` 使用 cast

  2. 02

    批量发送大量更新

  3. 03

    并行调用一个同步 get,记录延迟和 message_queue_len

在终端 / shell 中运行
:erlang.process_info(pid, :message_queue_len)
预期观察
  • 发送者很快结束不代表服务端已完成
  • 同步 get 可能排在大量 cast 后面
故意弄坏

在 handle_cast 中加入慢 I/O,继续提升发送速率。观察 mailbox 是否持续增长。

这个实验能证明

异步 API 会把等待从调用者转移到服务端队列。

这个实验不能证明

单机压测不能给出生产安全阈值;调度器、消息大小和外部 I/O 都会改变结果。

快速自测

下面哪个理由最适合使用 cast?

本章挑战

双语限流任务队列

任选一种语言写公开 API,另一种语言写 worker。最多并发 N 个任务,超出部分进入有界队列,满时返回 `busy`。

提示 1轻推一下

长期状态由一个进程拥有,但任务本身用独立进程执行。

提示 2缩小问题

用 monitor 回收 worker 完成与崩溃。

提示 3接近实现

不要让 API 静默接受无限任务。

提示 4用验收标准反推

从下面每一条验收标准倒推一个最小测试。若某条无法写成测试,先把表述改成可观察结果。

完成标准
  • 并发数与队列长度都有上限
  • worker 崩溃不会丢失容量计数
  • 拒绝策略对调用者可见
带走这三句话

复习卡

  1. 1behaviour 固定循环合约,不替代业务协议设计。
  2. 2客户端 API 应隐藏消息格式。
  3. 3cast 把压力移进 mailbox;过载策略仍需显式设计。
继续核对

本章一手资料

Elixir GenServerErlang gen_server