coordinator-node-client
Version:
分布式协调服务node客户端,用于获取微服务的可用地址,参照eureka-js-client.
619 lines (601 loc) • 21.1 kB
JavaScript
/**
* Copyright (c) 2010-2015 EEFUNG Software Co.Ltd. All rights reserved.
* 版权所有 (c) 2010-2015 湖南蚁坊软件有限公司。保留所有权利。
* Created by liwenjun on 2016/4/13.
*/
var Eureka = require("eureka-js-client").Eureka;
var DefaultConfig = require("./DefaultConfig").config;
var merge = require("deepmerge");
var request = require("request");
var util = require("./utils");
var proxyAgent = require('socks5-http-client/lib/Agent');
var _async = require('async');
var _merge = require('lodash/merge');
var _ = require('lodash');
function noop() {
}
/*
EurekaWrapper对象
@param config EurekaWrapper参数
*/
function EurekaWrapper(config) {
this.logger = null;
this.init(config);
}
/*
初始化
*/
EurekaWrapper.prototype.init = function (config) {
config = this.initConfig(config);
this.fetched = false;//是否获取到数据了
this.cachedServices = {};//历史服务信息
this.changedServices = {};
this.addedServices = {};
this.removedServices = {};
this._eureka = new Eureka(config);
this.setLogger();
rewriteFunc(this);
this.addListener();//监听服务变化
}
/**
* 监听服务变化
*/
EurekaWrapper.prototype.addListener = function () {
//初始化外部监听器
this.initOutListener();
var _this = this;
if (_this._eureka) {
//获取到所有服务更新后的处理
_this._eureka.on("registryUpdated", function () {
//第一次获取数据
if (!_this.fetched) {
_this.fetched = true;
//启动后存储一份服务
if (_.isEmpty(_this.cachedServices) && !_.isEmpty(_this._eureka.cache)) {
_this.cachedServices = _this._eureka.cache;
}
} else {
//对比服务
_this.compareServices();
}
});
//启动成功后
_this._eureka.on("started", function () {
});
}
}
/**
* 初始化监听器
*/
EurekaWrapper.prototype.initOutListener = function () {
this.listeners = {};
this.listeners[util.ADD] = [];
this.listeners[util.REMOVED] = [];
this.listeners[util.CHANGED] = [];
}
/**
* 解除监听
*/
EurekaWrapper.prototype.unbindListeners = function () {
this.listeners[util.ADD] = [];
this.listeners[util.REMOVED] = [];
this.listeners[util.CHANGED] = [];
this.listeners = {};
}
/**
* 对比服务
*/
EurekaWrapper.prototype.compareServices = function () {
var _this = this;
if (this._eureka) {
_.forEach(this._eureka.cache.app, function (instances, appId) {
var cachedInstances = _this.cachedServices.app[appId];
//缓存中有应用的实例
if (cachedInstances && cachedInstances.length > 0) {
//应用的实例个数不一致,说明有变化
if (cachedInstances.length != instances.length) {
_this.changedServices[appId] = instances;
} else {
_.forEach(cachedInstances, function (oldInstance, oldKey) {
//找到一样的实例
var findedInstance = _.find(instances,
{
"port": {"@enabled": oldInstance.port["@enabled"], "$": oldInstance.port["$"]},
"ipAddr": oldInstance.ipAddr,
"status": oldInstance.status,
"app": oldInstance.app,
"vipAddress": oldInstance.vipAddress
});
//如果没有找到一样的实例,说明有变化
if (!findedInstance) {
_this.changedServices[appId] = instances;
//没找到则返回
return false;
}
});
}
} else {
//之前没有相应的服务,则添加到addedServices
_this.addedServices[appId] = instances;
}
});
var differenceAppKeys = _.difference(_.keys(this.cachedServices.app), _.keys(this._eureka.cache.app));
_.forEach(differenceAppKeys, function (appId, key) {
//之前有的服务被删除了,则加到removedServices
_this.removedServices[appId] = _this.cachedServices.app[appId];
});
if (!_.isEmpty(this.removedServices) || !_.isEmpty(this.addedServices) || !_.isEmpty(this.changedServices)) {
this.handleChanges();
}
}
this.cachedServices = this._eureka.cache;
}
/**
* 服务更新后处理变化。
*/
EurekaWrapper.prototype.handleChanges = function () {
var services = [];
if (!_.isEmpty(this.addedServices)) {
services = this.addedServices;
_.forEach(this.listeners[util.ADD], function (fn) {
fn(util.serviceHandler(services));
});
this.addedServices = {};
}
if (!_.isEmpty(this.changedServices)) {
services = this.changedServices;
_.forEach(this.listeners[util.CHANGED], function (fn) {
fn(util.serviceHandler(services));
});
this.changedServices = {};
}
if (!_.isEmpty(this.removedServices)) {
services = this.removedServices;
_.forEach(this.listeners[util.REMOVED], function (fn) {
fn(util.serviceHandler(services));
});
this.removedServices = {};
}
}
/**
* 添加新增服务的监听
* @param fn
*/
EurekaWrapper.prototype.onAdded = function (fn) {
this.listeners[util.ADD].push(fn);
}
/**
* 移除新增服务的监听
* @param fn
*/
EurekaWrapper.prototype.unbindAddedListener = function (fn) {
removeListener(util.ADD, fn, this.listeners);
}
/**
* 添加删除服务的监听
* @param fn
*/
EurekaWrapper.prototype.onRemoved = function (fn) {
this.listeners[util.REMOVED].push(fn);
}
/**
* 移除删除服务的监听
* @param fn
*/
EurekaWrapper.prototype.unbindRemovedListener = function (fn) {
removeListener(util.REMOVED, fn, this.listeners);
}
/**
* 添加服务改变的监听
* @param fn
*/
EurekaWrapper.prototype.onChanged = function (fn) {
this.listeners[util.CHANGED].push(fn);
}
/**
* 移除服务改变的监听
* @param fn
*/
EurekaWrapper.prototype.unbindChangedListener = function (fn) {
removeListener(util.CHANGED, fn, this.listeners);
}
/**
* 移除监听
*/
function removeListener(type, fn, listeners) {
if (listeners && listeners[type] && listeners[type].length > 0 && _.isFunction(fn)) {
var index = _.indexOf(listeners[type], fn);
if (index != -1) {
var start = _.slice(listeners[type], 0, index);
var end = [];
if (index + 1 < listeners[type].length) {
end = _.slice(listeners[type], index + 1);
}
listeners[type] = _.concat(start, end);
}
}
}
/*
重写方法
*/
function rewriteFunc(eurekaWrapper) {
Eureka.prototype.fetchRegistry = eurekaWrapper.fetchRegistry.bind(eurekaWrapper);
Eureka.prototype.register = eurekaWrapper.register.bind(eurekaWrapper);
Eureka.prototype.renew = eurekaWrapper.renew.bind(eurekaWrapper);
Eureka.prototype.deregister = eurekaWrapper.deregister.bind(eurekaWrapper);
}
/**
* Build the base Eureka server URL + path
*/
/*
Helper method for making a request to the Eureka server. Handles resolving
the current cluster as well as some default options.
*/
EurekaWrapper.prototype.eurekaRequest = function eurekaRequest(opts, callback) {
var _this = this.getEureka();
var retryAttempt = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
(0, _async.waterfall)([
/*
Resolve Eureka Clusters
*/
function (done) {
_this.clusterResolver.resolveEurekaUrl(function (err, eurekaUrl) {
if (err) return done(err);
var requestOpts = (0, _merge)({}, opts, {
baseUrl: eurekaUrl,
gzip: true
});
done(null, requestOpts);
}, retryAttempt);
},
/*
Apply Request Middleware
*/
function (requestOpts, done) {
_this.requestMiddleware(requestOpts, function (newRequestOpts) {
if ((typeof newRequestOpts === 'undefined' ? 'undefined' : typeof newRequestOpts) !== 'object') {
return done(new Error('requestMiddleware did not return an object'));
}
done(null, newRequestOpts);
});
},
/*
Perform Request
*/
function (requestOpts, done) {
var method = requestOpts.method ? requestOpts.method.toLowerCase() : 'get';
request[method](requestOpts, function (error, response, body) {
done(error, response, body, requestOpts);
});
}],
/*
Handle Final Output.
*/
function (error, response, body, requestOpts) {
if (error) _this.logger.error('Problem making eureka request', error);
// Perform retry if request failed and we have attempts left
var responseInvalid = response && response.statusCode && String(response.statusCode)[0] === '5';
if ((error || responseInvalid) && retryAttempt < _this.config.eureka.maxRetries) {
var nextRetryDelay = _this.config.eureka.requestRetryDelay * (retryAttempt + 1);
_this.logger.warn('Eureka request failed to endpoint ' + requestOpts.baseUrl + ', ' + ('next server retry in ' + nextRetryDelay + 'ms'));
setTimeout(function () {
return _this.eurekaRequest(opts, callback, retryAttempt + 1);
}, nextRetryDelay);
return;
}
callback(error, response, body);
});
};
/*
初始化参数,记录全部配置,并生成eureka所需参数
*/
EurekaWrapper.prototype.initConfig = function (config) {
//保存参数
this._config = merge({}, DefaultConfig);
this._config = merge(this._config, config || {});
var coordinateConfig = merge({}, this._config);
var coordinator = this._config.coordinator;//地址配置
//如果domain不存在并且host或port也不存在时,抛出异常
if (!coordinator.domain && (!coordinator.host || !coordinator.port)) {
throw new TypeError('必须设置domain或者设置host和port的值');
}
//生成服务注册中心参数
coordinateConfig.eureka = this._config.coordinator;
coordinateConfig.eureka.registerWithEureka = this._config.registerSelf;
delete coordinateConfig.coordinator;
delete coordinateConfig.token;
delete coordinateConfig.agent;
return coordinateConfig;
}
/*
设置token
*/
EurekaWrapper.prototype.setToken = function (token) {
if (this._config) {
this._config.token = token;
}
}
/*
获取配置数据.
*/
EurekaWrapper.prototype.getConfig = function () {
return this._config;
}
/*
获取内部实体.
*/
EurekaWrapper.prototype.getEureka = function () {
return this._eureka;
}
/*
设置logger.
*/
EurekaWrapper.prototype.setLogger = function () {
var eureka = this.getEureka();
if (eureka) {
this.logger = eureka.logger;
}
}
/*
获取logger.
*/
EurekaWrapper.prototype.getLogger = function () {
return this.logger;
}
/*
启动
*/
EurekaWrapper.prototype.start = function (fn) {
var _client = this.getEureka();
var _config = this.getConfig();
if (_client) {
_client.start(fn);
}
}
/*
停止
*/
EurekaWrapper.prototype.stop = function () {
var _client = this.getEureka();
var _config = this.getConfig();
var callback = _.isFunction(arguments[0]) ? arguments[0] : noop;
if (_client) {
_client.stop(callback);
this.unbindListeners();
}
}
/*
检查配置参数 Eureka.prototype.validateConfig = validateConfig;
*/
EurekaWrapper.prototype.validateConfig = function (config) {
function validate(namespace, key) {
if (!config[namespace][key]) {
throw new TypeError('参数' + namespace + '.' + key + ' 未配置.');
}
}
//注册自己
if (config.registerSelf) {
validate('instance', 'app');
validate('instance', 'vipAddress');
validate('instance', 'port');
validate('instance', 'dataCenterInfo');
}
validate('eureka', 'host');
validate('eureka', 'port');
};
/*
获取服务列表并缓存
*/
EurekaWrapper.prototype.fetchRegistry = function () {
var _this = this.getEureka();
var _config = this.getConfig();
var _wrapper = this;
//检验token是否存在
this.checkToken(_config, _this.logger);
var callback = _.isFunction(arguments[0]) ? arguments[0] : noop;
//创建url并发送请求
var _this8 = this;
var callback = arguments.length <= 0 || arguments[0] === undefined ? noop : arguments[0];
var options = {
uri: '',
json: true,
headers: {
Accept: 'application/json',
token: _config.token
}
};
//如果使用代理
if (_config && _config.agent && _config.agent.open) {
options = _wrapper.setProxy(options);
}
this.eurekaRequest(options, function (error, response, body) {
if (!error && response.statusCode === 200) {
if (_this.logger) {
_this.logger.debug(new Date() + ' 获取服务列表成功!');
}
try {
_this.transformRegistry(body);
} catch (ex) {
return callback(ex);
}
_this.emit('registryUpdated');
return callback(null);
} else if (error) {
_this.logger.warn('从服务注册中心获取注册的服务失败:', error);
return callback(error);
}
callback(new Error('从服务注册中心获取注册的服务失败!'));
});
};
/**
* 设置代理
* @param options
* @returns {*}
*/
EurekaWrapper.prototype.setProxy = function (options) {
//设置代理
options.agentClass = proxyAgent;
var config = this.getConfig();
options.agentOptions = config && config.agent && config.agent.agentOptions;
return options;
};
/*
注册 。
*/
EurekaWrapper.prototype.register = function () {
var _this = this.getEureka();
var _config = this.getConfig();
var _wrapper = this;
//检验token是否存在
this.checkToken(_config, _this.logger);
var callback = arguments.length <= 0 || arguments[0] === undefined ? noop : arguments[0];
_config.instance.status = 'UP';
var connectionTimeout = setTimeout(function () {
_this.logger.debug('注册服务长时间未成功,可能是指定的注册中心地址出问题了,' +
'可以在启动应用前设置 NODE_DEBUG=request 参数(windows 中 set NODE_DEBUG=request ;linux 中 export NODE_DEBUG=request ),查看更多日志.');
}, 10000);
var options = {
method: 'POST',
uri: _config.instance.app,
json: true,
headers: {
token: _config.token
},
body: {instance: _config.instance}
};
//如果使用代理
if (_config && _config.agent && _config.agent.open) {
options = _wrapper.setProxy(options);
}
this.eurekaRequest(options, function (error, response, body) {
clearTimeout(connectionTimeout);
if (!error && response.statusCode === 204) {
_this.logger.info('注册服务: ', _this.config.instance.app + '/' + _this.instanceId);
_this.emit('registered');
return callback(null);
} else if (error) {
_this.logger.warn('服务注册失败.', error);
return callback(error);
}
return callback(new Error('服务注册失败: status: ' + response.statusCode + ' body: ' + body));
});
};
/*
更新自身服务状态
*/
EurekaWrapper.prototype.renew = function () {
var _this = this.getEureka();
var _config = this.getConfig();
var _wrapper = this;
//检验token是否存在
this.checkToken(_config, _this.logger);
var options = {
method: 'PUT',
uri: this._config.instance.app + '/' + _this.instanceId,
headers: {
token: _config.token
}
};
//如果使用代理
if (_config && _config.agent && _config.agent.open) {
options = _wrapper.setProxy(options);
}
this.eurekaRequest(options, function (error, response, body) {
if (!error && response.statusCode === 200) {
_this.logger.debug('心跳监测成功');
_this.emit('heartbeat');
} else if (!error && response.statusCode === 404) {
_this.logger.warn('心跳监测失败, 重新注册app.');
_this.register();
} else {
if (error) {
_this.logger.error('注册服务请求失败.', error);
}
_this.logger.warn('心跳监测失败: status: ' + (response ? response.statusCode : 'unknown') + ', body: ' + body);
}
});
};
/*
取消注册。
*/
EurekaWrapper.prototype.deregister = function () {
var _this = this.getEureka();
var _config = this.getConfig();
var _wrapper = this;
//检验token是否存在
this.checkToken(_config, _this.logger);
var callback = arguments.length <= 0 || arguments[0] === undefined ? noop : arguments[0];
var options = {
method: 'DEL',
uri: _config.instance.app + '/' + _this.instanceId,
headers: {
token: _config.token
}
};
//如果使用代理
if (_config && _config.agent && _config.agent.open) {
options = _wrapper.setProxy(options);
}
this.eurekaRequest(options, function (error, response, body) {
if (!error && response.statusCode === 200) {
_this.logger.info('取消注册服务: ', _this.config.instance.app + '/' + _this.instanceId);
_this.emit('deregistered');
return callback(null);
} else if (error) {
_this.logger.warn('取消注册服务失败', error);
return callback(error);
}
return callback(new Error('取消注册服务失败 : status: ' + response.statusCode + ' body: ' + body));
});
};
/*
根据appid获取服务实例列表
@param appId
*/
EurekaWrapper.prototype.getInstancesByAppId = function getInstancesByAppId(appId) {
var _this = this.getEureka();
return _this.getInstancesByAppId(appId);
};
/*
根据vipaddress获取服务实例列表
@param vipAddress
*/
EurekaWrapper.prototype.getInstancesByVipAddress = function getInstancesByVipAddress(vipAddress) {
var _this = this.getEureka();
return _this.getInstancesByVipAddress(vipAddress);
};
/**
* 根据过滤规则,获取服务
* @param filter 是appId,如user;或者是函数过滤appId,例如:function(appId){return appId.indexOf("user")!=-1;}
* @returns {{}} 对象,如{"user":["http://127.0.0.1:8080","https://127.0.0.1:443"],"server":["http://192.168.1.100:9090"]}
*/
EurekaWrapper.prototype.getServerListByFilter = function (filter) {
var _this = this.getEureka();
var apps = {};
if (_this) {
if (_.isFunction(filter) && _this.cache) {
_.forEach(_this.cache.app, function (instances, appId) {
if (filter(appId.toLowerCase())) {
apps[appId.toLowerCase()] = util.instanceHandler(instances);
}
});
} else if (_.isString(filter)) {
apps[filter.toLowerCase()] = util.instanceHandler(_this.getInstancesByAppId(filter))
}
}
return apps;
}
/*
验证token,并设置默认值
*/
EurekaWrapper.prototype.checkToken = function (config, logger) {
//如果config存在,config中token不存在
if (config && !config.token) {
if (logger) {
logger.warn(new Date() + 'token未设置!');
}
//设置默认值
config.token = "123";
}
}
module.exports = EurekaWrapper;