UNPKG

@tuia/eureka-client-ts

Version:

256 lines (221 loc) 7.62 kB
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]) } } }