小米小爱单机百万长连接网关架构
小爱接入层是小爱云端设备接入的核心服务。早期基于 Akka 和 Play Framework 实现,单实例长连接上限约 28 万;经过前端 C++、后端 Scala、ZeroMQ 通讯和协议优化后,单机连接数提升到 120 万以上,并节省了 30 多台机器。
一、接入层职责
小爱接入层主要承担三件事:
- 安全传输与鉴权;
- 维护设备长连接和心跳;
- 将设备请求转发到后端服务。
二、早期架构的问题
Akka + Play 的版本能快速搭建并实现异步化,但随着长连接数量增长,问题逐渐暴露:
- JVM GC 成为瓶颈,单实例约 28 万连接;
- Actor 之间存在大量状态依赖,代码像函数调用而非不可变消息传递;
- 协议解析依赖较重,升级会引发重连和雪崩风险;
- Play 框架无法获取底层 TCP 连接数据,长连接打点不准确。
三、重构思路:前后端拆分
新版接入层按“是否有状态”拆成两个进程:
| 模块 | 状态 | 功能 |
|---|---|---|
| 前端 | 有状态 | 连接保持、协议解析、加解密、顺序化 |
| 后端 | 无状态 | 请求解析、业务分发、鉴权 |
前端保持设备连接,后端无状态化后可以随时重启而不影响连接,避免版本升级引发长连接重连。
四、前端实现
前端使用 C++,由四个子模块组成:
- 传输层:WebSocket 解析、XMD 协议解析;
- 分发层:把不同传输层事件转换成统一事件;
- 状态机层:基于自研 XMFSM,单线程 Actor 抽象;
- ZeroMQ 通讯层:用两个线程把阻塞接口异步化。
自研 WebSocket 库基于 Boost ASIO 和 ASIO,无锁设计。压测中 100 万长连接、5 万 QPS 下 P99 延迟为 5ms。
传输层支持三种通道:
ws:WebSocket over TCP;wss:加密 WebSocket;xmd:UDP 通道。
不同通道的差异通过 C++ 多态抽象为 Channel 接口,状态机主逻辑保持统一。
五、后端实现
后端使用 Scala 和 Akka Actor。最关键的改造是无状态化:所有与连接状态相关的信息都从后端移除,一次连接上的多个请求在后端被当作独立请求处理。
前后端使用 Protobuf 交互,减少 JSON 解析开销。BIND、LOGIN、LOGOUT、UPDATE、PING、TEXT_MESSAGE、BINARY_MESSAGE 分别处理设备鉴权、登录、登出、状态更新和消息转发。
六、容量优化
从 28 万提升到 120 万以上,关键优化有:
- 前后端通信从 JSON 换成 Protobuf,CPU 占用明显下降;
- 业务层 JSON 采用部分解析,只解析 header 获取
namespace和name,CPU 下降 10%; - 心跳消息曾占收发消息总量的 75%,延长心跳时间有效降低 CPU;
- 使用自研 Boost ASIO 异步多线程 TCP 通讯库,提升与后端服务通信性能。
七、未来规划
2.0 版计划进一步用 C++ 重写后端,将前端非必要功能继续后移,使前端更小更稳定。如果前端与后端处理能力差异较大,还可以用网络库替换 ZeroMQ,实现前后端从 1:1 部署变为 1:N 部署,目标单前端达到 200 万以上连接。
八、总结
小爱网关的实践说明:长连接容量优化不能只盯着协议库,还要从状态边界、进程拆分、序列化、心跳、线程模型和内存管理同时下手。有状态前端最小化,无状态后端最大化,是降低上线风险的关键。