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”变简单的关键,是 三件事叠加:
-
BEAM 轻量进程:每个 HTTP 请求 / WebSocket 连接是一个独立 Elixir 进程,几 KB 内存,崩了不影响别人。类比:一栋楼里 100 万个独立小房间,一间漏水不会淹其他人。
-
Channel + PubSub 抽象:WebSocket 不再是裸字节流,而是按 topic 订阅。一个进程订阅”行情/btc”,把数据 broadcast,几十万订阅者同时收到。类比:广播电台分频道,谁调到哪个频道收哪个。
-
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])} endend逐部分解释:
mount是首次连接 — 订阅 PubSub topic、初始化 messages 为空列表handle_event接浏览器事件(用户敲回车)— 把消息广播到 topichandle_info接进程消息 — 把新消息塞进 socket,框架自动推 diff- 浏览器看到列表更新,没写一行 JS
案例 2:Channel 做行情广播
channel "quotes:*", QuoteChanneldefmodule 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() endend# Controller 只调一行:MyApp.Accounts.register_user(params)Accounts 是 Phoenix 推的 Context 模式:每个业务领域一个模块,对外暴露动词函数。Controller 变薄,业务逻辑可单独测,不和 HTTP 耦合。
踩过的坑
-
LiveView 状态在服务端:长连接断了、服务端进程挂了,UI 状态丢失。需要把关键状态用 Ecto 持久化或 PubSub 重建,不要把 LiveView 当浏览器 state 用。
-
Channel 广播默认单节点:多节点部署不配 PubSub 适配器(Redis/PG2),不同机器的客户端互相收不到消息。生产前必检。
-
Ecto changeset 不是 ActiveRecord:changeset 只做”校验 + 准备变更”,必须显式
Repo.insert/update才落库。新人常忘最后那一步,以为返回:ok就写进去了。 -
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 结构,模板错误前置到编译
学到什么
- 并发模型决定上限:Rails / Django 一个请求一个线程,Phoenix 一个连接一个 BEAM 进程;后者天然撑得起长连接和实时
- 服务端 UI 重新成立:LiveView 证明”前端不一定要 SPA”——在合适的并发模型下,diff 推送够用且省事
- OTP 哲学进入 web:让你写 web 也能享受 supervisor 树、let it crash、热升级这些 Erlang 老传统
- 生态决定上下限:不熟函数式或库少的领域,Phoenix 再快也用不起来——技术选型要看团队和场景
延伸阅读
- 官网教程:Phoenix Guides(从零搭一个应用,半天读完)
- LiveView 入门:Phoenix LiveView Docs(看 demo 比看文档快)
- 书:《Programming Phoenix LiveView》(Bruce Tate / Sophie DeBenedetto,2023)
- benchmark:2 Million WebSocket Connections(Phoenix 团队博客)
- erlang-otp —— Phoenix 站在 OTP 的肩膀上
关联
- 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 字节流