
今天在知乎上看到这样一个问题:“为什么游戏公司的 server 不愿意微服务化?”这个问题挺有意思,恰好戳中了一个很多人容易忽略的事实:微服务并非万能钥匙,在游戏领域它甚至会拖后腿。下面整理两位答主的回答,聊聊背后的技术逻辑。
背景介绍
笔者最近去面试了一家游戏公司,规模不小,已经上市了。
我问面试官,公司有没有做微服务架构的打算和考量。对方很惊讶地说:“微服务我没怎么听说过,你能不能先解释一下?”
我大致说了方便测试、方便维护、方便升级、服务之间松耦合、可多语言开发、自动扩容之类的优点。对方听完后说,游戏 server 不太需要微服务,因为游戏对 realtime 要求很高,做微服务反而会影响性能,分模块来开发就够了。
说实话当时我也不太确定,但微服务不是大公司的趋势吗?游戏 server 的服务应该很容易拆分才对,怎么会不适用呢?
hongjic93 是这样回答的
拿 MOBA 类游戏来说,比如王者荣耀、LOL。想想王者荣耀的客户端,账号系统、符文系统、英雄系统、皮肤系统、好友系统、好友之间的 messaging,这些外围功能如果流量够大,当然可以走微服务架构。
但这些都不是游戏的核心。核心是 MOBA——Multiplayer Online Battle Arena。它的特性是什么?是 10 个人之间各种游戏事件的高速多向通讯,streaming、broadcast、multicast、pubsub 各种通讯模式齐上阵。
所以游戏真正的核心在于小规模群体之间的高速网络通信,也就是面试官说的 realtime。多出 10ms 的延迟,玩家就要骂娘了。拆开来看,至少有几个关键原因:
-
微服务为了把业务完美拆解,把原来的同一个进程里的模块拆分成不同的服务,显著增加了额外的网络开销。更别提 service mesh、各种 gateway、proxy、sidecar,在游戏场景里简直是嫌延迟不够高。
-
微服务基本只有 request/response 这一种模式。做不了 streaming 吗?微服务通常要求应用是无状态的才能做到水平扩展,而 streaming 本身就引入了状态。
-
可以想象一下,为了提高通讯性能,一场英雄联盟对局很可能会由同一个服务器负责这 10 个玩家之间的通讯。这样数据可以在本地交换,性能最大化。这要求客户端或服务端统一网关必须支持 sticky routing——假设客户端连接断了,接下来必须重连回之前那台服务器。而微服务强调 stateless、水平扩展,本身就与 sticky routing 背道而驰,因为 sticky routing 本质就是状态。
-
从服务端集群的角度看,同时有无数场王者荣耀比赛在进行,每一场都可以看成一个沙盒,每个沙盒都处于不同状态:塔被推了几座、你被击杀了几次、对面几个人超神了、20 分钟到了没。这些都是长时间存在的状态,直到游戏结束,服务端才能清理整场游戏的状态。虽然这些状态不需要写入持久化存储,但必然会在内存中驻留很久。反正有状态,就别想用微服务。除非把这些状态移到 Redis 里去,那服务器在信息流传输到一半还要额外做一次 remote request,一来一回延迟又上去了。想象一下对方在 A 你的水晶,每一次普攻都是一个 event 被 streaming 到服务端沙盒,沙盒里的流处理器收到事件后立刻计算水晶爆了没。这个计算必须极快,你不可能把水晶生命值存在远端。
这类游戏对网络、内存、CPU 的优化需求都极高。整个游戏进行过程中几乎不存在 RPC call,真正需要 remote data 也是在游戏开局时 prefetch 预加载好。
说到底,微服务不是什么银弹,也就是方便拆解一下原来的 CRUD 应用罢了。它既没有触及高级的交互方式,也没触及分布式系统真正的难点——状态,其实没有大家想的那么万能。之所以感觉微服务好像改变了互联网,只不过是因为 90% 的互联网应用都只是简单小规模的 CRUD 而已。
面试官没听说过微服务完全没有问题,这本来就不是什么高深的概念。反而对方一听你的描述就能判断出微服务不适合游戏,说明他对游戏系统设计的理解足够深入。
brice 是这样回答的
我做过棋牌游戏,算是游戏里最简单的一类,可以从实际经验说几个点:
-
微服务本身是为了应对业务逻辑的复杂而提出的新组织接口方式。但游戏本身逻辑并没有那么复杂,大厅就是一些基本功能,比如改账号、登录等,核心玩法就是游戏自身的逻辑。
-
游戏逻辑服务器(比如斗地主这类棋牌)因为网络响应性能的要求极高——玩家对每个操作的反馈时长敏感度远高于业务系统——所以游戏服务器一定是有状态的,状态就放在内存里,偶尔接受 Redis,但直接读写 MySQL 绝对不可以。关系型数据库只用来定时异步持久化数据,仅从游戏服务器层面说,持久化到 Redis 就够了。
-
游戏服务器一般需要主动推送,第一代微服务网关根本满足不了这个需求。TCP 协议也没有对应的网关可用,Spring Cloud Gateway 的 WebSocket 或许能凑合,但从防攻击的角度讲,端游用 TCP 显然比 WebSocket 更合理。
-
服务间通信的 RPC 方案,Ribbon、Feign 这类基于 HTTP 的并不合适,HTTP 存在消息乱序的问题。比如玩家连续出两次牌,用 HTTP 就可能出现顺序不一致。游戏服务器集群一般使用长连接互联,可能需要用 dubbo?听说是长连接方案。
-
游戏逻辑服务器(比如斗地主服务器)一般不能用 Spring MVC 来做,因为线程模型完全不同。多线程模型处理游戏逻辑性能差而且非常复杂,通常采用单进程/线程驱动固定数量房间的方式。这也是为什么服务器一定有状态、一定不能直接读写 MySQL。一般直接用 Netty。
-
自动扩容在游戏领域叫做“开服”,早就有固定流程、工具和限流方式了。
-
游戏里很多操作不存在服务降级和熔断,出错就直接报错给用户。
-
大厅服务器的登录注册等功能确实可以做微服务,但其实也没必要真正微服务化,只需要几个接口具备自动水平扩容的方案就够了。服务注册发现在游戏里用处不大,开服都是提前确定的事情,还要配合一系列运营手段,关服也绝对不能随便关。
-
游戏处理的流量真的不算多。在线 1 万的棋牌游戏已经很赚钱了,10 万在线就是特别厉害的产品了。
-
一些独立的服务器比如充值系统需要微服务化吗?只能说,如果这种服务器都需要上微服务来应对流量,项目组做梦都能笑醒。
虽然上面列举了很多反对理由,但游戏集群也不是完全不能用 Spring Cloud 改造。游戏集群同样有注册中心、需要服务发现、需要编排启动顺序,只是 Spring Cloud 没有为游戏场景专门设计。比如至少要完全支持 WebFlux 才行,需要一个单线程的长连接 RPC 框架,最好支持 Protobuf,并且集成服务发现相关接口。网关需要支持 TCP,或者至少封装、暴露一些 Netty 的 decoder/encoder,允许注入自定义组件。这些需求在目前的 Spring Cloud 生态里都还比较薄弱。
想深入理解微服务架构的适用边界和落地细节,可以到云栈社区与技术人一起交流,那里有丰富的分布式与微服务实战资源。
|