Apollo 技术文档Apollo 技术文档
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
  • MMORPG 架构 QA

Q15: 如何实现消息压缩?

核心结论

消息压缩的关键,不是“选哪种压缩算法最强”,而是先判断:

  • 这条消息值不值得压
  • 压完是否真的省了总成本
  • 节省的是带宽,还是只是把压力转移到了 CPU

对 MMO 来说,最常见也最稳妥的原则通常是:

  • 高频小消息通常不压
  • 文本、大包、批量数据更适合压
  • 压缩必须做阈值控制和类型分流
  • 压缩策略要和消息频率、带宽压力、CPU 预算一起看

一、先明确压缩到底在换什么

压缩不是白送的优化。

它本质上是在做交换:

  • 用 CPU 时间换带宽
  • 用编码和解码复杂度换更小的数据体积

所以压缩是否划算,取决于当前真正缺的是什么。

例如:

  • 如果网络带宽紧张,压缩可能很值
  • 如果 CPU 已经很热,压缩可能适得其反
  • 如果消息本身很小,压缩元数据和算法开销可能比收益还大

所以第一原则不是“能压就压”,而是“先算账”。


二、不是所有消息都适合压缩

2.1 高频小消息通常不适合

例如:

  • 位置同步
  • 朝向同步
  • 动作状态
  • 小型战斗操作

原因通常有三个:

  • 包本身太小,压缩空间有限
  • 算法调用开销相对更贵
  • 高频链路更怕延迟抖动

这类消息最常见的优化往往不是压缩,而是:

  • 减字段
  • 定点化
  • 位打包
  • 降频
  • 合理广播裁剪

2.2 中大消息更值得压

例如:

  • 聊天长文本
  • 大批量背包或角色数据
  • 排行榜快照
  • 配置下发
  • 批量日志上报

这类消息通常:

  • 重复信息多
  • 文本占比高
  • 单包体积明显

因此压缩收益更稳定。


三、压缩前先做“结构优化”

很多系统的问题不是“没压缩”,而是消息结构本身太浪费。

例如:

  • 字段名重复出现
  • 明明是枚举却发字符串
  • 坐标用文本浮点而不是二进制数值
  • 发了大量客户端根本用不到的字段

这类问题更应该先做的是:

  • 精简字段
  • 选择更紧凑的序列化格式
  • 按客户端实际订阅裁剪数据
  • 避免冗余发送

如果结构还很臃肿,就先上压缩,通常只是把问题藏起来。


四、什么时候压缩最划算

4.1 文本型消息

例如:

  • JSON
  • 聊天内容
  • 配置文本
  • 日志文本

这类数据往往重复度高,压缩收益通常很好。

4.2 批量聚合消息

例如:

  • 批量同步背包
  • 批量同步排行
  • 批量日志上报

单条消息不大,但聚合后整体很大,这时压缩性价比往往更高。

4.3 周期性大包

如果某类消息不是高频实时,而是低频大包,通常更适合压缩。

因为:

  • 延迟敏感度没那么高
  • 带宽收益更明显

五、什么时候压缩反而不划算

5.1 小包

几十字节到一两百字节的小消息,很多时候压完也省不了多少,甚至更大。

5.2 时间极敏感消息

例如:

  • 高频实时操作
  • 战斗中的关键输入流

如果压缩和解压带来额外抖动,收益可能不值。

5.3 本身就难压的数据

例如:

  • 已经很紧凑的二进制结构
  • 随机性强的数据
  • 已经压缩过的内容

这类数据常见结果是:

  • 压缩率很差
  • 甚至出现“越压越大”

六、算法选择不要脱离场景

6.1 快速压缩类

例如:

  • LZ4
  • Snappy

特点:

  • 压得没那么狠
  • 但速度快

适合:

  • 在线链路
  • 更关注吞吐和时延

6.2 平衡型

例如:

  • zlib
  • zstd 的中低压缩级别

特点:

  • 压缩率和速度比较平衡

适合:

  • 通用消息压缩
  • 大多数中等负载场景

6.3 高压缩率类

例如:

  • zstd 高等级
  • LZMA

特点:

  • 压缩率高
  • CPU 成本也高

更适合:

  • 离线打包
  • 配置分发
  • 非实时大文件

不太适合主实时链路。


七、真正实用的策略:阈值压缩

消息压缩最常见的正确姿势不是“全开”,而是“过阈值才压”。

7.1 为什么要设阈值

因为压缩本身有固定成本:

  • 判断成本
  • 压缩函数调用
  • 解压函数调用
  • 头部标记和协议开销

如果消息太小,这些固定成本会把收益吃掉。

7.2 阈值不是写死的理论值

阈值应该根据实际测量决定,例如:

  • 128B 以下通常不压
  • 256B 到 1KB 按消息类型决定
  • 1KB 以上优先考虑压缩

但具体值要看:

  • 你的协议头大小
  • 压缩算法
  • CPU 预算
  • 网络压力

八、压缩策略必须和消息类型绑定

更成熟的实现一般不是“统一压缩器处理所有消息”,而是按消息类型分流。

8.1 典型策略

  • 高频状态消息:不压
  • 中频文本消息:按阈值压
  • 大型快照消息:优先压
  • 离线或后台消息:可用更高压缩率

8.2 为什么要这样分流

因为不同消息在乎的是不同指标:

  • 有的在乎时延
  • 有的在乎带宽
  • 有的在乎 CPU
  • 有的在乎总吞吐

如果所有消息统一策略,通常会把某一类消息优化好,但让另一类消息变差。


九、压缩不是单点优化,要和整体链路一起看

消息压缩常常和这些优化一起配合:

  • 合包
  • 差量同步
  • 位打包
  • 字段裁剪
  • AOI 限制
  • 批量发送

很多时候,真正有效的顺序应该是:

  1. 先减少不该发的消息
  2. 再精简必须发的消息
  3. 最后再压缩剩下的消息

如果顺序反过来,就很容易出现:

  • 先把垃圾流量压小
  • 但总量仍然很大

这不是最优路径。


十、线上必须监控哪些指标

如果做了消息压缩,就不能只凭感觉判断效果。

至少要看:

  • 压缩前字节数
  • 压缩后字节数
  • 平均压缩率
  • 压缩 CPU 时间
  • 解压 CPU 时间
  • 不同消息类型的命中率

否则你很可能会以为“压缩后一定更好”,但实际上只是:

  • CPU 被打高了
  • 延迟抖动变大了
  • 带宽节省并不明显

十一、一个更实用的工程建议

如果当前项目要做消息压缩,更稳妥的推进顺序通常是:

第一步:先按消息分类

  • 高频小包
  • 中等文本包
  • 低频大包

第二步:先做阈值压缩

  • 不要全量强制压
  • 先从明显大包开始

第三步:选算法时优先“稳定收益”

多数在线场景下,比起追求极限压缩率,更重要的是:

  • 压得够快
  • 解得够快
  • 行为可预测

第四步:上线后看数据再细调

真正合理的阈值和算法,往往不是拍脑袋定的,而是压测和线上数据共同决定的。


十二、总结

消息压缩的核心,不是“选最强算法”,而是先判断:

  • 这条消息是否值得压
  • 压缩节省的是不是当前真正的瓶颈
  • 压缩是否会反向伤害时延和 CPU

对 MMO 来说,更成熟的经验通常是:

  • 高频小消息先别压
  • 文本和大包优先压
  • 压缩做阈值控制和类型分流
  • 压缩前先做结构优化和消息裁剪

如果不先做这些判断,消息压缩很容易从优化手段变成新的负担。


参考资料

  • zlib
  • LZ4
  • Zstandard
在 GitHub 上编辑此页
最后更新: 3/20/26, 6:06 AM
贡献者: cuihairu