实时游戏架构:Redis + WebSocket 双引擎设计
最近在做快艇骰子联机对战项目,踩了实时联机通信的不少坑,今天用通俗的思路,讲清为什么实时游戏一定要用 Redis + WebSocket,以及这套架构该怎么落地。
一、一个真实联机场景,暴露三种方案的优劣
4 人同时在线联机玩骰子,玩家 A 掷完骰子,B、C、D 需要立刻收到结果并同步界面。简单想了三种实现方式,差距一目了然:
方案 A:纯 HTTP 轮询
B 每隔 500ms 就请求一次服务器:「A 掷骰子了吗?」
-
缺点:延迟高、服务器请求量大、客户端耗电
-
体验:玩家操作反馈卡顿,玩起来像看 PPT
方案 B:纯 WebSocket 长连接
A 完成操作后,服务器直接通过长连接推送给其他玩家。
-
优点:实时性拉满,毫秒级推送
-
致命问题:无状态、无持久化,服务器一旦重启 / 崩溃,整局游戏数据直接丢失,对局无法恢复
方案 C:Redis + WebSocket 双引擎(最终最优解)
A 操作完成 → 数据写入 Redis 持久缓存 → WebSocket 广播给所有玩家
-
服务器宕机重启:直接从 Redis 读取对局数据,继续游戏
-
实时性、稳定性、容错性全部兼顾
这就是我项目最终落地的实时游戏双引擎架构。
二、双引擎分工:黑板 + 喇叭,各司其职
用一个生活化的比喻,快速理解两者定位:
表格
| 组件 | 形象比喻 | 核心作用 | 特性 |
|---|---|---|---|
| Redis | 黑板 | 存储当前对局实时数据 | 内存读写、微秒级响应,宕机可恢复 |
| WebSocket | 喇叭 | 实时广播消息,不存数据 | 长连接推送,毫秒级通知,只传话 |
标准协作流程
plaintext
玩家执行操作 → 写入Redis固化数据 → WebSocket广播推送 → 所有玩家同步状态
核心铁则:必须先写 Redis,再发广播
如果顺序颠倒,服务器崩溃在广播前,数据就会丢失,无法回滚。
三、游戏三类数据,精准选择 Redis 结构
根据数据冷热程度,分层存储,兼顾性能与成本。
3.1 热数据:对局中高频变化,全放 Redis
游戏进行中实时变动的数据,优先 Redis,拒绝频繁查库拖慢速度:
表格
| 存储数据 | Redis 结构 | 选型原因 |
|---|---|---|
| 房间基础信息 | Hash | 房主、在线人数、房间状态,多字段独立更新 |
| 对局进度 | Hash | 当前轮次、轮到哪位玩家 |
| 玩家分数 | Hash | 总分、上下半区得分,按需更新字段 |
| 13 项计分格子 | Hash | 每个格子是否填写、对应分数,字段级修改 |
| 当前骰子状态 | Hash | 5 个骰子点数、锁定状态,独立更新 |
| 实时排行榜 | Sorted Set | 自动按分数排序,查询排名 O (logN),无需手动排序 |
为什么不用 String / List?
-
String:修改单个字段要重写整个对象,性能浪费
-
List:有序但随机查询效率低,不适合字段式更新
-
Hash:字段独立,改哪写哪,完美适配对局动态数据
-
Sorted Set:天然排序,排行榜一键实现
3.2 温数据:Redis + MySQL 双写归档
单局游戏结束后,对局最终战绩:
-
读取 Redis 内完整对局数据
-
批量写入 MySQL 持久化
-
主动清理 Redis 对局缓存(也可依靠 2 小时过期自动清理)
主动清理能减少 Redis 内存占用,避免缓存堆积。
3.3 冷数据:MySQL 长期存储
历史战绩、个人对局记录、全服长期排行榜:
-
历史对局复盘
-
个人游玩统计
-
全服长期排名榜单
这类数据查询频率低、要求永久存储,存入 MySQL 建立索引,保证数据可靠。
四、WebSocket 消息规范:上行下行清晰分层
把 WebSocket 消息分为三类,结构清晰,便于后端解析、前端对接。
4.1 上行消息:玩家 → 服务器
玩家主动发送的操作指令:
表格
| 指令 | 含义 | 服务器处理逻辑 |
|---|---|---|
| roll | 掷骰子 | 生成随机点数,写入 Redis,广播结果 |
| lock | 锁定骰子 | 更新 Redis 锁定状态,同步广播 |
| submit | 提交计分 | 计算得分、更新 Redis,广播并判断对局是否结束 |
| chat | 发送聊天 | 直接广播给房间内所有玩家 |
4.2 下行广播:服务器 → 房间所有玩家
对局状态变更,全员同步:
表格
| 指令 | 触发场景 |
|---|---|
| dice_result | 玩家掷骰子完成 |
| lock_update | 骰子锁定状态变更 |
| score_update | 玩家提交计分 |
| next_turn | 切换至下一位玩家回合 |
| game_over | 13 轮对局结束,结算战绩 |
| player_left | 玩家离线 / 退出房间 |
4.3 下行单发:服务器 → 单个玩家
个性化提示、错误拦截,仅发给对应玩家:
表格
| 指令 | 触发场景 |
|---|---|
| error | 非法操作(非自己回合、计分格子已填写) |
| your_turn | 轮到该玩家操作,前端激活操作按钮 |
五、核心一致性保障,规避联机 BUG
实时联机最怕并发问题、重复操作,用 Redis 原子特性轻松解决。
5.1 防重复提交
计分格子只能填一次,利用 Redis Hash 单字段查询校验:
redis
# 查询第8格是否已填写
HGET game:10001:scores:1 8
# 返回空值:可正常提交;返回分数:拒绝重复填写
单字段查询 O (1),性能极高,杜绝重复计分。
5.2 回合安全切换
玩家同时提交操作,依靠 Redis 串行执行保证原子性:
redis
# 查询当前操作玩家
HGET game:10001 current_player_id
# 切换至下一位玩家
HSET game:10001 current_player_id 1002
Redis 天然串行处理命令,不会出现多人同时操作、回合混乱的问题。
六、项目架构演进路线,从小玩法到多人联机
从极简版到高并发版,清晰的升级路径:
表格
| 阶段 | 架构方案 | 适用场景 |
|---|---|---|
| MVP 基础版 | HTTP + MySQL | 单机人机对战,验证核心玩法 |
| 简易联机版 | HTTP + MySQL + 轮询 | 2–4 人小规模联机,低并发场景 |
| 实时对战版 | WebSocket + Redis + MySQL | 多人实时联机,高并发核心架构 |
| 大规模服务版 | WebSocket + Redis 集群 + MySQL | 分房间模式,支持万人同时在线 |
本次快艇骰子项目,直接从简易联机版升级到实时对战版,核心改动:
-
传统 HTTP 接口 → WebSocket 消息通信
-
实时数据直接查 MySQL → 优先读取 Redis
-
对局结束单条写入 → 批量归档 MySQL
七、一句话总结核心架构原则
Redis 是真理,WebSocket 是嘴巴,MySQL 是档案。
-
对局进行时:状态以 Redis 为准,实时读写
-
消息通知:靠 WebSocket 实时推送,无需客户端轮询
-
对局结束后:存入 MySQL,永久留存可追溯
八、参考资料
-
Redis 数据类型选型:redis.io/topics/data-types
-
FastAPI WebSocket 开发:fastapi.tiangolo.com/advanced/websockets
本文结合实际项目落地经验,拆解了实时联机游戏的架构设计。
如果有架构疑问、代码实现细节,欢迎评论区交流~
更多推荐




所有评论(0)