要点
观察一篇 Elixir。定位一句话说完:跑在 BEAM 虚拟机上的函数式语言,把 Erlang/OTP 的并发与容错模型装进了现代语法;Phoenix 是它最主流的 Web 框架,LiveView 则把页面交互整个留在了服务端。截至本文(2025 年 5 月),稳定线是 Elixir 1.18.x(1.18.0 于 2024 年 12 月发布)、Phoenix 1.7 线、LiveView 1.0(2024 年 12 月 GA)。看完这篇你能回答四个问题:BEAM 的并发模型和”一个请求一个线程”差在哪、Phoenix 从生成到跑通要走几步、LiveView 怎么做到不写前端框架也有交互、以及它和 PHP 的常驻内存路线(Workerman/Swoole)到底是不是一回事。
BEAM 与 OTP:并发是语言的原生设定
Elixir 编译后运行在 Erlang 的虚拟机 BEAM 上。它的并发模型是 actor 模型:进程(process)是隔离的最小单位——每个 BEAM 进程有独立堆、独立垃圾回收(GC),进程间不共享内存,只靠消息传递(mailbox 收发)通信。两个直接后果:一个进程崩溃不拖累别人(错误隔离),GC 永远只停单个进程(没有全局暂停)。轻量到什么程度,公开资料的共识口径是单节点开数十万进程属常规操作,创建成本接近函数调用——本站未实测,以官方文档口径为准。
在 iex 里感受一下这套原语(可直接跑):
parent = self()
1. spawn 起一个独立进程:独立堆独立 GC,它崩了也不影响父进程
pid =
spawn(fn ->
receive do
{:add, a, b} -> send(parent, {:result, a + b})
end
end)
send(pid, {:add, 1, 2})
receive do
{:result, n} -> n # => 3
end
OTP(Open Telecom Platform)在这套原语之上给了成型的行为模式(behaviour):GenServer 管状态,Supervisor 管监控重启,设计哲学是著名的 let it crash——不写防御性代码,让出错的进程死掉,由监督树按策略拉起来,把故障隔离在最小范围。这套东西在 Phoenix 里无处不在:你写的每个 LiveView 页面就是一个 BEAM 进程。
上手:mix phx.new 到跑通
mix archive.install hex phx_new # 安装 Phoenix 项目生成器
mix phx.new hello # 生成项目;本机没有 PostgreSQL 就加 --no-ecto
cd hello
mix setup # 装依赖、建库、跑迁移(默认走 PostgreSQL)
mix phx.server # 启动开发服务器,默认 http://localhost:4000
几步跑通,但要知道每步在干什么:mix 是构建工具和任务运行器,位置相当于 Node 生态的 npm scripts 加打包器;Phoenix 默认带 Ecto——一个类型化的数据库查询层,查询以显式的管道组合,不是”模型对象藏 SQL”的 ORM 心智。想先看看框架本身,--no-ecto 跳过数据库是最省事的起步方式。
LiveView:交互回到服务端
LiveView 的主张:浏览器里只留一小段客户端运行时,负责维持 WebSocket 长连接和把服务端发来的补丁打到 DOM 上;状态和事件处理全在服务端。每个打开页面的浏览器会话对应一个 BEAM 进程,状态挂在进程里,事件消息由进程自己处理。一个计数器组件:
# lib/hello_web/live/counter_live.ex
defmodule HelloWeb.CounterLive do
use HelloWeb, :live_view
def mount(_params, _session, socket) do
1. assign 把状态挂到 socket 上;状态变化由 LiveView 算 diff 推给浏览器
{:ok, assign(socket, count: 0)}
end
1. phx-click 发来的事件在这里处理,整个链路不出服务端
def handle_event("inc", _params, socket) do
{:noreply, update(socket, :count, &(&1 + 1))}
end
def render(assigns) do
~H"""
<div>
<p>当前计数:<%= @count %></p>
<button phx-click="inc">加一</button>
</div>
"""
end
end
对比前后端分离路线,少掉的是一整层:REST/GraphQL 接口、前端状态管理、序列化协议。代价也同样明确——每次点击都是一次网络往返,交互的”手感”下限由延迟决定,重前端交互(拖拽、画布、复杂动画)不是它的主场。
反模式点名:把大体积数据整块 assign 进 LiveView。每个浏览器会话是一个常驻进程,你 assign 的列表、字符串都会活在进程堆里直到用户离开页面。一万条记录整块塞进去,一千个在线用户就是一千份拷贝——内存随会话数线性涨。正确姿势是分页查询、只 assign 渲染需要的最小集合,大列表交给流式渲染(stream)。
与 PHP 常驻内存路线的思维对照
国内 PHP 圈这几年一直在走常驻内存路线,站内写过Workerman 常驻内存编程和Swoole 协程运行时。表面上两者目标一致——进程常驻、省掉每请求的启动开销、天然能撑长连接——但实现并发的思路完全不同:
| 维度 | PHP 常驻内存(Workerman/Swoole) | Elixir/BEAM |
|---|---|---|
| 并发单位 | 事件循环加协程,共享进程内存 | 微进程,内存彼此隔离,消息传递 |
| 错误影响 | 未捕获的协程错误可能污染整个 worker | 单进程崩溃,Supervisor 拉起新进程顶上 |
| 内存安全网 | 靠约定(及时 unset、防内存泄漏) | 进程独立 GC,泄漏被圈在单个进程里 |
| 语言模型 | 既有 PHP 语法,改动成本低 | 函数式语言,心智切换成本高 |
| 生态现状 | PHP 生态内成熟组件可直接复用 | Hex 生态自成一体,PHP 组件带不过来 |
一句话总结差异:PHP 路线是在共享内存的进程里模拟并发,BEAM 是天生隔离的进程间传递消息。前者的优势是迁移成本低、既有生态直接复用;后者的优势是错误隔离的彻底性——这不是工程习惯问题,是虚拟机层面保证的。
要不要现在投入
诚实的判断分三种情况。你的场景是长连接、实时推送、高并发 IO(IM、行情、协作编辑),Phoenix 加 LiveView 是这个世界上数一数二的成熟方案,值得认真投入,但国内招聘市场小众,先想清楚人从哪来。你的诉求只是“PHP-FPM 每请求启动太浪费”,那 Workerman/Swoole 路线成本更低、生态更熟,不必为这个理由换语言。你想拓宽并发编程的眼界,Elixir/OTP 是性价比最高的教材之一——就算最后不拿它上生产,监督树和 let it crash 的思路回头看任何语言都用得上。单篇试水的建议:跑通上面的计数器,把玩一晚上 iex,再决定要不要往下走。
延伸阅读
本文定位是观察与试水,不展开 OTP 细节。三条去向:走 PHP 常驻内存路线的,按顺序读上节链接的 Workerman、Swoole 两篇;需要在这条路线上落地任务系统的,用 PHP 写一个可靠的队列任务系统里的队列状态机与超时重投设计可以直接套用;决定认真投入 Elixir 的,elixir-lang.org 的 Getting Started 指南与 Phoenix 官方文档是唯一权威入口,别从旧教程起步。
版本核实(以官方文档为准):Elixir 稳定线为 1.18.x(1.18.0 发布于 2024-12-19,内置 JSON 模块、函数调用类型检查;1.17 发布于 2024-06),运行时对应 Erlang/OTP 27 线;Phoenix 稳定线 1.7.x;Phoenix LiveView 1.0.0 于 2024-12 正式 GA(官方博客公告)。安装命令均不钉版本号,具体以 elixir-lang.org 与 phoenixframework.org 当前文档为准。