SpringCloud与微服务-第4章-客户端负载均衡(Ribbon)

第 4 章 客户端负载均衡(Ribbon)


负载均衡

众所周知,随着用户量的增加,应用的访问量也会随之增加,单台服务器已经远不能满足高并发的业务需求,这时就需要多台服务器组成集群来应对高并发带来的业务压力,同时也需要负均衡器来对流量进行合理分配。


负载均衡是一种基础的网络服务,它的核心原理是按照指定的负载均衡算法,将请求分配到后端服务集群上,从而为系统提供并行处理和高可用的能力。


负载均衡的方式有很多种,在 Spring Cloud 体系中,Ribbon 就是负载均衡的组件,所有的请求都是通过 Ribbon 来选取对应的服务信息的。


在活动 3.1 的试验中, 我们已经学习了如何使用 Spring Cloud Alibaba Nacos 来实现服务注册与发现。在存在多个服务实例的情况下,使用固定主机名和 ip 的方式来访问服务,会存在单点故障的问题,因此,我们需要使用负载均衡的方式来访问服务。



如上面代码所示, 我们可以通过服务发现客户端 DiscoveryClient 来获取服务实例,然后通过服务实例的主机名和端口号来拼接访问服务的 url。这里我们始终使用第一个服务实例,这样就会存在单点故障的问题。当然,我们也可以在这里实现不同的算法来选择服务实例,但是这样会增加我们的开发工作量。


Spring Cloud Ribbon 是 Spring Cloud Netflix 项目下的一个子项目,它是一个基于 HTTP 和 TCP 的客户端负载均衡工具。它基于 Netflix Ribbon 实现,可以很好的控制 HTTP 和 TCP 的一些行为。Spring Cloud Ribbon 提供了一系列完善的配置项如连接超时、重试等。


Spring Cloud Ribbon 虽然只是一个工具类框架,它不需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用, API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的。


所以,对 Spring Cloud Ribbon 的理解和使用,对于我们使用 Spring Cloud 来构建微服务非常重要。在这一章中,我们将具体介绍如何使用 Ribbon 来实现客户端的负载均衡,并且通过源码分析来了解 Ribbon 实现客户端负载均衡的基本原理。


目标:

在本章中,您将学习:

  • 负载均衡的原理
  • Ribbon 的应用

负载均衡方案

目前主流的负载方案分为两种:

  • 服务端负载均衡(集中式负载均衡)

  • 客户端负载均衡


服务端负载均衡(集中式负载均衡)

  • 集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的负载均衡器,比如 F5,也有软件,比如 Nginx。

bg right fit


集中式负载均衡的工作原理,负载均衡器负责维护需要负载的服务实例信息,如:192.168.1.1:8080 和 192.168.1.2:8080 这两个实例。

客户端不直接请求 192.168.1.1:8080 和 192.168.1.2:8080 这两个实例,而是通过负载均衡器来进行转发,客户端的请求到了负载均衡器这里,负载均衡器会根据配置的算法在 192.168.1.1:8080 和 192.168.1.2:8080 这两个实例中选取一个实例,转发到具体的实例上。

这样的好处是客户端不需要关心对应服务实例的信息,只需要跟负载均衡器进行交互,服务实例扩容或缩容,客户端不需要修改任何代码。


客户端负载均衡

  • 客户端负载均衡,客户端根据自己的请求情况做负载,Ribbon 就属于客户端自己做负载的框架。

客户端负载均衡需要自己维护服务实例的信息,然后通过某些负载均衡算法,从实例中选取一个实例,直接进行访问。

bg right fit


负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。


  • 硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如 F5 等;

F5 是一家美国的网络设备厂商,主要生产网络负载均衡器、应用程序交付控制器、Web 应用程序防火墙、DNS 服务器、内容分发网络、SSL VPN、身份管理系统、网络安全系统等产品。


  • 软件负载均衡则是通过在服务器上安装一些具有负载均衡功能或模块的软件来完成请求分发工作,比如 Nginx 等。

Nginx: 是一个高性能的 HTTP 和反向代理服务器,同时也提供了 IMAP/POP3/SMTP 服务。


硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。


而客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到的服务清单所存储的位置。


在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心,比如上一章我们介绍的 Nacos 服务端。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。


Spring Cloud Ribbon 快速实现负载均衡效果

通过 Spring Cloud Ribbon 的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:

  • 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。
  • 服务消费者直接通过调用被 @LoadBalanced 注解修饰过的 RestTemplate 来实现面向服务的接口调用。

这样,就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。


注释:
我们可以借助 IntelliJ IDEA 的配置复制功能,只用修改一下端口号即可很方便的启动多个实例.


小问题:
在微服务分布式项目中, 我们通常所说的负载均衡都指的是?

  1. 客户端负载均衡
  2. 前端负载均衡
  3. 服务端负载均衡
  4. 后端负载均衡
查看答案


Ribbon 主要组件

实现一个通用的负载均衡框架,则需要很多组件支持,Ribbon 中就提供了这些组件,有了这些组件,整个框架的扩展性便会更好,更灵活,我们可以根据业务需求,选择是否要自定义对应的组件来满足特定场景下的需求。



当我们需要通过 Ribbon 选择一个可用的服务实例信息,进行远程调用时,Ribbon 会根据指定的算法从服务列表中选择一个服务实例进行返回。

bg right fit


  • ServerList:

    • 在这个选择服务实例的过程中,服务实例信息是怎么来的呢?这就需要一个服务实例的存储组件来支持,ServerList 就是这个组件。存储分为静态和动态两种方式。静态存储需要事先配置好固定的服务实例信息,动态存储需要从注册中心获取对应的服务实例信息。
  • ServerListFilter

    • 有了服务信息后,在某些场景下我们可能需要过滤一部分信息,这个时候可以用 ServerListFilter 组件来实现过滤操作。

  • ServerListUpdater

    • Ribbon 会将服务实例在本地内存中存储一份,这样就不需要每次都去注册中心获取信息,这种场景的问题在于当服务实例增加或者减少后,本地怎么更新呢?这个时候就需要用到 ServerListUpdater 组件,ServerListUpdater 组件就是用于服务实例更新操作。
  • IPing

    • 缓存到本地的服务实例信息有可能已经无法提供服务了,这个时候就需要有一个检测的组件,来检测服务实例信息是否可用,这个组件就是 IPing。
  • IRule

    • Ribbon 会根据指定的算法来选择一个可用的实例信息,IRule 组件提供了很多种算法策略来选择实例信息。
  • ILoadBalancer

    • 最后就是我们使用的入口了,我们要选择一个可用的服务,怎么选择?问谁要这个服务?这时 ILoadBalancer 就上场了,ILoadBalancer 中定义了软件负载均衡操作的接口,比如动态更新一组服务列表,根据指定算法从现有服务器列表中选择一个可用的服务等操作。

总结

–组件– –职责–
ServerList 服务实例信息存储
ServerListFilter 服务实例信息过滤
ServerListUpdater 服务实例信息更新
IPing 服务实例信息检测
IRule 服务实例信息选择
ILoadBalancer 服务实例信息获取

这些组件都支持自定义,扩展性很强。


负载均衡的流程,可以用一幅图来总结一下:


bg fit


内置负载均衡规则类 规则描述
RoundRobinRule 简单轮询服务列表来选择服务器。它是 Ribbon 默认的负载均衡规则。
AvailabilityFilteringRule 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果 3 次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续 30 秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了 AvailabilityFilteringRule 规则的客户端也会将其忽略。
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。

内置负载均衡策略

w:38em


内置负载均衡规则类 规则描述
ZoneAvoidanceRule 以区域可用的服务器为基础进行服务器的选择。使用 Zone 对服务器进行分类,这个 Zone 可以理解为一个机房、一个机架等。而后再对 Zone 内的多个服务做轮询。(默认)
BestAvailableRule 忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule 随机选择一个可用的服务器。
RetryRule 重试机制的选择逻辑
WeightedResponseTimeRule 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
NacosRule Nacos 服务发现的负载均衡策略,根据 Nacos 中设置的权重随机访问。

除了上面的内置负载均衡策略外,Ribbon 还提供了一种自定义负载均衡策略的方式,即继承 AbstractLoadBalancerRule 类,然后实现 choose 方法,该方法的返回值就是选出的服务实例。这种方式的好处是可以根据自己的业务需求来实现自定义的负载均衡策略。


Ribbon 使用方式

Ribbon 的使用方式主要分为下面这三种,

  • 原生 API,Ribbon 是 Netflix 开源的,如果你没有使用 Spring Cloud,也可以在项目中单独使用 Ribbon,在这种场景下就需要使用 Ribbon 的原生 API。

  • Ribbon + RestTemplate,当我们项目整合了 Spring Cloud 时,就可以用 Ribbon 为 RestTemplate 提供负载均衡的服务。

  • Ribbon + Feign,关于 Feign 的使用方式会在后面的章节中进行详细的讲解。


原生 API

Ribbon 原生 API 使用非常方便,首先我们需要配置一个服务列表,数据格式为 IP + PORT,你可以固定写几个服务列表,也可以从别处读取,比如注册中心。



Ribbon + RestTemplate 配置详解

下面来详细介绍 Ribbon 在使用时的各种配置方式。


自动化配置

由于 Ribbon 中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,这使得第一次使用 Ribbon 的开发者很难上手,不知道如何选择具体的实现策略以及如何组织它们的关系。 Spring Cloud Ribbon 中的自动化配置恰恰能够解决这样的痛点,在引入 SpringCloud Ribbon 的依赖之后,就能够自动化构建下面这些接口的实现。


  • IClientConfig : Ribbon 的客户端配置,默认采用 com.netflix.client.config.DefaultClientConfiglmpl 实现。
  • IRule: Ribbon 的负载均衡策略,默认采用 com.netflix.loadbalancer.ZoneAvoidanceRule 实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
  • IPing: Ribbon 的实例检查策略,默认采用 com.netflix.loadbalancer.DummyPing 实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回 true, 默认认为所有服务实例都是可用的。
  • ServerList<Server> :服务实例清单的维护机制,默认采用 com.netflix.loadbalancer.ConfigurationBasedServerList 实现。

  • ServerListFilter<Server> : 服务实例清单过滤机制,默认采用 org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter实现,该策略能够优先过滤出与请求调用方处于同区域的服务实例。
  • ILoadBalancer : 负载均衡器,默认采用 com.netflix.loadbalancer.ZoneAwareLoadBalancer 实现,它具备了区域感知的能力。

上面这些自动化配置内容仅在没有引入 Spring Cloud Alibaba Nacos 等服务治理框架时如此, 在引入 Nacos 依赖时,自动化配置会有一些不同,后续会做详细的介绍。


通过自动化配置的实现,我们可以轻松地实现客户端负载均衡。同时,针对一些个性化需求,我们也可以方便地替换上面的这些默认实现。只需在 Spring Boot 应用中创建对应的实现实例就能覆盖这些默认的配置实现。比如可以在启动类中添加如下的配置内容,由于创建了 NacosRule 实例,所以默认的 ZoneAvoidanceRule 就不会被创建。


image-20220719165005737


参数配置

对于 Ribbon 的参数配置通常有两种方式:全局配置以及指定客户端配置。

  • 全局配置的方式很简单,只需使用 ribbon.<key>=<value>格式进行配置即可。 其中,<key>代表了 Ribbon 客户端配置的参数名,<value>则代表了对应参数的值。比如,可以像下面这样全局配置 Ribbon 的连接超时时间。


注意:全局配置的参数名是不区分大小写的,但是值是区分大小写的。比如,下面这两种配置方式是等价的。

  1. ribbon.ConnectTimeout=5000
  2. ribbon.connectTimeout=5000

客户端所有可配置的 key 可通过此类查看
com.netflix.client.config.CommonClientConfigKey

默认的客户端配置可以通过此类查看
com.netflix.client.config.DefaultClientConfigImpl


全局配置可以作为默认值进行设置,当指定客户端配置了相应 key 的值时,将覆盖全局配置的内容。

  • 指定客户端的配置方式采用<client>.ribbon.<key>=<value>的格式进行配置。其中,<key><value>的含义同全局配置相同,而<client>代表了客户端的名称,也可以将它理解为是一个服务名。假设现在没有例如 Nacos 服务治理框架的帮助来得到动态服务列表,为了实现负载均衡, 我们可以进行如下配置来设置静态的服务列表:


IRule 配置

上面使用代码的方式可以实现修改负载均衡规则,不过在启动类中修改,属于是全局应用的范围.也就是说,无论调用哪个微服务,我们都将使用该负载均衡的规则.通过另一种方式,我们可以实现更加细粒度的配置.

在 application.yml 文件中,添加新的配置也可以修改规则,只针对某个微服务起效:


饥饿加载

Ribbon 默认采用懒加载,也就是在第一次访问时 LoadBalanceClient 才会创建,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过如下配置即可开启饥饿加载:


与 Nacos 结合

当在 Spring Cloud 的应用中引入 Spring Cloud Alibaba Nacos 依赖时,会触发 Nacos 中实现的对 Ribbon 的自动化配置。

这时 ServerList 的维护机制实现将被 com.alibaba.cloud.nacos.ribbon.NacosServerList 的实例所覆盖,该实现会将服务清单列表交给 Nacos 的服务治理机制来进行维护。

image-20220719183708475


在与 Spring Cloud Alibaba Nacos 结合使用的时候,我们的配置将会变得更加简单。不再需要通过类似 userservice.ribbon.listOfServers 的参数来指定具体的服务实例清单,因为 Nacos 将会为我们动态维护所有服务的实例清单。而对于 Ribbon 的参数配置,依然可以采用之前的两种配置方式来实现,而指定客户端的配置方式可以直接使用 Nacos 中的服务名作为客户端名来完成针对各个微服务的个性化配置。


Spring Cloud Ribbon 默认实现了区域亲和策略。此策略会优先请求同一个 Zone 区域的服务实例。但在一些情况下:比如当不存在同区域的服务、并且同区域的服务负载过高等,仍会自动切换成其他区域的服务。



spring.cloud.nacos.discovery.cluster-name 属性用于指定当前服务所在的区域名称,当服务启动时,会将该属性的值作为服务的元数据信息,注册到 Nacos 服务注册中心中。当服务消费者调用服务提供者时,会根据服务提供者的元数据信息来选择服务实例。


重试机制

从 Camden SR2 版本开始, Spring Cloud 整合了 Spring Retry 来增强 RestTemplate 的重试能力,对于开发者来说只需通过简单的配置,原来那些通过 RestTemplate 实现的服务访问就会自动根据配置来实现重试策略。重试机制是默认开启的,以之前对 userservice 服务的调用为例,可以在配置文件中增加如下内容:



根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由 MaxAutoRetries 配置),如果不行,就换一个实例进行访问,如果还是不行,再换一次实例访问(更换次数由 MaxAutoRetriesNextServer 配置),如果依然不行,返回失败信息。


注意:

为了触发重试机制,断路器(第 5 章节讲解)的超时时间应该大于 Ribbon 的超时时间。

具体来说,当请求被发送到远程服务时,Ribbon 会开始计时。如果在超时时间内请求成功完成,则重试机制不会被触发。如果请求在超时时间内未能成功完成,则会尝试使用下一个可用实例进行重试。如果重试也未能成功,则断路器会被触发。

因此,为了触发重试机制,断路器的超时时间应该大于 Ribbon 的超时时间。这样,当重试机制被触发时,断路器才能够捕获到请求失败的事件,并执行相应的处理。


问题: The Raft Group [naming_instance_metadata] did not find the Leader node

原因: Nacos 服务端的注册信息不一致
解决: 先停服务,再停 nacos,再删掉 data 目录下的 protocol 文件夹,再重启 nacos.最后再重启需要注册的那些服务.


自定义负载均衡策略

Spring Cloud Ribbon 提供了一些默认的负载均衡策略,比如轮询、随机、响应时间加权等。但是在实际的开发过程中,我们可能会需要一些自定义的负载均衡策略,比如:根据服务实例的元数据信息来选择服务实例、根据服务实例的运行状态来选择服务实例等。


那么如何实现自定义的负载均衡策略呢?Spring Cloud Ribbon 提供了一个抽象类 AbstractLoadBalancerRule,我们只需要继承该类并实现其中的 choose 方法即可。在 choose 方法中,我们可以根据自己的需求来选择服务实例。



活动 4.1 :

使用 Ribbon 实现客户端负载均衡


附加活动

  1. 通过 Ribbon 实现客户端负载均衡
  2. 指定自定义的负载均衡策略
  3. 指定自定义的 IPing 实现类

com.example.ribbon.rule.MyRule


com.example.ribbon.rule.MyIPing


可以以配置文件方式指定自定义的负载均衡策略和 IPing 实现类.


也可以以代码方式指定自定义的负载均衡策略和 IPing 实现类, 优先级高于配置文件.

com.example.ribbon.config.RibbonClientConfig


com.example.ribbon.config.RibbonConfiguration


练习问题

  1. 以下哪个不是负载均衡器?
    a. DynamicServerListLoadBalancer
    b. BaseLoadBalancer
    c. AbstractLoadBalancer
    d. ServerListSubsetFilter

    查看答案


  1. 下面哪个不是 ServerListFilter 接口的实现类?
    a. ZonePreferenceServerListFilter
    b. DefaultNIWSServerListFilter
    c. ZoneAwareLoadBalancer
    d. ZoneAffinityServerListFilter

    查看答案


  1. AbstractLoadBalancer 是什么接口的抽象实现?
    a. ILoadBalancer
    b. IloadBalancr
    c. IoadBalancer
    d. IloadBlancer

    查看答案


  1. 对于 Ribbon 的参数配置通常有几种方法?
    a. 1
    b. 2
    c. 3
    d. 4

    查看答案


小结

在本章中,您学习了:

  • 负载均衡是对系统的高可以、网络压力的缓解和处理能力扩容的重要手段之一
  • 在 Ribbon 中通过 RestTemplate 实现客户端负载均衡
  • 负载均衡器
    • AbstractLoadBalancer
    • BaseLoadBalancer
    • DynamicServerListLoadBalancer
    • ZoneAwareLoadBalancer

  • 负载均衡策略
    • AbstractLoadBalancerRule
    • RandomRule
    • RoundRobinRule
    • RetryRule
    • WeightedResponseTimeRule
    • ClientConfigEnabledRoundRobinRule
    • BestAvailableRule
    • PredicateBasedRule
    • AvailabilityFilteringRule
    • ZoneAvoidanceRule

  • Ribbon 在使用时的各种配置方式
    • 自动化配置
    • 参数配置
    • 与 Nacos 结合
    • 加入了重试机制

Views: 5