最近工作上做了一些关于跨服玩法的内容,对于 RPC 通信实现这块做下学习笔记整理。

分布式架构

当下的大环境下,服务器架构基本都是分布式架构。分布式架构带来的最大好处在于服务扩展,我们 SLG 游戏服务器从游戏特性上来说,符合小服架构(一个服即一个生态),这样从分布式架构的设计理念,按功能划分,网关、支付、战场(这里不是指单服的战斗要实现一个节点,而是一些副本玩法,跨服玩法等设计的)、第三方等,这样的每个 server 我们称之为一个节点(Node)。

RPC

RPC:允许运行于一台计算机的程序调用另一个计算机的程序。RPC是一种服务器-客户端(Client/Server)模式。

RPC 的核心概念即调用远程服务就像调用本地的一个函数一样简单。

通过原理我们知道 RPC 需要实现的几个点:

  • 通信:在两个 server 之间建立连接
  • 寻址:如何定位需要调用 x-Server
  • 序列化:两个 server 之间是网络通信,那么二进制传输就需要序列化

一些常用的框架(轮子必然是有的):

  • gRPC: 谷歌开源 RPC 框架,基于 HTTP/2 协议通信和 ProtoBuf 序列化协议
  • Thrift: Apache 旗下,基于 Facebook 的 RPC 框架开发
  • JsonRPC: 无状态、轻量级,基于 json 序列化

框架选择:两个项目,早期的一个选择 thrift (当时 gRPC 才开源初期),另一个选择了 gRPC ,对比来说,两者在使用上区别不是很大,但是 gRPC 能拥有良好的文档,更加简洁和拥有广泛使用的 ProtoBuf,而 thrift 的大优势是支持多语言。选择根据项目自身的特性来,对于我们项目,文档和简洁比较重要。(技术选型还得是项目初始大佬们的选择:joy:)

节点间通信

如上,RPC 框架选择 gRPC,基于此,整个通信流程:

client -> gateway -> game server -> other server -> 处理后返回

游戏服务器一般选择长连接,因为游戏特性:交互频繁,数据量大。RPC 当然也有这种特性,所以 gRPC 也支持一次调用一次创建和流式(stream)调用两种方式,这个根据业务的情况进行处理。client 与 game server 的通信采用了双向 steam 方式,client 通过和 gate 建立 tcp 连接(一个 agent),然后通过 LoginReq 来创建和 game server 的流式管道(中间还有玩家信息验证服交互,目标服务器 check 等操作 | 目标服的 connect 信息通过 etcd 获取, etcd 做服务发现和管理),每个 agent 本身会有一个 pipe mgr 来管理这些创建的管道,用于和不同的 server 之间进行消息收发。这样就建立起 client <—> game server 的通信连接。

既然节点间的通信就是 client/server 的模式,那么 game server 和 battle server 之间的服务调用也就差别不大。以此为例,简述我们 gs 和 bs 之间的服务调用:

  • 一次调用:gRPC Asyn Call 调用 bs 的服务器,通过异步回调返回执行的数据,并处理返还给 client
  • steam: 每个 server 都有一个自己的 gate mgr 用于管理 RPC 消息通信和 agent data,首次调用 bs 服务时创建一个 steam 连接,后面调用直接 sendMsg
  • 没有玩家 agent 的 stream 调用:每个 server 都创建一个 owner agent 并且在需要的时候和目标 server 之间建立一个 steam pipe,来完成多次调用

举例说明:

我现在有个副本玩法,需要开房间实现 pvp:

-> clientA 通过 loginBattleReq 创建和 bs 的 steam 连接,并且在 bs 开启一个房间

-> clientB 通过 joinBattleReq 同样创建一个 stream 连接,加入到 A 的房间

-> 然后 bs 房间业务逻辑执行,A vs B 战斗开始

-> SLG 的一个特性,不同于 moba 游戏玩法,这里业务上可能多个玩家要持续一段时间 pvp 获得积分,在结束时,玩家可能已经不在线,steam 和 agent 也销毁了,结算信息传递给 gs 就需要通过 owner agent 来实现了。由此,一个完整的节点通信模型就实现了。

总结

  • 目前的这套架构实现比较简单,弱耦合单向依赖 gRPC call、强耦合双向依赖 stream,已经能满足当下的基本需求,其次在编码方面做了一些封装,对于开发人员来说比较方便。
  • 当需求变化伴随节点细分变多,耦合变重,那么网络拓扑也会变成一个问题,这种情况下的 steam 就不能很好的胜任。