UNPKG

coordinator-node-client

Version:

分布式协调服务node客户端,用于获取微服务的可用地址,参照eureka-js-client.

619 lines (601 loc) 21.1 kB
/** * 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;