一个RPC客户端可能同时需要调用多个远端(server)提供的服务,在pomelo里每个server
抽象为一个mailbox
。先来看看mailbox的实现:
var MailBox = function(server, opts) {
EventEmitter.call(this);
this.curId = 1;
this.id = server.id;
this.host = server.host;
this.port = server.port;
this.protocal = server.protocal || ‘http:‘;
this.requests = {};
this.timeout = {};
this.queue = [];
this.connected = false;
this.closed = false;
this.opts = opts;
this.timeoutValue = 1000;
this.buffMsg = opts.buffMsg;
this.interval= 300;
};
util.inherits(MailBox, EventEmitter);
配置信息比较简单,相比服务端客户端多了一个超时的处理:
var id = this.curId++;
this.requests[id] = cb;
setCbTimeout(this, id, cb);
var pkg = {id: id, msg: msg};
if(this.buffMsg) {
enqueue(this, pkg);
}
else {
this.socket.emit(‘message‘, pkg);
}
curId
可以理解为通信过程中的序列号,每次自增,唯一标示一个数据包,通常用来解决数据包的乱序问题。如果buffMsg
被设置则启用缓冲队列,和服务端一致。在发送数据之前会开启一个定时器,如果超时则回调通知上层。
mailstation
主要实现了几个功能:
- 客户端状态控制
- 远程服务端信息管理
- 过滤器
- 消息路由
消息路由模块采用延迟加载的方式,加给mailstation添加远程服务端配置信息的时候没有马上加载一个mailbox
与之对应,而是在真正对该服务器请求服务的时候创建对应的实例:
var lazyConnect = function(station, serverId, factory, cb) {
console.log(‘lazyConnect create mailbox and try to connect to remote server‘);
var server = station.servers[serverId];
var online = station.onlines[serverId];
if(!server) {
console.log(‘unkone server: ‘ + serverId);
return false;
}
if(!online || online !== 1) {
console.log(‘server is not onlone: ‘ + serverId);
}
var mailbox = factory.create(server, station.opts);
station.connecting[serverId] = true;
station.mailboxes[serverId] = mailbox;
station.connect(serverId, cb);
return true;
};
首次请求服务的时候先通过lazyConnect
建立链接,并把请求加入待处理任务队列:
var addToPending = function(station, serverId, args) {
console.log(‘add pending request to pending queue‘);
var pending = station.pendings[serverId];
if(!pending) {
pending = station.pendings[serverId] = [];
}
if(pending.length > station.pendingSize) {
console.log(‘station pending too much for: ‘ + serverId);
return;
}
pending.push(args);
};
pemelo实现了before
和after filter
可以注入函数在请求发生之前以及之后做一些处理:
var doFilter = function(err, serverId, msg, opts, filters, index, operate, cb) {
if(index < filters.length) {
console.log(‘doFilter ‘ + operate + ‘filter‘ + filters[index].name);
}
if(index >= filters.length || !!err) {
utils.invokeCallback(cb, err, serverId, msg, opts);
return;
}
var self = this;
var filter = filters[index];
if(typeof filter === ‘function‘) {
filter(serverId, msg, opts, function(target, message, options) {
index++;
if(utils.getObjectClass(target) === ‘Error‘) {
doFilter(target, serverId, msg, opts, filters, index, operate, cb);
}
else {
doFilter(null, target || serverId, message||msg, options||opts, filters, index, operate, cb);
}
});
return;
}
if(typeof filter[operate] === ‘function‘) {
filter[operate](serverId, msg, opts, function(target, message, options) {
index++;
if(utils.getObjectClass(target) === ‘Error‘) {
doFilter(target, serverId, msg, opts, filters, index, operate, cb);
}
else {
doFilter(null, target || serverId, message||msg, options||opts, filters, index, operate, cb);
}
});
return;
}
index++;
doFilter(err, serverId, msg, opts, filters, index, operate, cb);
};
看起来有点乱:),采用递归的方式依次调用各个过滤器。
来看个mailstation模块的大体流程图:
架在mailstation
模块上面的是服务端代理模块。该模块完成了对服务端的抽象,使得调用远程服务变的十分优雅。
Client.prototype.addProxies = function(records) {
if(!records || !records.length) {
return;
}
for(var i = 0, l = records.length; i < l; i++) {
this.addProxy(records[i]);
}
};
上层通过addProxies
接口添加远程服务器配置信息,客户端模块会自动为该服务生成代理:
var generateProxy = function(client, record, context) {
if(!record) {
return;
}
var res, name;
var modules = Loader.load(record.path, context);
if(modules) {
res = {};
for(name in modules) {
res[name] = Proxy.create({
service: name,
origin: modules[name],
attach: record,
proxyCB: proxyCB.bind(null, client)
});
}
}
return res;
};
和服务器端配置类似,record
注入一个文件路径,我们需要加载该文件提供的模块。如果record.namespace
为:user
, 远程服务器类型为test
, record.path
对应的文件路径为: /remore/test/service.js
该文件导出两个模块分别包含一个接口:func1
和func2
。在模块加载完毕之后对应的路由信息大致如下:
proxies : {
user: {
test: {
module1: {
func1-Proxy: ‘xxx‘
},
module2: {
func2-Proxy: ‘zzz‘
}
}
}
}
最终会为每个服务端的每个接口生成一个代理:
var genObjectProxy = function(service, origin, attach, proxyCB) {
var res = {};
for(var field in origin) {
if(typeof origin[field] === ‘function‘) {
res[field] = genFunctionProxy(service, field, origin, attach, proxyCB);
}
}
return res;
};
var genFunctionProxy = function(serviceName, methodName, origin, attach, proxyCB) {
return (function() {
var proxy = function() {
var args = Array.prototype.slice.call(arguments);
proxyCB.call(null, serviceName, methodName, args, attach);
};
proxy.toServer = function() {
var args = Array.prototype.slice.call(arguments);
proxyCB.call(null, serviceName, methodName, args, attach, true);
};
return proxy;
})();
};
可以看到我们看到所有接口的代理都是通过封装一个proxyCB函数来完成的。来看看proxyCB
的实现:
var proxyCB = function(client, serviceName, methodName, args, attach, target) {
if(client.state !== STATE_STARTED) {
console.log(‘fail to invoke rpc proxy client not running‘);
return;
}
if(args.length < 2) {
return;
}
var cb = args.pop();
var routrParam = args.shift();
var serverType = attach.serverType;
var msg = {namespace: attach.namespace, serverType: serverType,
service: serviceName, method: methodName, args: args};
if(target) {
target(client, msg, serverType, routrParam, cb);
}
else {
getRouteTarget(client, serverType, msg, routrParam, function(err, serverId) {
if(!!err) {
utils.invokeCallback(cb, err);
}
else {
client.rpcInvoke(serverId, msg, cb);
}
});
}
};
serviceName
表示模块名称,method
对应模块下的接口名称, args是调用接口传入的参数数组。attach
表示原始的record
路径信息。这里有个getRouteTarget
接口,我们知道当远程有多个提供类似服务的服务器为了均衡负载,需要把请求尽量平均的分配到各个服务器。
这样RPC模块基本了解完了,想要了解更多到这里下载代码
原文地址:http://blog.csdn.net/gentlycare/article/details/46425813