同主机运行ZeroRPC Client和Server时端口抢占问题的解决

如果你使用ZeroRPC;
如果你买不起服务器或者拿不到服务器用;
如果你决定把好多服务都放在一个物理主机上跑;

那你很有可能会遇到这个问题,当你的server进程因为某些意外挂了,而client还在不断有请求过来时,你会发现有一定可能server无法重新启动,理由是端口已被占用,看一下就会发觉被client给占了。这很气人,如果不把client全杀了server就起不来,而server起不来client就会一直卡着端口,重启client又不一定那么容易。

真实环境下这个问题真的坑到我一次,于是我决定花点时间解决一下这个问题,所以就做了个workaround。
原理其实很简单,如果client发现心跳包发不出去或者remote超时了,就把自己的连接给关了,下次再调用的时候重新连接。

多数情况下并不需要这套机制,重新建立tcp连接带来的开销是不小的,如果在线上环境请谨慎使用。

[python client]
import zerorpc

class RPCProxy(object):

    def __init__(self, connect_to=None, context=None, timeout=30, heartbeat=5,
                 passive_heartbeat=False):
        self._client = zerorpc.Client(connect_to, context,
                                      timeout, heartbeat, passive_heartbeat)
        self.endpoint = connect_to
        self.context = context
        self.timeout = timeout
        self.heartbeat = heartbeat
        self.passive_heartbeat = passive_heartbeat

    def connect(self, endpoint, resolve=True):
        self.endpoint = endpoint
        return self._client.connect(endpoint, resolve)

    def bind(self, endpoint, resolve=True):
        self.endpoint = endpoint
        return self._client.bind(endpoint, resolve)

    def __call__(self, method, *args, **kwargs):
        if self._client._events._socket.closed:
            self._client = zerorpc.Client(self.endpoint, self.context, self.timeout,
                                          self.heartbeat, self.passive_heartbeat)
        try:
            result = self._client(method, *args, **kwargs)
            return result
        except zerorpc.exceptions.LostRemote, e:
            self._client.close()
            raise e
        except zerorpc.exceptions.TimeoutExpired, e:
            self._client.close()
            raise e

    def __getattr__(self, method):
        return lambda *args, **kargs: self(method, *args, **kargs)

夸一句,python里 __getattr____call__配合使用真是精妙,很简单就模仿了local代码的调用方式。

[node.js client]
var zerorpc = require('zerorpc');

var rpcproxy = function _rpcproxy (endpoint){
    this.client = new zerorpc.Client();
    this.client.connect(endpoint);
    this.endpoint = endpoint;
    return this;
};

rpcproxy.prototype.invoke = function() {
    if (this.client.closed()){
        this.client = new zerorpc.Client();
        this.client.connect(this.endpoint);
    }
    var hasCallabck = arguments.length && typeof(arguments[arguments.length-1]) == 'function';
    if (hasCallabck){
        this.callerCallback = arguments[arguments.length-1];
    }
    var argList = [];
    for (var i = 0; i < hasCallabck?arguments.length-1:arguments.length; i++){
        argList.push(arguments[i]);
    }
    eval('this.client.invoke('+this.makeArgListString(argList)+',this._callback.bind(this));');
};

rpcproxy.prototype.makeArgListString = function(argList){
    var argString = []
    for (var i in argList){
        argString.push('argList['+i+']');
    }
    return argString.join(',');
};

rpcproxy.prototype._callback = function(err, res, more){
    if (err && err.name == 'HeartbeatError' || err.name == 'TimeoutExpired') {
        this.client.close();
    }
    if (this.callerCallabck){
        this.callerCallback(err, res, more);
    }
};

js的实现使用了比较邪恶的eval方法,不过也不用担心不安全,因为组织后的字符串是由argList[0], argList[1]...组成的,并不包含实际的值,不会被执行一些奇怪的东西。

如有错误,欢迎指正。以上。

你可能感兴趣的:(同主机运行ZeroRPC Client和Server时端口抢占问题的解决)