共47行
2026-04-20 18:22:40
Elixir/OTP 处理公用数据(尤其是秒杀类高并发场景)的核心思路是:避开单进程串行瓶颈,利用 BEAM 虚拟机级的原子操作实现无锁并发。针对“秒杀”这种极端场景,ETS 原子计数器是性能最优解。
这是 Discord 等超大规模应用验证过的方案,性能远超单 GenServer。
:ets.update_counter 是 VM 层面的原子操作,无需消息传递,直接在客户端进程中执行,吞吐量极高。write_concurrency: true,配合 :public 访问权限,让所有客户端进程直接读写,消除单点瓶颈。defmodule SpikeSale do
# 启动时创建 ETS 表(通常在 Supervision 树中)
def start_link do
:ets.new(:inventory, [:named_table, :set, :public, write_concurrency: true])
:ets.insert(:inventory, {:iphone, 100}) # 初始库存
end
# 秒杀扣减逻辑(直接由 Web 请求进程调用)
def grab(product) do
# 原子性:查询并递减,返回更新后的值
case :ets.update_counter(:inventory, product, -1, {:iphone, 100}) do
new_stock when new_stock >= 0 -> {:ok, new_stock}
_ -> :out_of_stock
end
end
end秒杀场景(高频写+强一致性)是特例,其他场景需根据读写模式选型:
| 方案 | 适用场景 | 秒杀场景痛点 |
|---|---|---|
| GenServer / Agent | 低频写、需复杂业务逻辑、顺序执行 | 单进程串行,消息队列易积压,成为性能瓶颈 |
| ETS + GenServer | 高频读、低频写(如缓存) | 写操作若经 GenServer 路由,仍存在单点瓶颈 |
| Registry | 进程发现、动态命名(如用户会话管理) | 不直接存储业务状态,通常配合其他方案使用 |
update_counter 原子性保证了不超卖,但极端情况下需考虑数据库最终一致性(如扣减成功后异步落库)。:counters 模块(OTP 22+),性能比 ETS 更高。决策建议:电商项目中,若遇到真正的秒杀场景(QPS > 1万),请直接使用 ETS 原子计数器;若只是普通抢购(QPS < 1000),使用 GenServer 保持代码简单即可。