架构实战营 模块三作业
前言
本文档是一个用于游戏业务运营相关子系统之间进行通讯的消息队列中间件的详细架构设计文档,定义了该消息队列的系统组成、实现细节、关键指标等,用于指导消息队列后续的开发、测试和运维。
词汇表
Reactor: 网络编程模式
Netty: 开源的网络编程框架
API:服务对外暴露的调用接口
Json:一种类似于嵌套字典的数据结构
MySQL:一款常用的开源关系型数据库
ZooKeeper:一款开源的分布式应用协调服务
SDK:软件开发工具包,为服务提供了客户端,并可整合到具体的业务应用中
1. 业务背景
随着移动互联网时代的到来,当前我司的游戏业务发展迅速,新的游戏产品层出不穷,相关的系统也越来越多,系统架构及系统间的通讯、协作变得越来越复杂,运作效率难以跟上业务发展的需求。游戏业务本身的多样性和复杂流程为后端系统带来了更大的挑战,例如以下几个业务场景:
1.1 新版本发布
游戏开发团队对游戏进行更新后,运营团队拉取更新内容,并登记版本更新信息。之后需要将新版的游戏代码上传到包管理系统打测试包,方便运营团队进行测试。同时,运营子系统会向玩家论坛发布预热通知。测试通过后,运营子系统会再次通知包管理系统打发布包,并在预定的发布上线时间通知 App、官网等站点更新版本链接。
1.2 玩家充钱
玩家充值后,充值子系统需要通知 VIP 子系统判断玩家等级,当达到 VIP 等级后需要通知福利子系统发放游戏福利、客服子系统安排专属客服、商品子系统进行商品打折等。
在过去,各子系统自行定制各自的接口和协议,系统之间相互调用的过程存在以下问题:
性能差,完成一个业务流程需要依次调用多个子系统
扩展难,向后端增加新的子系统需要修改已有子系统之间的调用关系
开发效率低,由于不同子系统间的接口不统一,实现业务流程的过程比较漫长,需要反复调试
基于此,考虑使用消息队列中间件来处理子系统之间的消息通知,目的是解耦各系统之间的相互依赖,统一系统间的调用方式和接口参数,以异步通信机制提升整体性能。
2.约束和限制
要求在三个月内上线
成本不超过 300 万
中间件团队规模小,只有 6 人
主要使用的开发语言为 Java,开发平台为 Linux,数据库使用 MySQL
整个业务使用单机房部署
满足基本性能要求即可,但必须稳定可靠,尽可能避免故障
3.总体架构
3.1 系统边界白盒图
3.2 内部 Relation
3.3 架构分析
3.3.1 高性能
该消息队列服务于内部系统之间的通知,游戏新版本发布和 VIP 充值的消息并不多,业务并发量低,不要求高性能,因此使用 MySQL 存储和主备架构完全够用。
3.3.2 高可用
该消息队列用于串联多个业务系统,实现复杂的游戏运营业务流程,消息队列本身故障对业务会产生决定性的影响,游戏版本发布和 VIP 充值,都是高优先级业务,需要保障可用性,如果消息丢了,对游戏上线发布,以及 VIP 用户的体验影响较大,导致用户流失而损失收入,因此系统必须确保高可用。
3.3.3 可扩展
消息队列的功能基本明确,无需扩展。
3.3.4 成本及安全
开发投入的成本不能太高,周期不能太长。由于仅服务于内网系统,安全性相对可控。
3.4 总体架构
对系统中的消息队列服务器和数据库服务器进行分组,每组分别负责一部分消息数据的读写,分组间数据不同步
每个分组内的消息队列服务器和数据库服务器均使用双机主备架构,正常情况下,分组内的主服务器对外提供消息写入和消息读取服务,备服务器不对外提供服务;主服务器宕机的情况下,备服务器对外提供消息读取的服务
客户端通过 SDK 轮询服务器进行消息读取和写入,当某个分组不可用时自动请求其他分组
4.详细设计
4.1 核心功能
4.1.1 消息发送流程
发送消息时,消息生产者请求 SDK,并由 SDK 轮询消息队列服务器,选择一台可用的服务器并建立连接,然后将消息转发给消息队列服务器,再由消息队列服务器存储到 MySQL 中。
4.1.2 消息消费流程
消费消息时,SDK 先轮询消息队列服务器并建立连接,然后持续监听该消息队列服务器上的新消息,获取到新消息时取回消息,并请求消息消费者进行消费,消费完成后再通知消息队列服务器更新该消息的消费状态。
4.2 关键设计
4.2.1 服务高性能
两台服务器组成一个分组,整个系统可以多个分组,每个分组包含一主一备两台服务器。主服务器提供消息读写操作,备服务器在主服务器故障的时候提供消息读取操作。
消息队列系统提供 SDK 供各业务系统调用,SDK 从配置中读取所有消息队列系统的服务器信息,SDK 采取轮询算法发起消息写入请求给主服务器。如果某个主服务器无响应或者返回错误,SDK 将发起请求发送到下一台主服务,相当于在客户端实现了分片的功能。
业务服务器中嵌入消息队列系统提供的 SDK,SDK 支持轮询发送消息,当某个分组的主服务器无法发送消息时,SDK 挑选下一个分组主服务器重发消息,依次尝试所有主服务器直到发送成功;如果全部主服务器都无法发送,SDK 可以缓存消息,也可以直接丢弃消息,具体策略可以在启动 SDK 的时候通过配置指定。
如果 SDK 缓存了一些消息未发送,此时恰好业务服务器又重启,则所有缓存的消息将永久丢失,这种情况 SDK 不做处理,业务方需要针对某些非常关键的消息自己实现永久存储的功能。
4.2.2 服务高可用
服务器基于 ZooKeeper 进行主备切换,主备服务器启动后,在 ZooKeeper 对应的 group 节点下建立 EPHEMERAL 节点,名称分为 master 和 slave。slave 监听 master 节点状态,当 master 节点超时被删除后,slave 接管消息读取,收到客户端 SDK 的读消息请求后返回。
以上设计可确保在某个分组挂掉的情况下,客户端 SDK 仍可自动连接到其他分组,继续使用消息队列服务。而当某一分组的主服务器挂掉后,备服务器仅提供读取服务,一方面使得该分组不再接受新的写入请求,确保了主备服务器之间的数据一致,另一方面确保了消息仍然可以被消费。
4.2.3 存储高可用
MySQL 采用双机主备架构,实现数据复制和备份。主备机应在同一机房的不同机柜上部署,这样既避免电源、网络设备故障导致主备机均不可用,又尽可能减少了复制延时。不过在复制延时期间如果主服务器宕机且数据损坏,则该时间段内写入的消息会丢失,需要系统对延时进行监控,超限时发出警报。
4.3 设计规范
MySQL 采用主备同步,每个消息队列对应一张表,每张表最多存储 30 天内的消息,过期自动删除
MySQL 使用 Innodb 存储引擎
客户端采用 Java 语言开发,基于 Netty 实现与服务端交互
客户端与服务端采用 TCP 连接,采用 Json 传递数据
服务器使用 Spring Boot+Netty 开发,采用 Reactor 网络模型
为了兼容非 Java 系统,服务端同时提供 HTTP 接口
5.质量设计
5.1 可测试性
可以在 SDK 中设置功能开关和路由,并单独配置某一分组为测试分组,对其进行的读写操作不会影响到线上业务。可以通过 SDK,也可以直接通过服务端提供的 HTTP 接口对服务的各项功能和性能进行测试。
5.2 可观测性
接入公司统一的运维监控系统,确保硬件资源状态的可监控、可统计,同时对 MySQL 数据表体积、消息总量、已消费和未消费消息数进行统计。
每次读写请求均记录日志,用于分析不同业务的请求量、请求频率、请求成功率等。
5.3 可维护性
架构设计本身可以支持故障切换,开发和运维团队本身对 Java 服务端和 MySQL 十分熟悉,出现应用故障可以快速解决。同时,消息队列后台管理系统可以对整个系统的配置信息进行动态管理。
5.4 成本
按照至少三个分组,每个分组至少两台消息队列服务器、两台 MySQL 数据库服务器计算,再加上单独的 ZooKeeper 集群和存储设备,整个消息队列系统的硬件成本约为 60 万左右。
整个系统的研发周期预计为两个月,按照 6 人开发团队、2 人运维团队计算,研发成本约为 40 万,运维成本约为 3 万/月。
5.5 安全
整个系统使用内网环境部署,不对外公开,且业务方必须通过 SDK 才能操作消息队列,在 SDK 中提供鉴权和限流。硬件设备按照机房统一安全策略进行管理。
6.演进规划
6.1 消息队列一期
采用单个分组的架构,SDK 无需轮询,验证系统是否能正常提供服务,验证主服务器故障情况下备服务器是否可提供服务
暂不提供 HTTP 接口
6.2 消息队列二期
采用多分组架构,SDK 实现自动轮询,服务端实现 HTTP 接口
接入运维监控系统
6.3 消息队列三期
开发消息队列后台管理系统
业务端和服务端针对消息丢失等边缘情况进行设计处理
评论