为什么 Elixir + Phoenix 是构建可扩展、容错 AI 网关的理想技术栈

9 min read

为什么 Elixir + Phoenix 是构建可扩展、容错 AI 网关的理想技术栈

没人聊的技术栈

2026 年的每一个 AI 网关和编排平台,几乎都跑在三种技术栈之一上:Python + FastAPI、TypeScript + Node、或 Go。这是默认的心智模型。你去搜"如何构建 AI 网关",每一篇教程、每一个开源模板、每一个 HackerNews 讨论帖都假设你用的是这三种之一。

我要为一个几乎没人考虑过的第四种选项辩护,而且我认为它在架构层面上更适合这类问题:Elixir + Phoenix,运行在 Erlang 虚拟机(BEAM)上。

这不是为了博眼球的激进观点。过去一年多,我一直在这套技术栈上构建生产级 AI 网关 — 处理多 Provider 路由、故障转移链、异步任务队列、WebSocket 交付、Prompt 缓存、加密密钥管理、限速、Webhook 交付、按量计费和实时可观测性。这篇文章中的每一个架构决策都来自生产环境的实战经验,不是理论。

让我逐步讲解为什么 BEAM 是 AI 基础设施最自然的运行时,以及 Elixir 生态如何提供你需要的每一个原语 — 通常用更少的代码和更少的外部依赖。


核心问题:AI 网关本质上是一个并发编排引擎

在比较技术栈之前,有必要先理解 AI 网关在运行时到底在做什么。它不是一个简单的代理。对于每一个传入的请求,系统需要:

  1. 认证和授权 — 验证项目级 API 密钥
  2. 加载 Workflow 配置 — 哪个 Provider、哪个模型、什么系统指令、是否启用结构化输出、有哪些备用 Provider
  3. 检查速率限制 — IP 级别和项目/组织级别
  4. 检查 Prompt 缓存(先查 ETS 内存缓存,再查数据库回退)以避免重复的 Provider 调用
  5. 执行内容审核 — 在转发给 Provider 之前
  6. 发起实际的 LLM API 调用 — 带超时预算、失败分类和自动故障转移到备用 Provider
  7. 记录请求和响应 — 完整的元数据:token 数、延迟、使用的 Provider、缓存状态、故障转移尝试
  8. 计量使用量 — 向 Stripe 的按量计费 API 报告超额事件
  9. 交付结果 — 同步通过 HTTP 响应,或异步通过 WebSocket 通道和带 HMAC 签名的 Webhook 交付
  10. 更新缓存 — 将结果存入 ETS 以加速后续查询

这些步骤中的每一个都涉及 I/O:数据库查询、对 Provider 的 HTTP 调用、对 Stripe 的 HTTP 调用、WebSocket 广播、ETS 读写。而且每一个都需要并发执行 — 你不能在等待 OpenAI 响应单个请求时阻塞整个服务器。

这就是 BEAM 改变游戏规则的地方。


为什么 BEAM 是天然适合的架构

轻量级进程实现请求隔离

Elixir/Phoenix 应用中的每个请求都运行在自己的 BEAM 进程中。不是操作系统线程,不是 goroutine,而是 BEAM 进程 — 一个轻量级、隔离的执行单元,拥有自己的堆、自己的垃圾回收调度和自己的邮箱。

实际效果:

TEXT
请求 A → BEAM 进程 #1 → 调用 OpenAI → 等待 3 秒 → 响应
请求 B → BEAM 进程 #2 → 调用 Anthropic → 等待 1.5 秒 → 响应
请求 C → BEAM 进程 #3 → 缓存命中 → 2ms 内响应

进程 #1 等待 OpenAI 不会阻塞进程 #2 或 #3。没有线程池可耗尽,没有事件循环可阻塞,不需要管理 async/await 仪式。并发是默认行为,不是你需要主动选择加入的东西。

在 Python/FastAPI 技术栈中,要实现这一点需要 asyncio 并保持严格的纪律 — 在错误的位置放一个阻塞调用就会卡住事件循环。在 Node 中,你默认拿到非阻塞 I/O,但单线程模型意味着 CPU 密集型工作(解析大型 JSON 响应、计算 HMAC 签名、SHA-256 缓存键哈希)会阻塞所有东西。在 Go 中,goroutine 是轻量级的但共享内存,使得隔离更难保证。

BEAM 进程是抢占式调度的,具有软实时保证。一个正在做大量计算的进程会在一定的 reduction 计数后被抢占 — 它不能饿死其他进程。这对于一个 AI 网关至关重要,因为有些请求只需要 200ms(缓存命中),有些需要 15 秒(复杂的多模型链)。

监督树:自我修复的基础设施

一个 AI 网关全天候都在与不可靠的外部服务通信。Provider 会宕机。Webhook 端点会返回 500。Stripe 的 API 偶尔会抽风。数据库连接池在流量高峰时可能会暂时耗尽。

在大多数技术栈中,你用 try/catch 块、重试库和健康检查端点来处理这些问题,然后在 PagerDuty 里监控。在 Elixir 中,你拥有 OTP 监督树 — 一个久经考验的进程管理框架,从 1980 年代起就一直在保持电信系统存活。

以下是生产环境中应用监督树的简化版本:

ELIXIR
children = [
# 加密 Vault — GenServer,密钥加载失败时自动重启
MyApp.Vault,
# 2FA token 存储 — 基于 ETS 的 GenServer
MyApp.TwoFactorTokens,
# 待处理异步通道 — 跟踪进行中的异步请求
MyApp.PendingChannels,
# 遥测监督器 — 指标收集
MyAppWeb.Telemetry,
# AWS 凭证初始化器 — 在 Repo 启动前创建 ETS 表
MyApp.ExAwsInitializer,
# 数据库连接池
MyApp.Repo,
# 后台任务处理器(Oban)
{Oban, Application.fetch_env!(:myapp, Oban)},
# 基于 DNS 的集群发现,用于水平扩展
{DNSCluster, query: Application.get_env(:myapp, :dns_cluster_query)},
# PubSub,用于实时广播
{Phoenix.PubSub, name: MyApp.PubSub},
# HTTP 客户端池,用于出站请求(邮件、Webhook)
{Finch, name: MyApp.Finch},
# WebSocket token 管理器 — 一次性 token 用于 WS 认证
MyApp.WebSocketTokens,
# CLI WebSocket token 管理器 — 更长寿命的可复用 token
MyApp.CLITokens,
# CLI 连接跟踪器
MyApp.CLIConnections,
# 实时在线状态追踪
MyAppWeb.Presence,
# HTTP 端点 — 最后启动,确保一切就绪后才开始处理请求
MyAppWeb.Endpoint
]
 
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)

每个子进程都被监督。如果 Vault 进程因加密密钥加载错误而崩溃,监督器会重启它。如果 WebSocket token 存储崩溃了,它会被重启。如果数据库连接池挂了又恢复,Repo 子进程会重启并重新连接。

strategy: :one_for_one 意味着每个子进程是独立的 — 一个崩溃不会拉垮其他的。启动顺序是有序且确定的:AWS 凭证的 ETS 表在 Repo 启动之前初始化,所以从 AWS Secrets Manager 获取的数据库 URL 可以立即使用。

在 Python、Node 或 Go 中,没有大量的手动工程你是做不到这些的。 OTP 监督机制已经打磨了 30 多年。它不是一个库 — 它是运行时的核心能力。

ETS:无需 Redis 的进程内缓存

每个 AI 网关都需要一个快速缓存。显而易见的选择是 Redis。但 Redis 引入了网络跳转、一个需要管理的独立进程,以及又一个故障点。

BEAM 有 ETS (Erlang Term Storage) — 一个存在于虚拟机内部的内存键值存储,任何进程都可以访问,具有常数时间查找,且没有序列化开销。

以下是 Prompt 缓存的工作方式:

ELIXIR
# 缓存键:user_id + workflow_name + 规范化请求参数的 SHA-256
def generate_cache_key(user_id, workflow_name, params) do
normalized_params =
params
|> normalize_params() # 排序键,去除不稳定字段
|> Jason.encode!()
 
data = "#{user_id}:#{workflow_name}:#{normalized_params}"
 
:crypto.hash(:sha256, data)
|> Base.encode16(case: :lower)
end
 
# 两级查找:ETS(快速路径)→ 数据库(持久化回退)
def get(user_id, workflow_name, params, cache_cutoff \\ nil) do
cache_key = generate_cache_key(user_id, workflow_name, params)
bucket_id = "prompt_cache:#{cache_key}"
now = System.system_time(:millisecond)
 
case :ets.lookup(:cache_table, bucket_id) do
[{^bucket_id, {data, created_at}, expires_at}]
when expires_at > now and created_at >= cache_cutoff_ms ->
{:ok, data}
 
_ ->
:miss
end
end

不需要管理 Redis 连接,不需要序列化/反序列化,没有网络延迟。缓存检查就是一次 ETS 查找 — 亚微秒级。如果 ETS 缓存未命中,它会回退到数据库查询,查找具有相同请求体哈希、相同 workflow、且插入时间在缓存窗口内的匹配请求日志。

这种两级方案意味着:

  • 热路径:ETS 在 < 1ms 内返回缓存响应
  • 温路径:数据库在 ~5ms 内返回缓存响应(来自之前的请求日志)
  • 冷路径:完整的 Provider 调用,1-15 秒

Redis 会给每次缓存检查增加约 0.5ms。听起来不多,但当你每分钟处理 10,000 个请求时,每一毫秒都在累积。


生态系统:专为此打造的工具,而非胶水代码

Oban:不需要独立队列服务的后台任务处理

异步运行的 AI 请求需要一个可靠的任务队列。大多数技术栈会选择 Celery(Python)、BullMQ(Node)、或像 RabbitMQ 或 SQS 这样的独立消息代理。

Elixir 有 Oban — 一个健壮的、基于 PostgreSQL 的任务处理库,运行在同一个应用内部。不需要外部队列服务,不需要独立的 worker 进程。只是应用监督树中的又一个被监督的子进程。

ELIXIR
# 队列配置
config :myapp, Oban,
queues: [
default: 10,
ai_requests: 10,
webhooks: 5,
emails: 10,
billing: 5
],
plugins: [
{Oban.Plugins.Cron,
crontab: [
{"*/5 * * * *", MyApp.Workers.DowngradeWorker, args: %{}}
]}
]

五个命名队列,各有独立的并发限制。ai_requests 队列处理异步 LLM 调用,webhooks 队列处理带重试的 Webhook 交付,billing 队列向 Stripe 报告使用量,emails 队列发送事务性邮件。还有一个 cron 插件运行周期性任务,比如检查订阅降级。

每个 worker 通过模式匹配提取其任务参数:

ELIXIR
defmodule MyApp.Workers.AIRequestWorker do
use Oban.Worker, queue: :ai_requests, max_attempts: 1
 
@impl Oban.Worker
def perform(%Oban.Job{args: %{
"channel_id" => channel_id,
"params" => params,
"workflow_name" => workflow_name,
"user_id" => user_id,
"project_id" => project_id
}}) do
# 加载 workflow → 检查缓存 → 调用 provider → 处理故障转移
# → 记录请求 → 通过 WebSocket 广播结果
# 全部在一个受监督的后台任务中完成
end
end

为什么这很重要:任务队列、cron 调度器和任务事务保证全部来自 PostgreSQL。如果服务器在任务执行中途崩溃,任务不会丢失 — 它仍然在数据库中,标记为处理中,重启后会被重新拾取。不需要 Redis,不需要 RabbitMQ,不需要 SQS。少了一个需要运维的基础设施组件。

对比:

  • Python + Celery 需要 Redis 或 RabbitMQ 作为 broker,另加独立的 worker 进程。除了你的应用外,还有两个基础设施组件。
  • Node + BullMQ 需要 Redis。一个额外组件。
  • Go 通常使用独立队列(SQS、NATS 或 RabbitMQ)。一到两个额外组件。
  • Elixir + Oban 使用 PostgreSQL,你的应用数据库本来就有。零个额外组件。

Phoenix Channels:无需 WebSocket 库的实时交付

异步 AI 请求需要一种方式将结果推送给客户端。标准做法:搭建 WebSocket 服务器、处理连接升级、管理认证、实现心跳、处理重连。

Phoenix 内置了 Channels — 一个建立在 WebSocket 之上的实时抽象,带有在线状态追踪、基于主题的发布/订阅,以及融入连接生命周期的认证。

ELIXIR
defmodule MyAppWeb.AIResponseChannel do
use Phoenix.Channel
 
def join("ai_response:" <> subtopic, _params, socket) do
# 从主题中解析 project_id 和 channel_id
# 通过 token 或数据库查找验证项目访问权限
# 追踪在线状态以实时监控连接
send(self(), :after_join)
{:ok, socket}
end
 
def handle_info(:after_join, socket) do
# 追踪在线状态,这样我们就知道哪些通道已连接
MyAppWeb.Presence.track(socket, socket.assigns.user_id, %{
online_at: inspect(System.system_time(:second)),
project_id: socket.assigns.project_id,
channel_id: socket.assigns.channel_id
})
{:noreply, socket}
end
end

当异步 AI 请求完成时,广播结果只需一行代码:

ELIXIR
MyAppWeb.Endpoint.broadcast(
"ai_response:#{project_id}:#{channel_id}",
"response",
payload
)

该通道上的每个已连接客户端都会收到结果。没有轮询,没有长生命周期的 HTTP 连接,没有 SSE 的重连问题。Phoenix Channels 通过官方 JavaScript 客户端在客户端处理连接管理、心跳和重连。

这也驱动着 CLI Webhook 交付 — CLI 客户端通过 WebSocket 连接,服务器将 Webhook 负载广播给项目的所有已连接 CLI 用户,使用 HMAC-SHA256 签名的负载进行验证:

ELIXIR
def generate_signature(payload, secret, timestamp) do
json_payload = Jason.encode!(payload)
signature_payload = "#{timestamp}.#{json_payload}"
 
:crypto.mac(:hmac, :sha256, secret, signature_payload)
|> Base.encode16(case: :lower)
end

Cloak:应用级加密

AI 网关存储 Provider API 密钥 — OpenAI 密钥、Anthropic 密钥、客户自带密钥(BYOK 场景)。这些必须在静态状态下加密。

Elixir 生态有 Cloak — 一个与 Ecto(数据库层)集成的加密库,提供透明的字段级 AES-256-GCM 加密:

ELIXIR
defmodule MyApp.Vault do
use Cloak.Vault, otp_app: :myapp
 
@impl GenServer
def init(config) do
key = fetch_key!("CLOAK_KEY")
 
config =
Keyword.put(config, :ciphers,
default: {
Cloak.Ciphers.AES.GCM,
tag: "AES.GCM.V1", key: key
}
)
 
{:ok, config}
end
end

Vault 作为一个受监督的 GenServer 在应用树中启动。标记为 Cloak.Ecto.Binary 的数据库字段在写入时自动加密,读取时自动解密。API 密钥永远不会以明文形式存储在数据库中,永远不会被记入日志,永远不会被序列化到磁盘。

密钥轮换是一个运行时操作 — 更新密钥,重启 Vault 进程,重新加密。不需要重新部署。监督树确保在任何进程尝试访问加密数据之前,Vault 已经可用。

Behaviours:多态的 Provider 适配器

一个 AI 网关需要与 OpenAI、Anthropic、Google、Mistral、Cohere、DeepSeek、Groq、xAI、Qwen 和自定义端点通信。每个 Provider 有不同的 API 格式、认证方案和响应结构。

Elixir 的 behaviours 为此提供编译时契约:

ELIXIR
defmodule MyApp.Providers.Behaviour do
@type request :: map()
@type response :: {:ok, map()} | {:error, term()}
 
@callback complete(request(), String.t()) :: response()
@callback models() :: list(String.t())
@callback provider_name() :: String.t()
end

每个 Provider 模块实现这个 behaviour。路由层根据 Provider 名称分发。添加一个新 Provider 就是写一个模块 — 实现 complete/2models/0provider_name/0。没有接口模板代码,没有工厂模式,没有依赖注入框架。只是一个在编译时强制执行的 behaviour 契约。

Hammer:无需外部状态的速率限制

AI 网关需要多层速率限制:按 IP、按项目、按 API 密钥。大多数技术栈使用 Redis 来做分布式速率限制。

Elixir 生态有 Hammer — 一个带有可插拔后端的速率限制库。对于单节点部署,ETS 就是后端。零外部依赖。

ELIXIR
# IP 级速率限制
case Hammer.check_rate("ip:#{ip}", 60_000, 100) do
{:allow, _count} -> :ok
{:deny, _limit} -> {:error, :rate_limited}
end
 
# 项目级速率限制
case Hammer.check_rate("project:#{project_id}", 60_000, 1000) do
{:allow, _count} -> :ok
{:deny, _limit} -> {:error, :rate_limited}
end

ETS 处理滑动窗口计数器。不需要 Redis,不需要 Lua 脚本,单节点部署不存在分布式时钟同步问题。


技术栈对比:具体的取舍

Python + FastAPI

优势: 最快的原型开发路径。AI/ML 生态是 Python 原生的 — LangChain、LlamaIndex、Hugging Face、每个 Provider SDK。如果你的团队已经会 Python,学习曲线几乎为零。

在 AI 网关场景的劣势:

  • 并发是后加的。 asyncio 能用但需要纪律。一个阻塞调用 — 一个同步数据库查询、一个忘记的 await、一个不支持 async 的库 — 就会卡住事件循环。在一个需要发起数百个并发 Provider 调用的 AI 网关中,这是 bug 的持续来源。
  • 没有监督机制。 如果后台任务崩溃了,你需要自己检测并手动重启它。Celery worker 在任务中途挂掉需要外部监控(Flower、supervisor 进程)来重启。
  • GIL 限制。 CPU 密集型工作(HMAC 计算、JSON 解析、缓存键哈希)受全局解释器锁限制。可以用多进程绕过,但增加了复杂度。
  • 独立基础设施。 你需要 Redis 给 Celery,Redis 给缓存(或 Memcached),还有独立的 WebSocket 服务器(或像 socket.io 这样拥有自己事件循环的库)。这是三个额外组件。

TypeScript + Node (Express/Fastify/Hono)

优势: 出色的 TypeScript 生态系统。良好的异步 I/O 模型。大量的人才储备。Vercel/Cloudflare Workers 让简单场景的部署变得极其简单。

在 AI 网关场景的劣势:

  • 单线程 CPU。 解析 50KB 的 LLM 响应 JSON、计算 SHA-256 缓存键、生成 HMAC 签名 — 所有这些都会阻塞事件循环。worker_threads 能帮忙但增加复杂度。
  • 没有内置任务队列。 你需要 BullMQ + Redis,或者像 SQS 这样的外部服务。每一个额外依赖都是另一个故障点。
  • WebSocket 管理。wssocket.io 这样的库能用,但没有 Phoenix 级别的抽象。你需要手动管理连接、房间、认证、心跳和重连。Phoenix Channels 声明式地处理了所有这些。
  • 没有监督树。 如果后台进程崩溃了,你用 process.on('uncaughtException') 捕获,然后祈祷 PM2 或 Docker 足够快地重启它。

Go

优势: 出色的性能。goroutine 带来的优秀并发模型。强大的标准库。编译成单一二进制文件。非常适合高吞吐量代理。

在 AI 网关场景的劣势:

  • 复杂领域逻辑冗长。 AI 网关的业务逻辑 — workflow 加载、结构化输出合并、两级回退的 Prompt 缓存、带尝试记录的多步故障转移 — 在 Go 里会产生大量代码。光是错误处理(if err != nil)就能让代码量翻倍。
  • 没有内置应用框架。 你需要从各种库中组装一切:路由器、中间件、数据库层、迁移工具、任务队列、WebSocket 服务器、速率限制器、加密。每个都有自己的约定。认知负担层层叠加。
  • 没有 OTP 等价物。 Go 有 goroutine 但没有监督树。你需要自己写进程管理、健康检查和优雅关闭。对于简单服务这没问题;对于一个有 15 个以上需要长期运行的被监督组件的系统来说,这是大量工作。
  • 没有内置热代码重载。 在开发环境中,每次改动都需要重启服务器。对微服务来说无所谓,但对拥有复杂启动序列的单体应用来说很烦人。

Elixir + Phoenix

优势:

  • 并发是默认行为。 每个请求都是隔离的。不需要 async/await 仪式,不存在事件循环阻塞,没有 GIL。
  • OTP 监督树。 打磨了 30 多年的自我修复进程管理。
  • ETS。 不需要 Redis 的进程内缓存。
  • Oban。 基于 PostgreSQL 的任务队列,不需要独立的 broker 服务。
  • Phoenix Channels。 内置实时 WebSocket 交付和在线状态追踪。
  • Cloak。 字段级 AES-256-GCM 密钥加密。
  • Behaviours。 编译时多态,用于 Provider 适配器。
  • 模式匹配。 所有主流语言中最清晰的错误处理:在每个调用点解构成功/失败元组。
  • Telemetry。 内置的指标和可观测性仪表化框架。
  • 热代码重载。 开发时即时反馈。

劣势:

  • 更小的生态系统。 库比 Python/Node/Go 少。没有 Provider SDK — 你需要自己写 HTTP 适配器(说实话,对 API 网关来说这没问题;你本来就想控制请求)。
  • 更小的人才库。 招聘更难。找 Elixir 开发者比找 Python 或 Go 开发者需要更多努力。
  • 学习曲线。 函数式编程、模式匹配和 OTP 概念,如果你的团队来自 OOP 背景,需要时间学习。
  • 部署。 Elixir releases 文档完善但不如 Docker + Node 或 Go 的单一二进制文件那样"一键部署"。Mix releases 和 Distillery/Burrito 增加了一个学习步骤。

在生产中的实际样子:执行管道

为了具体说明,以下是生产系统中单个 AI 请求的简化执行流程:

TEXT
HTTP 请求到达 Phoenix Endpoint
→ Plug 管道:CORS → API 密钥认证 → 项目速率限制 → IP 速率限制
→ Controller 提取 workflow 名称和参数
→ 执行模块:
1. 从数据库加载 workflow(通过 Ash 框架)
2. 检查是否为测试模式 → 返回样本数据
3. 如已配置,准备结构化输出 schema
4. 将系统指令合并到消息中
5. 加载 Provider 凭证(从数据库中 Cloak 解密)
6. 内容审核检查
7. Prompt 缓存查找(ETS → 数据库回退)
→ 缓存命中:返回缓存响应,记录为 cache_hit
→ 缓存未命中:继续调用 Provider
8. 调用主 Provider
→ 成功:记录、缓存、返回
→ 失败:尝试 backup_1 Provider
→ 失败:尝试 backup_2 Provider
→ 全部失败:尝试离线回退(结构化输出默认值)
9. 记录请求及完整元数据(Provider、模型、token 数、延迟、尝试次数)
10. 递增组织请求计数器(异步 Task)
11. 如有超额,向 Stripe meter 报告使用量(异步 Task)
12. 返回响应

对于异步请求,流程是:

TEXT
HTTP 请求到达
→ 验证和认证
→ 将 Oban 任务放入 :ai_requests 队列
→ 立即返回 channel_id(HTTP 202)
→ [后台]
→ Oban worker 拾取任务
→ 与上面相同的执行管道
→ 完成后:通过 Phoenix Channel 广播
→ 通过 :webhooks 队列交付给 Webhook(带 HMAC 签名)

管道中的每一步都在自己的 BEAM 进程中运行。Controller 进程、Oban worker 进程、PubSub 广播进程、Webhook 交付进程 — 全部隔离、全部受监督、全部并发。


Telemetry:内建于基础之中的可观测性

Phoenix 内置了 Telemetry — 一个轻量级的仪表化库,集成到技术栈的每一层。数据库查询、HTTP 请求、Channel 操作和自定义应用事件都会发出 Telemetry 事件。

ELIXIR
def metrics do
[
# Phoenix 端点和路由器指标
summary("phoenix.endpoint.stop.duration", unit: {:native, :millisecond}),
summary("phoenix.router_dispatch.stop.duration", tags: [:route]),
 
# 数据库指标
summary("myapp.repo.query.total_time", unit: {:native, :millisecond}),
summary("myapp.repo.query.queue_time", unit: {:native, :millisecond}),
 
# AI 网关专用指标
summary("myapp.openai_compat.request.duration", tags: [:model, :status]),
summary("myapp.openai_compat.stream.duration_ms", tags: [:model, :status]),
counter("myapp.openai_compat.error.count", tags: [:error_type]),
 
# VM 指标 — 免费的,因为 BEAM 原生暴露这些
summary("vm.memory.total", unit: {:byte, :kilobyte}),
summary("vm.total_run_queue_lengths.total"),
summary("vm.total_run_queue_lengths.cpu"),
summary("vm.total_run_queue_lengths.io")
]
end

不需要外部 APM agent,不需要猴子补丁。BEAM 虚拟机本身就报告内存使用量、运行队列长度和 I/O 指标。你接入任何报告器 — Prometheus、Datadog、自定义仪表板 — 指标就会自动流入。


基础设施简化

以下是最终的全景图 — 一个生产级 AI 网关的完整基础设施组成:

使用 Python/Node/Go:

  • 应用服务器
  • PostgreSQL
  • Redis(缓存 + 任务队列 broker)
  • 消息队列(RabbitMQ/SQS 用于可靠任务处理)
  • WebSocket 服务器(独立进程或库)
  • Cron 调度器(cron jobs 或 CloudWatch Events)
  • 速率限制状态(Redis 支持)
  • 加密服务或 vault

使用 Elixir + Phoenix:

  • 应用服务器(包含 WebSocket 服务器、任务处理器、cron 调度器、速率限制器、加密 vault、内存缓存)
  • PostgreSQL(用于数据 + Oban 任务队列)

就是这样。两个组件。其他所有能力都作为受监督的进程存在于 BEAM 运行时内部。部署时,你部署一样东西。调试时,你看一套日志。崩溃时,监督树在你醒来之前就处理好了。


什么时候不该用 Elixir

如果不提到 Elixir 不适用的场景,那就不诚实了:

  • 如果你的团队完全没有函数式编程经验,而且你只有 2 周的 deadline,学习曲线带来的成本会超过架构优势带来的收益。
  • 如果你需要深度 ML/AI 库集成(模型训练、embedding 生成、向量数据库客户端),Python 生态领先了好几年。
  • 如果你只是在构建一个轻量代理,没有业务逻辑 — 只是转发请求 — Go 或者 Cloudflare Workers 会更简单更快。
  • 如果招聘是你的瓶颈,Elixir 的人才库明显小于 Python、Node 或 Go。

Elixir 的甜蜜点恰恰就是 AI 网关这个场景:一个 I/O 密集、高并发、需要容错、管理大量长期连接、处理后台任务,并且需要在不依赖复杂基础设施的情况下可靠运行的系统。


结论

ModelRiver,整个后端 — API 网关、多级故障转移的 Provider 路由、Prompt 缓存、异步任务处理、WebSocket 交付、CLI 工具链、Webhook 交付、加密密钥管理、按量计费、速率限制、内容审核、遥测和完整的可观测性层 — 全部运行在 Elixir + Phoenix 上,搭配单个 PostgreSQL 数据库。

这套技术栈在 AI 圈里的讨论度不够,因为 AI 生态系统压倒性地以 Python 为中心。但如果你在构建的是基础设施层 — 那个位于应用代码和 LLM Provider 之间的路由、编排和可靠性层 — BEAM 虚拟机提供的原语是其他运行时要么缺失、要么需要通过第三方服务补上的。

构建 AI 应用的最佳技术栈是 Python。构建 AI 基础设施的最佳技术栈,可能是 Elixir。

如果你想看看实际效果,ModelRiver 的文档详细介绍了整个系统,或者你可以直接把 base_url 指向 https://api.modelriver.com/v1,发出你的第一个请求。