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ū)的概念)。
對應配置(假設(shè)調(diào)用的服務(wù)名字是service-provider)
# 當前實例所在區(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ā)斷路機制 niws.loadbalancer.service-provider.connectionFailureCountThreshold=3 niws.loadbalancer.service-provider.circuitTripTimeoutFactorSeconds=10 niws.loadbalancer.service-provider.circuitTripMaxTimeoutSeconds=30
但是,統(tǒng)一管理后臺就比較麻煩了。理想情況下,應該是每個微服務(wù)做自己的管理接口封裝為OpenFeignClient給管理后臺調(diào)用,但是在這種場景下,只能每個集群部署一個管理后臺。這樣很不方便。
能不能通過簡單地改造還有配置,實現(xiàn)傳入zone來指定OpenFeignClient調(diào)用哪個zone的實例呢?
分析
首先,Eureka是同一個集群。在Eureka上面有service-provider的所有不同zone的實例信息
Ribbon拉下來的本地緩存,是有定時任務(wù)從EurekaClient中拉取的
拉下來之后,通過NIWSServerListFilter進行過濾,如果我們制定過濾類為com.netflix.niws.loadbalancer.DefaultNIWSServerListFilter,那么就是什么也不過濾,直接返回從Eureka上面拉取的,也就是返回所有zone的所有對應實例
這里放上源碼
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); } ?
默認的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),默認就是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,而不是隨機,就能通過getLoadBalancer獲取到對應zone的loadbalancer從而返回對應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,擴展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會導致切換線程執(zhí)行 ? ? private static ThreadLocal<String> zoneThreadLocal = new ThreadLocal<>(); ? ? public static void setZone(String zone) { ? ? ? ? zoneThreadLocal.set(zone); ? ? } ? ? /** ? ? ?* 必須調(diào)用這個方法傳入對應的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對應要調(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 #指定對應微服務(wù)的list不過濾 service-provider.ribbon.NIWSServerListFilterClassName=com.netflix.niws.loadbalancer.DefaultNIWSServerListFilter
Spring cloud Eureka: 指定Zone
有坑。
先說結(jié)論
如果想給當前服務(wù)指定屬于哪個zone, 使用
eureka.instance.metadata-map.zone=myzone
屬性是無效的,而應該使用:
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(","); ? ? }
也就是說在判斷當前服務(wù)屬于哪個zone時,先從availabilityZone這個Map中查找,查找用的key是region名。
如果找不到,就使用默認值,即我們熟知的defaultZone。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決eclipse中maven引用不到已經(jīng)存在maven中jar包的問題
這篇文章主要介紹了解決eclipse中maven引用不到已經(jīng)存在maven中jar包的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10Java中增強for循環(huán)的實現(xiàn)原理和坑詳解
增強的for循環(huán)是在傳統(tǒng)的for循環(huán)中增加的強大的迭代功能的循環(huán),是在jdk1.5之后提出來的。下面這篇文章主要給大家介紹了關(guān)于Java中增強for循環(huán)的實現(xiàn)原理和坑的相關(guān)資料,需要的朋友可以參考下2018-04-04Mybatis Mybatis-Plus傳入多個參數(shù)的處理方式
這篇文章主要介紹了Mybatis Mybatis-Plus傳入多個參數(shù)的處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05Spring中Bean的加載與SpringBoot的初始化流程詳解
這篇文章主要介紹了Spring中Bean的加載與SpringBoot的初始化流程詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11