Spring?cloud如何實現(xiàn)FeignClient指定Zone調(diào)用
本文基于Spring Cloud Fincheley SR3
背景介紹
目前項目多個區(qū)域多個集群,這些集群共用同一個Eureka集群。
通過設(shè)置eureka.instance.metadata-map.zone設(shè)置不同實例所屬的zone,zone之間不互相調(diào)用,只有zone內(nèi)部調(diào)用(其實這里用zone做了集群隔離,實際上集群肯定是跨可用區(qū)的,這里的eureka中的zone在我們項目里面并不是可用區(qū)的概念)。
對應(yīng)配置(假設(shè)調(diào)用的服務(wù)名字是service-provider)
# 當(dāng)前實例所在區(qū)域,同時由于NIWSServerListFilterClassName配置的是ZoneAffinityServerListFilter并且EnableZoneAffinity和EnableZoneExclusivity都是true,只有處于同一個zone的實例才會被調(diào)用 eureka.instance.metadata-map.zone=local service-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.AvailabilityFilteringRule service-provider.ribbon.NIWSServerListFilterClassName=com.netflix.loadbalancer.ZoneAffinityServerListFilter service-provider.ribbon.EnableZoneAffinity=true service-provider.ribbon.EnableZoneExclusivity=true service-provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.AvailabilityFilteringRule # ribbon.ServerListRefreshInterval時間內(nèi)有多少斷路次數(shù)就觸發(fā)斷路機(jī)制 niws.loadbalancer.service-provider.connectionFailureCountThreshold=3 niws.loadbalancer.service-provider.circuitTripTimeoutFactorSeconds=10 niws.loadbalancer.service-provider.circuitTripMaxTimeoutSeconds=30
但是,統(tǒng)一管理后臺就比較麻煩了。理想情況下,應(yīng)該是每個微服務(wù)做自己的管理接口封裝為OpenFeignClient給管理后臺調(diào)用,但是在這種場景下,只能每個集群部署一個管理后臺。這樣很不方便。
能不能通過簡單地改造還有配置,實現(xiàn)傳入zone來指定OpenFeignClient調(diào)用哪個zone的實例呢?
分析
首先,Eureka是同一個集群。在Eureka上面有service-provider的所有不同zone的實例信息
Ribbon拉下來的本地緩存,是有定時任務(wù)從EurekaClient中拉取的
拉下來之后,通過NIWSServerListFilter進(jìn)行過濾,如果我們制定過濾類為com.netflix.niws.loadbalancer.DefaultNIWSServerListFilter,那么就是什么也不過濾,直接返回從Eureka上面拉取的,也就是返回所有zone的所有對應(yīng)實例
這里放上源碼
DynamicServerListLoadBalancer.java
?
public void updateListOfServers() {
? ? List<T> servers = new ArrayList<T>();
? ? if (serverListImpl != null) {
? ? ? ? servers = serverListImpl.getUpdatedListOfServers();
? ? ? ? LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
? ? ? ? ? ? ? ? getIdentifier(), servers);
? ? ? ? if (filter != null) {
? ? ? ? ? ? //通過指定NIWSServerListFilter過濾
? ? ? ? ? ? servers = filter.getFilteredListOfServers(servers);
? ? ? ? ? ? LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
? ? ? ? ? ? ? ? ? ? getIdentifier(), servers);
? ? ? ? }
? ? }
? ? updateAllServerList(servers);
}
?默認(rèn)的LoadBalancer是什么呢?
通過查看org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration的源代碼:
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
? ? ? ? return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
? ? }我們知道,只要沒自定義(通過@RibbonClient注解),或者配置(通過ribbon.NFLoadBalancerClassName),默認(rèn)就是ZoneAwareLoadBalancer。
注意這里構(gòu)造器也和其他的LoadBalancer不一樣,其他的都是調(diào)用IClientConfigAware接口方法,這里是直接構(gòu)造器。
ZoneAwareLoadBalancer的選擇Server源碼
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
? ? logger.debug("Zone aware logic disabled or there is only one zone");
? ? return super.chooseServer(key);
}
Server server = null;
try {
? ? LoadBalancerStats lbStats = getLoadBalancerStats();
? ? Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
? ? logger.debug("Zone snapshots: {}", zoneSnapshot);
? ? if (triggeringLoad == null) {
? ? ? ? triggeringLoad = DynamicPropertyFactory.getInstance().getDoubleProperty(
? ? ? ? ? ? ? ? "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".triggeringLoadPerServerThreshold", 0.2d);
? ? }
? ? if (triggeringBlackoutPercentage == null) {
? ? ? ? triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance().getDoubleProperty(
? ? ? ? ? ? ? ? "ZoneAwareNIWSDiscoveryLoadBalancer." + this.getName() + ".avoidZoneWithBlackoutPercetage", 0.99999d);
? ? }
? ? Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
? ? logger.debug("Available zones: {}", availableZones);
? ? if (availableZones != null && ?availableZones.size() < zoneSnapshot.keySet().size()) {
? ? ? ? //核心看這里,我們只要指定了zone,而不是隨機(jī),就能通過getLoadBalancer獲取到對應(yīng)zone的loadbalancer從而返回對應(yīng)zone的實例
? ? ? ? String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
? ? ? ? logger.debug("Zone chosen: {}", zone);
? ? ? ? if (zone != null) {
? ? ? ? ? ? BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
? ? ? ? ? ? server = zoneLoadBalancer.chooseServer(key);
? ? ? ? }
? ? }
} catch (Exception e) {
? ? logger.error("Error choosing server using zone aware logic for load balancer={}", name, e);
}
if (server != null) {
? ? return server;
} else {
? ? logger.debug("Zone avoidance logic is not invoked.");
? ? return super.chooseServer(key);
}我們來實現(xiàn)我們自己的LoadBalancer,擴(kuò)展ZoneAwareLoadBalancer即可
實現(xiàn)
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang.StringUtils;
@Log4j2
public class ZoneChosenLoadBalancer<T extends Server> extends ZoneAwareLoadBalancer {
? ? //通過ThreadLocal指定Zone,所以不能開啟Hystrix
? ? //所以配置:feign.hystrix.enabled=false
? ? //開啟hystrix會導(dǎo)致切換線程執(zhí)行
? ? private static ThreadLocal<String> zoneThreadLocal = new ThreadLocal<>();
? ? public static void setZone(String zone) {
? ? ? ? zoneThreadLocal.set(zone);
? ? }
? ? /**
? ? ?* 必須調(diào)用這個方法傳入對應(yīng)的Bean初始化,其他構(gòu)造器是不完整的
? ? ?* @see org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
? ? ?* @param clientConfig
? ? ?* @param rule
? ? ?* @param ping
? ? ?* @param serverList
? ? ?* @param filter
? ? ?* @param serverListUpdater
? ? ?*/
? ? public ZoneChosenLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList serverList, ServerListFilter filter, ServerListUpdater serverListUpdater) {
? ? ? ? super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
? ? }
? ? @Override
? ? public Server chooseServer(Object key) {
? ? ? ? try {
? ? ? ? ? ? String zone = zoneThreadLocal.get();
? ? ? ? ? ? if (StringUtils.isBlank(zone)) {
? ? ? ? ? ? ? ? log.info("zone is blank, use base loadbalancer");
? ? ? ? ? ? ? ? return super.chooseServer(key);
? ? ? ? ? ? }
? ? ? ? ? ? BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
? ? ? ? ? ? Server server = zoneLoadBalancer.chooseServer(key);
? ? ? ? ? ? if (server != null) {
? ? ? ? ? ? ? ? return server;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? log.info("server is null for zone {}, use base loadbalancer", zone);
? ? ? ? ? ? ? ? return super.chooseServer(key);
? ? ? ? ? ? }
? ? ? ? } finally {
? ? ? ? ? ? //無論如何都要remove
? ? ? ? ? ? zoneThreadLocal.remove();
? ? ? ? }
? ? }
}配置類
注意不能通過文件配置實現(xiàn)類,走IClientConfigAware,上面源代碼里說明了原因,ZoneAwareLoadBalancer的構(gòu)造本來就特殊:
import com.netflix.loadbalancer.MultiZoneLoadBalancerConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;
@Configuration
//name對應(yīng)要調(diào)用的微服務(wù)
@RibbonClient(name = "service-provider", configuration = MultiZoneLoadBalancerConfiguration.class)
public class ServiceScaffoldProviderLoadBalancerConfiguration {
}package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MultiZoneLoadBalancerConfiguration {
? ? @Bean
? ? public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
? ? ? ? return new ZoneChosenLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater);
? ? }
}需要修改的配置
#關(guān)閉feign hystrix feign.hystrix.enabled=false #指定對應(yīng)微服務(wù)的list不過濾 service-provider.ribbon.NIWSServerListFilterClassName=com.netflix.niws.loadbalancer.DefaultNIWSServerListFilter
Spring cloud Eureka: 指定Zone
有坑。
先說結(jié)論
如果想給當(dāng)前服務(wù)指定屬于哪個zone, 使用
eureka.instance.metadata-map.zone=myzone
屬性是無效的,而應(yīng)該使用:
eureka.client.availabilityZones.beijing=myzone # beijing是region
同時指定region:
eureka.client.region=beijing
至于原因,可以在EurekaClientConfigBean的源碼中找到:
@Override
? ? public String[] getAvailabilityZones(String region) {
? ? ? ? String value = this.availabilityZones.get(region);
? ? ? ? if (value == null) {
? ? ? ? ? ? value = DEFAULT_ZONE;
? ? ? ? }
? ? ? ? return value.split(",");
? ? }也就是說在判斷當(dāng)前服務(wù)屬于哪個zone時,先從availabilityZone這個Map中查找,查找用的key是region名。
如果找不到,就使用默認(rèn)值,即我們熟知的defaultZone。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決eclipse中maven引用不到已經(jīng)存在maven中jar包的問題
這篇文章主要介紹了解決eclipse中maven引用不到已經(jīng)存在maven中jar包的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Java中增強(qiáng)for循環(huán)的實現(xiàn)原理和坑詳解
增強(qiáng)的for循環(huán)是在傳統(tǒng)的for循環(huán)中增加的強(qiáng)大的迭代功能的循環(huán),是在jdk1.5之后提出來的。下面這篇文章主要給大家介紹了關(guān)于Java中增強(qiáng)for循環(huán)的實現(xiàn)原理和坑的相關(guān)資料,需要的朋友可以參考下2018-04-04
Mybatis Mybatis-Plus傳入多個參數(shù)的處理方式
這篇文章主要介紹了Mybatis Mybatis-Plus傳入多個參數(shù)的處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
Spring中Bean的加載與SpringBoot的初始化流程詳解
這篇文章主要介紹了Spring中Bean的加載與SpringBoot的初始化流程詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11

