Yivgame是用go语言基于go-kit写的一套微服务架构游戏服务器方案,它不是一个框架,是一整套游戏服务器实例,每个模块只保留了一份示例代码实现。除了游戏服务器(长连接),还包含针对前端和后台运营的API接口服务器,运营后台的界面会使用Angular实现。 除了服务器本身之外,还会涉及docker部署的详细配置。
- 微服务架构
- 客户端与游戏服务器通过 grpc 双向流(bidirectional streaming)实现透传
- 客户端与服务端websocket通信
- 实现 http endpoints 和 websocket endpoints 度量衡和日志
- 通过微服务架构,将传统的游戏服务器拆分成了不同的微服务
- 不同的微服务器可通过grpc进行同步通信和通过kafka进行异步通信
- 为了实现软件不同层次的解耦,各服务统一都参照领域驱动模型进行设计
- 将微服务的软件结构由内至外分成游戏领域业务层、用例层、接口层和设施依赖层
- 各层之间严格遵守由外向内的单向依赖
业务层主要实现游戏或服务器的是核心逻辑,不关心外部实现,对文件系统、数据库等的依赖,业务层使用interface定义接口,由依赖层实现接口方法,并在main中通过依赖注入的方式传递给业务层调用。所以业务层除了引用一些基本的标准库外,几乎不引用第三方包。
- 整个微服务系统之间的核心通信方式是grpc同步调用和以kafka作为流平台的异步事件通信
- 所有微服务内的被关注的活动都会以产生事件的方式发布到kafka,具有不同关注点的消费者分别订阅各自感兴趣的事件,kafka将事件推送给消费者,消费者做相以的事件处理和响应
- 由于由于go-kit实现的微服务架构,所以在目录结构上尽量与go-kit的官方示例保持一致
- 由于领域驱动模型是分层的,所以在设计工程目录结构的时候很自然的会把内层包的目录包含在外层内,由于我比较喜欢大多数go工程的偏平目录结构,所以没有严格按着领域层次来设计目录结构,反而是把不同层次包的目录放在了同级的目录下面,对我来说,这样显得直观、简单一些
- 为了让软件在代码逻辑上面更加清晰,严格避免全局变量
- 所以玩家数据直接保存在服务内存中,便于直接进行数据处理
- 玩家数据的修改通过WAL方式写入kafka,再由存盘服务异步地写入数据库
- 由于使用了WAL方式,则玩家数据的redo和undo则很容易实现
- 数据持久化使用支持分布式事务的关系数据库CockroachDB
- 使用CockroachDB可以很轻松的实现水平扩展、故障容错和自动恢复、负载均衡
我从v1.0开始使用CockroachDB,从v1.0到v1.0.6,CockroachDB在特定情况和压力下,一直存在崩溃的问题,自从v1.1发布,崩溃问题没再出现,但是性能一直没有大的改善。因为yivgame的数据几乎都存在内存中,只有存盘的时候需要写db,所以对整个yivgame系统来说,不存在db性能瓶颈。
- 通信方式
- HTTP:http为作短连接,主要用于后台运营系统的通信,另外,游戏中涉及到强交互的数据通信部分,也可以用http来通信
- WebSocket:客户端使用cocos creator开发,长连接通信支持WebSocket,WebSocket主要用于游戏中实时和强交互的难通信
- GRPC:基于HTTP/2协议GRPC,可以实现在一个socket连接上进行多stream通信,是go微服务生态中比较通用的通信方式
- 数据格式
- JSON:由于json格式的自解释性,主要将它作在游戏中短连接和后台运营系统接口的数据交换
- Protobuf:主要用于客户端与服务端websocket间和微务间的数据交换
- Agent:主要用于客户端的接入,它直接将数据报文透传、转发到后端微服务,是一个傻网关、薄网关,几乎不参与业务逻辑和编解码业务数据,所以其代码逻辑相对简单,也极易进行水平扩展
- UserCenter:所有玩家数据集中在 user center 进行管理,由 user center 负责游戏数据的读写删改查,它提供grpc接口供apigate、game server等其它需要请求玩家数据微服务使用
- Game Server:主要负责游戏业务逻辑的处理
- 服务与服务之间使用jwt进行身份认证
- 通过API gateway实现单点登陆
- 全局认证,每服务鉴权(do authentication globally, and authorization in every microservice)
- docker:所有依赖设施服务和游戏实例通过docker社区版进行布署
- rockcoach:作为持久化数据库
- kafka:作为message queue和stream platform
- etcd:用于服务发现
- gogs:使用gogs进行版本管理
- bind9:域名服务器,通过切换域名解析实现开发、测试网络的无缝切换
- yiv/gk: go-kit 代码生成器是一把手电钻,go-kit目前发现的唯一的不爽就是写一个服务太啰嗦,它设计了一套很优雅的服务输出方式,但为了写一个服务接口,每个都要写一整套endpoint、set和transport,代码模式都是相同的,这一大坨大坨的代码基本相似,写多了就会很烦燥,感觉在做重复的工作,而且极易出错,找了几遍都没有发现完全符合go-kit官方示例的generator,最后选中了kujtimiihoxha/gk,但它还没有很多地方并不完善,也不完全适用于我,于是fork下来自己改,通过它自动生成代码,在写服务接口的时候可以美减少60%的重复代码,更重要的是,它极大地降低了出错的概率。
- Ubuntu Server 16.04
- gonet/2: yivgame从gonet吸取了很多设计,如使用stream进行透传、引入kafka等
- go-kit: yivgame基于go-kit开发
- goddd: 一个用go写的基于领域模型的样例APP
- Practical Persistence in Go: Organising Database Access
- The Clean Architecture
- Applying The Clean Architecture to Go applications
- 一篇文章读懂分层架构
- 系统的复杂性只会转移,不会消失,直白简单背后都是脏活累活,简单都是有成本的,要么降低性能、要么回避一些特性如扩展性,要么由其它人来做,使用 go-kit 的好处是它看起来不那么简单,把设计目标直接体现在代码里,学习使用 go-kit 有助于提高软件设计能力
- go-kit 不适合追求易上手、短平快的目标,它使用分层来分离关注,必定引入复杂性,代码看起来可能穿插啰嗦,但是关注分离的设计有助于逻辑解耦
- go-kit 始于 service interface,始于关注业务领域,http 或是 grpc 只是对外发布的方式,放在最后处理
- 不追求写法上的自由,要追求软件适应性的自由,自由不是说我想怎么写就怎么写,代码要有规范,团队要统一编程风格,才便于沟通,go-kit 不自由,因为他定义了自己软件的设计范式,导致的结果就是用它写出来的服务,看起来都长得差不多。牛逼到把代码写得像幅画,还能编译运行,只适合玩,不适合拿来做工程。
- 微服务调用的编解码和通讯,引入的延时成本大概为 2 毫秒,国内互 Ping 延时通常为 40ms
- 无论是框架还是语言,都只是工具,挑一个业界优秀的、趁手的、合适的,用好用熟用深,没必要把时间花在争论和纠结最好的几个当中哪个是才是最好的,即使工具是最好的,用得不好也是烂,关键是学习和掌握它们的设计精髓,才能万变不离其宗