大家好,本文我将继续来剖析SpringCloud中负载均衡组件Ribbon的源码。本来我是打算接着OpenFeign动态代理生成文章直接讲Feign是如何整合Ribbon的,但是文章写了一半发现,如果不把Ribbon好好讲清楚,那么有些Ribbon的细节理解起来就很困难,所以我还是打算单独写一篇文章来剖析Ribbon的源码,这样在讲Feign整合Ribbon的时候,我就不再赘述这些细节了。好了,话不多说,直接进入主题。

系列文章还在持续更新,如有喜欢的小伙伴可以关注微信公众号 三友的java日记

一、Ribbon的核心组件

1、Server

这是个很简单的东西,就是服务实例数据的封装,里面封装了服务实例的ip和端口之类的,一个服务有很多台机器,那就有很多个Server对象。

2、ServerList

  1. public interface ServerList<T extends Server> {
  2.  
  3. public List<T> getInitialListOfServers();
  4.  
  5. /**
  6. * Return updated list of servers. This is called say every 30 secs
  7. * (configurable) by the Loadbalancer's Ping cycle
  8. *
  9. */
  10. public List<T> getUpdatedListOfServers();
  11.  
  12. }

  

ServerList是个接口,泛型是Server,提供了两个方法,都是获取服务实例列表的,这两个方法其实在很多实现类中实现是一样的,没什么区别。这个接口很重要,因为这个接口就是Ribbon获取服务数据的来源接口,Ribbon进行负载均衡的服务列表就是通过这个接口来的,那么可以想一想是不是只要实现这个接口就可以给Ribbon提供服务数据了?事实的确如此,在SpringCloud中,eureka、nacos等注册中心都实现了这个接口,都将注册中心的服务实例数据提供给Ribbon,供Ribbon来进行负载均衡。

3、ServerListUpdater

通过名字也可以知道,是用来更新服务注册表的数据,他有唯一的实现,就是PollingServerListUpdater,这个类有一个核心的方法,就是start,我们来看一下start的实现。

  1. @Override
  2. public synchronized void start(final UpdateAction updateAction) {
  3. if (isActive.compareAndSet(false, true)) {
  4. final Runnable wrapperRunnable = new Runnable() {
  5. @Override
  6. public void run() {
  7. if (!isActive.get()) {
  8. if (scheduledFuture != null) {
  9. scheduledFuture.cancel(true);
  10. }
  11. return;
  12. }
  13. try {
  14. updateAction.doUpdate();
  15. lastUpdated = System.currentTimeMillis();
  16. } catch (Exception e) {
  17. logger.warn("Failed one update cycle", e);
  18. }
  19. }
  20. };
  21.  
  22. scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
  23. wrapperRunnable,
  24. initialDelayMs,
  25. refreshIntervalMs,
  26. TimeUnit.MILLISECONDS
  27. );
  28. } else {
  29. logger.info("Already active, no-op");
  30. }
  31. }

  

通过这段方法我们可以看出,首先通过isActive.compareAndSet(false, true)来保证这个方法只会被调用一下,然后封装了一个Runnable,这个Runnable干了一件核心的事,就是调用传入的updateAction的doUpdate方法,然后将Runnable扔到了带定时调度功能的线程池,经过initialDelayMs(默认1s)时间后,会调用一次,之后都是每隔refreshIntervalMs(默认30s)调用一次Runnable的run方法,也就是调用updateAction的doUpdate方法。

所以这个类的核心作用就是每隔30s会调用一次传入的updateAction的doUpdate方法的实现,记住这个结论。

4、IRule

  1. public interface IRule{
  2. /*
  3. * choose one alive server from lb.allServers or
  4. * lb.upServers according to key
  5. *
  6. * @return choosen Server object. NULL is returned if none
  7. * server is available
  8. */
  9.  
  10. public Server choose(Object key);
  11.  
  12. public void setLoadBalancer(ILoadBalancer lb);
  13.  
  14. public ILoadBalancer getLoadBalancer();
  15. }

  

IRule是负责负载均衡的算法的,也就是真正实现负载均衡获取一个服务实例就是这个接口的实现。比如说实现类RandomRule,就是从一堆服务实例中随机选取一个服务实例。

5、IClientConfig

就是一个配置接口,有个默认的实现DefaultClientConfigImpl,通过这个可以获取到一些配置Ribbon的一些配置。

6、ILoadBalancer

  1. public interface ILoadBalancer {
  2.  
  3. public void addServers(List<Server> newServers);
  4.  
  5. public Server chooseServer(Object key);
  6.  
  7. public void markServerDown(Server server);
  8.  
  9. @Deprecated
  10. public List<Server> getServerList(boolean availableOnly);
  11.  
  12. public List<Server> getReachableServers();
  13.  
  14. public List<Server> getAllServers();
  15. }

这个接口的作用,对外主要提供了获取服务实例列表和选择服务实例的功能。虽然对外主要提供获取服务的功能,但是在实现的时候,主要是用来协调上面提到的各个核心组件的,使得他们能够协调工作,从而实现对外提供获取服务实例的功能。

这个接口的实现有好几个实现类,但是我讲两个比较重要的。

BaseLoadBalancer

  1. public class BaseLoadBalancer extends AbstractLoadBalancer implements
  2. PrimeConnections.PrimeConnectionListener, IClientConfigAware {
  3.  
  4. private final static IRule DEFAULT_RULE = new RoundRobinRule();
  5. protected IRule rule = DEFAULT_RULE;
  6. private IClientConfig config;
  7.  
  8. protected volatile List<Server> allServerList = Collections
  9. .synchronizedList(new ArrayList<Server>());
  10. protected volatile List<Server> upServerList = Collections
  11. .synchronizedList(new ArrayList<Server>());
  12.  
  13. public BaseLoadBalancer(String name, IRule rule, LoadBalancerStats stats,
  14. IPing ping, IPingStrategy pingStrategy) {
  15.  
  16. logger.debug("LoadBalancer [{}]: initialized", name);
  17.  
  18. this.name = name;
  19. this.ping = ping;
  20. this.pingStrategy = pingStrategy;
  21. setRule(rule);
  22. setupPingTask();
  23. lbStats = stats;
  24. init();
  25. }
  26.  
  27. public BaseLoadBalancer(IClientConfig config) {
  28. initWithNiwsConfig(config);
  29. }
  30. public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
  31. initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
  32. }
  33.  
  34. void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
  35. this.config = clientConfig;
  36. String clientName = clientConfig.getClientName();
  37. this.name = clientName;
  38. int pingIntervalTime = Integer.parseInt(""
  39. + clientConfig.getProperty(
  40. CommonClientConfigKey.NFLoadBalancerPingInterval,
  41. Integer.parseInt("30")));
  42. int maxTotalPingTime = Integer.parseInt(""
  43. + clientConfig.getProperty(
  44. CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
  45. Integer.parseInt("2")));
  46.  
  47. setPingInterval(pingIntervalTime);
  48. setMaxTotalPingTime(maxTotalPingTime);
  49.  
  50. // cross associate with each other
  51. // i.e. Rule,Ping meet your container LB
  52. // LB, these are your Ping and Rule guys ...
  53. setRule(rule);
  54. setPing(ping);
  55.  
  56. setLoadBalancerStats(stats);
  57. rule.setLoadBalancer(this);
  58. if (ping instanceof AbstractLoadBalancerPing) {
  59. ((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
  60. }
  61. logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
  62. boolean enablePrimeConnections = clientConfig.get(
  63. CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);
  64.  
  65. if (enablePrimeConnections) {
  66. this.setEnablePrimingConnections(true);
  67. PrimeConnections primeConnections = new PrimeConnections(
  68. this.getName(), clientConfig);
  69. this.setPrimeConnections(primeConnections);
  70. }
  71. init();
  72.  
  73. }
  74.  
  75. public void setRule(IRule rule) {
  76. if (rule != null) {
  77. this.rule = rule;
  78. } else {
  79. /* default rule */
  80. this.rule = new RoundRobinRule();
  81. }
  82. if (this.rule.getLoadBalancer() != this) {
  83. this.rule.setLoadBalancer(this);
  84. }
  85. }
  86.  
  87. public Server chooseServer(Object key) {
  88. if (counter == null) {
  89. counter = createCounter();
  90. }
  91. counter.increment();
  92. if (rule == null) {
  93. return null;
  94. } else {
  95. try {
  96. return rule.choose(key);
  97. } catch (Exception e) {
  98. logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
  99. return null;
  100. }
  101. }
  102. }
  103.  
  104. }

核心属性

allServerList:缓存了所有的服务实例数据

upServerList:缓存了能够使用的服务实例数据。

rule:负载均衡算法组件,默认是RoundRobinRule

核心方法

setRule:这个方法是设置负载均衡算法的,并将当前这个ILoadBalancer对象设置给IRule,从这可以得出一个结论,IRule进行负载均衡的服务实例列表是通过ILoadBalancer获取的,也就是 IRule 和 ILoadBalancer相互引用。setRule(rule)一般是在构造对象的时候会调用。

chooseServer:就是选择一个服务实例,是委派给IRule的choose方法来实现服务实例的选择。

BaseLoadBalancer这个实现类总体来说,已经实现了ILoadBalancer的功能的,所以这个已经基本满足使用了。

说完BaseLoadBalancer这个实现类,接下来说一下DynamicServerListLoadBalancer实现类。DynamicServerListLoadBalancer继承自BaseLoadBalancer,DynamicServerListLoadBalancer主要是对BaseLoadBalancer功能进行扩展。

DynamicServerListLoadBalancer

  1. public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(DynamicServerListLoadBalancer.class);
  3.  
  4. volatile ServerList<T> serverListImpl;
  5. volatile ServerListFilter<T> filter;
  6. protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
  7. @Override
  8. public void doUpdate() {
  9. updateListOfServers();
  10. }
  11. };
  12. protected volatile ServerListUpdater serverListUpdater;
  13. public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
  14. ServerList<T> serverList, ServerListFilter<T> filter,
  15. ServerListUpdater serverListUpdater) {
  16. super(clientConfig, rule, ping);
  17. this.serverListImpl = serverList;
  18. this.filter = filter;
  19. this.serverListUpdater = serverListUpdater;
  20. if (filter instanceof AbstractServerListFilter) {
  21. ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
  22. }
  23. restOfInit(clientConfig);
  24. }
  25.  
  26. @Override
  27. public void setServersList(List lsrv) {
  28. super.setServersList(lsrv);
  29. List<T> serverList = (List<T>) lsrv;
  30. Map<String, List<Server>> serversInZones = new HashMap<String, List<Server>>();
  31. for (Server server : serverList) {
  32. // make sure ServerStats is created to avoid creating them on hot
  33. // path
  34. getLoadBalancerStats().getSingleServerStat(server);
  35. String zone = server.getZone();
  36. if (zone != null) {
  37. zone = zone.toLowerCase();
  38. List<Server> servers = serversInZones.get(zone);
  39. if (servers == null) {
  40. servers = new ArrayList<Server>();
  41. serversInZones.put(zone, servers);
  42. }
  43. servers.add(server);
  44. }
  45. }
  46. setServerListForZones(serversInZones);
  47. }
  48.  
  49. protected void setServerListForZones(
  50. Map<String, List<Server>> zoneServersMap) {
  51. LOGGER.debug("Setting server list for zones: {}", zoneServersMap);
  52. getLoadBalancerStats().updateZoneServerMapping(zoneServersMap);
  53. }
  54.  
  55. @VisibleForTesting
  56. public void updateListOfServers() {
  57. List<T> servers = new ArrayList<T>();
  58. if (serverListImpl != null) {
  59. servers = serverListImpl.getUpdatedListOfServers();
  60. LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
  61. getIdentifier(), servers);
  62.  
  63. if (filter != null) {
  64. servers = filter.getFilteredListOfServers(servers);
  65. LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
  66. getIdentifier(), servers);
  67. }
  68. }
  69. updateAllServerList(servers);
  70. }
  71.  
  72. /**
  73. * Update the AllServer list in the LoadBalancer if necessary and enabled
  74. *
  75. * @param ls
  76. */
  77. protected void updateAllServerList(List<T> ls) {
  78. // other threads might be doing this - in which case, we pass
  79. if (serverListUpdateInProgress.compareAndSet(false, true)) {
  80. try {
  81. for (T s : ls) {
  82. s.setAlive(true); // set so that clients can start using these
  83. // servers right away instead
  84. // of having to wait out the ping cycle.
  85. }
  86. setServersList(ls);
  87. super.forceQuickPing();
  88. } finally {
  89. serverListUpdateInProgress.set(false);
  90. }
  91. }
  92. }
  93. }

成员变量

serverListImpl:上面说过,通过这个接口获取服务列表

filter:起到过滤的作用,一般不care

updateAction:是个匿名内部类,实现了doUpdate方法,会调用updateListOfServers方法

serverListUpdater:上面说到过,默认就是唯一的实现类PollingServerListUpdater,也就是每个30s就会调用传入的updateAction的doUpdate方法。

这不是巧了么,serverListUpdater的start方法需要一个updateAction,刚刚好成员变量有个updateAction的匿名内部类的实现,所以serverListUpdater的start方法传入的updateAction的实现其实就是这个匿名内部类。

那么哪里调用了serverListUpdater的start方法传入了updateAction呢?是在构造的时候调用的,具体的调用链路是调用 restOfInit -> enableAndInitLearnNewServersFeature(),这里就不贴源码了

所以,其实DynamicServerListLoadBalancer在构造完成之后,默认每隔30s中,就会调用updateAction的匿名内部类的doUpdate方法,从而会调用updateListOfServers。所以我们来看一看 updateListOfServers 方法干了什么。

  1. public void updateListOfServers() {
  2. List<T> servers = new ArrayList<T>();
  3. if (serverListImpl != null) {
  4. servers = serverListImpl.getUpdatedListOfServers();
  5. LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
  6. getIdentifier(), servers);
  7. if (filter != null) {
  8. servers = filter.getFilteredListOfServers(servers);
  9. LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
  10. getIdentifier(), servers);
  11. }
  12. }
  13. updateAllServerList(servers);
  14. }

这个方法实现很简单,就是通过调用 ServerList 的getUpdatedListOfServers获取到一批服务实例数据,然后过滤一下,最后调用updateAllServerList方法,进入updateAllServerList方法。

  1. protected void updateAllServerList(List<T> ls) {
  2. // other threads might be doing this - in which case, we pass
  3. if (serverListUpdateInProgress.compareAndSet(false, true)) {
  4. try {
  5. for (T s : ls) {
  6. s.setAlive(true); // set so that clients can start using these
  7. // servers right away instead
  8. // of having to wait out the ping cycle.
  9. }
  10. setServersList(ls);
  11. super.forceQuickPing();
  12. } finally {
  13. serverListUpdateInProgress.set(false);
  14. }
  15. }
  16. }

其实很简单,就是调用每个服务实例的setAlive方法,将isAliveFlag设置成true,然后调用setServersList。setServersList这个方法的主要作用是将服务实例更新到内部的缓存中,也就是上面提到的allServerList和upServerList,这里就不贴源码了。

其实分析完updateListOfServers方法之后,再结合上面源码的分析,我们可以清楚的得出一个结论,那就是默认每隔30s都会重新通过ServerList组件获取到服务实例数据,然后更新到BaseLoadBalancer缓存中,IRule的负载均衡所需的服务实例数据,就是这个内部缓存。

从DynamicServerListLoadBalancer的命名也可以看出,他相对于父类BaseLoadBalancer而言,提供了动态更新内部服务实例列表的功能。

为了便于大家记忆,我画一张图来描述这些组件的关系以及是如何运作的。

说完一些核心的组件,以及他们跟ILoadBalancer的关系之后,接下来就来分析一下,ILoadBalancer是在ribbon中是如何使用的。

8、AbstractLoadBalancerAwareClient

ILoadBalancer是一个可以获取到服务实例数据的组件,那么服务实例跟什么有关,那么肯定是跟请求有关,所以在Ribbon中有这么一个抽象类,AbstractLoadBalancerAwareClient,这个是用来执行请求的,我们来看一下这个类的构造。

  1. public AbstractLoadBalancerAwareClient(ILoadBalancer lb) {
  2. super(lb);
  3. }
  4.  
  5. /**
  6. * Delegate to {@link #initWithNiwsConfig(IClientConfig)}
  7. * @param clientConfig
  8. */
  9. public AbstractLoadBalancerAwareClient(ILoadBalancer lb, IClientConfig clientConfig) {
  10. super(lb, clientConfig);
  11. }

  

通过上面可以看出,在构造的时候需要传入一个ILoadBalancer。

AbstractLoadBalancerAwareClient中有一个方法executeWithLoadBalancer,这个是用来执行传入的请求,以负载均衡的方式。

  1. public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
  2. LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
  3.  
  4. try {
  5. return command.submit(
  6. new ServerOperation<T>() {
  7. @Override
  8. public Observable<T> call(Server server) {
  9. URI finalUri = reconstructURIWithServer(server, request.getUri());
  10. S requestForServer = (S) request.replaceUri(finalUri);
  11. try {
  12. return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
  13. }
  14. catch (Exception e) {
  15. return Observable.error(e);
  16. }
  17. }
  18. })
  19. .toBlocking()
  20. .single();
  21. } catch (Exception e) {
  22. Throwable t = e.getCause();
  23. if (t instanceof ClientException) {
  24. throw (ClientException) t;
  25. } else {
  26. throw new ClientException(e);
  27. }
  28. }
  29.  
  30. }

  

这个方法构建了一个LoadBalancerCommand,随后调用了submit方法,传入了一个匿名内部类,这个匿名内部类中有这么一行代码很重要。

  1. URI finalUri = reconstructURIWithServer(server, request.getUri());

这行代码是根据给定的一个Server重构了URI,这是什么意思呢?举个例子,在OpenFeign那一篇文章我说过,会根据服务名拼接出类似http://ServerA的地址,那时是没有服务器的ip地址的,只有服务名,假设请求的地址是http://ServerA/api/sayHello,那么reconstructURIWithServer干的一件事就是将ServerA服务名替换成真正的服务所在的机器的ip和端口,假设ServerA所在的一台机器(Server里面封装了某台机器的ip和端口)是192.168.1.101:8088,那么重构后的地址就变成http://192.168.1.101:8088/api/sayHello,这样就能发送http请求到ServerA服务所对应的一台服务器了。

之后根据新的地址,调用这个类中的execute方法来执行请求,execute方法是个抽象方法,也就是交给子类实现,子类就可以通过实现这个方法,来发送http请求,实现rpc调用。

那么这台Server是从获取的呢?其实猜猜也知道,肯定是通过ILoadBalancer获取的,因为submit方法比较长,这里我直接贴出submit方法中核心的一部分代码

  1. Observable<T> o =
  2. (server == null ? selectServer() : Observable.just(server))

就是通过selectServer来选择一个Server的,selectServer我就不翻源码了,其实最终还是调用ILoadBalancer的方法chooseServer方法来获取一个服务,之后就会调用上面的说的匿名内部类的方法,重构URI,然后再交由子类的execut方法来实现发送http请求。

所以,通过对AbstractLoadBalancerAwareClient的executeWithLoadBalancer方法,我们可以知道,这个抽象类的主要作用就是通过负载均衡算法,找到一个合适的Server,然后将你传入的请求路径http://ServerA/api/sayHello重新构建成类似http://192.168.1.101:8088/api/sayHello这样,之后调用子类实现的execut方法,来发送http请求,就是这么简单。

到这里其实Ribbon核心组件和执行原理我就已经说的差不多了,再来画一张图总结一下

二、SpringCloud中使用的核心组件的实现都有哪些

说完了Ribbon的一些核心组件和执行原理之后,我们再来看一下在SpringCloud环境下,这些组件到底是用的哪些实现,毕竟有写时接口,有的是抽象类。

Ribbon的自动装配类:RibbonAutoConfiguration,我拎出了核心的源码

  1. @Configuration
  2. @RibbonClients
  3. public class RibbonAutoConfiguration {
  4.  
  5. @Autowired(required = false)
  6. private List<RibbonClientSpecification> configurations = new ArrayList<>();
  7. @Bean
  8. public SpringClientFactory springClientFactory() {
  9. SpringClientFactory factory = new SpringClientFactory();
  10. factory.setConfigurations(this.configurations);
  11. return factory;
  12. }
  13. }

  

RibbonAutoConfiguration配置类上有个@RibbonClients注解,接下来讲解一下这个注解的作用

  1. @Import(RibbonClientConfigurationRegistrar.class)
  2. public @interface RibbonClients {
  3.  
  4. RibbonClient[] value() default {};
  5.  
  6. Class<?>[] defaultConfiguration() default {};
  7.  
  8. }

看过我写的OpenFeign的文章小伙伴肯定知道,要使用Feign,得需要使用@EnableFeignClients,@EnableFeignClients的作用可以扫描指定包路径下的@FeignClient注解,也可以声明配置类;同样RibbonClients的作用也是可以声明配置类,同样也使用了@Import注解注解来实现的,RibbonClientConfigurationRegistrar这个配置类的作用就是往spring容器中注入每个服务的Ribbon组件(@RibbonClient里面可以声明每个服务对应的配置)的配置类和默认配置类,将配置类封装为RibbonClientSpecification注入到spring容器中,其实就跟@FeignClient注解声明配置的作用是一样的。

RibbonAutoConfiguration的主要作用就是注入了一堆RibbonClientSpecification,就是每个服务对应的配置类,然后声明了SpringClientFactory这个bean,将配置类放入到里面。

SpringClientFactory是不是感觉跟OpenFeign中的FeignContext很像,其实两个的作用是一样的,SpringClientFactory也继承了NamedContextFactory,实现了配置隔离,同时也在构造方法中传入了每个容器默认的配置类RibbonClientConfiguration。至于什么是配置隔离,我在OpenFeign那篇文章说过,不清楚的小伙伴可以后台回复feign01即可获得文章链接。

配置优先级问题

这里我说一下在OpenFeign里没仔细说的配置优先级的事情,因为有这么多配置类,都可以在配置类中声明对象,那么到底使用哪个配置类声明的对象呢。

优先级最高的是springboot启动的时候的容器,因为这个容器是每个服务的容器的父容器,而在配置类声明bean的时候,都有@ConditionalOnMissingBean注解,一旦父容器有这个bean,那么子容器就不会初始化。

优先级第二高的是每个客户端声明的配置类,也就是通过@FeignClient和@RibbonClient的configuration属性声明的配置类

优先级第三高的是@EnableFeignClients和@RibbonClients注解中configuration属性声明的配置类

优先级最低的就是FeignContext和SpringClientFactory构造时传入的配置类

至于优先级怎么来的,其实是在NamedContextFactory中createContext方法中构建AnnotationConfigApplicationContext时按照配置的优先级一个一个传进去的。

RibbonClientConfiguration提供的默认的bean

接下来我们看一下RibbonClientConfiguration都提供了哪些默认的bean

  1. @Bean
  2. @ConditionalOnMissingBean
  3. public IClientConfig ribbonClientConfig() {
  4. DefaultClientConfigImpl config = new DefaultClientConfigImpl();
  5. config.loadProperties(this.name);
  6. config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
  7. config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
  8. config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
  9. return config;
  10. }

配置类对应的bean,这里设置了ConnectTimeout和ReadTimeout都是1s中。

  1. }

IRule,默认是ZoneAvoidanceRule,这个Rule带有过滤的功能,过滤哪些不可用的分区的服务(这个过滤可以不用care),过滤成功之后,继续采用线性轮询的方式从过滤结果中选择一个出来。至于这个propertiesFactory,可以不用管,这个是默认读配置文件的中的配置,一般不设置,后面看到都不用care。

  1. @Bean
  2. @ConditionalOnMissingBean
  3. @SuppressWarnings("unchecked")
  4. public ServerList<Server> ribbonServerList(IClientConfig config) {
  5. if (this.propertiesFactory.isSet(ServerList.class, name)) {
  6. return this.propertiesFactory.get(ServerList.class, config, name);
  7. }
  8. ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
  9. serverList.initWithNiwsConfig(config);
  10. return serverList;
  11. }

默认是ConfigurationBasedServerList,也就是基于配置来提供服务实例列表。但是在SpringCloud环境中,这是不可能的,因为服务信息是在注册中心,所以应该是服务注册中心对应实现的,比如Nacos的实现NacosServerList,这里我贴出NacosServerList的bean的声明,在配置类NacosRibbonClientConfiguration中

  1. @Bean
  2. @ConditionalOnMissingBean
  3. public ServerList<?> ribbonServerList(IClientConfig config,
  4. NacosDiscoveryProperties nacosDiscoveryProperties) {
  5. NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties);
  6. serverList.initWithNiwsConfig(config);
  7. return serverList;
  8. }

至于为什么容器选择NacosServerList而不是ConfigurationBasedServerList,主要是因为NacosRibbonClientConfiguration这个配置类是通过@RibbonClients导入的,也就是比SpringClientFactory导入的RibbonClientConfiguration配置类优先级高。

  1. @Bean
  2. @ConditionalOnMissingBean
  3. public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
  4. return new PollingServerListUpdater(config);
  5. }

ServerListUpdater,就是我们剖析的PollingServerListUpdater,默认30s更新一次BaseLoadBalancer内部服务的缓存。

  1. @Bean
  2. @ConditionalOnMissingBean
  3. public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
  4. ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
  5. IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
  6. if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
  7. return this.propertiesFactory.get(ILoadBalancer.class, config, name);
  8. }
  9. return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
  10. serverListFilter, serverListUpdater);
  11. }

ILoadBalancer,默认是ZoneAwareLoadBalancer,构造的时候也传入了上面声明的的bean,ZoneAwareLoadBalancer这个类继承了DynamicServerListLoadBalancer,所以这个类功能也符合我们剖析的源码,至于ZoneAwareLoadBalancer多余的特性,也不用care。

到这里,Ribbon在SpringCloud的配置我们就讲完了,主要就是声明了很多核心组件的bean,最后都设置到ZoneAwareLoadBalancer中。但是,AbstractLoadBalancerAwareClient这个对象的声明我们并没有在配置类中找到,主要是因为这个对象是OpenFeign整合Ribbon的一个入口,至于是如何整合的,这个坑就留给下篇文章吧。

那么在springcloud中,上图就可以加上注册中心。

三、总结

本文剖析了Ribbon这个负载均衡组件中的一些核心组件的源码,并且将这些组件之间的关系一一描述清楚,同时也剖析了在发送请求的时候是如何通过ILoadBalancer获取到一个服务实例,重构URI的过程。希望本篇文章能够让你知道Ribbon是如何工作的。至于OpenFeign整合Ribbon,可以看这篇文章【SpringCloud原理】OpenFeign原来是这么基于Ribbon来实现负载均衡的

系列文章还在持续更新,如有喜欢的小伙伴可以关注微信公众号 三友的java日记

以上就是本篇文章的全部内容,如果你有什么不懂或者想要交流的地方,可以关注我的个人的微信公众号 三友的java日记,我们下篇文章再见。

如果觉得这篇文章对你有所帮助,还请帮忙点赞、在看、转发一下,码字不易,非常感谢!

往期热门文章推荐

【SpringCloud原理】Ribbon核心组件以及运行原理万字源码剖析的更多相关文章

  1. 万字剖析Ribbon核心组件以及运行原理

    大家好,本文我将继续来剖析SpringCloud中负载均衡组件Ribbon的源码.本来我是打算接着OpenFeign动态代理生成文章直接讲Feign是如何整合Ribbon的,但是文章写了一半发现,如果 ...

  2. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  3. 老李推荐:第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源

    老李推荐:第5章6节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 初始化事件源   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试 ...

  4. 老李推荐:第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本

    老李推荐:第5章3节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动脚本   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...

  5. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用   上一节我们描述了monkey的命令处理入口函数run是如何调用optionP ...

  6. 老李推荐:第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览

    老李推荐:第5章2节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动流程概览   每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就 ...

  7. 老李推荐:第5章1节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 官方简介

    老李推荐:第5章1节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 官方简介   在MonkeyRunner的框架中,Monkey是作为一个服务来接受来自Monkey ...

  8. 老李推荐:第14章5节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-查询ViewServer运行状态

    老李推荐:第14章5节<MonkeyRunner源码剖析> HierarchyViewer实现原理-装备ViewServer-查询ViewServer运行状态   poptest是国内唯一 ...

  9. 老李推荐:第14章1节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-面向控件编程VS面向坐标编程

    老李推荐:第14章1节<MonkeyRunner源码剖析> HierarchyViewer实现原理-面向控件编程VS面向坐标编程   poptest是国内唯一一家培养测试开发工程师的培训机 ...

随机推荐

  1. sublime text3 好用的插件

    sublime text3 推荐插件 Package Controller安装 1.打开sublime text 3,按ctrl+~或者菜单View > Show Console打开命令窗口.2 ...

  2. HTML5摇一摇(上)—如何判断设备摇动

    刚刚过去的一年里基于微信的H5营销可谓是十分火爆,通过转发朋友圈带来的病毒式传播效果相信大家都不太陌生吧,刚好最近农历新年将至,我就拿一个"摇签"的小例子来谈一谈HTML5中如何调 ...

  3. java堆排序

    直接贴源代码: package com.java.fmd; import java.util.Scanner; public class HeapSort { int[] arr; public st ...

  4. Oracle中between 和 in

    select * from test_s where id between 2 and 12; between 就是左右全闭区间. SELECT columnsFROM tablesWHERE col ...

  5. 微信小程序获取今天,昨天,后天

    today 是需要计算的某一天的日期例如"2018-12-12",传 null 默认今天,addDayCount 是要推算的天数, -1是前一天,0是今天,1是后一天. onLoa ...

  6. localStorage存储返回过来的对象 显示object object的问题

    localStorage.setItem() 不会自动将Json对象转成字符串形式 用localStorage.setItem()正确存储JSON对象方法是: 存储前先用JSON.stringify( ...

  7. Python学习报告及后续学习计划

    第一次有学习Python的想法是源于寒假在家的时候,高中同学问我是否学了Python(用于深度学习),当时就到b站收藏了黑马最新的教学视频,但是"收藏过等于我看了",后续就是过完年 ...

  8. Java学习day17

    继续学习了IO流的一些常用类以及GUI基础 做了自己的第一个Frame窗口 在做第一个Frame窗口时程序报错:java: 无法从静态上下文中引用非静态 变量 this 查看后发现不小心把MyFram ...

  9. go interface{}使用

    先上代码 func In(haystack []interface{}, needle interface{}) (bool, error) { sVal := reflect.ValueOf(hay ...

  10. 【高并发】不得不说的线程池与ThreadPoolExecutor类浅析

    大家好,我是冰河~~ 今天,我们一起来简单聊聊线程池中的ThreadPoolExecutor类,好了,不多说了,开始进入今天的正题. 一.抛砖引玉 既然Java中支持以多线程的方式来执行相应的任务,但 ...