最后更新于 .

发现想抽时间写博客实在是太难了,不过我觉得今天这一篇还是很值得写一下的。

熟悉我的同学应该都知道,我之前做了一款《矩阵危机》的产品,使用的是帧同步的技术。

简单画一下V1架构图:

  • Gateway

    网关服务器。

    负责客户端连接的接入,使用协议TCP。

    使用C++编写。

  • KcpProxy

    Kcp协议的代理服务器。

    客户端默认使用Kcp连接服务器,如果失败会自动回退到Tcp。

    使用C++编写。

  • RoomServer

    房间服务器。运行战斗逻辑,每个房间同时仅能运行一场战斗,帧率为15帧/秒。

    房间服务器与Gateway间通过Tcp连接,每个房间建立一个一条独立的连接。

    使用Python编写。

整个架构还是比较清晰的,但是里面有个极大的问题:性能

因为python的性能实在是太差了,对于RoomServer每秒15帧这种cpu密集型的业务场景完全不适合。

至于python的性能有多差,我当时做过一个简单的测试,同样的业务代码,c++是python性能的10倍左右。

可能直接说这个数字大家也没什么感觉,但是要知道换算成服务器的话,那就是10倍的服务器量,10倍的成本。

所以这也是要做架构升级的原因。

而升级的方案也有多种,其中一个方案如下:

其核心逻辑是将CPU密集的RoomServer放到Gateway中去,而额外多出来一组RoomController负责对Gateway和Room进行控制。

RoomController可以继续使用python实现。

这样的方案虽然解决了性能问题,但是却导致gateway的功能过于耦合,不是好的设计方案。

还有一个方案就是将RoomServer直接使用C++重写,每个Room开一个线程。

这样确实能解决性能方面的问题,但是也有自己的缺点:

  1. C++的开发维护成本太高
  2. 每个Room开一个线程的设计并不好,线程切换的代价太高

那么既然已经想到替换语言这一步了,有没有别的语言可以选择呢?

找来找去,我发现了有一门语言天生就是来干这件事情的:Go

先说下Go语言本身的优点:

  1. 原生支持协程

    这可是非原生协程打patch模拟出来的效果所比不了的。Python的gevent每次在用的时候都有种心虚的感觉,更不用说有些库还和gevent不兼容(如mysqlclient)。

    Python3引入的async/await等关键字也是各种限制,生怕用到不支持异步操作的库。

    而Go则完全不用担心,其协程支持是语言级的,其所有的标准库/第三方库都是可以放心使用的。

    另外协程的数量也是基本不用担心数量的问题,几万的量级完全不在话下。

  2. 协程支持多核

    这个特性简直太棒了。之前因为Python多线程无法利用多核的问题,很简单的代码都要分成多进程的模型。

    而Go直接将多核利用抽象在了底层,对上层看到的都只是协程。

    当然,如果习惯了gevent的话,用Go的协程一定要小心,即使一个变量的访问没有涉及到IO访问,在并发访问时也是要加锁的。

  3. 编译型语言

    Go的编译速度极快无比,让你有时候甚至怀疑他是不是执行了编译。

    而且Go支持交叉编译,即你可以在Windows上编译出Linux的可执行文件。

    所以Go是可以跨平台的,并且是直接编译成了对应平台的字节码,没有用到虚拟机。这也大幅增加了程序反编译的难度,如果你想对外提供二进制程序的话,会更加安全。

    当然,这些也是有些代价的:

    1. 编译出来的可执行文件偏大
    2. 在代码中不能使用Cgo

    而与Python对比的话,到底是编译型语言还是脚本更加适合业务编码呢?

    这个地方可能见仁见智吧。

    自己年轻的时候特别喜欢Python语言,感觉写起来很随意,没有那么多约束。很像年轻的自己,没有那么多顾忌。

    而到了现在这个年纪,却逐渐怀念起编译型语言的好来,那种编译通过就很少有错误的感觉还是更有安全感一些。

  4. 自动GC

    可以像Python一样简单的编写代码,又不用特别考虑每一次内存释放的感觉实在是太棒了。

  5. 运行效率极高

    Python就不用比了,即使与C++相比,也是毫不逊色。

    而且如果C++代码写的不好的话,可能效率还不如用Go写的。

  6. 大量的性能调优工具

    毕竟Go诞生的初心就是为了解决服务器端高并发问题,所以Google给配备了完善的性能调优工具,这个绝对是超级利器。

  7. 易于团队维护

    Go对很多规范做了约束,比如 { 必须在上一行末尾,要用tab缩进等等,甚至还单独提供了GoFmt来自动。

    我一开始也不是很习惯,毕竟C入行这么多年,对于类型后置这种写法还是不太习惯,但是后来学会偏见,你会发现Go的这些做法都是有原因的。

    比如类型后置var foo *int可以直接避免掉 int* foo 还是 int *foo的争论,哈哈。

    而且其实Python/Typescript中引入的类型声明类型都是后置的,只是分割符是用的 : 而已。

    一旦过了磨合期之后,你就会发现Go的代码写起来越来越顺畅,基本不会比Python慢多少。

    而于此同时,团队所有人写的代码风格都是一样的,很难出现C/C++时代风格迥异的代码了。

最近已经逐渐在把所有的Python技术架构都通过Go来实现了。

得益于Go的高度抽象能力,代码实现的速度比想象得快的多,只是要注意改变之前的编码思路:

不要通过共享内存来通信,而应该通过通信来共享内存

这个其实是个很重要的概念,说起来可能很简单,但是实际开发的时候就会发现还是蛮多要注意的地方的,具体这里就不说了,大家可以自己写一些代码试试。

回到我们的帧同步服务器上。

最终我们使用Go实现了RoomServer,也即我们的V2架构,架构图如下:

基本的架构与V1一样,只是RoomServer使用Go来实现了。

Go在这里与RoomServer实在是太契合了,我们将每个Room的帧循环运行在单独的协程上,这样即使同时启动几千个房间也没有任何问题。

而新引入的RoomBackend则是为了将一些业务逻辑与房间服务器解耦,比如游戏的结算等等。
RoomBackend对性能要求比较低,而且为了减少开发量,就还是复用了之前的Python代码。 RoomServer与RoomBackend之间的通信是标准的request/response模式,我用的是TCP+自定义协议的模式,其实后来发现用Grpc可能也不错。

整个就是这样了,睡觉。。

Pingbacks

Pingbacks已打开。

Trackbacks

引用地址

评论

暂无评论

发表评论