@tuia/eureka-client-ts
Version:
256 lines (221 loc) • 7.62 kB
text/typescript
import { Eureka, EurekaClient } from 'eureka-js-client';
import axios from 'axios';
import { checkType, jj, random } from './utils';
import * as address from 'address';
const Netmask = require('netmask').Netmask
const ip = address.ip() || '127.0.0.1';
export interface IEurekaObjs {
[name: string]: string[];
}
export default class Pool {
private eurekaObjs: IEurekaObjs = {};
private instances: EurekaClient.EurekaInstanceConfig[] = [];
constructor(eureka: Eureka, services: string[]) {
setTimeout(() => {
this.getEurekas(eureka, services);
}, 3000);
setInterval(() => {
this.filterPool();
}, 4000);
}
/**
* 遍历eureka
* @param {Eureka} eureka Eureka
* @param {string[]} services 服务名
*/
private async getEurekas(eureka: Eureka, services: string[]) {
for (let i = 0; i < services.length; i++) {
const name = services[i];
const instances = eureka.getInstancesByAppId(name);
let tmpList = [];
instances.map(v => {
tmpList.push(`http://${v.hostName}:${(<{ $: number }>v.port).$}`);
});
this.eurekaObjs[name] = tmpList;
this.instances.push(...instances);
}
// 每30秒刷新eurekaObjs列表
setTimeout(() => {
this.getEurekas(eureka, services);
}, 30000);
}
private async filterPool() {
let { eurekaObjs } = this;
for (let i in eurekaObjs) {
const obj = eurekaObjs[i];
for (let j = 0; j < obj.length; j++) {
try {
let params = '';
if (i === 'duiba-manager-web') {
params = 'newmanager/'
}
const { data } = await axios.get(`${obj[j]}/${params}monitor/check`);
if (checkType(data, 'String')) {
if (data.toLowerCase() !== 'ok') {
eurekaObjs[i].splice(j, 1, undefined);
}
} else {
eurekaObjs[i].splice(j, 1, undefined);
}
} catch (e) {
eurekaObjs[i].splice(j, 1, undefined);
}
}
eurekaObjs[i] = eurekaObjs[i].filter(v => v !== undefined)
}
}
/**
* 获取所有eureka
*/
getAllEurekas(): EurekaClient.EurekaInstanceConfig[] {
return this.instances;
}
/**
* 获取所有实例下的host
*/
getAllHostName() {
return this.eurekaObjs;
}
private getRandomHost(host: string[]) {
const len = host.length;
const num = random(0, len - 1);
return host[num] || ''
}
getHost(name: string | number, localIp: string, filterGroup?: string) {
const { eurekaObjs, instances } = this;
const pool = eurekaObjs[name];
if (!pool) {
console.error('请先注册service');
process.exit(1)
}
const { NODE_ENV } = process.env;
if (NODE_ENV === 'prod') {
return this.filterHybridCloud(instances, pool);
}
// 优先走本地
let hasLocalIp = false;
let clientIp = '';
for (let i = 0; i < pool.length; i++) {
if (pool[i].includes(localIp)) {
hasLocalIp = true;
clientIp = pool[i];
}
}
if (hasLocalIp) {
return clientIp;
}
/** 最优先匹配具体标记的,即多环境 */
let highestPriorityList = []; // 最高优先级 匹配到具体的metadata
let havePriorityList = []; // 优先转发目标 除具体metadata之外的metadata
/** 没有标记则默认转发到公共服务 */
let thirdPriorityList = []; // 第三优先级 runInSingleJarMode=true的是公共服务
/** 个人服务 */
let noGroupService = []; // 没有分组标记 没有任何标记
for (let i in instances) {
const ins = instances[i];
const host = `http://${ins.hostName}:${(<{ $: number }>ins.port).$}`;
const { metadata } = ins;
const group = metadata['duiba.service.group.key'];
const runInSingleJarMode = metadata['runInSingleJarMode'] === 'true';
if (filterGroup && group) { // 如果存在_duibaServiceGroupKey 且存在多场景
if (group === filterGroup) { // 完全匹配metadata
highestPriorityList.push(host);
} else if (group) { // 匹配不到,多场景为最低优先级
noGroupService.push(host);
} else {
if (runInSingleJarMode) {
havePriorityList.push(host);
} else {
thirdPriorityList.push(host);
}
}
} else {
if (!group) {
if (runInSingleJarMode) {
havePriorityList.push(host);
} else {
thirdPriorityList.push(host);
}
} else {
noGroupService.push(host);
}
}
}
highestPriorityList = jj(highestPriorityList, pool);
havePriorityList = jj(havePriorityList, pool);
thirdPriorityList = jj(thirdPriorityList, pool);
let tmpPool: string[] = [];
if (highestPriorityList.length > 0) {
// 如果完全匹配
return this.getRandomHost(highestPriorityList);
} else if (havePriorityList.length > 0) {
// 存在优先级高的
return this.getRandomHost(havePriorityList)
} else if (thirdPriorityList.length > 0) {
return this.getRandomHost(thirdPriorityList)
} else {
// 同网段的优先
// @ts-ignore
const { cidr } = address.interface();
var block = new Netmask(cidr);
pool.map(v => {
if (block.contains(v.split('http://')[1])) {
tmpPool.push(v)
}
});
if (tmpPool.length === 0) { // 如果不存在同网段,随便
tmpPool = pool;
}
return this.getRandomHost(tmpPool)
}
}
/**
* 筛选混合云符合条件IP
* http://cf.dui88.com/pages/viewpage.action?pageId=38788463
* @param {EurekaClient.EurekaInstanceConfig[]} instances 服务实例
* @param {*} pool ip池
* @returns
* @memberof Pool
*/
filterHybridCloud(instances: EurekaClient.EurekaInstanceConfig[], pool: any) {
let currentApplicationZone = 'defaultZone';
let aliyunCloud = [];
let huaweiCloud = [];
for (let i = 0; i < instances.length; i++) {
const ins = instances[i];
const { hostName, metadata } = ins;
const host = `http://${hostName}:${(<{ $: Number }>ins.port).$}`;
const { zone } = metadata;
if (hostName === ip) {
currentApplicationZone = zone === 'huawei' ? 'huawei' : 'defaultZone';
}
// 阿里云机器环注册的metadata为zone=defaultZone,华为云注册的metadata为huawei (假如获取ZONE为空,则视为defaultZone,注册到eureka的zone值也为defaultZone)
if (zone === 'huawei') {
huaweiCloud.push(host);
} else {
aliyunCloud.push(host);
}
}
aliyunCloud = jj(aliyunCloud, pool);
huaweiCloud = jj(huaweiCloud, pool);
const aliyunCloudLength = aliyunCloud.length;
const huaweiCloudLength = huaweiCloud.length;
// 如果任一机房没有实例,则去另一个机房随机获取实例
if (aliyunCloudLength === 0) {
return this.getRandomHost(huaweiCloud);
} else if (huaweiCloudLength === 0) {
return this.getRandomHost(aliyunCloud);
}
const [min, max] = [aliyunCloudLength, huaweiCloudLength].sort((a, b) => a - b);
// 判断服务在两个机房的分布是不是小于3,小于3做同机房优先调用,否则随机调用
if (max / min < 3) {
if (currentApplicationZone === 'huawei') {
return this.getRandomHost(huaweiCloud);
} else {
return this.getRandomHost(aliyunCloud);
}
} else {
return this.getRandomHost([...aliyunCloud, ...huaweiCloud])
}
}
}