欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > 【SpringCloud】Ribbon源码解析

【SpringCloud】Ribbon源码解析

2024/10/23 23:32:51 来源:https://blog.csdn.net/qq_45797138/article/details/139730980  浏览:    关键词:【SpringCloud】Ribbon源码解析

e246a1dda09849a5a89535a62441565d.png

ribbon是一个负载均衡组件,它可以将请求分散到多个服务提供者实例中,提高系统的性能和可用性。本章分析ribbon是如何实现负载均衡的

1、@LoadBalanced

消费者在引入ribbon组件后,给http客户端添加@LoadBalanced注解就可以启用负载均衡功能。@LoadBalanced注解比较简单,本身没有包含什么业务逻辑,值得一提的是@Qualifier注解。@Qualifier通常配合@Autowired一起使用,当有多个同类型的bean时可以根据@Qualifier指定的bean名称完成注入;此外在bean的入口和出口使用@Qualifier修饰,也能建立稳定的供应关系,ribbon收集所有被@LoadBalanced修饰的restTemplate,用的就是这种方法

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
@Component
@Qualifier
public class A {}public class B {// 精准注入A@Autowired@Qualifierprivate A a;
}

2、LoadBalancerAutoConfiguration

和负载均衡相关的配置在LoadBalancerAutoConfiguration内,查看spring-cloud-common包的spring.factories文件,我们知道这是一个自动配置类

# AutoConfiguration
...
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
...

LoadBalancerAutoConfiguration注入了所有使用@LoadBalanced修饰的restTemplate,为什么加了@LoadBalanced就能引入其他其他被@LoadBalanced修饰的bean,原因就是1中提到的@Qualifier。

LoadBalancerAutoConfiguration内部还有一个loadBalancedRestTemplateInitializerDeprecated方法,这个方法会对restTemplate做定制化处理

@LoadBalanced
@Autowired(required = false
)
private List<RestTemplate> restTemplates = Collections.emptyList();@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {return () -> {restTemplateCustomizers.ifAvailable((customizers) -> {Iterator var2 = this.restTemplates.iterator();while(var2.hasNext()) {RestTemplate restTemplate = (RestTemplate)var2.next();Iterator var4 = customizers.iterator();while(var4.hasNext()) {RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();// 定制化处理customizer.customize(restTemplate);}}});};
}

RestTemplateCustomizer接口的实现类在LoadBalancerAutoConfiguration内,它们的定制化处理其实就是给restTemplate添加RetryLoadBalancerInterceptor拦截器

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {return (restTemplate) -> {List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());list.add(loadBalancerInterceptor);// 客户端添加拦截器restTemplate.setInterceptors(list);};
}

3、RetryLoadBalancerInterceptor

查看RetryLoadBalancerInterceptor的拦截方法,核心语句是loadBalancer.choose,也就是说服务实例的选择功能其实是由LoadBalancerClient接口实现类完成的

private final LoadBalancerClient loadBalancer;public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {...if (serviceInstance == null) {if (LOG.isDebugEnabled()) {LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. Reattempting service instance selection");}// 选择服务实例serviceInstance = this.loadBalancer.choose(serviceName);if (LOG.isDebugEnabled()) {LOG.debug(String.format("Selected service instance: %s", serviceInstance));}}...ClientHttpResponse response = (ClientHttpResponse)this.loadBalancer.execute(serviceName, serviceInstance, this.requestFactory.createRequest(request, body, execution));       ...
}

RibbonLoadBalancerClient是LoadBalancerClient接口实现类之一,查看它的choose方法

// 选择合适的服务实例
public ServiceInstance choose(String serviceId, Object hint) {Server server = this.getServer(this.getLoadBalancer(serviceId), hint);return server == null ? null : new RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
}// 选择合适的服务器
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}

选择服务器的任务移交给了ILoadBalancer的实现类,如ZoneAwareLoadBalancer。ZoneAwareLoadBalancer会调用父类BaseLoadBalancer的choose方法,按照指定的策略选取服务,如轮询、加权等

public Server chooseServer(Object key) {if (this.counter == null) {this.counter = this.createCounter();}this.counter.increment();if (this.rule == null) {return null;} else {try {// 按规则选择服务return this.rule.choose(key);} catch (Exception var3) {logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});return null;}}
}

再聊聊服务的来源,LoadBalancer是从哪里挑选的服务?

ZoneAwareLoadBalancer在创建时会调用父类DynamicServerListLoadBalancer的构造方法,然后调用updateListOfServers方法获取服务列表

public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {...this.restOfInit(clientConfig);
}void restOfInit(IClientConfig clientConfig) {...this.updateListOfServers();...
}@VisibleForTesting
public void updateListOfServers() {List<T> servers = new ArrayList();if (this.serverListImpl != null) {// 获取服务列表servers = this.serverListImpl.getUpdatedListOfServers();LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);if (this.filter != null) {servers = this.filter.getFilteredListOfServers((List)servers);LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);}}this.updateAllServerList((List)servers);
}

看到服务实例自然联想到eureka,跟踪getUpdatedListOfServers方法

调用链:
-> DynamicServerListLoadBalancer.updateListOfServers
-> this.serverListImpl.getUpdatedListOfServers;
-> DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery
-> eurekaClient.getInstancesByVipAddress
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {...// eureka客户端拉取服务List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);Iterator var8 = listOfInstanceInfo.iterator();...
}

eureka客户端从远程拉取服务实例,然后ribbon从服务实例中挑选服务

3、总结

ribbon组件向restTemplate中添加拦截器,实现负载均衡功能增强

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com