1. 业务背景
【新版本发布】
- 游戏厂家更新游戏版本后,运营人员获取最新的游戏包,更新版本信息,然后上传包 到包管理系统打测试包,运营人员进行基本测试。运营子系统通知论坛有新的包将要 发布,进行预热。
- 测试完成后,运营管理子系统要通知包管理系统进行打包
- 游戏准点正式发布的时候,运营子系统要通知 App、Web 站点等即时更新到新版本
【玩家充钱】
- 玩家进行充值,充值完成后充值子系统通知 VIP 子系统;
- VIP 子系统判断玩家等级,达到 VIP 后,等级子系统要通知福利子系统进行奖品发放,要通知客服子系统安排专属服务人员,要通知商品子系统进行商品打折处理等级子系统的开发人员也是不胜其烦。
随着游戏业务发展很快,业务上拆分的子系统越来越多,由此带来几个明显的系统问题:
- 性能问题:当玩家充值成功后,会依次通知其他的子系统,而这个过程是同步调用,导致系统响应时间很长。
- 耦合问题:系统之间不同程度的依赖关系,会导致牵一发而动全身,比如当一个系统接口变动时,依赖这个系统的接口的系统都需要作相应的变动。
- 效率问题:每个子系统提供的接口参数和实现都有一些细微的差别,导致每次都需要重新设计接口和联调接口,开发团队和测试团队花费了许多重复工作量。
基于以上背景,我们需要引入消息队列进行系统解耦,将目前的同步调用改为异步通知。
2. 约束和限制
- 中间件团队规模不大,大约 6 人左右。
- 中间件团队熟悉 Java 语言。
- 开发平台是 Linux,数据库是 MySQL。
- 目前整个业务系统是单机房部署,没有双机房。
- 系统的需要嵌入到已有运维体系,且维护成本不能太高
- 需要保证可用性,丢消息对业务影响严重
3. 总体架构
3.1 架构分析
- 可维护性:各种维护操作要方便,例如收发消息情况、权限控制、上下线等
- 高性能:不需要高性能,游戏新版本发布和 VIP 充值的消息并不多
- 高可用:需要,游戏版本发布和 VIP 都是高优先级业务
- 可扩展:不需要,消息队列的功能基本明确,无需扩展
- 成本:开发投入人力和时间不能太长
综合来看消息队列需要可维护性,高可用,以及较低的开发投入成本,由于游戏新版本发布和 VIP 充值的消息并不多,消息队列的功能基本明确,所以不需要高新能和较高的可扩展性。
3.2 总体架构
描述:
- Java 语言编写消息队列服务器
- 消息存储采用 MySQL
- SDK 轮询服务器进行消息写入
- SDK 轮询服务器进行消息读取
- MySQL 双机保证消息尽量不丢
- 使用 Netty 自定义消息格式,并且支持 HTTP 接口
4. 详细设计
4.1 核心功能
4.1.1 消息发布流程
- 消息生产者(Message Produce Server)通过 SDK 从注册中心(Queue Manager)中获取已经注册的可用的消息队列服务器(Queue Server)的地址列表。
- 消息生产者(Message Prodecu Server)通过 SDK 负载均衡策略(Round Robin)选择一台消息主服务器,发送消息写请求。
- 消息队列服务器(Queue Server)接受到请求,将消息写入到数据库中,并响应结果信息。
- 消息服务器主节点处理读写请求,从节点处理读请求。
4.1.2 消息读取流程
- 消息消费者(Message Consume Server)通过 SDK 从消息管理系统(Queue Manager)中获取已经注册的可用的消息队列服务器(Queue Server)的地址列表。
- 消息消费者(Message Consume Server)通过 SDK 负载均衡策略(Round Robin),向消息服务器发起消息读取请求。
- 消息消费者(Message Consume Server)消费消息完成后,消息队列更新消息的消费状态,同时,异步刷新数据库中的消息状态。
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,每一个消息的内容就是数据库表的一列。建表语句如下:
CREATE TABLE `message_queue_name` (
`message_id` int(11) NOT NULL AUTO_INCREMENT,
`message_header` varchar(256) NOT NULL,
`message_body` text NOT NULL,
`message_property` varchar(256) NOT NULL,
PRIMARY KEY (`message_id`),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
4.3 设计规范
4.3.1 消息服务端
- 服务器基于 Netty 开发,采用 Reactor 网络模型
- 两台服务器组成一个 sharding,整个系统可以多个 sharding,每个 sharding 包含一主一从两台服务器
- 主服务器提供消息读写操作,从服务器只提供消息读取操作
- 服务器基于 KeepAlived 进行主从切换
4.3.2 消息客户端
- 客户端采用 Netty 实现
4.3.3 消息存储
- 采用 MySQL 主从同步
- 每个消息队列对应一个表
- 直接用 MySQL 的主从复制来实现数据复制
4.3.4 交互协议
- 客户端与服务端采用 TCP 连接,采用 protobuf 传递数据
- 为了兼容非 Java 系统,服务端同时提供 HTTP 接口
5. 质量设计
5.1 消息队列管理后台
6. 演进规则
6.1 消息队列一期
完成消息发送流程、消息消费流程的功能性开发和测试,提供 SDK 给各个子系统做业务联调。
6.2 消息队列二期
完成主从切换流程、消息发送可靠性、消息存储可靠性的开发和测试,在保障整体 SLA 的情况下,提供给不强依赖消息队列管理后台的子系统使用。
6.3 消息队列二期
完成消息队列管理后台,保证系统的可测试性、可维护性、可观测性,提供给所有需要消息队列系统的子系统线上正式使用。
本文由 biezhi 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2021/05/12 14:13