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. Cent OS 6/7 中通过yum安装软件时提示cannot find a valid baseurl...的解决方法

    目录 1 问题描述 2 解决方法一 (Cent OS 7中有效) 3 解决方法二 (Cent OS 7中无效) 1 问题描述 新申请了虚拟机, 系统版本是Cent OS 7.2. 在安装软件的过程中, ...

  2. JDK多任务执行框架(Executor框架)

    Executor的常用方法 为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效地进行线程控制.它们都在java.util.concurrent包中,是JDK开发包的核心. ...

  3. Chrome 最小化恢复之后部分黑屏

    解决办法:设置->显示高级设置->关闭硬件加速

  4. SQLServer 里的三种条件判断的用法:Where GroupBy Having

    HAVING 子句对 GROUP BY 子句设置条件的方式与 WHERE 子句和 SELECT 语句交互的方式类似.WHERE 子句搜索条件在进行分组操作之前应用:而 HAVING 搜索条件在进行分组 ...

  5. (转)JavaScript深入之从原型到原型链

    构造函数创建对象 我们先使用构造函数创建一个对象: function Person() { } var person = new Person(); person.name = 'Kevin'; co ...

  6. i合拍应用客户端安卓项目源码

    i合拍是一款新型的拍照社交App它可以让你和朋友,亲人,陌生人合拍一张照片参与别人的合拍,体现自己的创意使用说明:使用了gpuimage进行滤镜渲染 实现远程合拍,分享合拍等一系列功能. 手机app下 ...

  7. SAP computer之input and MAR

    Input and MAR Below the program counter is the input and MAR block. It includes the address and data ...

  8. appium处理app与web页面的转换

      测微信页面的时候使用谷歌app,进入微信页面的链接 def setUp(self): print("set up env for android testing...") se ...

  9. Pjax无刷新跳转页面实现,支持超链接与表单提交

    什么是pjax? 当你点击一个站内的链接的时候,不是做页面跳转,而是只是站内页面刷新.这样的用户体验,比起整个页面都闪一下来说, 好很多. 其中有一个很重要的组成部分, 这些网站的ajax刷新是支持浏 ...

  10. spring cloud(五) hystrix

    开启feign 熔断 hystrix    整合hystrix-dashboard监控面板 1. 服务调用者boot工程 pom引入依赖 <!-- hystrix-dashboard 监控依赖 ...