发现想抽时间写博客实在是太难了,不过我觉得今天这一篇还是很值得写一下的。
熟悉我的同学应该都知道,我之前做了一款《矩阵危机》的产品,使用的是帧同步的技术。
简单画一下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开一个线程。
这样确实能解决性能方面的问题,但是也有自己的缺点:
- C++的开发维护成本太高
- 每个Room开一个线程的设计并不好,线程切换的代价太高
那么既然已经想到替换语言这一步了,有没有别的语言可以选择呢?
找来找去,我发现了有一门语言天生就是来干这件事情的:Go
先说下Go语言本身的优点:
原生支持协程
这可是非原生协程打patch模拟出来的效果所比不了的。Python的gevent每次在用的时候都有种心虚的感觉,更不用说有些库还和gevent不兼容(如mysqlclient)。
Python3引入的
async/await
等关键字也是各种限制,生怕用到不支持异步操作的库。而Go则完全不用担心,其协程支持是语言级的,其所有的标准库/第三方库都是可以放心使用的。
另外协程的数量也是基本不用担心数量的问题,几万的量级完全不在话下。
协程支持多核
这个特性简直太棒了。之前因为Python多线程无法利用多核的问题,很简单的代码都要分成多进程的模型。
而Go直接将多核利用抽象在了底层,对上层看到的都只是协程。
当然,如果习惯了gevent的话,用Go的协程一定要小心,即使一个变量的访问没有涉及到IO访问,在并发访问时也是要加锁的。
编译型语言
Go的编译速度极快无比,让你有时候甚至怀疑他是不是执行了编译。
而且Go支持交叉编译,即你可以在Windows上编译出Linux的可执行文件。
所以Go是可以跨平台的,并且是直接编译成了对应平台的字节码,没有用到虚拟机。这也大幅增加了程序反编译的难度,如果你想对外提供二进制程序的话,会更加安全。
当然,这些也是有些代价的:
- 编译出来的可执行文件偏大
- 在代码中不能使用Cgo
而与Python对比的话,到底是编译型语言还是脚本更加适合业务编码呢?
这个地方可能见仁见智吧。
自己年轻的时候特别喜欢Python语言,感觉写起来很随意,没有那么多约束。很像年轻的自己,没有那么多顾忌。
而到了现在这个年纪,却逐渐怀念起编译型语言的好来,那种编译通过就很少有错误的感觉还是更有安全感一些。
自动GC
可以像Python一样简单的编写代码,又不用特别考虑每一次内存释放的感觉实在是太棒了。
运行效率极高
Python就不用比了,即使与C++相比,也是毫不逊色。
而且如果C++代码写的不好的话,可能效率还不如用Go写的。
大量的性能调优工具
毕竟Go诞生的初心就是为了解决服务器端高并发问题,所以Google给配备了完善的性能调优工具,这个绝对是超级利器。
易于团队维护
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可能也不错。
整个就是这样了,睡觉。。
评论
暂无评论