网络游戏架构的前世今生——微服务

上文:网络游戏架构的前世今生——热更

3.7 微服务

记得两年前,在知乎上看到一则回答,详细阐述了网络游戏不能应用微服务的原因。当时的我正好在 aws 研究微服务在游戏行业的应用,看到了不少日本游戏制作商应用微服务的案例,产生了想在知乎上回复的想法。但我知道网络上重拳出击没有任何意义,语言是苍白的,这些公司案例大部分都不能直接对外讲。而且最关键的一点是,我并不清楚内部的实现细节,在上海给数家游戏公司去讲游戏微服务时,我的内心是虚的,究竟怎么落地生产?工程上如何执行?那时候的我也不知道如何去实现游戏微服务。我只知道很多游戏公司用容易来测试、部署游戏后端,但容器并不是微服务——容器只是技术手段,微服务是理念架构。

是什么(what)、为什么(why)、怎么做(how),是我思考一个问题的基础逻辑,思考游戏微服务也是这个逻辑。在本章中我们重点解决是什么和为什么的问题,而怎么做是整个专栏的话题。

首先,微服务是什么?我所理解的微服务是将单一应用程序划分成一组小的服务,服务满足低耦合、高内聚的特性。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通。每个服务都围绕着具体业务进行构建,并且能够独立地部署到生产环境、类生产环境等。

关于微服务的定义还有很多阐述,例如有说“应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据上下文,选择合适的语言、工具对其进行构建。”在我看来,这些表述是表象,或者说是扩展后的微服务定义。但实则,微服务是对单体应用程序的一种拆分方式,我们上文讲的网关服,也是从逻辑服中拆分的一种方式。这种拆分方式唯一需要满足的特点是——低耦合、高内聚

网络游戏架构的前世今生——微服务_第1张图片

 

服务与服务之间低耦合。游戏组队功能故障,不影响正常匹配,匹配时不让玩家组队就可以。游戏商店功能故障,不影响玩家查看自己的背包,例如炉石传说就是这样。游戏中有很多功能模块,传统的写法是将多个功能模块包在一个应用程序内,牵一发而动全身,组队功能有一个崩溃点或是内存泄漏,那就会导致匹配连带着崩溃无法使用,因为这两个模块是写在一个应用内的。而如果我们在最开始,将功能模块拆分,彼此互不影响,通过 API 进行交互,那就是初步的低耦合。

网络游戏架构的前世今生——微服务_第2张图片

 

你可能会认为,单体应用做好错误异常处理就不会有崩溃。理想是好的,但我从来没在任何一个商业游戏项目里看到过完美全覆盖的错误异常处理,工程上怎么保证程序的完美无瑕呢?况且就算应用做到了理想中的完美无瑕,来自操作系统层的偶发性问题导致的崩溃,又该如何规避呢?Linux 系统内核都不能做到完美无瑕不出问题。要能意识到一切都有可能出错,为了容错而设计架构和编写程序,而不是盲目自信的认为程序不会出错

Everything will ultimately fail —— Michael Nygard

服务内高内聚。为了满足低耦合,是不是服务越多越好?并不是,需要满足服务内高内聚的特性。一个服务要精准定位它的用户场景,例如我们写一个组队服务,那这个服务的边界范围就是组队相关的所有功能,创建队伍、成为队长、踢出成员、移交队长、离开队伍等,这些功能聚合在一起形成组队服务,而不能再把这些功能点单独拆除去。为什么?拆的太零散会导致链路过于复杂、性能差、难维护等,失去了微服务的初衷。高内聚就是指,将强相关的所有功能写在一起,这些功能可以描述一个完整的用户场景,也就是可以完全独立进行使用,不依赖其他服务。

其次,游戏为什么微服务?有如下几个优势:

  1. 稳定
  2. 可独立部署
  3. 精确缩放
  4. 开发人员友好
  5. 迭代速度快

前三点主要是游戏运维、游戏测试层面考虑。稳定性主要体现在低耦合之上。服务与服务间的低耦合,保证了 bug 的影响面最低。由于服务与服务之间是低耦合的,所以我们完全可以单独部署其中某一个服务,例如组队,测试部门就可以针对性的进行功能测试和性能测试,而不需要像单体应用那样每次都做整体性的测试。如果我们要测试组队匹配功能,那就需要部署组队+匹配两个服务,还是可以专注在测试的功能点本身之上,如果商店有什么严重 bug 导致完全无法使用,这个 bug 完全不会影响到组队匹配功能的测试与部署,这就是低耦合的好处。

精确缩放是指,我们可以精确的指定每个服务分别使用多少 CPU 多少内存,最大化利用服务器资源。如果这个服务支持多开——指能够水平扩展到任意数量,那我们还可以精确的缩放该服务的数量。即玩家少时开一个实例,玩家多了根据策略动态调整服务数量。

这里提到了不少点,例如限制服务资源、服务多开、服务单独测试,但并没有展开讲述具体怎么做。这些都是后文中会单独拆开讲述的。

网络游戏架构的前世今生——微服务_第3张图片

 

后两点是游戏开发层面考虑。如果游戏工作室中只有一个服务器开发,那这两点是否成立存疑。如果项目不小,那我们至少需要一个后端团队来做服务器开发,那这时这两点随着项目功能越加越多,会体现的越来越明显。如果是做一个单体应用,开发者们不得不在同一个应用下干活,代码冲突是常事,几乎每个开发者都必须掌握各功能模块的实现,必须了解模块的启动顺序。这在项目后期维护时尤为痛苦。而如果我们使用微服务,那么每位开发可以单独负责几个服务的开发,开发小唐专门负责组队、匹配这两个服务的开发,需要调用其他服务时只需要看接口文档即可,不需要知道其他服务是谁写的,怎么实现的。同时,每个服务增加/修改/删除一个功能点,要比在一个大的单体项目中做同样的操作简单很多,编译、验证所需的时间明显更低。

如果你用 C++ 开发过游戏后端的话,应该也饱受编译时间长的折磨,特别是公司没买联合编译的软件。

网络游戏架构的前世今生——微服务_第4张图片

 

理论上,完全重写一个核心服务只需要两周时间。当然以我当前项目的实践经验,你可能需要给开发三周到四周,这也远比在一个大的单体应用内,重写一个核心功能模块轻松。事实上,到了项目后期,当需要在一个单体应用内重写一个功能模块是非常痛苦的一件事,不管需要多久时间,验证和测试的过程就会狠狠折磨一个开发——你需要保证写的功能正确的同时,其他功能也都能正常工作!

最后,让我们回顾一下上一章留下的一个尾巴,如何做滚动更新。对于那些支持多开(水平扩展)的服务,我们可以直接应用滚动更新。即更新时保留旧版本不动,新开一个新版本的服务,将网关路由指向新版本的服务,等到旧版本所有请求处理完毕时,再发出关闭信号,等最后扫尾工作做完,结束旧版本的生命周期。

服务如何能做到多开,例如组队服务如何做到多开?那些组队的状态信息如何存储?且待下一大章 《数据库方案演进》 分解。

你可能感兴趣的:(游戏微服务架构实践,游戏,服务器,微服务,架构)