pomelo研究筆記-RPC用戶端,pomelo筆記-rpc

來源:互聯網
上載者:User

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實現了beforeafter 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該檔案匯出兩個模組分別包含一個介面:func1func2。在模組載入完畢之後對應的路由資訊大致如下:

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模組基本瞭解完了,想要瞭解更多到這裡下載代碼

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.