pomelo研究筆記-RPC用戶端,pomelo筆記-rpc
1. mailbox資料收發模組
一個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
被設定則啟用緩衝隊列,和服務端一致。在發送資料之前會開啟一個定時器,如果逾時則回調通知上層。
2. mailstation 訊息路由
mailstation
主要實現了幾個功能:
1. 訊息路由
訊息路由模組採用消極式載入的方式,加給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);};
2. 過濾器
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模組的大體流程圖:
3. 服務端代理模組
架在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模組基本瞭解完了,想要瞭解更多到這裡下載代碼