如果你使用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]...组成的,并不包含实际的值,不会被执行一些奇怪的东西。
如有错误,欢迎指正。以上。