UNPKG

@tbmini/eureka-client-ts

Version:

--- id: eureka-client-ts ---

259 lines (225 loc) 7.63 kB
/** @format */ 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) const tmpList = instances.map(v => { return `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() { const { eurekaObjs } = this for (const 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) } } else { eurekaObjs[i].splice(j, 1) } } catch (e) { eurekaObjs[i].splice(j, 1) } } 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的是公共服务 /** 个人服务 */ const noGroupService = [] // 没有分组标记 没有任何标记 for (const i in instances) { const ins = instances[i] const host = `http://${ins.hostName}:${(<{ $: number }>ins.port).$}` const { metadata } = ins const group = (metadata as Record<string, string>)['duiba.service.group.key'] const runInSingleJarMode = (metadata as Record<string, string>).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() const block = new Netmask(cidr) pool.forEach(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 as Record<string, string> 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]) } } }