架构案例
客户端请求进来先通过 Nginx 反向代理分发请求到不同的应用服务器上处理请求,解决应用服务器承载问题。数据库读写分离,把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。数据库压力随着用户增加加大后可以根据业务需求拆分数据库,不同的业务分配到不同的数据存取,比如用户数据业务、订单业务等拆分到不同的库。
业务层并发解决之后 Nginx 成了单点,Nginx 工作在网络第七层,LVS 和 F5 是工作在网络第四层的负载均衡解决方案,其中 LVS 是软件,运行在操作系统内核态,可对 TCP 请求或更高层级的网络协议进行转发,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5 是一种负载均衡硬件,与 LVS 提供的能力类似,性能比 LVS 更高,但价格昂贵。多个 Nginx 之间通过 keepalived 实现高可用。
由于 LVS 仍然是单点,当 LVS 宕机后整个应用将无法使用,通过 DNS 轮询实现机房间的负载均衡,DNS 服务器会使用轮询策略或其他策略,来选择某个 IP 供用户访问,从而实现机房间的负载均衡。
Keepalived
Keepalived 的作用是检测服务器的状态,如果有一台服务器宕机,或工作出现故障,Keepalived 将检测到,并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工作正常后 Keepalived 自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务器。
Layer3:Keepalived 会定期向服务器群中的服务器发送一个 ICMP 的数据包,如果发现某台服务的 IP 地址没有激活,Keepalived 便报告这台服务器失效,并将它从服务器群中剔除,同样,在 Layer4 检测端口,Layer5 对 RUL 计算 MD5 值求和比对,将失效的服务器移除服务器群中。
负载均衡
- 访问端拿到所有服务器的列表,随机选择一个服务器访问
- 访问端拿到所有服务器的列表以及相关的负载情况,选择负载最低的进行访问
- 使用 DNS 负载均衡技术,使用域名来访问,通过 DNS 轮询(DNS 后台配置多个 IP,根据请求依次路由到不同的 IP)
- 选择连接最少的路由
- 请求轮询,根据请求顺序依次轮询到不同的服务器
- IP 哈希,根据哈希值指定到某台服务器,适合有状态请求,比如 session
服务器架构
分区分服
传奇世界手游架构图
- 客户端从运营后台获取获取 Gateway 的 IP 跟 Port 连接 Gateway
- 未登录的连接把消息转发到 Session 服务器,Session 负责登录验证、新用户创建、排队管理,通过验证允许登录的玩家通知 World 加载玩家数据
- Session 通过验证允许登录后返回 Gateway 用户信息,Gateway 把用户信息及 Socket 句柄进行绑定,绑定后 Gateway 把客户端消息转发到 World,同时带上用户 ID
- 所有游戏功能及逻辑在 World 进行,包括场景管理、场景、技能、背包及各种系统
- World 提供一个 Http 端口对外访问,主要是 GM 命令及运营活动等相关的数据的接收
每个服务器都跟 Listen 服务器相互连接,通过心跳保持相互的确认,当 Listen 判断某个服务器宕机之后,将会自动拉起相应的服务器
传奇 H5 架构图
- 客户端连接服务器 Skynet 进程
- Gateway 接管连接,管理所有连接句柄,当连接超过最大数或者建立连接一直未登录的 Socket 将断开连接
- 指定登录协议转发到 Login 进行登录验证及新账号注册,未绑定之前除登录相关协议外的协议 Gateway 将忽略
- Login 验证成功后通知 PlayerManager 创建玩家服 Agent,同时带回 Agent 的地址返回给 Gateway,Gateway 对玩家服跟连接句柄进行绑定,后续玩家消息 Gateway 将直接转发给 Agent,Agent 还需负责消息的编解码,避免所有编解码工作堆积到 Gateway 造成性能瓶颈
- SceneManager 管理场景服,常驻地图服务器启动后创建,个人副本地图待玩家请求后创建,Agent 向 SceneManager 请求进入场景,并把场景地址返回给 Agent,后续 Scene 跟 Agent 的交互消息直接通过通信
- 此外还有一些公共服务,比如排行榜服务器、离线服务器、数据库服务器等等,都是 Agent 直接通信消息进行通讯
所有游戏服都将与 Center 服务器连接,Center 服务器管理整个服务器列表,开服关服维护,以及 转发GM 后台消息到指定的游戏服务器执行 GM 指令,游戏服务器启动时会去 Center 获取自己的 IP 跟 Port
全区全服
大逃杀 Demo
服务器采用星状图的架构,服务器都连接 Router 服务器路由,Router 是单点,采用主从备份的方式部署,其他服务器连接主 Router 的同时也连接从 Router
此模型各个服务器之间单点服务,在分区分服的游戏类型里面已经符合设计的要求。全区全服的架构理解在此模型之上进行考虑各个服务器的负载跟平行扩展
-
Router:功能解耦(拆分功能逻辑服务)、系统间路由、无状态、多进程部署平行扩容、提供容灾扩容的基础(某台服务器宕机可以设置为不可用)、需支持多种路由策略
路由策略:- mod 取模
- co_hash 一致性哈希
- random 随机
- master_slave 主备
- backup 备份
1、2 有状态服务,下次请求能找到相应的状态
3 无状态服务
4 请求同时到主备服务器,只有主服务器处理请求
5 主机挂了备份接收请求
-
服务器扩容
-
通讯消息设计
消息头:包含消息 ID,消息长度,
消息 ID:服务器类型 « 7 « 路由策略 « 3 « ID -
Gateway:客户端通过 HTTP 发送请求到 Openresty 获取一个 Gateway 的IP 跟 Port,Openresty 采用随机分配的方式分配,分配好 Gateway 之后客户端后续通过该 Gateway 发送消息到服务器,Gateway 需要做用户跟句柄的映射
-
Login:设计成无状态服务,用户登录时 Router 随机分配一个 Login 服务器,成功后 Gateway 把用户信息跟 Socket 句柄进行绑定,同时通过一致性哈希分配一个 Logic 为玩家所在的逻辑服,客户端消息也是通过玩家 ID 一致性哈希找到玩家所在的逻辑服
-
Match:采用两级匹配机制
- 根据 Logic 分布式的优势分摊匹配压力,在 Logic 内进行匹配
- Logic 提供获取匹配玩家列表接口,Match 通过逆向思维采用主动式的方式进行匹配。例如,需要匹配钻石段位比赛,Match 分别从各个 Logic 里面拿到钻石段位的待匹配玩家进行匹配,此方式可以通过多个 Match 服务器来分摊匹配的压力
- 匹配服匹配好队伍之后通知 FightManager 分配战斗服务器,同时把战斗服务器的信息发送给 Logic,Logic 返回 Fight 的 IP 跟端口让客户端直连 Fight,降低战斗的延时
-
FightManager:只是简单管理战斗服务器,单点采用主备的方式部署
-
Chat:聊天模块按聊天频道(世界/帮会/私聊)分别部署,聊天广播消息直接转发的所有 Gateway 到各个客户端
-
好友、活动、成就等逻辑系统按低耦合、功能简单的原则拆分部署通常是有状态服务,固定服务器数量的采用取模指定玩家所在的业务逻辑服,否则采用一致性哈希
-
DB:部署 32 个 Redis 服务器缓存所有玩家数据,根据哈希值指定玩家所在的 Redis 服务器,每一台 Redis 服务器对应部署一个 DB 代理服务器,玩家下线时在 Redis 的 OffLine 字段标识下线玩家,代理按顺序存储玩家数据到 MySQL,同时设置玩家的缓存过期时间
-
MySQL存储层设计
-
第一种基于增量区间的分片,它的优点是可以实现动态在线扩容,但是存在性能热点的问题,因为新分片永远是访问量最大的分片,而老分片会随着玩家流失出现性能闲置的情况;
-
第二种方法是根据ID 的散列值将数据均匀分散到不同的分片,没有性能热点的问题,但是在对系统进行扩容时候,往往需要对数据进行搬迁,比较难以实现快速自动扩容;
-
第三种方法就是将两者结合,即先分片,片内根据 ID 做散列分布。可以同时解决两个问题,但是需要增加中间数据路由层,有研发负担和性能损耗。
设计相关
-
在线更新
- 数据可以采用共享内存
- 配置支持 reload 操作
- 针对无状态服务能够 disable 服务,待修复后再灰度发布
-
故障隔离
- 非关键核心流程做到可屏蔽
- 定义屏蔽协议,出错时进行友好提示
- 按模块/命令屏蔽对应的操作
-
削峰
- 离线发送道具
- 自动到账改成玩家手动领取
- 道具旁路发送(绕过各种检测直接发放给玩家)
-
第三方系统
- 用一个模块对第三方系统集中包装,做独立部署和流控、支持扩容
守卫雅典娜
- 客户端根据域名获取 Nginx 的 IP(DNS 做简单的负载均衡),连接指定的 Nginx 服务器,Nginx 服务器返回客户端一个 Login 服务器的地址。
- Login 服务器无状态,负责账号创建、登录验证,验证成功后查询 LoginCache,LoginCache 缓存玩家登录状态,如果登录成功设置所在 Role Server 的地址,角色 ID 跟 Role Server 节点的映射关系,别的玩家可以通过 LoginCache 来获得对方的服务器位置,LoginCache 支持部署多个,通过角色 ID 取模定位到指定的 LoginCache,以及玩家服务器 Role Server 的信息,里面记录了每个 Role Server 服务器在线的玩家数量,多个 LoginCache 时玩家登录登出需要写各个LoginCache 的 Role Server 信息。Login 登录验证成功后查找人数最少的 Role Server 服务器指定为玩家所在的服务器,同时把服务器的地址返回客户端。
- 客户端连接 Role Server,此连接就是玩家游戏过程中与服务器交互的连接,Role Server 里面一个玩家一个 agent
- Role Server 根据玩家 ID 去 DBCache 中加载玩家信息,没有则加载 DB
- 加载完玩家信息后通知各个系统服务器加载玩家数据,邮件服务器、好友服务器等
- Mail Server、Friend Server、Chat Server、Activity Server 开指定数量的进程(这些服务器是不管玩家在不在线都会进行相应的服务器),每个进程启动跟线程数相当的服务,每个服务管理若干的玩家,定位玩家所在哪个进程的哪个服务里面,通过角色 ID 对进程数取模指定哪个进程,然后对该进程的服务数进行取模得到玩家所在的服务
- Match Server 负责匹配工作,优先在一个服里面的玩家进行匹配,当不满足匹配条件后把匹配信息发送到 Match Server 进行匹配,匹配成功后发送给 Fight Manager 队伍信息,创建战斗服,并把队伍成员所在的服位置进行映射,同时也发送回战斗服的地址跟服务 ID 给各个玩家所在的服,后续战斗消息直接转发到战斗服
- Fight Server 允许平行扩容,由 Fight Manager 管理,战斗完成后通知 Fight Manager 进行回收,战斗服进程同样开启多个战斗服务,由 Fight Manager 管理,开局的时候通知给玩家服当前玩家战斗的进程地址跟服务 ID
- Center 服务器管理所有服务器开启跟关闭、GM 命令、热更新、运营活动等