本文主要是介绍SpringCloud构建微服务系统的Ribbon负载均衡器和网络请求框架RestTemplate,另外将会分析负载均衡器的源码,通过实例证明如何通过Ribbon和RestTemplate相结合实现负载均衡。现在假设有一个分布式系统,该系统由在不同计算机上运行的许多服务组成。当用户数量很大时,通常会为服务创建多个副本。每个副本都在另一台计算机上运行,此时有助于在服务器之间平均分配传入流量。

客户端发现与服务端发现

在一个系统中,服务通常需要调用其他服务。单体应用中,服务通过语言级别的方法或者过程调用另外的服务。在传统的分布式部署中,服务运行在固定,已知的地址主机和端口,因此可以请求的通过HTTP/REST或其他RPC机制调用。 然而,一个现代的微服务应用通常运行在虚拟或者容器环境,服务实例数和它们的地址都在动态改变。因此需要实现一种机制,允许服务的客户端向动态变更的一组短暂的服务实例发起请求,这就是服务注册与发现,服务注册与发现是微服务架构中最重要的基础组件

我们接下来需要搞清楚的是什么是客户端发现,什么是服务端发现?

这里的注册中心其实就相当于青楼的老鸨,A是嫖客,B是小姐。这样一比喻相信各位老司机都知道三者之间的交互逻辑了。客户端发现就是当A需要调用B服务时,请求注册中心(B服务在启动时会将信息注册到注册中心),注册中心将一份完整的可用服务列表返回给 A 服务,A 服务自行决定使用哪个 B 服务。客户端发现的特点:

  • 简单直接,不需要代理的介入
  • 客户端(A)知道所有实际可用的服务地址
  • 客户端(A)需要自己实现负载均衡逻辑

使用客户端发现的例子:Eureka

服务端发现相对于客户端发现,多了一个代理,代理帮A从众多的B中挑选一个B。服务端发现的特点:

  • 由于代理的介入,服务(B)与注册中心,对 A 是不可见的

使用服务端发现的例子:Nginx、ZooKeeper、Kubernetes

客户端与服务端负载均衡

通过理解客户端发现与服务端发现的区别,我们明白其实调用哪个服务取决于客户端还是服务端是由什么决定的呢?那就是取决于服务注册与发现使用的是客户端发现还是服务端发现。

服务端负载均衡

服务器端负载均衡器,我们比较常见的例如Nginx、F5是放置在服务器端的组件。当请求来自客户端时,它们将转到负载均衡器,负载均衡器将为请求指定服务器。负载均衡器使用的最简单的算法是随机指定。在这种情况下,大多数负载平衡器是用于控制负载平衡的硬件集成软件。

服务端负载均衡的特点:

  • 对客户端不透明,客户端不知道服务器端的服务列表,甚至不知道自己发送请求的目标地址存在负载均衡器。
  • 服务器端维护负载均衡服务器,控制负载均衡策略和算法。

客户端负载均衡

当负载均衡器位于客户端时,客户端得到可用的服务器列表然后按照特定的负载均衡策略,分发请求到不同的服务器 。

客户端负载均衡的特点:

  • 对客户端透明,客户端需要知道服务器端的服务列表,需要自行决定请求要发送的目标地址。
  • 客户端维护负载均衡服务器,控制负载均衡策略和算法。
  • 目前单独提供的客户端实现比较少(本文只分析Ribbon),大部分都是在框架内部自行实现。

RestTemplate三种使用方式

RestTemplate是Spring框架提供的一种用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。 之前我们使用的较多的是Apache的OKHttp这个包库,或者是根据HttpUrlConnection封装的库,现在有了更好的选择,那就是RestTemplate:

1、直接填写服务地址

Order应用想要直接访问Shop应用的接口,填写服务地址直访问即可。

2、使用LoadBalancerClient

使用LoadBalancerClient的choose()获得ServiceInstance,也就是这两个应用必须先向Eureka Server注册,然后通过Client的名称来选择对应的服务实例:

3、注入RestTemplate Bean

注入RestTemplate bean,使用服务名称访问即可:

Ribbon负载均衡源码分析

在上面的例子中我们使用了RestTemplate并且开启了客户端负载均衡功能,开启负载均衡很简单,只需要在RestTemplate的bean上再添加一个@LoadBalanced注解即可,我们可以从这个注解开始分析:

/** * Annotation to mark a RestTemplate or WebClient bean to be configured to use a * LoadBalancerClient. * @author Spencer Gibb */@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {

}

这个注解是用来给RestTemplate做标记,配置LoadBalancerClient,那么我们需要关注的类就是LoadBalancerClient了,LoadBalancerClient表示客户端负载均衡器,并且继承了ServiceInstanceChooser:

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; // 为系统构建一个合适的URI    // 如 http://SHOP-CLIENT/shop/show -> http://localhost:8080/shop/show URI reconstructURI(ServiceInstance instance, URI original);}

ServiceInstanceChooser从名字上我们就可以看出,这是需要给出服务实例选择的具体实现,也就是实现choose方法: 根据传入的服务名serviceId从客户端负载均衡器中挑选一个对应服务的实例:

ServiceInstance choose(String serviceId);

至于具体的配置我们还需要看LoadBalancerAutoConfiguration类的源码,该类是客户端负载均衡服务器的自动化配置类,该类的源码如下:

/** * Auto-configuration for Ribbon (client-side load balancing). */@Configuration(proxyBeanMethods = false)@ConditionalOnClass(RestTemplate.class)@ConditionalOnBean(LoadBalancerClient.class)@EnableConfigurationProperties(LoadBalancerRetryProperties.class)public class LoadBalancerAutoConfiguration {

  @LoadBalanced    @Autowired(required = false)    private List<RestTemplate> restTemplates = Collections.emptyList();

  @Autowired(required = false)    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

 @Bean    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(          final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {     return () -> restTemplateCustomizers.ifAvailable(customizers -> {           for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {              for (RestTemplateCustomizer customizer : customizers) {                   customizer.customize(restTemplate);               }         }     });   }

   @Bean    @ConditionalOnMissingBean    public LoadBalancerRequestFactory loadBalancerRequestFactory(         LoadBalancerClient loadBalancerClient) {      return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); }

   @Configuration(proxyBeanMethods = false)    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")  static class 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);           };        }

   }

   @Configuration(proxyBeanMethods = false)    @ConditionalOnClass(RetryTemplate.class) public static class RetryAutoConfiguration {

        @Bean        @ConditionalOnMissingBean        public LoadBalancedRetryFactory loadBalancedRetryFactory() {          return new LoadBalancedRetryFactory() {           };        }

   }

   @Configuration(proxyBeanMethods = false)    @ConditionalOnClass(RetryTemplate.class) public static class RetryInterceptorAutoConfiguration {

     @Bean        @ConditionalOnMissingBean        public RetryLoadBalancerInterceptor ribbonInterceptor(                LoadBalancerClient loadBalancerClient,                LoadBalancerRetryProperties properties,               LoadBalancerRequestFactory requestFactory,                LoadBalancedRetryFactory loadBalancedRetryFactory) {          return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,                   requestFactory, loadBalancedRetryFactory);        }

       @Bean        @ConditionalOnMissingBean        public RestTemplateCustomizer restTemplateCustomizer(             final RetryLoadBalancerInterceptor loadBalancerInterceptor) {         return restTemplate -> {               List<ClientHttpRequestInterceptor> list = new ArrayList<>(                       restTemplate.getInterceptors());              list.add(loadBalancerInterceptor);                restTemplate.setInterceptors(list);           };        }

   }

}

LoadBalancerAutoConfiguration类上有两个关键注解,分别是@ConditionalOnClass(RestTemplate.class)和@ConditionalOnBean(LoadBalancerClient.class),说明Ribbon如果想要实现负载均衡的自动化配置需要满足两个条件:第一个,RestTemplate类必须存在于当前工程的环境中;第二个,在Spring容器中必须有LoadBalancerClient的实现Bean。

RetryInterceptorAutoConfiguration类的ribbonInterceptor方法返回了一个拦截器叫做LoadBalancerInterceptor,这个拦截器的作用主要是在客户端发起请求时进行拦截,进而实现客户端负载均衡功能, 其中的restTemplateCustomizer方法返回了一个RestTemplateCustomizer,这个方法主要用来给RestTemplate添加LoadBalancerInterceptor拦截器。LoadBalancerAutoConfiguration中的restTemplates是一个被@LoadBalanced注解修饰的RestTemplate对象列表,通过restTemplateCustomizer方法对每个 RestTemplate对象添加上LoadBalancerInterceptor拦截器。

那其实就是这些拦截器让一个普通的RestTemplate对象拥有了负载均衡的功能,LoadBalancerInterceptor的源码可以来看下:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

   private LoadBalancerClient loadBalancer;

    private LoadBalancerRequestFactory requestFactory;

  public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,           LoadBalancerRequestFactory requestFactory) {      this.loadBalancer = loadBalancer;        this.requestFactory = requestFactory;    }

   public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {     // for backwards compatibility        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer)); }

   @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)); }

}

@FunctionalInterfacepublic interface ClientHttpRequestInterceptor {    ClientHttpResponse intercept(HttpRequest var1, byte[] var2, ClientHttpRequestExecution var3) throws IOException;}

当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会被LoadBalancerInterceptor类的intercept方法拦截,在这个方法中直接通过getHost方法就可以获取到服务名(因为我们在使用RestTemplate调用服务的时候,使用的是服务名而不是域名,所以这里可以通过getHost直接拿到服务名然后去调用execute方法发起请求)。

接下来我们去看看LoadBalancerClient的具体实现 —— RibbonLoadBalancerClient:在execute方法的具体视线中,不难发现首先获取到的就是ILoadBalancer:

这是一个接口,添加服务实例,选择服务实例,获取所有服务实例等方法均在其中:

public interface ILoadBalancer {    // 向负载均衡器中维护的实例列表增加服务实例    void addServers(List<Server> var1);

    // 表示通过某种策略,从负载均衡服务器中挑选出一个具体的服务实例    Server chooseServer(Object var1);

    // 表示用来通知和标识负载均衡器中某个具体实例已经停止服务    void markServerDown(Server var1);

    // 表示获取当前正常工作的服务实例列表    List<Server> getReachableServers();

    // 表示获取所有的服务实例列表,包括正常的服务和停止工作的服务    List<Server> getAllServers();}

我们看最基础的BaseLoadBalancer即可:

不难发现,其实默认的负载均衡策略采用的是轮询的方式。至于负载均衡的策略,其实也有很多种实现:

总结一下就是RestTemplate发起一个请求,这个请求被LoadBalancerInterceptor给拦截了,拦截后将请求的地址中的服务逻辑名转为具体的服务地址,然后继续执行请求的一个过程。

谢谢你请我喝咖啡

支付宝

微信

  • 本文作者: Tim
  • 本文链接: https://zouchanglin.cn/2020/05/15/3125899592.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!

RestTemplate与负载均衡器相关推荐

  1. Spring Cloud微服务系统架构的一些简单介绍和使用

    Spring Cloud 目录 特征 云原生应用程序 Spring Cloud上下文:应用程序上下文服务 引导应用程序上下文 应用程序上下文层次结构 改变Bootstrap的位置Properties ...

  2. 测试您的neo4j nest js应用程序

    This article is one of a series of blog posts that accompany the Livestream on the Neo4j Twitch Chan ...

  3. Spring Cloud 中文文档

    Spring Cloud 官方文档 Spring Cloud为开发人员提供了用于快速构建分布式系统中某些常见模式的工具(例如,配置管理,服务发现,断路器,智能路由,微代理,控制总线).分布式系统的协调 ...

  4. Spring Cloud Document翻译(二)--Spring Cloud Commons

    原文链接:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/multi/multi__spring_cloud_commons_com ...

  5. SpringBoot系列: RestTemplate 快速入门

    ==================================== 相关的文章 ==================================== SpringBoot系列: 与Sprin ...

  6. Spring Cloud Alibaba基础教程:几种服务消费方式(RestTemplate、WebClient、Feign)

    热门:Spring Cloud Greenwich.RELEASE 正式发布!一个非常有看头的版本! 通过<Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现&g ...

  7. 为什么 RestTemplate 那么棒,看这篇就够了!

    作者:duanxz 来源:cnblogs.com/duanxz/p/3510622.html 在微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端.我们可以使用 ...

  8. Spring Cloud Alibaba基础教程:支持的几种服务消费方式(RestTemplate、WebClient、Feign)

    通过<Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现>一文的学习,我们已经学会如何使用Nacos来实现服务的注册与发现,同时也介绍如何通过LoadBal ...

  9. SpringCloud微服务架构,Spring Cloud 服务治理(Eureka,Consul,Nacos),Ribbon 客户端负载均衡,RestTemplate与OpenFeign实现远程调用

    什么是SpringCloud 微服务架构 • "微服务"一词源于 Martin Fowler的名为 Microservices的博文,可以在他的官方博客上找到 http://mar ...

最新文章

  1. wcf客户端动态嗅探服务实例
  2. 哈希表(散列查找)(c/c++)
  3. java中super关键字_Java中关键字super与this的区别
  4. 美国研发出第一台计算机的时间,研发世界第一台电脑的核心人物,被美国隐藏35年,只因他是个华人...
  5. linux 如何在命令行下改系统时间
  6. OpenShift 4 - DevSecOps (4) - 实现一个 CICD Pipeline,并用 RHACS 发现安全隐患
  7. ZOJ 3867 Earthstone: Easy Version
  8. Bower介绍及用法(转)
  9. post和get请求方式的区别
  10. micro 和 macro F1 值 的区别?
  11. kotlin入门教程
  12. 百度导航怎么不显示服务器,win7系统百度首页导航不见了怎么办
  13. Nginx代理百度地图离线瓦片
  14. 国外优秀免费空间不完全名单
  15. 当你在进行SDK安装更新时,遇到了一些不能安装的项目时,你可以酱紫····
  16. 为自己的站点实现访客统计
  17. java和python工资-Java和Python哪个薪资更高?
  18. Blog-Freshman
  19. 手把手学会 VS Code 快捷任务神技,成为项目组最靓的崽!
  20. 新浪微博客户端(12)-判断当前软件是否是新版本(是否显示新特性)

热门文章

  1. 【大华摄像机hls拉流vue使用videojs展示 一个页面多个视频同时播放】
  2. Log Concave Sequences(矩阵快速幂求递推)
  3. python-nodejs基于安卓Android/微信小程序的自来水收费系统APP
  4. 海康摄像机抓拍SDK集成java(包括windows和linux)
  5. 首次服务器装系统教程,服务器安装系统教程
  6. 什么是重定位?为什么需要重定位?【转】
  7. 地理信息系统(ArcGIS)在水文水资源、水环境中的实践
  8. IOS培训资料以及Demo
  9. QTreeWidget应用 -- 酷我音乐下载
  10. GObject signal机制