有了单进程的服务器之后,在我想加入第一个游戏功能的时候,我又纠结了,这货不是我最后想要的东西啊,我就算用单进程写完了整个游戏,也绝不该是这样的啊,我想要的是一个多进程的服务器,而且进程之间应该是可以通信的,进程还可以是动态增删的。这样才可以扛住我自认为会出现的压力啊。


于是我开始思考,我的进程之间需要怎么去通信,数据该怎么去传输,服务器间的进程通信跟服务器与客户端的通信方式又不相同,跟客户端的通信,需要保证安全,同时需要数据量尽量的小,而服务器进程间却通常不存在这样的问题,因为大部分的服务器进程通常都会在同一台机器上,即使在不同的机器上,也会是在外网保护之下,所以内部通信基本不需要考虑安全问题,当然,这也是我一面之词,我是这么认为的,所以我想要一种快速,高效,而且简单的通信方式,我想要通信的双方可以随便进入或者离开,我想要进程的变化不影响现有的逻辑,而且我也不想要去限制进程的启动顺序,因为那样就必然会涉及到进程的状态需要同步,同时我还想要pomelo那样的无状态进程,这样可以方便增删进程来改变服务器的承载能力。


众里寻他千百度,我发现了 zmq。一颗闪亮的积木。虽然 LGPL 协议给它抹了一点黑,但是依然掩盖不了这种设计的光彩,它基本满足我的所有想法。


zmq 的官方文档写的非常有意思,建议大家都去看看,我在 github 上找到了一个非常不错的中文翻译:https://github.com/anjuke/zguide-cn

基本上把这个文档的内容全部看完,对 zmq 就了解完了,这也是它简单的体现,nodejs 的 zmq 绑定存在一些问题,但都不是太大的问题,我用着用着就解决掉了。我通过 zmq 首先设计的模型是 req - rep 模型,也是最简单,最基础的模型,当然在 req 和 rep 之间,我使用的是 route-dealer 组件来做消息的分发,又因为此时我已经大致想到了游戏所需的各个功能模块,于是我同时也抽象出不同的服务器进程,然后让他们之间能够相互发送消息,整个设计看起来就是这个样子的:


上面这个看起来像菊花一样的东西就是我最初的设计,所有的进程逻辑进程都通过 router-dealer 来进行中转,每个进程都有若干的请求链接req,也有若干的响应链接rep,由于有多个不同的进程,所以每个进程都需要保存跟其他进程通信的 zmq链路,zmq 的协议,并不是一个 req 能够对应多个 rep,而是 req 和 rep 数量需要一致,一开始我觉得这很ok,可写着写着就发现,这货明显过于复杂了,而且似乎也没有发挥出 zmq 最大的力量,于是我又埋头开始读 zmq 的文档。RTFM 也许是一个痛苦的过程,但是 zmq 的文档写的非常不错,赏心悦目,我也发现了一个意外的惊喜,zmq 的异步管家模式,这种模式其实是使用 dealer 链路代替 req 和 rep 链路,这样就可以采取一对多的方式进行消息的分发,也就不会再像我之前那样维护一朵菊花了。npm 库里有另一个老外写的 zmq 管家模式:https://www.npmjs.com/package/pigato,不过他写的还不满足我的需求,我就自己动手,用 js 写了一套符合我口味的异步管家模式,后来新招了一个后台,他在我介绍之后立马就理解了,并且迅速的动手进行了功能的添加,证明我写的还是不错的:)


有了这个分发器之后,我很快就把上面的菊花改的漂亮多了,同时我还设计了自己的通信协议,装逼的支持了很多数据类型,事实上根本没有用,然后用 lua 写了一个协议解析程序,可以根据协议配置文件,我用的是 ini 文件,来生成对应的 lua 版本和 js 版本的协议代码:

接着,我还设计了一个命令行进程来管理我的这一套服务器进程系统,于是图变成了这个样子:


每个进程提供一条请求链路和一条工作链路,从请求链路发送请求信息,工作链路用来接收别的进程发来的请求信息并处理返回。由 broker 来对请求和工作回复进行转发,pub 和 sub 链路是为了监听命令行进程的消息而准备的。这样服务器的基本框架就算完成了,我也长长出了一口气。


菊花设计的出现说明了我并没有完全的理解 zmq 的行为模式,同时也表明了我对 tcp 协议的理解还欠火候。下一篇我将继续改进这个设计。