Discord 同时维护数百万 WebSocket 长连接、每秒百万级消息写入、全球 2.5M+ 并发语音用户(2018 年公开数据),以及万亿级历史消息的检索与搜索。没有任何单一语言或数据库能覆盖这条链路:Gateway 与 Guilds 用 Elixir 的 Actor 模型扛连接扇出;Read States 与 message data services 在 Go 碰壁后迁到 Rust;持久层从 MongoDB 经 Cassandra 落到 ScyllaDB;语音媒体面是 C++/Rust SFU,信令仍走 Elixir。
这不是「全栈 Rust」的故事,而是按热路径选运行时的工程样本。Go 在 Read States 上暴露了 GC 与巨型 LRU 的结构性冲突;Rust 用所有权模型换可预测的尾延迟;Elixir 用进程隔离换实时系统的可运维性——直到 supervisor 邮箱成为 2026 年 voice outage 的瓶颈。
本文依据 Discord Engineering Blog 的一手复盘,串联架构全景、存储演进、语音与边缘迁移、事故教训与语言决策矩阵。与 Slack 实时协作案例 对照阅读,可看到两家 IM 在 WebSocket 网关与消息存储上的不同取舍;事件驱动架构 与 线程模型 则分别解释 Gateway 扇出与 Rust async/Tokio 选型的背景。
一、Discord 架构全景
1.1 产品负载特征
Discord 的负载有几条硬约束,直接塑造了后端形态:
| 负载类型 | 访问模式 | 架构后果 |
|---|---|---|
| Gateway WebSocket | 每用户长连接;连接、发消息、读消息均触发下游 | 需要百万级连接管理与低延迟事件推送 |
| Read States | 每次连接、每条消息发送/已读都访问 | 极端热路径;内存内 LRU + 持久化双写 |
| 频道消息 | 按 channel 分区;大服与小服流量差 orders of magnitude | 易出现 Cassandra/Scylla hot partition |
| 语音/视频 | 全双工 UDP;单通话选单一 SFU 宿主 | 区域选型、SFU 容量、边缘 PoP 与对等互联 |
| 搜索 | 懒索引;guild/DM 分片 | Elasticsearch cell 架构与 BFG 特例 |
Discord 早期在 2015 年用 MongoDB 单副本集快速迭代,但
2015 年 11 月前后已出现索引无法常驻
RAM、尾延迟不可预测的问题,随后迁移到 Cassandra(2017
年博客)。到 2022 年初,仅 cassandra-messages
集群就有 177 个节点、数万亿条消息(Bo
Ingram, 2023)。
1.2 多语言栈总览
# Discord 后端语言分工(据官方博客归纳,非完整服务清单)
realtime_signaling:
language: Elixir (BEAM)
services: Gateway, Guilds, Sessions, Voice 信令, Voice Syncers
rationale: Actor 模型、热代码升级、进程级隔离
hot_path_services:
language: Rust
examples: Read States, Authentication, Message Data Services, 迁移工具
rationale: 无 GC、可控尾延迟、fearless concurrency
legacy_and_api:
language: Go, Python, 等
note: Read States 曾用 Go;Discord 明确不建议「因 Rust 流行而全面重写」
media_plane:
language: C++ / Rust
role: SFU 选择性转发;WebRTC 原生栈定制
data_stores:
primary: ScyllaDB (Cassandra 兼容)
search: Elasticsearch on Kubernetes (ECK)
discovery: etcd → Valkey (语音主机发现演进)1.3 高层架构图
graph TB
subgraph 客户端
C1[Desktop / Mobile / Web]
end
subgraph 实时层_Elixir
GW[Gateway<br/>WebSocket 入口]
SESS[Sessions<br/>每设备一会话进程]
GUILDS[Guilds<br/>频道/语音状态]
VSYNC[Voice Syncers<br/>SFU 路由]
end
subgraph 热路径_Rust
RS[Read States]
AUTH[Authentication]
DS[Message Data Services<br/>请求合并]
end
subgraph 媒体层
SFU[SFU 集群<br/>UDP/WebRTC]
end
subgraph 数据层
SCYLLA[(ScyllaDB 集群)]
ES[(Elasticsearch Cells)]
end
C1 -->|Gateway WS| GW
GW --> SESS
SESS --> GUILDS
GUILDS --> VSYNC
VSYNC -->|HTTPS RPC| SFU
C1 -->|Voice WS + UDP| SFU
GW --> AUTH
GUILDS --> RS
GUILDS --> DS
DS --> SCYLLA
RS --> SCYLLA
AUTH --> SCYLLA
GUILDS --> ES
1.4 学术与工业谱系(简)
实时信令层可追溯到 Actor 模型(Hewitt et
al., 1973;Erlang/OTP 是生产级实现)。Discord 的
Gateway/Guilds 不是「微服务拼盘」,而是百万
GenServer 进程 + 进程监控(:DOWN)
驱动的分布式 Actor 系统——2026 年 voice outage
正是这一模型副作用的教科书案例(见第七节)。
消息存储路径则来自 LSM-tree
家族:Cassandra/Scylla 的分区有序写入与读放大权衡,与 O’Neil
等人 1996 年 LSM 定义及后续 leveled/tiered compaction
争论同构。Discord 的 (channel_id, bucket) 分区
+ Snowflake 聚簇键,是在 AP
系统上为按频道顺序扫描做的经典宽表建模(2017
billions 博文)。
语音面采用 SFU(Selective Forwarding Unit) 而非 MCU:每个客户端只向服务器上传一路流,由 SFU 转发给其他参与者——这与 WebRTC 标准栈(W3C/IETF)一致,Discord 在原生客户端上裁剪 SDP/ICE,在服务端用自研 SFU 做桥接(2018 WebRTC 博文)。
二、Gateway 与 Elixir 实时层
2.1 Gateway WebSocket 的角色
用户在线时,客户端与 Discord Gateway 维持一条 WebSocket(Gateway 连接)。Guild 列表、频道、消息、presence、typing 等事件经此连接下发。2018 年语音架构博文明确:Gateway、Guilds、Voice 信令服务均用 Elixir 编写,以复用代码与 OTP 监督树。
连接建立后,客户端通过 Gateway 更新 voice state;加入语音频道时,由 Discord Guilds 为 guild 分配 Discord Voice 服务器(详见第五节流程图)。
2.2 Sessions:一切推送的中枢
2026 年 voice outage 复盘揭示:Sessions 服务为每个设备维护一个 session 进程——同一用户在 Web 与 Desktop 各有一个 session;所有经 WebSocket 推送到客户端的聊天、presence 等,都经过对应 session(Discord Engineering, 2026/04)。
Sessions 与 Gateway 的关系:
1. 客户端连接 Gateway
2. Gateway 向 Sessions 服务创建 session
3. Gateway 持有 socket,向客户端 dispatch 事件
4. Session 退出时,监控该 session 的 Guild 等进程收到 {:DOWN, ...}
5. Gateway 指示客户端重连;优先同 zone resume,否则新建 session
这一设计在单 session 断开时表现稳健;但当 17% 的
session
同时非优雅退出(2026/03/25),:DOWN
消息风暴成为级联故障起点。
2.3 为何 Elixir 适合 Gateway
| 维度 | Elixir/BEAM | 典型 thread-per-request |
|---|---|---|
| 并发单元 | 轻量进程(KB 级) | OS 线程(MB 栈) |
| 状态隔离 | 进程邮箱串行处理 | 需锁或无共享可变状态 |
| 故障域 | Supervisor 重启子树 | 常波及整进程 |
| 运维 | :sys.get_state、Recon、热补丁 |
依赖外部 APM |
Discord 在 Rust 博客中亦提到:团队曾早期采用
Elixir、React、Scylla
等「有前景但当时尚未完全稳」的技术栈,并以相对精简的工程规模支撑大规模实时产品。Elixir
的价值在于连接扇出 +
有状态进程;代价是必须严格管控单 GenServer
邮箱深度——Holster.Pool 的
DynamicSupervisor 在 2026 事故中成为反例。
2.4 与线程模型的关系
Gateway 的 BEAM 调度器将大量 I/O bound 连接映射到少量 OS
线程,语义上接近 Reactor
+ 协程 路线,但错误隔离粒度是进程而非
goroutine/task。Rust Read States 则走 Tokio 异步
I/O(Jesse Howarth, 2020),在语言层用
async/await 表达并发,用所有权替代 GC。
2.5 Gateway 事件扇出(概念模型)
Discord 未公开 Gateway 源码,但 2018 语音博文与 2026 Sessions 复盘可拼出事件路径:
用户 A 在 guild G 的 text channel 发消息
→ API 持久化消息(经 Data Services → ScyllaDB)
→ Guild G 的进程收到新消息事件
→ 查找 G 内在线成员各自的 session 进程
→ 各 session 经 Gateway 连接 push MESSAGE_CREATE 给客户端
这与 Slack 的 Redis Pub/Sub 扇出 不同:Discord 更依赖 进程监控图(谁 monitor 谁)而非独立消息总线。优势是状态局部性;劣势是 fan-out 与 monitor 图 在 mass disconnect 时成为放大器——2026 年 17% session 同时退出即为例证。
2.6 Kubernetes 迁移与有状态 Elixir
Realtime Infrastructure 团队将 Elixir 服务迁到 K8s「blessed path」(Discord Engineering, 2026/04)。有状态服务与普通 stateless Deployment 的关键差异:
| 操作 | Stateless 服务 | Discord Sessions/Guilds |
|---|---|---|
| 缩容 | 直接减少 replica | 必须等 entity count = 0(进程 handoff 完成) |
| 滚动升级 | 替换 pod | 监控每 pod 上 GenServer 是否已迁移 |
| 故障域 | 单 pod 无状态 | 单 pod 承载数千 session/guild 进程 |
2026/03/25 的 bug 正在于:缩容触发 pod 终止,但 handoff 安全等待 与 K8s terminationGracePeriod 竞态,导致 17% session 被硬杀。事后 validating admission webhook 拒绝未完成 drain 的缩容——这是控制平面对 Actor 运行时语义 的补偿。
三、Read States:Go 的 GC 困境与 Rust 迁移
3.1 服务职责与规模
Read States 唯一职责:记录用户在每个频道「读到哪里」、未读/@mention 计数等。访问发生在:
- 每次连接 Discord
- 每次发送消息
- 每次标记已读
Discord 称该服务处于 hot path,必须「super snappy」。
数据规模(Jesse Howarth, 2020):
- 全局 数十亿 Read State(每用户每频道一条)
- 每台服务器 LRU:数百万 User、数千万 Read State
- 每秒数十万次缓存更新
- 持久化:Cassandra 集群;驱逐时写库 + 更新后 30 秒定时提交
- 每秒数万次数据库写入
3.2 Go 实现:每约 2 分钟的延迟尖刺
Go 版「多数时间很快」,但每隔几分钟出现巨大 latency spike,伤害体验。团队归因于 Go 内存模型与 GC。
关键机制:
- 驱逐不立即释放内存:对象无引用后仍驻留,等 GC 判定可回收。
- Go 至少每 2 分钟强制 GC 一次(即使堆增长不快)——源码行为,与分配速率无关。
- Read States 代码已「非常高效、极少分配」,尖刺并非垃圾量大,而是 GC 需 扫描整个 LRU 以确定指针可达性。
- 缩小 LRU → GC 扫描变快、尖刺变小 → p99 上升(缓存命中率下降 → 更多 Cassandra 读)。
团队曾动态调节 GOGC、拆分多分区
LRU,找到「还算可以」的配置后运行相当长时间,但仍不满意。
脚注(同文 [1]):图表来自 Go 1.9.2;曾试 1.8、1.9、1.10 无改善;Go→Rust 初版移植完成于 2019 年 5 月;博文发布于 2020 年 2 月。
Go Read States 延迟尖刺因果链(据 Discord 博客)
LRU 驱逐 → 对象变为不可达但未释放
→ 每 ~2min 强制 GC
→ GC 扫描数千万条目 LRU
→ STW/标记工作导致 CPU 与延迟尖刺
→ 缩小 LRU 缓解尖刺但损害 p99
3.3 Rust:驱逐即释放与 800 万缓存
Rust 无 GC;Read State 从 LRU 驱逐时 立即释放(所有权结束),无运行时扫描整个缓存。
2019 年移植时使用 nightly Rust async(稳定版 async 故事尚不完善);Discord 集体决定跑 nightly 直至 stable 支持 async——后证明值得(Jesse Howarth, 2020)。
负载测试结果:
- Rust 初版仅基础优化即 无 Go 式尖刺
- profiling 后在 延迟、CPU、内存 全面优于高度手工调优的 Go 版
- 优化手段包括:LRU 内 BTreeMap 替 HashMap 降内存、换用更现代的 metrics 库、减少内存拷贝
Raising the cache capacity:无 GC 后,团队提高 LRU 上限至 800 万 Read States;图表显示平均耗时降至 微秒级,max @mention 在 毫秒级(同文 Launch 图表)。
3.4 机制对比表
| 维度 | Go 1.9.2 Read States | Rust Read States |
|---|---|---|
| 内存回收 | tracing GC,驱逐延迟释放 | 所有权,驱逐即时 free |
| 尖刺 | ~每 2 分钟 | 负载测试中无同类尖刺 |
| LRU 规模 | 大缓存加剧 GC 扫描 | 提至 800 万条 |
| 并发模型 | goroutine + 手动跨 goroutine 保护 | 编译期线程安全 |
| 持久层 | Cassandra | Cassandra(同) |
3.5 不应过度泛化的结论
Discord 明确写:不应因 Rust 流行而重写一切;Read States 是小而自洽的服务,适合证明 Rust 作为服务语言。Go 在大量 IO 型 API 中仍合适;此处失败模式是 巨型长寿命堆 + 低频强制 full heap scan 的交集,而非 Go 「慢」。
3.6 Read State 数据结构与持久化策略
每条 Read State 含多个需 原子更新 的计数器(如 @mention 数),并常被重置为 0。热路径更新发生在 LRU 内;驱逐与定时器触发 Cassandra 写入(Jesse Howarth, 2020)。
// 概念结构(非 Discord 源码,仅说明博客描述的行为)
struct ReadState {
user_id: Snowflake,
channel_id: Snowflake,
mention_count: u32,
// ... 其他计数器
}
// LRU 驱逐路径(Rust):Drop 立即回收
// Go 路径:驱逐 → 等待 GC 扫描 LRU 图持久化双路径:
- 驱逐时 commit — 保证不在缓存中的状态已落盘
- 更新后 30 秒 scheduled commit — amortize 写入,降低丢更新风险
这一模式与 事件驱动 中「写后异步刷盘」类似,但 Read States 在 产品语义上接近强一致(用户期望未读数即时正确),故 30 秒窗口是工程折中而非最终一致故意设计。
3.7 Rust 侧优化与 Tokio 生态
Discord 列出的 Rust 优化(Jesse Howarth, 2020):
- BTreeMap — 在 LRU 中换 HashMap,降低内存占用(有序遍历对 GC 无意义,但对 Rust 内存布局有意义)
- Metrics 库 — 换用更现代的并发 metrics 实现
- 减少 memcpy — 热路径拷贝 elimination
移植后 tokio 0.2 升级 带来「免费」CPU 下降(同文 Evolving ecosystem 图表,约 16 日前后 CPU 曲线下移)。这说明 运行时升级 与 语言迁移 收益需分开评估。
3.8 Go GC 与 Java/Cassandra GC 的类比
Read States 的 Go 问题与 Cassandra messages 集群的 JVM GC(Bo Ingram, 2023)形态不同但同属 managed heap + 大型常驻结构 类:
| 系统 | 堆上 giant structure | 周期/workload | 症状 |
|---|---|---|---|
| Go Read States | LRU 数千万 entry | 2min 最小 GC + 全堆扫描 | API 热路径尖刺 |
| JVM Cassandra | row cache + memtable | compaction + GC 调参 | 全集群尾延迟、gossip dance |
Discord 在存储层用 Scylla(C++,无 JVM GC) 解决后者;在 Read States 用 Rust 解决前者——不是统一换语言,而是消除对应层的 GC。
四、消息存储:MongoDB → Cassandra → ScyllaDB
4.1 2017:120 万消息/日与 120 节点 Cassandra
2017 年 1 月博文(Stanislav Vishnevskiy)要点:
- 2015 年初 MongoDB 单副本集快速迭代;channel_id + created_at 复合索引
- 2015 年 11 月约 1 亿 条消息时 RAM 放不下数据与索引
- 选型 Cassandra:线性扩展、自动 failover、开源、非 blob 行存储
- 主键演进:
((channel_id, bucket), message_id),Snowflake 作聚簇键;bucket 约 10 天消息以防分区 >100MB - 2017 年运行 12 个 Cassandra 节点,存储数十亿消息
读写特征:读写约 50/50;读「极度随机」——小服一年不足 1000 条消息却也要随机 seek;大服则集中在最近一小时。
4.2 2022 初:177 节点与运维噩梦
2023 年 follow-up(Bo Ingram)描述
cassandra-messages 在 2022 年初:177
节点、数万亿消息:
- on-call 频繁;延迟不可预测
- Hot partition:频道+bucket 内并发读压垮节点;quorum 读放大到全集群尾延迟
- Compaction 落后 → 「gossip dance」:节点轮流出集群做 compaction,再回来吃 hinted handoff
- JVM GC 暂停导致尖刺;极端时需人工重启节点
2017 博文已预见 ScyllaDB(C++、shard-per-core、无 JVM GC);2020 年前 Discord 已将除 messages 外全部 Cassandra 迁到 ScyllaDB——messages 集群最大,迁移风险最高。
4.3 Rust Data Services:请求合并(Request Coalescing)
在换库之前,团队先在 API 与数据库间插入 Rust data services(Bo Ingram, 2023):
- 约 每个 DB 查询一个 gRPC 端点,无业务逻辑
- 请求合并:多用户同时读同一行 → 只查库一次,后续订阅同一 worker task
- 一致性哈希路由:按 channel_id 路由到同一 data service 实例,提高合并率
Tokio + Cassandra/Scylla driver;Discord 称「一旦编译通过通常就能跑」。这对 @everyone 大公告等流量尖刺尤为关键——否则 hot partition 直接打穿数据库。
4.4 万亿消息迁移与 ScyllaDB 收益
迁移要点(同文):
- 新 Scylla 集群:Local SSD + RAID 镜像持久盘(super-disk 拓扑)
- Spark migrator 估计 3 个月 → 用 Rust 扩展 data service 库做 migrator → 9 天;峰值 320 万条/秒
- 2022 年 5 月切主;双读校验后庆祝
数月后指标(仅引用该文数字):
| 指标 | Cassandra p99 | ScyllaDB p99 |
|---|---|---|
| 拉取历史消息 | 40–125 ms | 15 ms |
| 消息插入 | 5–70 ms | 稳定 5 ms |
节点:177 台 Cassandra → 72 台 ScyllaDB;每节点磁盘 4 TB → 9 TB。2022 世界杯决赛期间消息发送尖刺与进球事件在监控曲线上可见——系统未崩溃。
4.5 消息读写路径(Mermaid)
sequenceDiagram
participant Client
participant API as API Monolith
participant DS as Rust Data Service
participant SC as ScyllaDB
Client->>API: GET channel messages
API->>DS: gRPC (channel_id routing key)
Note over DS: 同 channel 并发读合并为单次查询
DS->>SC: quorum read (partition: channel+bucket)
SC-->>DS: rows
DS-->>API: coalesced result
API-->>Client: messages
Client->>API: POST message
API->>DS: insert
DS->>SC: write (append to memtable)
Note over SC: 大服 hot partition 仍可能发生<br/>合并层减轻但未消除
4.6 Hot partition:未完全解决的问题
Scylla 消除了 JVM GC 类问题,但 hot partition 仍可存在。Data services bought time;根本张力在于:按 channel 分区与超大型 guild 流量的几何不匹配。这与 Cassandra 论文假设(相对均匀分区)到生产 skew 的「工程间隙」一致——Discord 用合并与迁移工程化应对,而非重新发明存储引擎。
4.7 2017 Cassandra 建模与最终一致陷阱
Stanislav Vishnevskiy (2017) 还记录了 Cassandra AP 语义下的生产坑:
- Cassandra 读-before-write 是反模式;列级 last-write-wins
- Dark launch 双写 Mongo + Cassandra 时出现
author_idnull — 竞态下部分列被覆盖 - 教训:消息编辑/删除与插入并发时,需在应用层定义 冲突语义
Snowflake ID 保证 (channel_id, message_id)
有序,使「加载频道最近 N 条」成为 range scan on
clustering key,而非全分区扫描。Bucket 将单分区大小
bound 在 ~100MB 内,避免 2017 日志中 >100MB
partition 警告与 compaction 压力。
-- 简化 CQL(2017 博文)
CREATE TABLE messages (
channel_id bigint,
bucket bigint,
message_id bigint,
author_id bigint,
content text,
PRIMARY KEY ((channel_id, bucket), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);4.8 迁移工程:Spark vs Rust migrator
Bo Ingram (2023) 对比两条迁移路径:
| 方案 | 估计耗时 | 特点 |
|---|---|---|
| Scylla Spark migrator | ~3 个月 | 需大量 tuning |
| Rust 扩展 data service | ~9 天 | token range + SQLite checkpoint + firehose |
迁移卡住 99.9999%:Cassandra 末段 token range 含 巨型 tombstone 未 compaction → 读超时 → compact 后秒级完成。这说明 历史技术债(Cassandra 运维已吃紧)会拖慢最后一英里。
4.9 super-disk 与认证集群(交叉引用)
Mark Smith (2023) 描述的 hybrid RAID super-disk(Local SSD + 持久盘镜像)与 messages/Scylla 拓扑同族:
- 实例启动需 ~40 分钟(随持久盘增大)RAID sync 才能启动 Scylla
- 区域升级时 detach 盘 → 45 分钟 无法恢复该 zone 算力
认证 outage 与 messages 迁移共享存储哲学,但 集群规模与 cache 行为 不同——认证集群更小、row cache 命中率对重启更敏感。架构上不能把「messages 集群能扛 zone 下线」的经验直接套到 全局 gate 的 auth 集群。
五、语音架构:Guilds、SFU 与 WebRTC
5.1 设计原则(2018)
Jozsef Vass (2018) 总结:
- 语音/视频均为 多方;大频道可达 1000 人轮流发言 → 必须 client-server,P2P 不可扩展
- 流量经 Discord 服务器 → 隐藏用户 IP、支持 moderation(server mute 丢包)
- 客户端 主动 发起语音连接;基础设施故障时可重连新后端
5.2 客户端:统一 WebRTC 栈
- 浏览器用浏览器 WebRTC;桌面/iOS/Android 用 同一 C++ media engine(基于 WebRTC native)
- 原生端裁剪 SDP/ICE(客户端直连 media relay,无需 ICE 协商)
- 用 Salsa20 替 DTLS/SRTP 路径以降低开销;静默期不发音频包
5.3 后端三件套
| 服务 | 语言 | 职责 |
|---|---|---|
| Gateway | Elixir | WebSocket;voice state 更新 |
| Guilds | Elixir | 为 guild 选择 Voice 服务器;维护 voice state |
| Voice | Elixir 信令 + C++ SFU | 信令 + 媒体转发 |
Voice 服务器分配(2018):
- Voice 服务器周期上报健康与负载 → etcd 服务发现
- Guilds 选择区域内 负载最低 的 Voice 服务器
- 同一 guild 所有语音频道共用一个 Voice 服务器
- 客户端显示 “Awaiting Endpoint” = Guilds 正在选型;“Voice Connected” = UDP 与 SFU 互通
Voice 服务器含 信令组件 + SFU(Selective Forwarding Unit):SFU 转发音视频,桥接浏览器与原生传输/加密差异,处理 RTCP 带宽反馈。
2018 规模数据:850+ 语音服务器、13 区域、30+ 数据中心;260 万+ 并发语音用户;出口 220 Gbps、120 Mpps。
5.4 语音加入流程(Mermaid)
sequenceDiagram
participant Client
participant GW as Gateway
participant Guilds
participant etcd
participant Voice as Voice Server
participant SFU
Client->>GW: 更新 voice state(加入频道)
GW->>Guilds: 转发 voice state
alt guild 尚无 Voice 服务器
Guilds->>etcd: 查询区域负载
etcd-->>Guilds: 候选 Voice 节点
Guilds->>Guilds: 选最低负载
end
Guilds->>Voice: 推送全部 voice state
Guilds-->>Client: Voice endpoint(地址/端口/密钥)
Client->>Voice: Voice WebSocket(信令)
Client->>SFU: UDP 媒体(经 Voice 协调)
Note over SFU: 单通话单 SFU 实例<br/>全员连接同一宿主
5.5 故障转移
Voice 服务器宕机:从 etcd 移除 → 客户端经 Gateway 请求 ping → Guilds 分配新 Voice 服务器 → 推送 voice state → 客户端重连。DDoS 与 更换 voice region 走类似流程。SFU 崩溃:信令监控并重启 SFU,状态由信令重建,客户端无感(少量丢包)。
5.6 Voice Syncers 与 SFU 控制面(2026 视角)
2018 博文描述 Guilds 直接协调 Voice 服务器;2026 outage 博文细化 voice syncers 层:
Call owning service (discord_guilds / discord_calls)
→ voice state 更新 → voice syncers
→ HTTPS RPC → 全球 25,000+ SFU 实例(Cloudflare + 传统主机)
→ SFU 转发 E2E 加密媒体
Syncer 职责:
- 评估 active voice states,决定 call 放置与 SFU endpoint
- Session 退出 → disconnecting voice state → SFU 断开 RPC
- 最后一个 session 离开 → blocking stop RPC 后清理 syncer 状态
这一层是 Elixir 进程 与 Rust/C++ SFU 的边界:信令/路由在 BEAM;媒体在 SFU。2026 事故证明 syncer → SFU 的 HTTP 连接池 必须与 etcd 服务发现 解耦,否则 BEAM 邮箱背压会「假装」整个 A/V 控制面宕机(Awaiting Endpoint)。
5.7 WebRTC 简化交换(字节量对比)
Jozsef Vass (2018) 给出数量级:
| 路径 | 协商数据量 | 协议 |
|---|---|---|
| 标准 WebRTC P2P | ~10 KB RT | SDP |
| Discord 原生 ↔︎ SFU | ~1000 B | 自定义(地址/端口/密钥/codec/stream id) |
| Discord 浏览器 | ~1200 B RT | 客户端合成 SDP |
省 ICE 是因为 客户端只连 Discord relay;省 SDP 体积降低 join latency。代价是 供应商锁定在 Discord SFU 语义,与标准 WebRTC 互操作需 SFU 内 browser/native 桥接。
5.8 SFU vs MCU(理论注记)
MCU(Multipoint Control Unit) 混流后再下发,客户端只收一路——CPU 随参与者平方增长。SFU 只转发,客户端收 N-1 路,上行仍为一路。Discord 选择 SFU 支撑 千人级频道轮流发言 与 Go Live 等场景;MCU 更适合终端能力弱、带宽不对称的会议系统。WebRTC 生态默认 SFU;学术上 multiparty 拓扑仍是开放设计空间(见 IETF RFC 8825 与 WebRTC 架构文档)。
六、语音上边缘:Cloudflare 与 recv starvation
2026 年 David Chen 博文描述 Discord 将 80%+ 语音/视频流量迁到 Cloudflare 300+ PoP(对比传统 hyperscaler 约 30–40 区域)。70%+ 已迁移区域 YoY 质量改善;法兰克福 ping 降 34%、丢包 降 42%。
6.1 冰岛与鹿特丹:地理不等于最优宿主
- 冰岛 PoP:本国通话 ping -9%;但混合区域通话 ping 升 2.7x——因 Discord 整通话选单一 SFU,冰岛用户发起则德国参与者流量绕冰岛
- 鹿特丹→阿姆斯特丹:Orange ISP 经 Telia transit 饱和 → 峰值延迟 >1s → 回滚;之后 rollout 前做 peering 分析
教训:最近 PoP ≠ 最佳 call host;混合区域需 smarter call placement(仍在 roadmap)。
6.2 主机发现:etcd → Valkey
Cloudflare 上主机 自发注册(非预先 provision):Voice host 启动 → 注册 Valkey(10 分钟 TTL)。约 25,000 Cloudflare voice hosts 使旧 etcd 发现延迟上升;迁移期 双写 后退役 etcd(同文)。
6.3 容器密度与 NIC 队列
Cloudflare 容器最初 4 workers/host → US East 丢包 1.5–2%(基线 <0.5%)→ 根因 单 NIC 队列上四 worker 争用 → 暂降为 2 workers → UDP buffer 修复后 2025 年 9 月 默认 8 workers。
6.4 recv starvation 与 CPU affinity(欧洲 ping 尖刺)
2025 末 Bucharest/Stockholm 迁到 Cloudflare 上 Rust SFU 后,峰值 client ping >500ms。
Cloudflare eBPF 探针定位:问题在 VM 内应用。根因 1 — 写饥饿:
- 每 worker:单线程 Tokio +
select!中 recv 与 flush timer - recv buffer 非空时 recv future 始终
Ready → 循环 poll recv → flush 饿死 →
media_batch.latency_microseconds从 ~1ms 升至 数百 ms - Tokio 128-poll yield 在高峰 微秒级耗尽仍不够
- 修复:9ms 发送预算——距上次
send()_flush ≥9ms 则本轮 disable recv,强制 flush
根因 2 — softirq noisy neighbor:单 virtio-net 队列 → Receive softirq 与 worker 同 vCPU → taskset 将 worker pin 离 IRQ vCPU,配合 RPS。
修复后欧洲流量回迁 Cloudflare。这与 线程模型 中「Reactor 饥饿」同类:事件循环中永远 ready 的读端点 starve 写端——实时媒体比 HTTP 更苛刻。
6.5 25 秒「假 outage」
LA PoP 实例 25s 无响应无日志:Cloudflare 侧 page cache 写满后 数 GB 一次性 flush → block device 上其他 writer stall。属平台层问题,Discord telemetry 协助定位。
6.6 Cloudflare 主机生命周期与 Discord Deployer
边缘与 hyperscaler 假设不同(David Chen, 2026):
| 假设 | 传统云 | Cloudflare |
|---|---|---|
| 主机身份 | 稳定 hostname | 调度器随时 reclaim |
| 部署 | rolling restart | recreate 容器 |
| 维护 | 自选窗口 | 至少 每月 reboot |
Discord 构建 deployer:poll 直到旧镜像 host 被 recreate;PoP 级协调 recreate 时 supervisor 延迟 5 分钟 exit,避免实例数瞬归零。这与 Cloudflare 案例 中「边缘机器可抢占、可重建」哲学一致——但有状态 UDP 通话要求 更复杂的 drain 语义。
6.7 recv 饥饿与 Tokio select!(机制细读)
Rust SFU worker 结构(同文):
// 概念化:Discord 边缘 SFU 事件循环困境
loop {
tokio::select! {
Some(pkt) = socket.recv() => { /* 处理入站 */ }
_ = flush_timer.tick() => { socket.send(flush_batch)?; }
}
}当 socket recv buffer 非空,recv() future
立即 Ready → select 重复 poll recv →
flush_timer 永不就绪。Discord
修复:距上次 flush ≥9ms 则本轮从 select 移除
recv——本质是 人工 write bias,补偿
Tokio cooperative scheduling 在 always-ready
读 场景下的不足。Cloudflare eBPF 探针测得
futex stall 最高 860ms、单线程 100%
CPU 与 socket recv buffer
增长同步——与上述机制一致。
七、事故复盘:2026/03/25 Voice Outage
Discord 在 authentication outage(2023/11/06) 与 voice outage(2026/03/25) 中暴露了不同架构层脆弱性。前者是 Scylla 认证集群容量 + 区域升级 + degraded NVMe + retry storm(Mark Smith, 2023);后者是 Elixir 进程邮箱 + HTTP 连接池 supervisor + etcd 服务发现耦合。本文以 voice outage 为主——因其直接关联 Gateway/Guilds/Voice Syncers 架构,且用户指定关注 supervisor 与 etcd。
7.1 时间线与影响
- 2026/03/25 12:13–15:30 PDT:语音/视频严重降级;用户多见 「Awaiting Endpoint」
- 根因链起点:Sessions Kubernetes 垂直扩缩 → 单 zone 50% pod 终止 → 安全等待导致 grace period 内未 handoff → 三 zone 均衡下 17% session 非优雅退出
7.2 级联:Gateway → Voice Syncers
- 17% session 同时
:DOWN→ 各监控实体每 session 一条消息 - Gateway(us-east1-b)重连风暴;session 速率限制未随 K8s 迁移重调 → 内存 spike → 该 zone Gateway 重启 → 更多 failover 到 c/d
- Voice Syncers:session 退出 → disconnecting voice state → 大量 SFU RPC;重连 → 重建 call → HTTPS 出站连接洪水
7.3 Holster.Pool 与 supervisor 邮箱
Voice Syncers 经 Holster 池化 gun Erlang HTTP 客户端。新建连接路径经过两个瓶颈 Supervisor GenServer:
Holster.Pool的DynamicSupervisor- gun 连接 supervisor
spawn 子进程时用 selective receive 等子进程 ACK。Postmortem 测试:~100k 邮箱深度时 selective receive 增加 ~1ms/spawn;邮箱 1M、100 spawn/s 时无法追上。
后果:
- 新 gun 连接超时失败
- 无法从 pool checkout → 现有连接也阻塞
- etcd 刷新注册 同样走 Holster → 无法续约 → 60s TTL 后掉出 hash ring
- 15 台 voice syncers 邮箱长度集体上升;SFU RPC 暴跌
Recon 快照:Holster.Pool DynamicSupervisor 邮箱 #1。
7.4 恢复与改进
- 多次全集群重启因 thundering herd 失败;13:43 降低 guilds/calls/streams syncer spawn 速率限制后部分恢复
- 14:15 扩容 15 台实例(集群翻倍);14:26 全面健康
事后(Discord Engineering, 2026/04):
- K8s admission webhook:缩容前必须 drain 完成
- PartitionSupervisor 替 Holster.Pool supervisor,并行化连接创建
- gun 生命周期下沉到 Holster.Pool,移除 gun connection supervisor
- 上游 rate limit 与 endpoint selection 限流;邮箱监控增强
- A/V 控制面 多区域水平扩展(长期)
7.5 架构教训
| 教训 | 说明 |
|---|---|
| 隐藏共享瓶颈 | 通用 HTTP 池 supervisor 与 etcd 共用 → 故障域扩大 |
| Actor 串行化代价 | 单 GenServer 处理 spawn ACK + 全池流量 → 邮箱 O(n) selective receive |
| 容量经济学 | 大流量尖刺要么 增供给(扩容 syncers)要么 减需求(rate limit) |
| 与存储事故对比 | 2023 auth outage:RAID rebuild ~45 min、双 zone 脆弱窗口;2026 voice:进程级而非 Scylla |
Authentication outage 补充一点(Mark Smith,
2023):scylla-authentication
underprovisioned;一 zone 离线 + 另一 zone
degraded NVMe(RAID rebuild 慢)→ quorum
饱和 → HTTP ~25% 可用;关 typing/read
事件后约 50% 恢复。Discord
双倍扩容认证集群并改进磁盘退化检测——说明
小集群 critical path 与 mega messages
集群运维逻辑不同。
7.6 时间线详表(PDT)
| 时刻 | 事件 |
|---|---|
| 12:13 | Sessions K8s 缩容;17% session 非优雅终止 |
| 12:13+ | Gateway us-east1-b 重连风暴、OOM 重启 |
| 12:13+ | Voice syncers 邮箱增长;SFU RPC 下降 |
| 12:43 | 重启 voice syncers 实例 2-13 |
| 12:47 | 重启 2-1 上 Holster.Pool DynamicSupervisor |
| 13:05 | 全集群 voice syncers 重启(失败) |
| 13:09 | 再次全员 mailbox 积压 |
| 13:43 | 上游 rate limit 生效后再次全集群重启 |
| 14:03 | 实例 2-3 重启成功(低负载 + 限流) |
| 14:15 | 5 台中 4 台恢复;15 台新实例上线 |
| 14:26 | 最后一台恢复;集群双倍容量健康 |
| 15:30 | 用户侧影响基本结束(博文窗口) |
7.7 与 2023 Authentication Outage 对照
| 维度 | 2023/11/06 Auth | 2026/03/25 Voice |
|---|---|---|
| 触发 | Scylla 区域 OS 升级 | Sessions K8s 缩容 |
| 瓶颈层 | Scylla + Rust Auth 读路径 | Elixir HTTP pool supervisor |
| 可用性 | HTTP ~25% → ~50%( shed load 后) | 语音 Awaiting Endpoint |
| 存储 | RAID rebuild ~45 min | Scylla「表现良好」(博文原话) |
| 架构回应 | 2× 集群、磁盘检测、control plane | PartitionSupervisor、drain webhook |
两起事故共同主题:计划内维护窗口 + hidden degradation + retry/重连风暴 → 超过设计冗余。Discord 2026 博文称已建 API circuit breaking 减载能力;本次瓶颈在 voice 而非 DB——说明 multi-hop 架构 中瓶颈会迁移。
八、搜索索引:Elasticsearch Cell 架构
Vicki Niu (2025) 描述从 billions(2017 Elasticsearch 两篇集群)到 trillions 索引的演进。
8.1 旧系统痛点
- Redis 索引队列:积压时 CPU 打满 丢消息
- Bulk 50 条跨 ~50 节点 → 单节点失败则 整批失败;100 节点集群单节点宕机约 40% bulk 失败
- 集群 200+ 节点 → master OOM、无法滚动升级;Log4Shell 补丁需 全站离线
- 大 guild 索引触 Lucene MAX_DOC ~20 亿 上限
8.2 新架构要点
- ECK 上 Kubernetes 部署;cell = 多小型 ES 集群分组
- 索引目标:2 亿消息 / 50GB 以内;40 个 ES 集群、数千索引
- 队列:PubSub 保证投递;MessageRouter(Rust/Tokio)按 Destination(cluster+index) 动态 spawn task,同目标批量索引
- user-dm-messages cell:DM 按 user_id 分片,支持跨 DM 搜索(双写 recipient 索引)
- BFG(Big Freaking Guilds):专用 cell、多 primary shard、在线 dual-index 与历史 reindex 切换
8.3 公开性能数字(同文)
- 索引 数万亿 消息;索引吞吐 2× 旧系统
- 查询 median 500ms → <100ms;p99 1s → <500ms
- 集群升级与滚动重启 无服务影响
搜索路径与消息主库 解耦:Scylla 负责权威存储与频道内顺序读;ES 负责全文与跨 DM——符合 事件驱动 中「读模型异步构建」模式,但 Discord 强调 lazy indexing(非全员用搜索则不索引)。
8.4 旧 bulk 失败概率(博文推导)
Vicki Niu (2025) 给出估算:100 节点 ES 集群,bulk 50 条随机落盘,单节点失败时 至少一条命中该节点 的概率约 40%。这解释了 Redis 队列积压 + 节点故障 → 连锁 re-enqueue 的脆弱性。
8.5 Cell 拓扑与 zone 容错
每个小型 ES 集群(cell 内):
master_eligible: 3 # 每 zone 1
ingest_nodes: ">=3" # 每 zone 至少 1,可弹性伸缩
data_nodes:
shard_allocation: zone-aware + forced awareness
primary/replica: 跨 zoneingest 与 data 分 pool 调度到不同 machine type——避免 indexing spike 与 query 争用 heap。与 messages Scylla 的 72 节点 规模对比,搜索选择 更多更小 集群,因 ES cluster state 不随节点数线性扩展(master OOM 是 2025 年前旧系统痛点)。
8.6 BFG reindex 状态机
BFG 流程(同文)可表化为:
stateDiagram-v2
[*] --> IndexA: 正常服务 index-a
IndexA --> DualWrite: 创建 new-bfg-index
DualWrite --> DualWrite: 历史 job 回填
DualWrite --> QuerySwitch: 回填完成
QuerySwitch --> Cleanup: 验证 new-bfg-index
Cleanup --> [*]: 停止写 index-a,删除旧数据
Dual-write 期间 查询仍走
index-a,避免用户感知 reindex;切换后 cleanup
删旧索引——与 messages Scylla 迁移的
双读校验 类似,都是
零停机换引擎 范式。
8.7 搜索查询路径(简图)
flowchart LR
Client --> API
API --> Router{索引路由}
Router -->|guild 消息| GM[guild-messages cell]
Router -->|跨 DM| UDM[user-dm-messages cell]
Router -->|BFG| BFGcell[BFG dedicated cell]
GM --> ES1[(ES 小集群)]
UDM --> ES2[(ES 小集群)]
BFGcell --> ES3[(多 shard 索引)]
九、Elixir / Go / Rust 决策矩阵
Discord 官方叙述可归纳为「按问题选语言,而非按 fashion」:
| 选型维度 | Elixir | Go | Rust |
|---|---|---|---|
| 典型场景 | Gateway、Guilds、Sessions、Voice 信令 | 曾用于 Read States;一般 backend | Read States、Auth、Data Services、迁移器、SFU(新) |
| 并发模型 | 进程 + 邮箱 | goroutine + GC | async/Tokio + 所有权 |
| 尾延迟 | 依赖单进程邮箱深度 | 大堆 + 强制 GC 周期 | 无 GC;需防 async 饥饿(voice SFU 案例) |
| 运维/debug | Recon、热补丁、:sys |
pprof、成熟生态 | 编译期保证;nightly 历史包袱已缓解 |
| 风险 | Supervisor 单点、selective receive | 巨型 in-memory 结构 | 学习曲线、async 生态演进 |
| Discord 态度 | 实时有状态核心 | 合适处仍用;Read States 不适用 | 新组件优先考虑;非全面重写 |
Go → Rust 触发条件(Read States):
- 服务在 product hot path
- 数百万级 in-memory 对象 + LRU 驱逐
- GC 扫描成本与 缓存大小 强绑定
- 团队已有 Rust 基础设施(SDK、NIF、其他服务)
Elixir 保留条件:百万级 有状态 连接/会话;需要 监督树 与进程隔离;可接受严格 mailbox 与 rate limit 纪律。
Rust 扩展条件:数据库前 合并/限流;CPU/内存敏感且无 GC 容忍;C++ SFU 旁路新模块(边缘 Rust SFU)。
9.1 决策流程(工程判断)
Discord 未发布正式 RFC,但从多篇博客可反推 新组件 checklist:
1. 是否在 user-visible hot path?(是 → 优先考虑 Rust)
2. 是否百万级有状态连接/进程?(是 → Elixir/OTP)
3. 是否巨型 in-memory working set + 严格 p99?(警惕 Go/JVM GC)
4. 是否可用 data service 层合并/限流 shield DB?(Rust + Tokio)
5. 是否计划内维护会去掉整 zone?(验证 quorum 余量 + cache 命中率)
6. 共享连接池/supervisor 是否服务多条关键路径?(禁止 — 2026 教训)
9.2 Rust 在 Discord 的其他落点(博客提及)
Jesse Howarth (2020) 与 Bo Ingram (2023) 还提到 Rust 用于:
- Game SDK
- Go Live 视频编码管线
- Elixir NIF(CPU 密集段下沉 native)
- 多个 backend services(Read States 之外)
这表明 Rust 角色是 「C++ 的安全替代 + Go 的热路径替代」,而非替换 Elixir 实时层。
9.3 与 Slack 技术栈对照
| 层面 | Slack(见 case-slack) | Discord |
|---|---|---|
| 实时网关 | Go WebSocket | Elixir Gateway |
| 主存储 | Vitess/MySQL 分片 | Scylla/Cassandra 宽表 |
| 边缘缓存 | Flannel | 无直接等价;data services 合并 |
| 搜索 | 自研/Solr→ES | ES cell + Rust router |
| 语言迁移 | PHP→Hack | Go→Rust(点状) |
两家都证明:IM 核心矛盾是 fan-out + 历史消息随机读 + 搜索,但 Discord 更激进地采用 Scylla + Elixir + Rust 三角,Slack 则深耕 Vitess + Go 网关。
十、开放问题与未决张力
10.1 Call placement vs 边缘覆盖
Cloudflare 300+ PoP 与 Discord 单 SFU 宿主整通话 模型冲突。冰岛实验表明:地理覆盖与最优宿主需联合优化。开放问题:如何在 低信令开销 下实现 region-aware SFU 选择(含混合国籍通话)?工业界仍在 SFU 拓扑(单宿主 vs 级联 vs 混合 mesh)之间权衡,无 peer-reviewed「最优解」。
10.2 Hot partition 与超级 guild
Scylla + data services 后 World Cup 流量通过,但 partition key = channel 的根本 skew 仍在。开放问题:是否在 API 层引入 按 guild 等级的分层存储或缓存(类似 Slack Flannel 但针对消息历史)?Cassandra 社区长期争论 materialized view / 二级索引 与 应用层反规范化 的边界。
10.3 Actor 监督树 vs 连接池共享
2026 事故证明:跨-cutting 基础设施(HTTP 池、etcd) 不应与 高 churn spawn 路径 共用单 GenServer。开放问题:OTP 生态是否需 PartitionSupervisor 类模式 成为标准库一等公民,以避免每个团队重复踩 selective receive 坑?
10.4 小集群 critical path
Auth Scylla 集群(远小于 messages)在 zone 维护窗口 下仍可能全局不可用。开放问题:更高 replication factor vs 跨 region hot-standby 对 session 一致性的影响——Discord 2023 博文称尚未定论。
10.5 搜索与存储双写规模
BFG dual-index 与 DM 双 recipient 索引使 索引字节量 > 消息字节量。开放问题:在 数万亿 规模下,是否 eventual 搜索索引仍够用,还是需 增量 log + 独立 query engine(类似 OLAP 分离)——Discord 已用 cell 架构缓解,但 Lucene MAX_DOC 仍会逼出 online reindex 运维。
10.6 E2E 加密与 SFU 可见性
Discord 2026 产品博文宣布语音/视频 E2E 加密(DAVE 等) 全平台推进。SFU 传统上需解密或参与密钥协商才能做 moderation/server mute(2018 博文:mute 时 drop packets)。开放问题:E2E + SFU 路由 + 合规 moderation 三目标能否同时满足?工业界在 MLS、Insertable Streams 等标准上仍在演进;Discord 工程博客对 SFU 在加密模式下 mute 语义着墨较少,属 产品-架构边界 未完全公开领域。
10.7 研究入口
- Actor 调度与 mailbox 复杂度:Erlang/OTP 文档;Hewitt 1973;关注 selective receive 的 O(n) 邮箱扫描(2026 postmortem 实测)
- LSM 与 wide partition:O’Neil LSM-tree;ScyllaDB shard-per-core 文档;Dayan & Idreos compaction 权衡(SIGMOD 2017)
- WebRTC SFU 拓扑:IETF WebRTC 架构;无单一「最优 SFU 布局」peer-reviewed 结论
十一、小结
Discord 架构不是单一技术栈的胜利,而是 分层语言分工 的累积:
- Elixir 承载 WebSocket 与有状态实时逻辑,直到进程邮箱 discipline 被违反。
- Go 在 Read States 上输给 强制 GC + 巨型 LRU 的组合;Rust 用所有权换可预测延迟,并把 LRU 提到 800 万。
- Mongo → Cassandra → Scylla 追踪消息量从 数十亿到数万亿;Rust data services 用合并与路由 shield 数据库。
- SFU + WebRTC 支撑语音;Cloudflare 边缘 改善 RTT,但引入 recv starvation、NIC 队列、call placement 新问题。
- 2026 voice outage 揭示 Holster supervisor + etcd 耦合;PartitionSupervisor 与 K8s drain webhook 是架构级回应。
- 搜索 迁到 ECK + cell + PubSub + Rust MessageRouter,索引 数万亿 消息。
读到这里再打开 Cloudflare 边缘案例,可以把 Discord 语音迁移当作 「边缘同构栈」哲学的压力测试——同一套 Cloudflare PoP 对静态 CDN 成立,对有状态 UDP SFU 则必须重写发现、密度与 event loop 纪律。
参考资料
Discord Engineering Blog(A 级)
- Jesse Howarth, “Why Discord is Switching from Go to Rust,” Discord Blog, 2020-02-04. https://discord.com/blog/why-discord-is-switching-from-go-to-rust (Go 1.9.2 图表;2019-05 初版移植;800 万 Read States)
- Stanislav Vishnevskiy, “How Discord Stores Billions of Messages,” Discord Blog, 2017-01-13. https://discord.com/blog/how-discord-stores-billions-of-messages (MongoDB→Cassandra;12 节点)
- Bo Ingram, “How Discord Stores Trillions of Messages,” Discord Blog, 2023-03-06. https://discord.com/blog/how-discord-stores-trillions-of-messages (177 节点;gossip dance;Rust data services;72 Scylla 节点;p99 数字)
- Jozsef Vass, “How Discord Handles Two and Half Million Concurrent Voice Users using WebRTC,” Discord Blog, 2018-09-10. https://discord.com/blog/how-discord-handles-two-and-half-million-concurrent-voice-users-using-webrtc
- David Chen, “How We Moved Discord Voice to the Edge,” Discord Blog, 2026-06-09. https://discord.com/blog/how-we-moved-discord-voice-to-the-edge
- Discord Engineering, “You’ve Got (Too Much) Mail: Behind the Scenes of the 3/25/26 Voice Outage,” Discord Blog, 2026-04-29. https://discord.com/blog/behind-the-scenes-of-the-3-25-26-voice-outage
- Mark Smith, “25% or 6 to 4: The 11/6/23 Authentication Outage,” Discord Blog, 2023-11-15. https://discord.com/blog/authentication-outage
- Vicki Niu, “How Discord Indexes Trillions of Messages,” Discord Blog, 2025-04-24. https://discord.com/blog/how-discord-indexes-trillions-of-messages
规范与经典文献
- Hewitt, Bishop & Steiger, “A Universal Modular ACTOR Formalism for Artificial Intelligence,” IJCAI, 1973. (Actor 模型起源)
- O’Neil, Cheng, Gawlick & O’Neil, “The Log-Structured Merge-Tree (LSM-Tree),” Acta Informatica, 1996. (LSM / Cassandra 存储谱系)
- W3C WebRTC 1.0 Specification; IETF RFC 8825 (WebRTC Overview). (客户端媒体栈标准语境)
工具与运维
- Fred Hebert, Erlang in Anger(Recon 工具链;Discord 2026 postmortem 引用)
- Discord 相关:Tokio、ScyllaDB shard-per-core 架构文档(辅助理解 data services 选型)
上一篇:Shopify
架构
下一篇:Cloudflare
架构
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【金融科技工程】撮合引擎实现:撮合算法、价格优先时间优先、状态机、低延迟工程
深入剖析中央限价订单簿(CLOB)撮合引擎的数据结构与算法,覆盖价格时间优先、Pro-Rata、订单类型、自成交预防、集合竞价、确定性回放、WAL 快照、单线程事件循环与 Disruptor 模型,并给出 Rust/Go 简化实现与单元测试清单。
【系统架构设计】Slack 架构:实时协作的工程挑战
Slack 每天为超过一千万活跃用户提供实时消息服务,峰值时段同时维持数百万条 WebSocket(全双工通信协议)长连接。一条消息从发送到被同一频道所有成员看到,端到端延迟通常控制在 200 毫秒以内。这套系统并非一蹴而就:它从一个 PHP 单体应用起步,历经数次关键重构,逐步演变为以 Hack、Go、Java 为核…
【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT
推送系统的核心难度不在协议选型,而在连接管理、心跳检测、断线重连、消息可靠投递这些工程细节。本文从 WebSocket 帧格式、SSE 重连机制、MQTT QoS 三级语义讲起,拆解百万长连接的 epoll 单机架构,深入分析心跳探活、指数退避重连、离线消息队列的设计取舍,结合即时通讯和物联网两个工程案例,讨论推送系统从单机到集群的水平扩展路径。
【金融科技工程】行情分发:MBP/MBO、快照+增量、组播/TCP、FIX/ITCH
从 L1/L2/L3 分级、FIX FAST/ITCH/SBE 协议到 UDP 组播+重传通道、内核旁路与微波传输,系统梳理交易所行情系统的生产、分发与消费;给出 snapshot+incremental 恢复机制、Conflation 与 Full Tick 权衡、kdb+/ClickHouse/QuestDB tick 存储、事件时间回放与 K 线合成实现,并附 Binance WebSocket Diff Depth 维护本地订单簿的 Go/Python 示例。