SpringCloud超详细讲解负载均衡组件Ribbon源码
作者:_时光煮雨 发布时间:2021-06-17 18:39:47
前言
上一篇文章中我们通过自己开发了一个负载均衡组件,实现了随机算法的负载均衡功能,如果要实现其他算法,还需要修改代码增加相应的功能。这一篇文章,我们将介绍一个更简单的负载均衡实现,使用**@LoadBalanced**注解实现负载均衡的功能。
项目实战
创建项目
同样的,我们的项目现在依然有一个registry注册中心,一个provider服务提供者,接下来,我们再次修改一下consumer服务消费者的代码:
@EnableEurekaClient
@SpringBootApplication
@RestController
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@GetMapping("/hello2")
public String hello2(String name) {
String returnInfo = restTemplate.getForObject( "http://provider/hello?name={1}", String.class, name);
return returnInfo;
}
}
在这个版本,同样的,还是创建RestTemplate Bean对象,不同的是上面仅仅增加了注解。
启动项目验证
依然正确返回了结果!
太神奇了吧,比我们自己开发的负载均衡组件简单太多了吧,仅仅在restTemplate() 方法上面增加了一个@LoadBalanced注解,怎么就实现的呢?废话不说,为了一探究竟,扒一扒源码吧!
源码分析
首先,点击@LoadBalanced注解进去,没有什么特别之处,那么我们在想想,Spring在创建Bean实例的时候,注解在什么地方起了作用?什么?不知道?翻一下这篇文章吧:
肝了两周,一张图解锁Spring核心源码
通过回顾Spring启动以及Bean的生命周期创建过程,我们就会发现加上@LoadBalancer注解后,项目启动时就会加载LoadBalancerAutoConfiguration这个配置类(通过spring-cloud-commons包下面的的spring.factories)。通过查看该配置类源码,发现其有个静态内部类LoadBalancerInterceptorConfig,其内部又创建了一个负载均衡 * :LoadBalancerInterceptor,该 * 包含有一个loadBalancerClient参数:
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
static class LoadBalancerInterceptorConfig {
LoadBalancerInterceptorConfig() {
}
@Bean
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
我们继续点击LoadBalancerInterceptor类进入,发现intercept方法,该方法中调用了LoadBalancerClient的execute方法,
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
LoadBalancerClient是一个接口,点击进去我们发现其实现类是RibbonLoadBalancerClient,查看其继承关系:
通过接口中的方法名称,我们可以猜想,choose方法就是选择其中服务列表中其中一个服务,reconstructURI方法就是重新构造请求的URI。
选择服务
choose方法是在RibbonLoadBalancerClient实现类中实现的
public ServiceInstance choose(String serviceId, Object hint) {
Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
}
在这个方法中,先调用 getServer 方法获取服务,这个方法最终会调用 ILoadBalancer 接口的 chooseServer 方法,而 ILoadBalancer 接口的实现类默认是ZoneAwareLoadBalancer。
ZoneAwareLoadBalancer 继承自 DynamicServerListLoadBalancer ,而在 DynamicServerListLoadBalancer 的构造方法中,调用了 this.restOfInit(clientConfig);在restOfInit这个方法中,通过 this.updateListOfServers()来获取服务列表;
而在chooseServer ()方法中,就会根据负载均衡算法,选择其中一个服务并返回:
BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
地址替换
选择其中一个服务信息后,怎么将接口从 http://provider/hello 变为 http://localhost:8003/hello 呢?还记得上面我们说的reconstructURI方法吗?通过配置类LoadBalancerAutoConfiguration加载后,会注入LoadBalancerInterceptor * ,该 * 会拦截我们的请求,并对请求地址进行处理,重构方法的具体实现在 LoadBalancerContext 类的 reconstructURIWithServer 方法中
public URI reconstructURIWithServer(Server server, URI original) {
String host = server.getHost();
int port = server.getPort();
String scheme = server.getScheme();
if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
return original;
} else {
if (scheme == null) {
scheme = original.getScheme();
}
if (scheme == null) {
scheme = (String)this.deriveSchemeAndPortFromPartialUri(original).first();
}
try {
StringBuilder sb = new StringBuilder();
sb.append(scheme).append("://");
if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
sb.append(original.getRawUserInfo()).append("@");
}
sb.append(host);
if (port >= 0) {
sb.append(":").append(port);
}
sb.append(original.getRawPath());
if (!Strings.isNullOrEmpty(original.getRawQuery())) {
sb.append("?").append(original.getRawQuery());
}
if (!Strings.isNullOrEmpty(original.getRawFragment())) {
sb.append("#").append(original.getRawFragment());
}
URI newURI = new URI(sb.toString());
return newURI;
} catch (URISyntaxException var8) {
throw new RuntimeException(var8);
}
}
}
可以看到该方法中,将原始的请求地址original,替换成了选取的服务的IP和端口。并最终调用该服务的接口方法。
看到这里,再想想我们上一章的内容,是不是有异曲同工之妙?
来源:https://coder965.blog.csdn.net/article/details/125305901


猜你喜欢
- 有的时候需要根据要查询的参数动态的拼接SQL语句常用标签:- if:字符判断- choose【when...otherwise】:分支选择-
- android开发中有时候碰到切换语言的需求,这时候需要通过代码动态改变当前运行语言。package com.example.android
- 通过自定义注解的方式(如:@SysLog(obj = "操作对象", text = "操作内容"),
- 本文实例讲述了Android编程简单实现雷达扫描效果。分享给大家供大家参考,具体如下:在eoe看到有一篇关于雷达扫描的文章,然后看了下,很简
- 前言:在 Spring 中, IOC 是很重要的概念,其本质就是 map 结构,存储容器和业务 Be
- Spring @RequestParam对象绑定在Spring中,如果在方法参数列表中使用@RequestParam标注多个参数,会让映射方
- VC程序设计中屏幕上的文字大都是由gdi32.dll的以下几个函数显示的:TextOutA、TextOutW、ExtTextOutA、Ext
- mac版本:点击Finder,在应用程序中找到android studio----->Contents文件夹----->bin文
- string str="aaa|||bbb|||ccc"; string[] sArray=str.Split(new[
- 题目描述Java创建线程的几种方式Java使用Thread类代表线程,所有线程对象都必须是Thread类或者其子类的实例。Java可以用以下
- 示例代码:<%@ Page Language="C#" AutoEventWireup="true&qu
- 前言在项目中一般使用使用volley方式如下,用起来给人一种很乱的感觉,于是一种盘它的想法油然而生。public void get() {S
- 前言 在微信刚流行的时候,在摇一摇还能用来那啥的时候,我也曾深更半夜的拿着手机晃一晃。当时想的最多的就是
- 一、题目描述-批量重命名1、题目题目:在window操作系统中,支持对文件名重命名,但不支持批量重命名。实现:做一个批量重命名的工具。2、解
- 本文实例为大家分享了java生成字母验证码的具体代码,供大家参考,具体内容如下import java.awt.BasicStroke;imp
- Set系列集合介绍Set集合概述Set系列集合特点:无序:存取数据的顺序是不一定的, 当数据存入后, 集合的顺序就固定下来了不重复:可以去除
- C#中的null与SQL中的NULL是不一样的,SQL中的NULL用C#表示出来就是DBNull.Value。注意:SQL参数是不能接受C#
- 本文实例讲述了C#实现用于操作wav声音文件的类。分享给大家供大家参考。具体如下:有了这个C#类,我们可以很轻易的调用本地wav文件进行同步
- 介绍Dubbo 是一款高性能、轻量级的 Java RPC 框架,由阿里巴巴开源并贡献至 Apache 基金会。它能够提供服务的注册与发现、负
- spring-mybatis获取mapper方式汇总项目背景:pojo下面有一个user实体类Dao包下面写了usermapper.xml