题目:设计一个支持万人在线的语音聊天室,如何保证房间状态(如麦位信息、在线人数)的实时一致性?
难度:high
标签:practice/business, architecture/realtime
评分:⭐⭐⭐⭐☆(4/5)
直播间这个东西本身就是一个独立的个体。然后直播间状态、麦位信息、在线人数这些,属于直播间个人的、直播间的属性,独立的进行维护。当用户上线或者申请麦位的时候,检查直播间属性上的值,然后进行更新。这个更新使用 Redis 更新,然后同步的展现就可以了。
用户上线的时候,对应的用户在线人数进行原子增,就可以更新上线在线的人数。当用户下线的时候,对应的更新对应的 Redis 就可以实现了。有时候为了保证业务更新,可能会用到 MQ。当用户上线下线的时候,发对应的消息到 MQ,然后监听 MQ 的变更,对应的去更新房间状态,来达到数据的最终一致性;
万人直播间的数据更新,直接用 Redis 本身就可以,并不一定需要 MQ 再去异步处理。在 MQ 异步处理可以提升它更高的兼容性,但是 Redis 本身万人直播间的更新就可以扛得住。新用户获取信息的时候直接获取直播间的信息,这个信息的更新通过长连接,进行对应的实时更新,或者使用服务端推送给客户端进行更新;但是不能使用客户端轮询,这样的客户端轮询产生的并发会让业务服务器崩溃
优点:
不足:
补充:
SADD + 唯一索引 防并发(SETNX 占位)List),有空位时按顺序分配INCR/DECR 原子操作核心考点定位:考察"分布式实时系统"的设计能力——状态存储、广播机制、一致性模型三者的权衡。
知识点拆解:
INCR/DECR 原子操作,key 为 room:{roomId}:onlineSET 存当前麦位用户列表,key 为 room:{roomId}:micsLIST 存排队用户,key 为 room:{roomId}:mics:queueHASH 存用户详细信息,key 为 room:{roomId}:user:{userId}SETNX 或 Lua 脚本保证原子性EXPIRE 自动过期)INCR/DECR 原子操作,避免并发错乱考察方向:
延伸递进面试题:
SETNX 占位(key 为 mic:{micId}),失败则排队;Lua 脚本保证"检查 + 占位"的原子性;空位时 BLPOP 阻塞等待下一个申请【一句话结论】
面试官您好,万人在线的核心是"状态存 Redis + 广播走 Pub/Sub + 长连接走 WebSocket",麦位管理用 SETNX + Lua 保证原子性,在线人数用心跳保活 + 超时清理。
【三点展开】
第一,架构分层:WebSocket 网关(Netty)负责用户长连接,无状态化便于横向扩展;房间状态服务负责业务逻辑,状态全部存 Redis(在线人数 INCR/DECR、麦位 SET、排队 LIST);Redis Pub/Sub 负责跨服务器广播状态变更——用户连在 WS 网关 A,状态变更发生在服务器 B,B 发消息到 Pub/Sub,A 订阅后推送给本地连接。
第二,麦位管理有并发陷阱:多用户同时申请同一麦位,用 SETNX mic:{micId} userId 原子占位,失败则进排队队列(LIST);空位时 BLPOP 阻塞等待下一个申请;用户下线/心跳超时触发释放,释放后通知排队用户。Lua 脚本保证"检查麦位空 + 占位"的原子性,避免竞态。
第三,在线人数用心跳保活:客户端 30s 心跳,服务器 60s 超时清理(EXPIRE 自动过期,避免死连接);新用户进房直接读 Redis 拿当前状态,之后通过 WebSocket 订阅房间状态变更,秒级感知。不能用客户端轮询——万人 QPS 会打爆服务器。强一致(麦位状态同步广播,延迟 < 100ms)和最终一致(聊天消息走 MQ,秒级收敛)按场景选型。
【一句边界】
当然,如果是百万级并发(YY 语音、KOOK),自建成本高,直接用 IM 云(融云/环信/腾讯云 IM)+ WebSocket 自建弹幕系统更划算——核心是"麦位状态自建,聊天消息用云"。