为何一个@LoadBalanced注解就让RestTemplate拥有负载均衡的能力?【享学Spring Cloud】(中)

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 为何一个@LoadBalanced注解就让RestTemplate拥有负载均衡的能力?【享学Spring Cloud】(中)

LoadBalancerInterceptor


该类唯一被使用的地方就是LoadBalancerAutoConfiguration里配置上去~


public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
  // 这个命名都不叫Client了,而叫loadBalancer~~~
  private LoadBalancerClient loadBalancer;
  // 用于构建出一个Request
  private LoadBalancerRequestFactory requestFactory;
  ... // 省略构造函数(给这两个属性赋值)
  @Override
  public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
    return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
  }
}


此拦截器拦截请求后把它的serviceName委托给了LoadBalancerClient去执行,根据ServiceName可能对应N多个实际的Server,因此就可以从众多的Server中运用均衡算法,挑选出一个最为合适的Server做最终的请求(它持有真正的请求执行器ClientHttpRequestExecution)。


LoadBalancerClient


请求被拦截后,最终都是委托给了LoadBalancerClient处理。


// 由使用负载平衡器选择要向其发送请求的服务器的类实现
public interface ServiceInstanceChooser {
  // 从负载平衡器中为指定的服务选择Service服务实例。
  // 也就是根据调用者传入的serviceId,负载均衡的选择出一个具体的实例出来
  ServiceInstance choose(String serviceId);
}
// 它自己定义了三个方法
public interface LoadBalancerClient extends ServiceInstanceChooser {
  // 执行请求
  <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
  <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
  // 重新构造url:把url中原来写的服务名 换掉 换成实际的
  URI reconstructURI(ServiceInstance instance, URI original);
}


它只有一个实现类RibbonLoadBalancerClient(ServiceInstanceChooser是有多个实现类的~)。


RibbonLoadBalancerClient


首先我们应当关注它的choose()方法:


public class RibbonLoadBalancerClient implements LoadBalancerClient {
  @Override
  public ServiceInstance choose(String serviceId) {
    return choose(serviceId, null);
  }
  // hint:你可以理解成分组。若指定了,只会在这个偏好的分组里面去均衡选择
  // 得到一个Server后,使用RibbonServer把server适配起来~~~
  // 这样一个实例就选好了~~~真正请求会落在这个实例上~
  public ServiceInstance choose(String serviceId, Object hint) {
    Server server = getServer(getLoadBalancer(serviceId), hint);
    if (server == null) {
      return null;
    }
    return new RibbonServer(serviceId, server, isSecure(server, serviceId),
        serverIntrospector(serviceId).getMetadata(server));
  }
  // 根据ServiceId去找到一个属于它的负载均衡器
  protected ILoadBalancer getLoadBalancer(String serviceId) {
    return this.clientFactory.getLoadBalancer(serviceId);
  }
}


choose方法:传入serviceId,然后通过SpringClientFactory获取负载均衡器com.netflix.loadbalancer.ILoadBalancer,最终委托给它的chooseServer()方法选取到一个com.netflix.loadbalancer.Server实例,也就是说真正完成Server选取的是ILoadBalancer。


ILoadBalancer以及它相关的类是一个较为庞大的体系,本文不做更多的展开,而是只聚焦在我们的流程上


LoadBalancerInterceptor执行的时候是直接委托执行的loadBalancer.execute()这个方法:


RibbonLoadBalancerClient:
  // hint此处传值为null:一视同仁
  // 说明:LoadBalancerRequest是通过LoadBalancerRequestFactory.createRequest(request, body, execution)创建出来的
  // 它实现LoadBalancerRequest接口是用的一个匿名内部类,泛型类型是ClientHttpResponse
  // 因为最终执行的显然还是执行器:ClientHttpRequestExecution.execute()
  @Override
  public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
    return execute(serviceId, request, null);
  }
  // public方法(非接口方法)
  public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
    // 同上:拿到负载均衡器,然后拿到一个serverInstance实例
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    Server server = getServer(loadBalancer, hint);
    if (server == null) { // 若没找到就直接抛出异常。这里使用的是IllegalStateException这个异常
      throw new IllegalStateException("No instances available for " + serviceId);
    }
    // 把Server适配为RibbonServer  isSecure:客户端是否安全
    // serverIntrospector内省  参考配置文件:ServerIntrospectorProperties
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
        isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server));
    //调用本类的重载接口方法~~~~~
    return execute(serviceId, ribbonServer, request);
  }
  // 接口方法:它的参数是ServiceInstance --> 已经确定了唯一的Server实例~~~
  @Override
  public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
    // 拿到Server)(说白了,RibbonServer是execute时的唯一实现)
    Server server = null;
    if (serviceInstance instanceof RibbonServer) {
      server = ((RibbonServer) serviceInstance).getServer();
    }
    if (server == null) {
      throw new IllegalStateException("No instances available for " + serviceId);
    }
    // 说明:执行的上下文是和serviceId绑定的
    RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
    ... 
    // 真正的向server发送请求,得到返回值
    // 因为有拦截器,所以这里肯定说执行的是InterceptingRequestExecution.execute()方法
    // so会调用ServiceRequestWrapper.getURI(),从而就会调用reconstructURI()方法
      T returnVal = request.apply(serviceInstance);
      return returnVal;
    ... // 异常处理
  }


returnVal是一个ClientHttpResponse,最后交给handleResponse()方法来处理异常情况(若存在的话),若无异常就交给提取器提值:responseExtractor.extractData(response),这样整个请求就算全部完成了。



相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
10天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
136 73
|
5天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
40 21
|
10天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
10天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
1月前
|
消息中间件 监控 Java
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + RabbitMQ应用程序部署到Pivotal Cloud Foundry (PCF)
36 6
|
1月前
|
Java 关系型数据库 MySQL
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot + MySQL应用程序部署到Pivotal Cloud Foundry (PCF)
58 5
|
1月前
|
缓存 监控 Java
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
如何将Spring Boot应用程序部署到Pivotal Cloud Foundry (PCF)
41 5
|
2月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
142 2
|
2月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
59 2
|
1月前
|
负载均衡 Java Nacos
常见的Ribbon/Spring LoadBalancer的负载均衡策略
自SpringCloud 2020版起,Ribbon被弃用,转而使用Spring Cloud LoadBalancer。Ribbon支持轮询、随机、加权响应时间和重试等负载均衡策略;而Spring Cloud LoadBalancer则提供轮询、随机及Nacos负载均衡策略,基于Reactor实现,更高效灵活。
80 0