为什么 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 网关在运行时到底在做什么。它不是一个简单的代理。对于每一个传入的请求,系统需要:
- 认证和授权 — 验证项目级 API 密钥
- 加载 Workflow 配置 — 哪个 Provider、哪个模型、什么系统指令、是否启用结构化输出、有哪些备用 Provider
- 检查速率限制 — IP 级别和项目/组织级别
- 检查 Prompt 缓存(先查 ETS 内存缓存,再查数据库回退)以避免重复的 Provider 调用
- 执行内容审核 — 在转发给 Provider 之前
- 发起实际的 LLM API 调用 — 带超时预算、失败分类和自动故障转移到备用 Provider
- 记录请求和响应 — 完整的元数据:token 数、延迟、使用的 Provider、缓存状态、故障转移尝试
- 计量使用量 — 向 Stripe 的按量计费 API 报告超额事件
- 交付结果 — 同步通过 HTTP 响应,或异步通过 WebSocket 通道和带 HMAC 签名的 Webhook 交付
- 更新缓存 — 将结果存入 ETS 以加速后续查询
这些步骤中的每一个都涉及 I/O:数据库查询、对 Provider 的 HTTP 调用、对 Stripe 的 HTTP 调用、WebSocket 广播、ETS 读写。而且每一个都需要并发执行 — 你不能在等待 OpenAI 响应单个请求时阻塞整个服务器。
这就是 BEAM 改变游戏规则的地方。
为什么 BEAM 是天然适合的架构
轻量级进程实现请求隔离
Elixir/Phoenix 应用中的每个请求都运行在自己的 BEAM 进程中。不是操作系统线程,不是 goroutine,而是 BEAM 进程 — 一个轻量级、隔离的执行单元,拥有自己的堆、自己的垃圾回收调度和自己的邮箱。
实际效果:
进程 #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 年代起就一直在保持电信系统存活。
以下是生产环境中应用监督树的简化版本:
每个子进程都被监督。如果 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 缓存的工作方式:
不需要管理 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 进程。只是应用监督树中的又一个被监督的子进程。
五个命名队列,各有独立的并发限制。ai_requests 队列处理异步 LLM 调用,webhooks 队列处理带重试的 Webhook 交付,billing 队列向 Stripe 报告使用量,emails 队列发送事务性邮件。还有一个 cron 插件运行周期性任务,比如检查订阅降级。
每个 worker 通过模式匹配提取其任务参数:
为什么这很重要:任务队列、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 之上的实时抽象,带有在线状态追踪、基于主题的发布/订阅,以及融入连接生命周期的认证。
当异步 AI 请求完成时,广播结果只需一行代码:
该通道上的每个已连接客户端都会收到结果。没有轮询,没有长生命周期的 HTTP 连接,没有 SSE 的重连问题。Phoenix Channels 通过官方 JavaScript 客户端在客户端处理连接管理、心跳和重连。
这也驱动着 CLI Webhook 交付 — CLI 客户端通过 WebSocket 连接,服务器将 Webhook 负载广播给项目的所有已连接 CLI 用户,使用 HMAC-SHA256 签名的负载进行验证:
Cloak:应用级加密
AI 网关存储 Provider API 密钥 — OpenAI 密钥、Anthropic 密钥、客户自带密钥(BYOK 场景)。这些必须在静态状态下加密。
Elixir 生态有 Cloak — 一个与 Ecto(数据库层)集成的加密库,提供透明的字段级 AES-256-GCM 加密:
Vault 作为一个受监督的 GenServer 在应用树中启动。标记为 Cloak.Ecto.Binary 的数据库字段在写入时自动加密,读取时自动解密。API 密钥永远不会以明文形式存储在数据库中,永远不会被记入日志,永远不会被序列化到磁盘。
密钥轮换是一个运行时操作 — 更新密钥,重启 Vault 进程,重新加密。不需要重新部署。监督树确保在任何进程尝试访问加密数据之前,Vault 已经可用。
Behaviours:多态的 Provider 适配器
一个 AI 网关需要与 OpenAI、Anthropic、Google、Mistral、Cohere、DeepSeek、Groq、xAI、Qwen 和自定义端点通信。每个 Provider 有不同的 API 格式、认证方案和响应结构。
Elixir 的 behaviours 为此提供编译时契约:
每个 Provider 模块实现这个 behaviour。路由层根据 Provider 名称分发。添加一个新 Provider 就是写一个模块 — 实现 complete/2、models/0 和 provider_name/0。没有接口模板代码,没有工厂模式,没有依赖注入框架。只是一个在编译时强制执行的 behaviour 契约。
Hammer:无需外部状态的速率限制
AI 网关需要多层速率限制:按 IP、按项目、按 API 密钥。大多数技术栈使用 Redis 来做分布式速率限制。
Elixir 生态有 Hammer — 一个带有可插拔后端的速率限制库。对于单节点部署,ETS 就是后端。零外部依赖。
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 管理。 像
ws或socket.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 请求的简化执行流程:
对于异步请求,流程是:
管道中的每一步都在自己的 BEAM 进程中运行。Controller 进程、Oban worker 进程、PubSub 广播进程、Webhook 交付进程 — 全部隔离、全部受监督、全部并发。
Telemetry:内建于基础之中的可观测性
Phoenix 内置了 Telemetry — 一个轻量级的仪表化库,集成到技术栈的每一层。数据库查询、HTTP 请求、Channel 操作和自定义应用事件都会发出 Telemetry 事件。
不需要外部 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,发出你的第一个请求。
