POMELO 採用多进程的架构能够非常好的实现游戏server(进程)的扩展性,达到支撑较多在线用户、减少server压力等要求。

进程间通信採用RPC的形式来完毕,pomelo的RPC实现的相当静止。

採用相似例如以下的方式就能够调用remoteserver提供的服务:

proxies.user.test.service.echo(routeParam, 'hello', function(err, resp) {
if(err) {
console.error(err.stack);
return;
}
console.log(resp);
});

上面的一段RPC调用能够理解为:

调用namespace类型为user、server类型为test的service模块的echo接口

如今听着有些拗口。没关系,且听我慢慢来分析:)


服务端源代码分析

pomelo-rpc的源代码我阅读+debug了不下30次,以下我将按照从底层数据交换模块到上层业务逻辑分发处理的方式依次介绍服务端与client的源代码架构。

1. 基于socket.io模块的数据通信模块

一般来说我们在写socket数据通信模块有几个问题是必需要去解决的,譬如说:
  • 粘包的问题
  • 丢包以及乱序的问题
  • ip地址过滤
  • 缓冲队列的实现
  • 与上层模块的交互模式
这里把pomelo-rpc实现过的来说一说。nodejs 内置一个events模块。这也导致了把一个模块封装成一个事件收发器是相当自然的一件事情:
var Acceptor = function(opts, cb) {
EventEmitter.call(this);
this.bufferMsg = opts.bufferMsg;
this.interval = opts.interval || 300;
this.whitelist= opts.whitelist; this._interval = null;
this.sockets = {};
this.msgQueues = {}; this.server = null;
this.notify = cb;
}; util.inherits(Acceptor, EventEmitter);

利用node内置的util提供的继承函数。简单两句话Acceptor继承了events.翻开nodejs源代码 inherits 函数的实现也是相当简单:

var inherits = function(sub, super) {
var tmp = function() {}
tmp.prototype = super.prototype;
sub.prototype = new tmp();
}

通过这样的寄生组合式的继承避免了调用两次父类的构造函数,这里就不多展开了。

看到Acceptor构造函数接收一些配置信息:

bufferMsg: 配置是否启用缓冲队列interval: 配置定时数据发送模块的间隔, Acceptor开启监听的时候。依据配置信息来确定是否开启一个定时器,定时刷新缓冲:

    if(this.bufferMsg) {
this._interval = setInterval(function() {
flush(self);
}, this.interval);
}

flush函数主要做的是负责把缓冲的数据通过socket.io接口写出去:

var flush = function(acceptor) {
var sockets = acceptor.sockets;
var queues = acceptor.msgQueues;
var queue, socket; for(var socketId in queues) {
socket = sockets[socketId];
if(!socket) {
delete queues[socketId];
continue;
}
queue = queues[socketId];
if(!queue.length) {
continue;
}
socket.emit('message', queue);
queues[socketId] = [];
}
};

每一个client链接相应一个数据缓冲队列,通过发送’message’消息的方式把数据发出。


IP地址过滤

开启监听后,假设有client链接(on connection 事件),第一件事情是IP地址过滤,IP地址白名单也是通过构造函数注入:whitelist.若IP地址非法则关闭链接,输出警告信息。


数据处理模块

上层模块通知配置信息注入一个notify回调函数, acceptor监听到数据后首先把数据抛给上层。上层处理完毕后推断假设需要缓冲则写入队列。否则立即发送出去:

 acceptor.notify.call(null, pkg.msg, function() {
var args = Array.prototype.slice.call(arguments);
for(var i = 0, l = args.length; i < l; i++) {
if(args[i] instanceof Error) {
args[i] = cloneError(args[i]);
}
}
var resp = {id: pkg.id, resp: Array.prototype.slice.call(args)};
if(acceptor.bufferMsg) {
enqueue(socket, acceptor, resp);
}
else {
socket.emit('message', resp);
}
});

2. 路由请求分发模块

架在acceptor模块上面的是gateway模块,该模块主要负责acceptor模块的创建销毁以及状态控制。首先在创建acceptor模块的时候传入一个函数:

this.acceptor = this.acceptorFactory.create(opts, function(msg, cb) {
dispatcher.route(msg, cb);
});

通过工厂方法来构建一个acctpor实例。这样底层数据处理模块能够方便的更换通信协议。这里回调函数做的一个工作是调用分发函数,把请求交给详细的服务提供方。来看看dispatcher的实现:

var Dispatcher = function(services) {
EventEmitter.call('this');
var self = this;
this.on('reload', function(services) {
self.services = services;
});
this.services = services;
};
util.inherits(Dispatcher, EventEmitter);

相同Dispatcher模块也变成一个事件收发器。同一时候构造器接收一个services參数。

依据改參数配合路由请求时传入的參数,就能把请求交给详细的子模块。

所以,dispatcher.route(msg, cb);仅仅只是是匹配下參数调用相应接口罢了。看到构造器还监听了一个reload事件,该事件有什么作用呢?这事实上就是pomelo的RPC 热拔插模块的实现。实现起来比較简单:

var watchServices = function(gateway, dispatcher) {
var paths = gateway.opts.paths;
var app = gateway.opts.context; for(var i = 0; i < paths.length; i++) {
(function(index) {
fs.watch(paths[index].path, function(event, name) {
if(event === 'change') {
var res = {};
var item = paths[index];
var m = Loader.load(item.path, app);
if(m) {
res[namespace] = res[namespace] || {};
for(var s in m) {
res[item.namespace][s] = m[s];
}
}
dispatcher.emit('reload', res);
}
});
})(i);
}
};

gateway模块在启动的时候会依据配置信息调用一个watchServices监听模块的变化。假设数据文件发生变化则又一次载入services并通知路由分发模块。

为了保证server与client的正常通信。除了底层数据格式的一致,另一个是路由信息的匹配。假设调用Gateway传入的配置路径是例如以下形式:

var paths = [
{namespace: 'user', path: __dirname + '/remote/test'}
];

假设当前文件夹下有/remote/test/service.js文件。文件包括两个接口test1/test2

load之后返回的对象形式例如以下:

{
service: {
test1: 'function xxx',
test2: 'function yyy'
}
}

同一时候在pomelo你们有系统RPC服务以及自己定义RPC服务,完整的路由信息例如以下:

services: {
sys: {
sys_module1: {
sys_module1_interface1: 'xxx'
}
},
user: {
user_module1: {
user_module1_interface1: 'yyy'
}
}
}

服务端其它东西都比較简单了,为了理清楚脉络,以上代码是经过删减的。假设有兴趣能够到这里取。

pomelo研究笔记-RPC服务端的更多相关文章

  1. Hadoop RPC源码阅读-服务端Server

    Hadoop版本Hadoop2.6 RPC主要分为3个部分:(1)交互协议 (2)客户端(3)服务端 (3)服务端 RPC服务端的实例代码: public class Starter { public ...

  2. 使用 neon-wallet-db + neon-js + NEO-cli /rpc 搭建轻钱包服务端

    本文将搭建一个不具有任何功能的NEO轻钱包,所有的精力都仅集中于成功运行neon-wallet-db项目并搭配全节点的neo-cli /rpc接口为轻钱包客户端提供服务. 首先需要准备几个项目: ne ...

  3. 基于netty实现rpc框架-spring boot服务端

    demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...

  4. java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

    说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...

  5. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  6. Thrift 个人实战--RPC服务的发布订阅实现(基于Zookeeper服务)

    前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...

  7. RPC服务的发布订阅实现Thrift

    Thrift 个人实战--RPC服务的发布订阅实现(基于Zookeeper服务) 前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的 ...

  8. 【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.什么是RPC RPC是"远程调用(Remote Procedure Call)"的一个名称的缩写,并不是任何规范化的 ...

  9. NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成

    本篇内容属于非实用性(拿来即用)介绍,如对框架设计没兴趣的朋友,请略过. 快一个月没有写博文了,最近忙着两件事;    一:阅读刘墉先生的<说话的魅力>,以一种微妙的,你我大家都会经常遇见 ...

随机推荐

  1. bzoj2763: [JLOI2011]飞行路线(分层图spfa)

    2763: [JLOI2011]飞行路线 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3234  Solved: 1235[Submit][Stat ...

  2. bzoj1708[Usaco2007 Oct]Money奶牛的硬币(背包方案数dp)

    1708: [Usaco2007 Oct]Money奶牛的硬币 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 763  Solved: 511[Submi ...

  3. Docker 常用命令和命令集结

    常用命令 查看版本 docker version 查看系统信息 docker info 显示 Docker 系统信息,包括镜像和容器数. 搜索镜像 docker search keyword 从 Do ...

  4. Swagger UI改造 增加 Token验证、显示控制器注释、自定义泛型缓存应用、

    /// <summary> /// Swagger 增加 Token 选项和控制器描述 /// </summary> public class CustomOperationF ...

  5. 基于Myeclipse+Axis2的WebService开发实录

    最近开始学习了下在Myeclipse开发工具下基于WebSerivce的开发,下面将相关相关关键信息予以记录 Myeclipse的安装,本文以Myeclipse2014-blue为开发环境,相关配置执 ...

  6. 【HDU1698】 Just a Hook 【线段树入门】

    原题:原题链接 题意:(机器翻译的...) 让我们将钩子的连续金属棒从1到N编号.对于每次操作,Pudge可以将连续的金属棒(从X到Y编号)改为铜棒,银棒或金棒. 钩的总值计算为N个金属棒的值的总和. ...

  7. 2-bitmap

    在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数. 思路: bitmap用一个bit来代表存在还是不存在,现在我们要判断重不重复,则需要三个状态:不存在,存在一个,存在多个.2b ...

  8. java热部署

    最近使用java做项目,研究了一下热部署,能够提高工作效率. 需要准备的工具: 1.安装文件http://update.zeroturnaround.com/update-site/ 2.破解 下载破 ...

  9. buf.readInt16LE函数详解

    offset {Number} 0 noAssert {Boolean} 默认:false 返回:{Number} 从该 Buffer 指定的带有特定尾数格式(readInt16BE() 返回一个较大 ...

  10. poj1101 the game 广搜

    题目大意: 类似于连连看,问从起点到终点最少需要几条线段. 规则: 1.允许出界. 2.空格的地方才能走. 分析: 题目做下来发现没有卡时间,所以主要还是靠思路.也就是说不用考虑离线算法.直接以每个起 ...