网易在2012年11月开源的一个网游服务器框架,使用javascript作为开发语言,运行在node.js环境下。具体说明请查阅:https://github.com/NetEase/pomelo
pomelo项目主页上已经有比较详细的文档说明,本文是我在阅读其源代码过程中对其整体架构整理出来的一点补充文档。欢迎对服务器架构有兴趣的同学对本文多提意见。
servers包下每个服务器一个子包,子包下每个文件为一个module,module中export的方法为可被分派请求的method。以某场景服务器的文件结构为例说明:
> filter: 该服务器各module.method执行各阶段的filter(before,after等)
> handler: 处理客户端请求,客户端通过connector server的socket通道发送过来的request请求,请求路由格式:serverType.module.method
> remote包: 非客户端直接请求,而是由服务器发出的rpc调用处理逻辑
> 前端(webSocket): 配置中有wsPort
> 后端(rpc): 配置中有port
> master: master.json配置文件中指定(注: wsPort与port均有,但不属于前两者)
> 所有服务器: monitor
> master服务器: 启动所有服务器及相应的监控/统计等服务
> 非master服务器均会启动:
> server: 服务器对外服务接口(路由解释、转发、请求处理)
> proxy: rpc客户端代理, 服务器帐号策略由app配置, 默认路由算法: server_index = Math.abs(crc32(uid) % sever_count)
> channel: 为广播消息服务
> 前端服务器:
> connection: 统计用
> connector: 客户端与服务器的直接连接, 可在加载connector组件时指定使用自己实现的connector,以选择合适的连接模式(tcp/websocket)或数据通信协议(例如protobuff)
> session: session管理
> rpc服务器:
> remote: rpc服务器组件
> localSession: 由connector发送消息时copy过来的session数据
async: 解决回调函数嵌套造成可读性差的问题
stream-pkg: js对象序列化与反序列化
服务器之间通信采用rpc调用的方式,依赖pomelo-rpc项目,pomelo-rpc项目使用stream-pkg库序列化rpc通信数据,可选择使用wsSocket或tcpSocket连接模式。
remote与proxy组件结合pomelo内部使用的rpc协议分别实现了rpc-server与rpc-client的功能封装。
n pomelo内部rpc协议:
msg{
route: nameSpace.serverType.module.method // 路由信息
args: [] // 调用参数
}
n nameSpace只有两种:
l user: 对应app.rpc方法; 由服务器内部组件发起的rpc调用,无需经过common.remote包下的系统rpc服务处理;最终逻辑由app.servsers.serverType.remote.modulel.method执行
l sys: 对应app.sysRpc及app.rpcInvoke方法;pomelo系统内部处理的rpc事件,需经过common.remote包下的系统rpc服务处理:
² sessionRemote: 仅在前端服务器(connector)提供,以供后端服务器将其本地的session同步到前端服务器(bind/push之类方法)
² channelRemote: 仅在前端服务器(connector)提供,实现广播消息(后端要广播消息时可调用此rpc服务)
² msgRemote: 仅在后端服务器提供,通过forwardMessage方法委托server组件按路由信息(serverType.module.method)分派消息到对应的handler处理
n application中三个rpc方法说明:
l app.sysRpc: namespace为sys为rpc调用封装
l app.rpc: namespace为user的rpc调用封装
l app.rpcInvoke: 直接的rpc调用,与pomelo-rpc/client中的rpc调用方法一致,需指定所有调用参数(context, serverId, namespace, service, method, args)
注:app.sysRpc中的service对应app.rpc(app.rpc)参数中的module
1) 客户端请求的rpc调用事件处理流程(路由格式:serverType.module.method)
2) 服务器发起的rpc调用事件处理流程
3) rpc服务器启动流程
namespace的分类方法是从实现角度来说。sys包括:
1) 系统提供的一些辅助服务(sessionRemote与channelRemtoe), 突显connector负责为所有后端服务器广播消息给客户端,从而直到隔离了后端服务器的作用;
2) 系统实现基于长连接模式(wsSocket)下的c/s架构的request/response通信框架(msgRemote的forwardMessage消息转发服务)。
根据上述两大类基础功能,我们可将sys命名空间内的rpc调用理解为客户端与服务器之间的基础通信框架,故pomelo将其实现放在common.remote包下。
user命名空间下,pomelo未作特别处理,pomelo-server库直接把rpc调用分派到对应的由pomelo使用者开发的后端服务器rpc接口上,故称这个命名空间为user,这是对pomelo user而言。
这个分类方法略显复杂,尤其是仅从代码上看不太直观。其中的路由函数(同类服务器下的serverId选择策略)则更不易察觉(在初始化rpc-client时初始参数中指定),可由app配置,不配置则使用默认路由函数(components.proxy.defaultRoute)。
关于connector: Pomelo虽然声明可使用自己实现的connector以更改接入服务的底层通信协议,但connector组件使用到的connector实现(默认为sioConnector与sioSocket)并未被抽象出来(即没有一个类似connector接口之类的声明)
pomelo的服务器架构属于比较主流(connector与gate server+业务服务器),其"多进程胜于多线程"的思想有值得借鉴的地方。虽然代码量不大,但层次之间的耦合度较高,依赖很多,导致有些地方不直观。当然,这个也与js的代码风格有关。
较为不直观的地方:
>服务器启动(master启动所有服务器)
> rpc请求的路由
> rpc服务
(待续…)