消息队列详细设计架构文档
前言
本文是游戏业务线消息队列中间件详细架构设计文档,用于指导消息队列中间件后续的开发、测试和运维。
词汇表
Netty:开源的网络编程框架
Reactor:网络编程模型
Sharding:数据分片存储方案
Zookeeper:开源的分布式高可靠性云应用
Persistent Nodes:ZooKeeper 永久节点
Ephemeral Nodes:ZooKeeper 临时节点
1. 业务背景
随着游戏业务的加速发展,业务上拆分的子系统越来越多。由于历史原因,各系统之间使用的协议多种多样,并且都是接口方式的同步调用,由此带来几个明显的系统问题:
性能问题:
当游戏发布时,“运营子系统”要通知“包管理子系统”、论坛、App、Web 站点等多个系统,往往出现同步调用导致的发布时延较高,性能较低等问题。
当玩家充值时,“充值子系统”要通知 “VIP 子系统”,“VIP 子系统”判断玩家达到相应 VIP 等级后,需要通知“福利子系统”、“客服子系统”、“商品子系统”等多个系统,流程冗长,性能很低。
耦合问题:当新增一个子系统时,各相关子系统都要开发新的接口,耦合严重。
效率问题:每个子系统提供的接口参数和实现都有一些细微的差别,导致每次都需要重新设计接口和联调接口,开发团队和测试团队花费了许多重复工作量。
基于以上背景,我们需要引入消息队列实现消息异步化,进行系统解耦,提升系统间协同效率。
2. 约束和限制
中间件团队规模为 6 人,熟悉 Java 语言,其中一人熟悉 C/C++。
开发平台是 Linux,数据库使用 MySQL。
整个业务单机房部署。
与阿里打通机房,至少需要 3 年。
运维不考虑 RabbitMQ 方案。
业务优先考虑可用性。
维护操作要方便,如消息收发、权限控制、上下线等。
3. 总体架构
3.1 架构分析
3.1.1 高可用
游戏版本发布和 VIP 业务都是高优先级业务,如果不能保证高可用,将造成用户体验断崖式下降,从而导致收入损失。而相比普通用户,VIP 用户群体更加注重服务可用性。所以综合来看,消息队列需要高可用性,包括消息写入、消息存储、消息读取均需要保证。
3.1.2 高性能
游戏新版本发布和用户充值不属于高频场景,故不需要保证超高性能,满足正常使用即可。
3.1.3 可扩展
消息队列主要使用场景为解耦、异步、削峰填谷,对目前主要业务场景来说,解耦和异步是系统最需要的,只要符合这些功能需求,后续无需扩展功能。
3.1.4 成本
随着游戏行业景气度上升,新游版号发放加速,业务加速发展,所以开发时间不能太长,开发成本不能太高。
3.1.5 安全
用户充值交易信息涉及少量用户隐私敏感信息,如充值付款账号、户名等等,需要考虑数据脱敏存储和对应的加解密措施。
3.2 总体架构
经过备选架构决策,最后决定使用的方案是“自研集群 + MySQL 存储”。
采用 sharding 分组的集群架构,每个 sharding 存储一部分消息。
每个分组采用两台 MySQL 服务器组成 master-replica 主备架构,使用主从数据复制,不同 sharding 数据不做同步。
正常情况下,主服务器对外提供消息写入和读取服务,备机不提供服务。
主服务器宕机情况下,被服务器对外提供读取服务。
客户端采用轮询策略写入和读取消息。
4. 详细设计
4.1 核心功能
4.1.1 消息发送流程
业务服务器集成消息队列系统 SDK,并在系统初始化时读取所有消息队列系统的服务器信息,启动后定期更新此配置(这步没有在时序图中体现)。
业务服务器调用 SDK 发送消息,SDK 对消息进行封装,包括标准化消息格式、按需加密消息字段和报文序列化,然后通过加权轮询算法决定将消息发送给 Sharding 中的哪台主服务器。
SDK 发送消息,如果服务器返回错误或超时,SDK 将重新进行负载均衡,发送消息给下一台服务器。
消息队列服务器接收到消息后,做幂等校验及实体映射。
消息队列服务器通过 MySQL 将消息持久化。
消息队列服务器返回结果给 SDK。
SDK 返回调用结果给业务服务器。
4.1.2 消息消费流程
业务服务器集成消息队列系统 SDK,并在系统初始化时读取所有消息队列系统的服务器信息,启动后定期更新此配置(这步没有在时序图中体现)。
SDK 根据配置信息轮询所有消息队列服务器查询未消费消息。
消息队列服务器返回消息给 SDK,SDK 返回消息给业务服务器进行消息消费。
业务服务器通过 SDK 返回消费确认。
消息队列服务器更新 MySQL 中消费状态。
4.1.3 服务器主从切换
同一组主从服务器配置相同的 group 名称,在 ZooKeeper 建立对应的 Persistent 节点。
主从服务器启动后,在 ZooKeeper 对应的 group 节点下简历 Ephemeral 节点,名称分别为 master 和 replica。
从服务器 watch 主服务器的 master 节点状态,当 master 节点被删除后,从服务器接管读消息,收到客户端 SDK 的读消息请求后返回消息,收到客户端 SDK 的写请求直接拒绝。
4.2 关键设计
4.2.1 消息发送可靠性
业务服务器中嵌入消息队列系统提供的 SDK,SDK 支持轮询发送消息,当某个分组的主服务器无法发送消息时,SDK 挑选下一个分组主服务器重发消息,依次尝试所有主服务器直到发送成功;如果全部主服务器都无法发送,SDK 可以缓存消息,也可以直接丢弃消息,具体策略可以在启动 SDK 的时候通过配置指定。
如果 SDK 缓存了一些消息未发送,此时恰好业务服务器又重启,则所有缓存的消息将永久丢失,这种情况 SDK 不做处理,业务方需要针对某些非常关键的消息自己实现永久存储的功能。
4.2.2 消息存储可靠性
消息存储在 MySQL 中,每个分组有一主一备两台 MySQL 服务器,MySQL 服务器之间复制消息以保证消息存储高可用。如果主备间出现复制延迟,恰好此时 MySQL 主服务器宕机导致数据无法恢复,则部分消息会永久丢失,这种情况不做针对性设计,DBA 需要对主备间的复制延迟进行监控,当复制延迟超过 30 秒的时候需要及时告警并进行处理。
4.2.3 消息如何存储
每个消息队列对应一个 MySQL 表,消息队列名就是表名,表结构设计大致如下:
消费队列表,关键字段包括消息 ID,消息内容。
消费状态表,记录每个消费者消费到哪个队列的哪条消息,关键字段包括消费者 ID,队列名,消费位置。
4.2.4 消息内容安全
SDK 提供消息明文字段脱敏功能,可根据传入参数,将指定参数进行脱敏处理,具体脱敏规则可由业务方自行扩展接口实现。
4.3 设计规范
消息队列服务器使用 Spring Boot 框架搭建。
客户端 SDK 与消息队列服务器的通讯使用 Netty 实现,采用 Reactor 网络模型。
负载均衡算法集成在 SDK 中,采用加权轮询算法。
通讯报文使用 JSON 格式。
为兼容非 Java 语言业务,需同时提供符合 RESTful 规范的 HTTP 接口。
MySQL 使用 InnoDB 存储引擎。
5. 质量设计
5.1 消息队列管理后台
为了方便维护,需建设消息队列管理后台系统,用于完成集群管理、消费队列配置、重复消费等等功能。集群启动后,消息队列服务器应主动向消息队列管理后台上报机器信息。消息队列管理后台负责维护集群配置信息,并定期下发配置和命令给消息队列服务器。
5.2 成本
研发 * 6 人
服务器
消息队列服务器 * 6 台
MySQL * 6 台
消息队列管理后台 * 2 台
6. 演进规划
6.1 消息队列一期
3 个月内完成系统建设,实现消息队列系统消息发送、消息存储及消息读取功能。
6.2 消息队列二期
2 个月内完成迭代,实现消息有序、队列监控、告警、死信队列、半自动扩缩容功能。
版权声明: 本文为 InfoQ 作者【Hesher】的原创文章。
原文链接:【http://xie.infoq.cn/article/c842d1477483420f806c44d9c】。文章转载请联系作者。
评论