欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Cloud Ribbon负载均衡器处理方法

程序员文章站 2022-03-25 15:44:19
接下来撸一撸负载均衡器的内部,看看是如何获取服务实例,获取以后做了哪些处理,处理后又是如何选取服务实例的。 分成三个部分来撸: 配置 获取服务 选择...

接下来撸一撸负载均衡器的内部,看看是如何获取服务实例,获取以后做了哪些处理,处理后又是如何选取服务实例的。

分成三个部分来撸:

  • 配置
  • 获取服务
  • 选择服务

配置

在上一篇《撸一撸spring cloud ribbon的原理》的配置部分可以看到默认的负载均衡器是zoneawareloadbalancer。

看一看配置类。

位置:

spring-cloud-netflix-core-1.3.5.release.jar
org.springframework.cloud.netflix.ribbon
ribbonclientconfiguration.class
@suppresswarnings("deprecation")
@configuration
@enableconfigurationproperties
//order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@import({okhttpribbonconfiguration.class, restclientribbonconfiguration.class, httpclientribbonconfiguration.class})
public class ribbonclientconfiguration {
// 略
 @bean
 @conditionalonmissingbean
 public iloadbalancer ribbonloadbalancer(iclientconfig config,
 serverlist<server> serverlist, serverlistfilter<server> serverlistfilter,
 irule rule, iping ping, serverlistupdater serverlistupdater) {
 if (this.propertiesfactory.isset(iloadbalancer.class, name)) {
 return this.propertiesfactory.get(iloadbalancer.class, config, name);
 }
 return new zoneawareloadbalancer<>(config, rule, ping, serverlist,
 serverlistfilter, serverlistupdater);
 }
// 略
}

在实例化zoneawareloadbalancer的时候注入了,config、rule、ping、serverlist、serverlistfilter、serverlistupdater实例。

config:配置实例。

rule:负载均衡策略实例。

ping:ping实例。

serverlist:获取和更新服务的实例。

serverlistfilter:服务过滤实例。

serverlistupdater:服务列表信息更新实例。

@suppresswarnings("deprecation")
@configuration
@enableconfigurationproperties
//order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@import({okhttpribbonconfiguration.class, restclientribbonconfiguration.class, httpclientribbonconfiguration.class})
public class ribbonclientconfiguration {
 // 略
 @bean
 @conditionalonmissingbean
 public iclientconfig ribbonclientconfig() {
 defaultclientconfigimpl config = new defaultclientconfigimpl();
 config.loadproperties(this.name);
 return config;
 }
 @bean
 @conditionalonmissingbean
 public irule ribbonrule(iclientconfig config) {
 if (this.propertiesfactory.isset(irule.class, name)) {
 return this.propertiesfactory.get(irule.class, config, name);
 }
 zoneavoidancerule rule = new zoneavoidancerule();
 rule.initwithniwsconfig(config);
 return rule;
 }
 @bean
 @conditionalonmissingbean
 public iping ribbonping(iclientconfig config) {
 if (this.propertiesfactory.isset(iping.class, name)) {
 return this.propertiesfactory.get(iping.class, config, name);
 }
 return new dummyping();
 }
 @bean
 @conditionalonmissingbean
 @suppresswarnings("unchecked")
 public serverlist<server> ribbonserverlist(iclientconfig config) {
 if (this.propertiesfactory.isset(serverlist.class, name)) {
 return this.propertiesfactory.get(serverlist.class, config, name);
 }
 configurationbasedserverlist serverlist = new configurationbasedserverlist();
 serverlist.initwithniwsconfig(config);
 return serverlist;
 }
 @bean
 @conditionalonmissingbean
 public serverlistupdater ribbonserverlistupdater(iclientconfig config) {
 return new pollingserverlistupdater(config);
 }
 @bean
 @conditionalonmissingbean
 public iloadbalancer ribbonloadbalancer(iclientconfig config,
 serverlist<server> serverlist, serverlistfilter<server> serverlistfilter,
 irule rule, iping ping, serverlistupdater serverlistupdater) {
 if (this.propertiesfactory.isset(iloadbalancer.class, name)) {
 return this.propertiesfactory.get(iloadbalancer.class, config, name);
 }
 return new zoneawareloadbalancer<>(config, rule, ping, serverlist,
 serverlistfilter, serverlistupdater);
 }
 @bean
 @conditionalonmissingbean
 @suppresswarnings("unchecked")
 public serverlistfilter<server> ribbonserverlistfilter(iclientconfig config) {
 if (this.propertiesfactory.isset(serverlistfilter.class, name)) {
 return this.propertiesfactory.get(serverlistfilter.class, config, name);
 }
 zonepreferenceserverlistfilter filter = new zonepreferenceserverlistfilter();
 filter.initwithniwsconfig(config);
 return filter;
 }
 @bean
 @conditionalonmissingbean
 public ribbonloadbalancercontext ribbonloadbalancercontext(
 iloadbalancer loadbalancer, iclientconfig config, retryhandler retryhandler) {
 return new ribbonloadbalancercontext(loadbalancer, config, retryhandler);
 }
 // 略
}

在这里配置相关的实例

config:defaultclientconfigimpl。

rule:zoneavoidancerule。

ping:dummyping。

serverlist:configurationbasedserverlist,基于配置的服务列表实例。

serverlistfilter:zonepreferenceserverlistfilter。

serverlistupdater:pollingserverlistupdater。

要注意的是,在这里serverlist的实例是configurationbasedserverlist,这是在未使用eureka时获取服务信息的实例,是从配置文件中获取。

那么在和eureka配合使用时,需要从 eureka server获取服务信息,那该是哪个实例来做这件事情呢。

在启用eureka服务发现时,会首先会采用eurekaribbonclientconfiguration配置类。

位置:

spring-cloud-netflix-eureka-client-1.3.5.release.jar
org.springframework.cloud.netflix.ribbon.eureka
eurekaribbonclientconfiguration.class
@configuration
@commonslog
public class eurekaribbonclientconfiguration {
 // 略
 @bean
 @conditionalonmissingbean
 public iping ribbonping(iclientconfig config) {
 if (this.propertiesfactory.isset(iping.class, serviceid)) {
 return this.propertiesfactory.get(iping.class, config, serviceid);
 }
 niwsdiscoveryping ping = new niwsdiscoveryping();
 ping.initwithniwsconfig(config);
 return ping;
 }
 @bean
 @conditionalonmissingbean
 public serverlist<?> ribbonserverlist(iclientconfig config, provider<eurekaclient> eurekaclientprovider) {
 if (this.propertiesfactory.isset(serverlist.class, serviceid)) {
 return this.propertiesfactory.get(serverlist.class, config, serviceid);
 }
 discoveryenabledniwsserverlist discoveryserverlist = new discoveryenabledniwsserverlist(
 config, eurekaclientprovider);
 domainextractingserverlist serverlist = new domainextractingserverlist(
 discoveryserverlist, config, this.approximatezonefromhostname);
 return serverlist;
 }
 // 略
}

在首先采用了eurekaribbonclientconfiguration配置后,实际上各实例变成了

config:defaultclientconfigimpl。

rule:zoneavoidancerule。

ping:niwsdiscoveryping。

serverlist:domainextractingserverlist,内部是discoveryenabledniwsserverlist,实际上是通过服务发现获取服务信息列表。

serverlistfilter:zonepreferenceserverlistfilter。

serverlistupdater:pollingserverlistupdater。

获取服务

在找到获取服务信息入口前,先把负载均衡器的类继承关系撸一下。

Spring Cloud Ribbon负载均衡器处理方法

在zoneawareloadbalancer的构造中调用了父类dynamicserverlistloadbalancer构造。

位置:

ribbon-loadbalancer-2.2.2.jar

com.netflix.loadbalancer

zoneawareloadbalancer.class

在dynamicserverlistloadbalancer的构造中,调用了restofinit函数。

ribbon-loadbalancer-2.2.2.jar

com.netflix.loadbalancer

dynamicserverlistloadbalancer.class

 void restofinit(iclientconfig clientconfig) {
 boolean primeconnection = this.isenableprimingconnections();
 // turn this off to avoid duplicated asynchronous priming done in baseloadbalancer.setserverlist()
 this.setenableprimingconnections(false);
 enableandinitlearnnewserversfeature();
 updatelistofservers();
 if (primeconnection && this.getprimeconnections() != null) {
 this.getprimeconnections()
  .primeconnections(getreachableservers());
 }
 this.setenableprimingconnections(primeconnection);
 logger.info("dynamicserverlistloadbalancer for client {} initialized: {}", clientconfig.getclientname(), this.tostring());
 }

先是通过调用enableandinitlearnnewserversfeature方法启动定时更新服务列表,然后立即调用updatelistofservers函数马上获取并更新服务列表信息。

先看下enableandinitlearnnewserversfeature方法,实际上是调用了服务列表信息更新实例的start方法启动定时更新功能。

 /**
 * feature that lets us add new instances (from amis) to the list of
 * existing servers that the lb will use call this method if you want this
 * feature enabled
 */
 public void enableandinitlearnnewserversfeature() {
 logger.info("using serverlistupdater {}", serverlistupdater.getclass().getsimplename());
 serverlistupdater.start(updateaction);
 }

这里的服务列表信息更新实例就是配置阶段配置的pollingserverlistupdater实例,看一下这个类的构造和start方法。

public class pollingserverlistupdater implements serverlistupdater {
 // 略
 private static long listofservers_cache_update_delay = 1000; // msecs;
 private static int listofservers_cache_repeat_interval = 30 * 1000; // msecs; 
 // 略
 private final atomicboolean isactive = new atomicboolean(false);
 private volatile long lastupdated = system.currenttimemillis();
 private final long initialdelayms;
 private final long refreshintervalms;
 // 略
 public pollingserverlistupdater(iclientconfig clientconfig) {
 this(listofservers_cache_update_delay, getrefreshintervalms(clientconfig));
 }
 public pollingserverlistupdater(final long initialdelayms, final long refreshintervalms) {
 this.initialdelayms = initialdelayms;
 this.refreshintervalms = refreshintervalms;
 }
 @override
 public synchronized void start(final updateaction updateaction) {
 if (isactive.compareandset(false, true)) {
 final runnable wrapperrunnable = new runnable() {
 @override
 public void run() {
  if (!isactive.get()) {
  if (scheduledfuture != null) {
  scheduledfuture.cancel(true);
  }
  return;
  }
  try {
  updateaction.doupdate();
  lastupdated = system.currenttimemillis();
  } catch (exception e) {
  logger.warn("failed one update cycle", e);
  }
 }
 };
 scheduledfuture = getrefreshexecutor().schedulewithfixeddelay(
  wrapperrunnable,
  initialdelayms,
  refreshintervalms,
  timeunit.milliseconds
 );
 } else {
 logger.info("already active, no-op");
 }
 }
 // 略
}

从构造和常量定义看出来,延迟一秒执行,默认每隔30秒执行更新,可以通过配置修改间隔更新的时间。

从start方法看,就是开了一个定时执行的schedule,定时执行 updateaction.doupdate()。

回到start方法调用方dynamicserverlistloadbalancer类中看一下updateaction实例的定义。

protected final serverlistupdater.updateaction updateaction = new serverlistupdater.updateaction() {
 @override
 public void doupdate() {
 updatelistofservers();
 }
 };

实际上就是调用了dynamicserverlistloadbalancer类的updatelistofservers方法,这跟启动完定时更新后立即更新服务信息列表的路径是一致的。

 继续看updatelistofservers方法。   

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) {
 servers = filter.getfilteredlistofservers(servers);
 logger.debug("filtered list of servers for {} obtained from discovery client: {}",
  getidentifier(), servers);
 }
 }
 updateallserverlist(servers);
 }

1.通过serverlist实例获取服务信息列表。

2.通过serverlistfilter 实例对获取到的服务信息列表进行过滤。

3.将过滤后的服务信息列表保存到loadbalancerstats中作为状态保持。

接下分别看一下。

1.通过serverlist实例获取服务信息列表。

serverlist实例就是配置阶段生成的domainextractingserverlist,获取服务信息都是委托给discoveryenabledniwsserverlist。

public class discoveryenabledniwsserverlist extends abstractserverlist<discoveryenabledserver>{
 // 略
 @override
 public list<discoveryenabledserver> getinitiallistofservers(){
 return obtainserversviadiscovery();
 }
 @override
 public list<discoveryenabledserver> getupdatedlistofservers(){
 return obtainserversviadiscovery();
 }
 private list<discoveryenabledserver> obtainserversviadiscovery() {
 list<discoveryenabledserver> serverlist = new arraylist<discoveryenabledserver>();
 if (eurekaclientprovider == null || eurekaclientprovider.get() == null) {
 logger.warn("eurekaclient has not been initialized yet, returning an empty list");
 return new arraylist<discoveryenabledserver>();
 }
 eurekaclient eurekaclient = eurekaclientprovider.get();
 if (vipaddresses!=null){
 for (string vipaddress : vipaddresses.split(",")) {
 // if targetregion is null, it will be interpreted as the same region of client
 list<instanceinfo> listofinstanceinfo = eurekaclient.getinstancesbyvipaddress(vipaddress, issecure, targetregion);
 for (instanceinfo ii : listofinstanceinfo) {
  if (ii.getstatus().equals(instancestatus.up)) {
  if(shoulduseoverrideport){
  if(logger.isdebugenabled()){
  logger.debug("overriding port on client name: " + clientname + " to " + overrideport);
  }
  // copy is necessary since the instanceinfo builder just uses the original reference,
  // and we don't want to corrupt the global eureka copy of the object which may be
  // used by other clients in our system
  instanceinfo copy = new instanceinfo(ii);
  if(issecure){
  ii = new instanceinfo.builder(copy).setsecureport(overrideport).build();
  }else{
  ii = new instanceinfo.builder(copy).setport(overrideport).build();
  }
  }
  discoveryenabledserver des = new discoveryenabledserver(ii, issecure, shoulduseipaddr);
  des.setzone(discoveryclient.getzone(ii));
  serverlist.add(des);
  }
 }
 if (serverlist.size()>0 && prioritizevipaddressbasedservers){
  break; // if the current vipaddress has servers, we dont use subsequent vipaddress based servers
 }
 }
 }
 return serverlist;
 }
 // 略
}

可以看到其实就是通过eureka客户端从eureka服务端获取所有服务实例信息并把上线的包装成discoveryenabledserver实例,带有zone信息,做到服务列表中。

2.通过serverlistfilter 实例对获取到的服务信息列表进行过滤。

serverlistfilte实例就是配置阶段生成的zonepreferenceserverlistfilter,通过调用该实例的getfilteredlistofservers方法进行过滤。

@data
@equalsandhashcode(callsuper = false)
public class zonepreferenceserverlistfilter extends zoneaffinityserverlistfilter<server> {
 private string zone;
 @override
 public void initwithniwsconfig(iclientconfig niwsclientconfig) {
 super.initwithniwsconfig(niwsclientconfig);
 if (configurationmanager.getdeploymentcontext() != null) {
 this.zone = configurationmanager.getdeploymentcontext().getvalue(
  contextkey.zone);
 }
 }
 @override
 public list<server> getfilteredlistofservers(list<server> servers) {
 list<server> output = super.getfilteredlistofservers(servers);
 if (this.zone != null && output.size() == servers.size()) {
 list<server> local = new arraylist<server>();
 for (server server : output) {
 if (this.zone.equalsignorecase(server.getzone())) {
  local.add(server);
 }
 }
 if (!local.isempty()) {
 return local;
 }
 }
 return output;
 }
}

在getfilteredlistofservers方法里面,一上来是调用父类的同名方法先过滤,其实父类也是把和消费端同区域的服务给过滤出来使用,不仅如此,增加了些智能的判定,保证在故障/负载较高时或者可用实例较少时不进行同区域的过滤。

但是在zonepreferenceserverlistfilter.getfilteredlistofservers这里,就算父类没做过过滤,这里依然要把同zone的服务给滤出来使用,谁叫这里的类是zonepreference的呢。

这是比较怪异的地方,感觉父类的智能判定没什么作用。

还是看看zoneaffinityserverlistfilter.getfilteredlistofservers做的辛苦工作吧。

public class zoneaffinityserverlistfilter<t extends server> extends
 abstractserverlistfilter<t> implements iclientconfigaware {
 // 略
 private boolean shouldenablezoneaffinity(list<t> filtered) { 
 if (!zoneaffinity && !zoneexclusive) {
 return false;
 }
 if (zoneexclusive) {
 return true;
 }
 loadbalancerstats stats = getloadbalancerstats();
 if (stats == null) {
 return zoneaffinity;
 } else {
 logger.debug("determining if zone affinity should be enabled with given server list: {}", filtered);
 zonesnapshot snapshot = stats.getzonesnapshot(filtered);
 double loadperserver = snapshot.getloadperserver();
 int instancecount = snapshot.getinstancecount(); 
 int circuitbreakertrippedcount = snapshot.getcircuittrippedcount();
 if (((double) circuitbreakertrippedcount) / instancecount >= blackoutserverpercentagethreshold.get() 
  || loadperserver >= activereqeustsperserverthreshold.get()
  || (instancecount - circuitbreakertrippedcount) < availableserversthreshold.get()) {
 logger.debug("zoneaffinity is overriden. blackoutserverpercentage: {}, activereqeustsperserver: {}, availableservers: {}", 
  new object[] {(double) circuitbreakertrippedcount / instancecount, loadperserver, instancecount - circuitbreakertrippedcount});
 return false;
 } else {
 return true;
 }
 }
 }
 @override
 public list<t> getfilteredlistofservers(list<t> servers) {
 if (zone != null && (zoneaffinity || zoneexclusive) && servers !=null && servers.size() > 0){
 list<t> filteredservers = lists.newarraylist(iterables.filter(
  servers, this.zoneaffinitypredicate.getserveronlypredicate()));
 if (shouldenablezoneaffinity(filteredservers)) {
 return filteredservers;
 } else if (zoneaffinity) {
 overridecounter.increment();
 }
 }
 return servers;
 }
 // 略
}

首先会将与消费端相同的zone的服务过滤出来,然后通过shouldenablezoneaffinity(filteredservers)来判定是否可以采纳同zone的服务,还是采用所有的服务。

在shouldenablezoneaffinity方法内,对相同zone的服务做了一次snapshot,获取这些服务的实例数量,平均负载,断路的实例数进行计算判定。

可以看一下initwithniwsconfig方法中关键指标的值。

判定条件:

断路实例百分比>=0.8(断路的实例数/服务的实例数量)

平均负载>=0.6

可用实例数<2(实例数量-断路的实例数)

如果达到判定条件,那么就使用全部的服务,保证可用性。

但,上面也说了,因为zonepreferenceserverlistfilter本身总是会选用和消费端zone一致的服务,所以zoneaffinityserverlistfilter.getfilteredlistofservers中做的智能操作并没什么用。

不过,当然可以通过自定义配置来采用zoneaffinityserverlistfilter实例。

3.将过滤后的服务信息列表保存到loadbalancerstats中作为状态保持。

跟进updateallserverlist(servers);去,一步步深入,会发现,实际上是保存到loadbalancerstats中,并且这时候的服务是按照zone分组以hashmap<string, list<server>>结构保存的,key是zone。

选择服务

实现了iloadbalancer接口的负载均衡器,是通过实现chooseserver方法来进行服务的选择,选择后的服务做为目标请求服务。

看一下zoneawareloadbalancer.chooseserver方法。

@override
 public server chooseserver(object key) {
 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()) {
 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);
 }
 }

注意这里有两种用法:

1.通过配置zoneawareniwsdiscoveryloadbalancer.enabled=false关闭区域感知负载均衡,或者zone的个数<=1个。

2.采用区域感知,或者zone的个数>1。

一个个来看一下

1.通过配置zoneawareniwsdiscoveryloadbalancer.enabled=false关闭区域感知负载均衡,或者zone的个数<=1个。

这种情况下,调用了父类baseloadbalancer.chooseserver方法。

public server chooseserver(object key) {
 if (counter == null) {
 counter = createcounter();
 }
 counter.increment();
 if (rule == null) {
 return null;
 } else {
 try {
 return rule.choose(key);
 } catch (exception e) {
 logger.warn("loadbalancer [{}]: error choosing server for key {}", name, key, e);
 return null;
 }
 }
 }

这里使用的负载均衡策略rule实际上就是构造zoneawareloadbalancer时传进来的,在配置阶段生成的zoneavoidancerule策略实例。   

public void setrule(irule rule) {
 if (rule != null) {
 this.rule = rule;
 } else {
 /* default rule */
 this.rule = new roundrobinrule();
 }
 if (this.rule.getloadbalancer() != this) {
 this.rule.setloadbalancer(this);
 }
 }

假设,如果没有配置,默认用的是roundrobinrule策略实例。

2.采用区域感知,或者zone的个数>1。

public server chooseserver(object key) {
 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()) {
 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);
 }
 }

在这种情况下默认使用zoneavoidancerule负载均衡策略。

获取zone的snapshot信息。

获取可用的zone,通过观察zoneavoidancerule.getavailablezones定义,不是可用zone的条件是:

  • 所属实例数==0。
  • 故障率>0.99999或者平均负载<0。
  • 如果不是上面两种情况,就选择负载最高的一个去除不作为可用的zone。

可用zone都获取后,随机选一个。

并从该zone中,通过zoneawareloadbalancer的父类baseloadbalancer.chooseserver选取服务,上面整理过,baseloadbalancer里如果没有传入rule,那么默认使用roundrobinrule策略轮寻一个服务。

其实,还是上面获取服务中zonepreferenceserverlistfilter过滤器的问题,实际上过滤出来的只有一个和消费端相同的一个zone的服务,所以第2.部分的从可用zone中选取服务的功能是走不到,要走到就得把过滤器给换掉。

总结:

配置的负载均衡器会启动schedule获取服务信息,在使用了eureka客户端时,会从eureka服务获取所有服务实例信息,通过过滤器过滤出可以使用的服务,过滤器默认只过滤出与消费端相同zone的服务,如果要保证高可用可配置zoneaffinityserverlistfilter过滤器,过滤后的服务列表,通过实现了irule接口的负载均衡策略选取对应的服务,如果是使用zone感知的策略,可以从负载情况良好的zone中选取合适的服务。