• pomelo研究笔记-RPCclient


    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. 客户端状态控制
    2. 远程服务端信息管理
    3. 过滤器
    4. 消息路由

    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模块的大体流程图:
    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模块基本了解完了,想要了解很多其它到这里下载代码

  • 相关阅读:
    AOP 学习
    微服务架构理解[架构图](转)
    C# TSC打印二维码和条形码(转)
    C#注册表操作类--完整优化版(转)
    C#调用TSC条码打印机打印二维码(转)
    C#调用TSC条码打印机打印条码(转)
    TSC打印机使用教程终极版(转)
    海尔电商峰值系统架构设计最佳实践(转)
    亿级Web系统搭建——单机到分布式集群(转)
    数据库扩展性设计:使用二进制解决一条记录关联多个状态的问题(转),可以尝试一下
  • 原文地址:https://www.cnblogs.com/blfshiye/p/5102291.html
Copyright © 2020-2023  润新知