面试场景题——全服聊天
如果几万人同时发言,服务器怎么扛住?
背景
如果几万人同时发言,服务器怎么扛住?
痛点
- 所有人发消息,所有人都要收到。如果10万人同时发,消息量是 N×N,这是巨大的广播风暴。
核心策略
限流、消息队列、分频道
具体设计
① 接入层(限流与风控):
首先,我不会让所有消息都透传到逻辑服。在网关层(Gate Server)做频率限制(Rate Limit),比如每秒最多发1条。同时做敏感词过滤和禁言检测,拦截非法请求,减少后端压力。
② 逻辑层(服务拆分与广播):
我会把聊天做成一个独立的微服务(Chat Server)。
-
普通世界频道:考虑到世界频道人多,我倾向于写扩散,即消息只发一份到消息队列,由消息队列推送给在线的人。
-
分治思想:如果在线人数超过5万,我会按 “频道分片” 部署多个Chat Node,每个Node负责一部分玩家,通过一致性哈希路由,避免单点瓶颈。
③ 存储层(Redis缓存):
聊天记录不需要入库,只需要在 Redis 里缓存最近 100 条(List结构),玩家上线或断线重连时拉取最近记录即可。
④ 降级方案:
如果服务器负载过高,我会自动关闭世界频道的广播,或者开启“VIP发言”模式,保证核心用户体验。
为何采取写扩散
在游戏服务器中,全服聊天通常是“伪全服”(比如只显示最近100条),且对实时性要求极高。
-
如果用读扩散:玩家每次滑动聊天框,都要去DB/Redis读一次全服最新的100条。如果10万人同时读,DB压力巨大。
-
如果用写扩散:玩家发消息时,直接把消息写入到每个玩家的“未读消息列表”或“公共频道缓存”中。玩家上线时,直接从自己的缓存里拿,速度快,压力小。
写扩散的具体实现步骤
- 数据结构设计(Redis)
我们需要两个核心结构:
-
Channel List (Sorted Set):存全服最近的100条消息(作为数据源)。
-
User Inbox (List):存每个玩家私人的消息队列(可选,用于离线推送)。
# 1. 全服频道(Key: chat:world)
# Score 是时间戳,Member 是消息内容(JSON)
ZADD chat:world 1678888888 "{uid:1, name:'A', msg:'你好'}"
# 2. 玩家B的收件箱(Key: inbox:uid:2)
# 只存消息ID,或者存完整消息(看内存大小)
LPUSH inbox:uid:2 "msg_id_123"
- 发送消息流程
-
入口校验:玩家A发消息,GateServer 先校验频率(1秒1条)和敏感词。
-
写入公共池:逻辑服收到消息,生成一个唯一 MsgID,把消息内容(JSON)存入 Redis 的 chat:world有序集合。同时用 ZREMRANGEBYRANK删除排名100以后的消息,保证只存最新的100条。
-
触发扩散:
-
方案A(简单粗暴):直接查询当前全服在线玩家列表(比如从 SkyNet 的 Agent 里拿),循环给每个人发 Socket 推送。
- 缺点:如果在线5万人,这一瞬间的 CPU 和网络 IO 会爆表。
-
方案B(推荐:结合消息队列):不直接推,而是把“广播任务”丢进消息队列。
-
MQ的作用
-
削峰填谷(Peak Shaving):春节活动,1秒内来了1000条全服喊话。如果没有 MQ,服务器直接卡死。有了 MQ,Worker 可以按每秒处理200条的速度慢慢推,虽然玩家看到消息有1秒延迟,但服务器活下来了。
-
解耦(Decoupling):发消息的逻辑(写)和推消息的逻辑(推)分开。万一推送服务挂了,消息还在 MQ 里,重启后能继续推,不会丢消息。
-
失败重试:如果推送给玩家B失败了(网络波动),Worker 可以从 MQ 里重新拉取这条消息再试一次。
频道分片
[Gate Server 1] \
[Gate Server 2] ---> [Router/Proxy] ---> [Chat Node A] (负责玩家 1~16666)
[Gate Server 3] / \-> [Chat Node B] (负责玩家 16667~33333)
\-> [Chat Node C] (负责玩家 33334~50000)
分片策略选择

这里我采取一致性哈希
核心难点:跨分片聊天
如果分片了,玩家A在 Node A,玩家B在 Node B,A说话B看不见,这就不是“世界频道”了。
解决方案:引入“路由层(Router/Proxy)
流程:
[Node A] -> [Router] -> [Kafka/RabbitMQ] -> [Node B Consumer] -> [Push to Players]
\--> [Node C Consumer] -> [Push to Players]
- 玩家A 在 Node A 发消息。
- Node A 处理完本节点的广播后,把消息发送给 Router。
- Router 知道全服所有的 Chat Node 列表(A, B, C)。
- Router 把消息复制MQ。
- MQ异步转发给 Node B 和 Node C。
- Node B/C 收到消息后,再推送给各自负责的玩家。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)