跳转到内容

Phoenix — Elixir/OTP 上的实时 web 框架

是什么

Phoenix 是一个跑在 Elixir 语言上的 web 框架,长得像 Ruby on Rails(有路由、控制器、模板),但骨子里是另一套引擎:底层 BEAM 虚拟机让一台机器同时撑几十万 WebSocket 连接,几乎不费力。

日常类比:Rails 像一辆 8 缸轿车,单线程跑得快;Phoenix 像一辆 100 个小马达并排的电车,单个不快但一起拉很多人。BEAM 用”轻量进程”把每个用户连接做成一个独立小演员,互不打扰。

你写:

defmodule MyAppWeb.PageController do
use MyAppWeb, :controller
def index(conn, _params), do: render(conn, "index.html")
end

就是一个标准 web 控制器。但 Phoenix 真正的杀手锏不在这——是 LiveView:服务端直接渲染 HTML,事件触发后只把”变了哪几个字节”推给浏览器,让你不写 JS 也能做实时 UI。

为什么重要

不理解 Phoenix,下面这些事都没法解释:

  • 为什么一台机器能挂 200 万 WebSocket 连接(业界做过 benchmark),而 Node 单进程到几万就吃力
  • 为什么 Discord、Pinterest 早期用 Elixir/Phoenix 做实时消息和通知系统
  • 为什么 LiveView 出现后,“小团队也能做协作型应用”成为可能——不必再养一个 React/Vue 前端组
  • 为什么”框架像 Rails 但写起来像 Erlang”成立——它继承了 OTP 的容错哲学

核心要点

Phoenix 让”高并发实时 web”变简单的关键,是 三件事叠加

  1. BEAM 轻量进程:每个 HTTP 请求 / WebSocket 连接是一个独立 Elixir 进程,几 KB 内存,崩了不影响别人。类比:一栋楼里 100 万个独立小房间,一间漏水不会淹其他人。

  2. Channel + PubSub 抽象:WebSocket 不再是裸字节流,而是按 topic 订阅。一个进程订阅”行情/btc”,把数据 broadcast,几十万订阅者同时收到。类比:广播电台分频道,谁调到哪个频道收哪个。

  3. LiveView 服务端 UI:UI 状态住在服务端进程里,浏览器只是显示器;事件经 WebSocket 回服务端,diff 推回浏览器。类比:你坐在电视机前按遥控器,电视台那边算”画面下一帧应该长啥样”。

三件事合起来叫 Phoenix 的实时栈

实践案例

案例 1:用 LiveView 写一个最小聊天室

defmodule ChatLive do
use Phoenix.LiveView
def mount(_p, _s, socket) do
Phoenix.PubSub.subscribe(MyApp.PubSub, "chat")
{:ok, assign(socket, messages: [])}
end
def handle_event("send", %{"text" => t}, socket) do
Phoenix.PubSub.broadcast(MyApp.PubSub, "chat", {:msg, t})
{:noreply, socket}
end
def handle_info({:msg, t}, socket) do
{:noreply, update(socket, :messages, &[t | &1])}
end
end

逐部分解释

  • mount 是首次连接 — 订阅 PubSub topic、初始化 messages 为空列表
  • handle_event 接浏览器事件(用户敲回车)— 把消息广播到 topic
  • handle_info 接进程消息 — 把新消息塞进 socket,框架自动推 diff
  • 浏览器看到列表更新,没写一行 JS

案例 2:Channel 做行情广播

channel "quotes:*", QuoteChannel
defmodule QuoteChannel do
use Phoenix.Channel
def join("quotes:" <> sym, _, socket), do: {:ok, assign(socket, :symbol, sym)}
end
# 后台进程拿到行情后:
MyAppWeb.Endpoint.broadcast("quotes:btc", "tick", %{price: 67_000})

一个进程订阅交易所 feed,把每条 tick broadcast 到 quotes:btc topic;几十万浏览器加入这个 topic,谁加入谁就收到。多节点部署时给 PubSub 配 Redis/PG2 适配器,跨机器也能广播。

案例 3:Context 把业务逻辑从 Controller 抽出

defmodule MyApp.Accounts do
alias MyApp.Repo
def register_user(attrs) do
%User{} |> User.changeset(attrs) |> Repo.insert()
end
end
# Controller 只调一行:
MyApp.Accounts.register_user(params)

Accounts 是 Phoenix 推的 Context 模式:每个业务领域一个模块,对外暴露动词函数。Controller 变薄,业务逻辑可单独测,不和 HTTP 耦合。

踩过的坑

  1. LiveView 状态在服务端:长连接断了、服务端进程挂了,UI 状态丢失。需要把关键状态用 Ecto 持久化或 PubSub 重建,不要把 LiveView 当浏览器 state 用。

  2. Channel 广播默认单节点:多节点部署不配 PubSub 适配器(Redis/PG2),不同机器的客户端互相收不到消息。生产前必检。

  3. Ecto changeset 不是 ActiveRecord:changeset 只做”校验 + 准备变更”,必须显式 Repo.insert/update 才落库。新人常忘最后那一步,以为返回 :ok 就写进去了。

  4. BEAM 不擅长 CPU 密集:图像编解码、大矩阵运算会卡 scheduler。该交给 Rust NIF(Rustler)或外部 worker 服务,别硬塞 Elixir。

适用 vs 不适用场景

适用

  • 高并发实时应用(聊天、协作、看板、行情、通知系统)
  • 需要长连接但前端不想搞 SPA 的中小团队(用 LiveView 替代 React)
  • 容错要求高的后端服务(OTP supervisor 树自动重启崩溃组件)
  • 数据库为主、IO 密集的传统 CRUD(也能跑,性能比 Rails 好)

不适用

  • CPU 密集型计算(机器学习推理、视频转码)→ 用 Python / Rust / Go
  • 团队完全不熟悉函数式语言,且没人愿意花两周入门 Elixir
  • 已有 Node/Java 生态深度依赖(库、SDK 找不到对应 Elixir 版本)
  • 需要 SSR + SEO 但又重静态生成 → Astro / Next.js 更直接

历史小故事(可跳过)

  • 2011 年:Elixir 语言诞生,作者 José Valim 想给 BEAM 加上现代语法
  • 2014 年:Chris McCord 受 Rails 启发开始写 Phoenix,目标是”Rails 的开发体验 + BEAM 的并发能力”
  • 2015 年:Phoenix 1.0 发布,Channel 成熟,业界开始用它做实时系统
  • 2018 年:LiveView 公开预览,把”声明式 UI + 服务端状态 + WebSocket diff”打包成默认能力
  • 2022 年:LiveView 0.18 引入 HEEx 模板,编译期校验 HTML 结构,模板错误前置到编译

学到什么

  1. 并发模型决定上限:Rails / Django 一个请求一个线程,Phoenix 一个连接一个 BEAM 进程;后者天然撑得起长连接和实时
  2. 服务端 UI 重新成立:LiveView 证明”前端不一定要 SPA”——在合适的并发模型下,diff 推送够用且省事
  3. OTP 哲学进入 web:让你写 web 也能享受 supervisor 树、let it crash、热升级这些 Erlang 老传统
  4. 生态决定上下限:不熟函数式或库少的领域,Phoenix 再快也用不起来——技术选型要看团队和场景

延伸阅读

关联

  • erlang-otp —— 提供轻量进程 / supervisor / 容错原语,Phoenix 的并发能力都来自它
  • rails —— Phoenix 在路由 / 控制器 / 视图 / 迁移这些表层概念上抄 Rails
  • django —— 同代 MVC 框架,Phoenix 在并发和实时上拉开差距
  • fastapi —— 现代 Python 框架,对比看 Phoenix 在长连接场景的优势
  • axum —— Rust web 框架,与 Phoenix 同样追求”高并发 + 类型安全”,但走零成本抽象路线
  • aspnetcore —— 微软系高性能框架,对比可看不同语言怎么解决同一类问题
  • tcp —— Phoenix 的 Channel/LiveView 都跑在 WebSocket 上,本质是 TCP 长连接

反向链接

  • aspnetcore —— ASP.NET Core — 微软跨平台 web 框架
  • axum —— axum — 用 Rust 类型系统当『路由参数表』的 Web 框架
  • django —— Django — 全功能 batteries-included 的 Python web 框架
  • erlang-otp —— Erlang OTP — 容错并发系统设计
  • fastapi —— FastAPI — 用 Python 类型注解写 API
  • rails —— Ruby on Rails — 约定大于配置的全栈 Web 框架教科书
  • tcp —— TCP — 在不可靠的 IP 上凿出一条 reliable 字节流