神经系统:使用 Redis 和 RabbitMQ 设计分布式信号传递
Source: Dev.to
请提供您希望翻译的正文内容(除代码块和 URL 之外的文本),我将为您翻译成简体中文并保持原有的格式和 Markdown 语法。
The Split‑Brain Signaling Crisis
在每个成功的 real‑time 应用的生命周期中,总会有一天架构会崩溃——通常是当你部署第二个 signaling server 时。
第一天 – 单进程
使用单个 Python 进程(或单台服务器),WebRTC 信令非常简单。
你维护一个简单的内存字典,将 user_id → websocket_connection 进行映射。
当 User A 想要呼叫 User B 时,代码会在字典中查找 User B 并将 SDP offer 通过套接字推送下去。它快速、原子且简单。
第 100 天 – 横向扩展
您在三个信令节点前面添加了负载均衡器,以处理 50 000 条并发连接。系统突然进入 Split‑Brain(脑裂) 状态。
- 用户 A 连接到 节点 1。
- 用户 B 连接到 节点 3。
当用户 A 向用户 B 发送 offer 时,节点 1 检查本地内存,未发现用户 B 的连接,于是丢弃该消息——更糟糕的是,返回 “User Offline(用户离线)” 错误,而用户 B 正在另一台服务器上等待。用户被各自的进程孤岛隔离,无法协商媒体。
这就是 WebRTC 中的根本分布式状态问题。
与标准的 HTTP REST API(无状态、由共享数据库支持)不同,信令是 有状态且短暂 的。把每个 SDP 包都写入 Postgres 会毁掉呼叫建立的延迟。您需要一个神经系统——一个高速、分布式的消息总线,用来桥接孤立的进程。
Source: …
两种范式:速度 vs. 内存
在设计这一层时,工程师通常倾向于两种主流技术:
| 范式 | 技术 | 哲学 |
|---|---|---|
| 短暂 | Redis Pub/Sub | “如果你现在没有在监听,就不需要知道。” |
| 持久 | RabbitMQ | “我会保留这条消息,直到你确认已处理它。” |
在生产环境的 WebRTC 系统中,你常常需要 两者兼顾,并将其应用于不同类别的流量。
Redis Pub/Sub – 速度层
Redis 因其唯一指标——延迟——成为 WebRTC 信令的行业标准。
- 发布/订阅模型 – 发布者向频道发送消息;Redis 立即将其转发给所有活跃的订阅者。
- 无存储、无排队、无回溯。
内部实现与性能
PUBLISH 会遍历该频道的订阅者链表,并将数据写入它们的输出缓冲区。这使得单个 Redis 实例能够以 毫秒以下的延迟 处理 每秒数百万条消息。
对于 WebRTC 来说,这种速度在 ICE 候选交换 期间至关重要。典型客户端可能在一次突发中生成 10‑20 条候选;它们必须 客户端 A → 服务器 → 客户端 B 立即传递。若每条候选额外增加 50 ms 的排队延迟,会推迟 首次媒体时间 (TTFM),导致用户只能看到黑屏。
“至多一次” 的权衡
这种速度的代价是 至多一次 的投递保证。如果信令节点崩溃或重启,它会与 Redis 断开连接;在此期间发送给其订阅者的任何消息都会 永久丢失。
- 对 ICE 候选而言,这通常是可以接受的——WebRTC 本身具备鲁棒性;丢失的候选只会导致 ICE 代理尝试下一个配对。
- 对关键状态转换(例如 “通话结束”)而言,丢失消息会导致房间在数据库中永久标记为 “活跃”,从而泄漏资源。
提振士气的表情包
RabbitMQ – 可靠性层
RabbitMQ 实现 AMQP(高级消息队列协议),并充当 消息代理,不仅仅是路由器。
内部实现与可靠性
- 消息通过 exchange 进入 queue。
- 确认(ACK) 与 持久化 保证消息在消费者确认之前不会从队列中移除。
- 若消费者崩溃,TCP 连接会断开,RabbitMQ 会将消息重新入队,以供其他消费者处理。
这种 至少一次 的保证对 控制平面 事件是不可或缺的。
示例:
Room Created→Start Cloud Recording。
若通过 Redis 发送且录制服务短暂失效,录制将永远不会启动——通话仍会继续,但合规文件缺失,导致 HIPAA 违规风险。
使用 RabbitMQ 时,Start Recording任务会驻留在持久队列中,直至录制服务恢复并处理它。
延迟成本
可靠性是有代价的。RabbitMQ 将持久化消息写入磁盘(或持久存储),并为 ACK 执行额外的握手,这会比 Redis Pub/Sub 增加 数十毫秒 的延迟。实际使用中,这种额外延迟对控制平面流量是可以接受的,但对高频率的 ICE 候选流则不适用。
综合全部
| 流量类型 | 推荐总线 | 保证 | 典型延迟 |
|---|---|---|---|
| ICE 候选、SDP 提供/应答(高频率、对延迟敏感) | Redis Pub/Sub | 至多一次(可容忍丢失) | < 1 ms |
| 通话控制事件(房间创建、录制开始/停止、通话结束) | RabbitMQ | 至少一次(持久) | 10‑30 ms |
| 混合场景(例如 Redis 不可用时的回退) | Both(Redis 为主,RabbitMQ 为回退) | 取决于回退逻辑 | – |
通过拆分信令平面为快速的易失层(Redis)和可靠的持久层(RabbitMQ),可以避免脑裂隔离,同时保持呼叫建立延迟低,并确保关键状态变化永不丢失。
信令愉快!
# The Hybrid Architecture: A Dual‑Bus Approach
The most robust production systems utilize a **Hybrid Architecture**.
We classify traffic into two lanes:
* **Hot Path** (Ephemeral) – low‑latency, fire‑and‑forget signals.
* **Cold Path** (Durable) – transactional events that must be persisted.
---
通道 1:热点路径(Redis)
流量: SDP offer/answer、ICE candidate、光标移动、输入指示。
目标: 最低延迟。
实现
- 每个用户连接到一个信令节点。
- 该节点订阅唯一的 Redis 通道
user:{uuid}。 - 当其他节点需要向该用户发送数据时,向该通道发布消息。
库: redis.asyncio(原 aioredis)。
模式: Fire‑and‑forget。如果消息丢失,UI 负责重试或直接忽略(例如,100 ms 后丢失的光标更新已不再相关)。
通道 2:冷路径(RabbitMQ)
流量: 房间生命周期事件(创建/销毁)、Webhook 触发、计费计量、录制任务。
目标: 事务完整性。
实现
当会议结束时,信令节点向 RabbitMQ 中的 topic 交换机发布 room.ended 事件。该事件被路由到多个队列:
| 队列 | 用途 |
|---|---|
billing_queue | 计算时长并向客户计费 |
cleanup_queue | 关闭媒体服务器(SFU)资源 |
analytics_queue | 汇总质量统计 |
库: aio_pika。
模式: Publisher confirms + consumer acks —— 我们依赖 RabbitMQ 确保每个计费事件 恰好一次(或至少一次并配合幂等性检查)处理。
Source: …
实现 Python 中的异步架构
在使用基于 asyncio 的框架(如 Quart、FastAPI 等)时,必须谨慎管理连接池。为每个 WebSocket 打开一个新的 Redis 连接会瞬间耗尽文件描述符。
多路复用 Redis 监听器
为发布保持 一个 全局 Redis 连接,为订阅在每个进程中保持 一个 连接。subscribe() 是阻塞操作,因此应在专用的后台任务中运行它,并将消息分发给相应的 WebSocket 实例。
# Conceptual architecture for multiplexed Redis → WebSocket
active_websockets = {} # Map user_id → websocket
async def redis_reader(channel):
async for message in channel.listen():
target_user = extract_target(message)
if ws := active_websockets.get(target_user):
await ws.send_json(message["data"])
# On startup
asyncio.create_task(redis_reader(global_pubsub_channel))
异步 AMQP 消费者
aio_pika 提供了强大的通道状态处理。一个关键的生产环境模式是 背压:如果你的信令服务器因接收的 WebSocket 帧过多而不堪重负,就不应继续从 RabbitMQ 拉取更多消息。设置 prefetch_count,让服务器只消费它能够处理的消息,将多余的消息留给其他节点(自动负载均衡)。
决策矩阵:何时使用何种方案
| 特性 | Redis Pub/Sub | RabbitMQ |
|---|---|---|
| 主要指标 | 延迟 (< 1 毫秒) | 可靠性 (持久性) |
| 投递保证 | 至多一次 (可能丢失) | 至少一次 (持久) |
| 吞吐量 | 高(每秒数百万) | 中等(每秒数千) |
| 复杂度 | 低(简单命令) | 高(交换机、绑定) |
| 理想负载 | ICE 候选、鼠标位置 | 计费事件、开始/停止录制 |
| Python 库 | redis.asyncio | aio_pika |
结论:规模的神经系统
- 单个信令服务器是原型。
- 分布式集群是产品。
引入消息总线将套接字连接与应用逻辑解耦。信令节点变成无状态的“哑管”,仅在客户端和“神经系统”之间传递数据。
在 Redis 与 RabbitMQ 之间的选择不是二元的。最具弹性的 WebRTC 架构区分信号(如水般流动)和事件(必须像石头一样被记录)。通过混合这些技术,你可以构建一个对用户而言即时、对业务而言具备审计防伪的平台。
关注作者
频道: The Lalit Official

