第四章 客户端负载均衡:Spring Cloud Ribbon
Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 NetflixRibbon 实现。 通过 Spring Cloud 的封装, 可以让我们轻松地将面向服务的 REST 模板请求自动转换成客户端负载均衡的服务调用
客户端负载均衡
我们通常所说的负载均衡都指的是服务端负载均衡, 其中分为硬件负载均衡和软件负载均衡。 硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如 F5 等;而软件负载均衡则是通过在服务器上安装一些具有均衡负载功能或模块的软件来完成请求分发工作, 比如Nginx 等
硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候, 该设备按某种算法(比如线性轮询、 按权重负载、 按流量负载等)从维护的可用服务端清单中取出一台服务端的地址, 然后进行转发
客户端负载均衡和服务端负载均衡最大的不同点在千上面所提到的服务清单所存储的位置。 在客户端负载均衡中, 所有客户端节点都维护着自己要访问的服务端清单, 而这些服务端的清单来自于服务注册中心。我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步:
- 服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心
- 服务消费者直接通过调用被@LoadBalanced 注解修饰过的 RestTemplate 来实现面向服务的接口调用
负载均衡器
Spring Cloud中定义了LoadBalancerClient作为负载均衡器的通用接口, 并且针对Ribbon实现了RibbonLoadBalancerClient, 但是它在具体实现客户端负载均衡时,是通过Ribbon的ILoadBalancer接口实现的
Abstractload Balancer
AbstractLoadBalancer是ILoadBalancer接口的抽象实现。在该抽象类中定义了一个关于服务实例的分组枚举类 ServerGroup, 它包含以下三种不同类型:
- ALL:所有服务实例
- STATUS UP:正常服务的实例
- STATUS_NOT_UP:停止服务的实例
另外, 还实现了一 个 chooseServer () 函数, 该函数通过调用接口中的chooseServer (Object key)实现, 其中参数key为 null, 表示在选择具体服务实例时忽略key的条件判断
public abstract class AbstractLoadBalancer implements ILoadBalancer {
public enum ServerGroup{
ALL,
STATUS UP,
STATUS NOT UP
}
public Server chooseServer() {
return chooseServer(null);
}
//定义了根据分组类型来获取不同的服务实例的列表
public abstract List<Server> getServerList(ServerGroup serverGroup);
//定义了获取 LoadBalancerStats 对象的方法,LoadBalancerStats 对象被用来存储负载均衡器中各个服务实例当前的属性和统计信息
public abstract LoadBalancerStats getLoadBalancerStats(};
}
BaseloadBalancer
BaseLoadBalancer 类是和Ribbon 负载均衡器的基础实现类,在该类中定义了很多关于负载均衡器相关的基础内容:
- 定义并维护了两个存储服务实例 Server 对象的列表。 一个用千存储所有服务实例的清单, 一个用于存储正常服务的实例清单
- 定 义了之前我们提到的用来存储负载均衡器各服务实例属性和统计信息的LoadBalancerStatus 对象
- 定义了检查服务实例是否正常服务的工贮ng 对象, 在 BaseLoadBalancer 中默认为 null, 需要在构造时注入它的具体实现
- 定义了检查服务实例操作的执行策略对象IPingStrategy,在BaseLoadBalancer中默认使用了该类中定义的静态内部类 SerialPingStrategy 实现
- ···
DynamicServerlistloadBalancer
DynamicServerListLoadBalancer 类继承于 BaseLoadBalancer 类, 它是对基础负载均衡器的扩展。 在该负载均衡器中, 实现了服务实例清单在运行期的动态更新能力;同时, 它还具备了对服务实例清单的过滤功能, 也就是说, 我们可以通过过滤器来选择性地获取一批服务实例清单
还有ServerListUpdater、ServerListFilte、ZoneAwareloadBalancer
负载均衡策略
自动化配置
由于Ribbon中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有 一定的依赖关系,这使得第一次使用Ribbon的开发者很难上手,不知道如何选择具体的实现策略以及如何组织它们 的关系。 Spring Cloud Ribbon中的自动化配置恰恰 能够解决这样的
痛点,在引入Spring Cloud Ribbon的 依赖之后, 就能够自动化构建下面这些接口的实现:
- IClientConfig: Ribbon的客户端配置,默认采用 com.netfix.client.config.DefaultClientConfigimpl实现
- IRule::Ribbon的负载均衡策略,默认采用 com.netflix.loadbalancer.ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问
- IPing: Ribbon的实例检查策略,默认采用com.netffix.loadbalancer.NoOpPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用, 而是始终返回true, 默认认为所有服务实例都是可用的
- ServerList<Server>:服务实例清单的维护机制,默认采用 com.netflix.loadbalancer.ConfigurationBasedServerList实现
- ServerListFilter<Server>:服务实例清单过滤机制, 默认采用 org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter实现, 该策略能够优先过滤出与请求调用方处于同区域的服务实例
- ILoadBalancer:负载均衡器, 默认采用com.netflix.loadbalancer.ZoneAwareLoadBalancer实现, 它具备了区域感知的能力
参数配置
对千Ribbon的参数 配置通常有两种方式: 全局配置以及指定客户端配置
- 全局配置的方式很简单, 只需使用 ribbon.< key>= < value>格式进行配置即可。其中, <key>代表了Ribbon客户端配置的参数名, < value>则代表了 对应参数的值。比如, 我们可以像下面这样全局配置Ribbon创建连接的超时时间:ribbon.ConnectTimeout=250 全局配置可以作为默认值进行设置, 当指定客户端配置 了相应key的值时,将覆盖全局配置的内容
- 指定客户端的配置方式 采用 <client> .ribbon.< key>=< value>的格式进行配置。 其中, < key>和< value>的含义同全局配置相同, 而 <client>代表了客户端的名称
与Eureka结合
当在Spring Cloud的应用中同时引入Spring Cloud Ribbon和Spring Cloud Eureka依赖时,会触发Eureka中 实现的对Ribbon的自动化配置。这时 ServerList的维护机制实现将被com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList的实例所覆盖, 该实现会将服务清单列表交给Eureka的服务治理机制来进行维护;IPing的实现将被com.netflix.niws.loadbalancer.NIWSDiscoveryPing的实例所覆盖, 该实现也将实例检查的任务交给了 服务治理框架来进行维护。 默认情况下, 用 于获取实例请求的ServerList接口实现将采用Spring Cloud Eureka中封装的org.springframework.cloud.netftx.ribbon.eureka.DomainExtractingServerList,其目的是为了让实例维护策略更加通用, 所以将使用物理元数据来进行负载均衡, 而不是使用原生的AWSAMI元数据
重试机制
由于Spring Cloud Eureka实现的服务治理机制强调了CAP原理中的AP, 即可用性与可靠性,它与Zoo Keeper这类强调CP( 一致性、可靠性)的服务治理框架最大的区别就是,Eureka为了实现更高的服务可用性, 牺牲了一定的一致性, 在极端情况下它宁愿接受故障实例也不要丢掉 “ 健康” 实例, 比如, 当服务注册中心的网络发生故障断开时, 由于所有的服务实例无法维持续约心跳, 在强调AP的服务治理中将会把所有服务实例都剔除掉,而Eureka则会因为超过85%的实例丢失心跳而会触发保护机制,注册中心将会保留此时的所有节点, 以实现服务间依然可以进行互相调用的场景, 即使其中有部分故障节点, 但这样做可以继续保障大多数的服务正常消费
以hello-service服务的调用为例, 可以在配置文件中增加如下内容:
1 spring.cloud.loadbalancer.retry.enabled=true 2 hystrix.command.default.execution.isolation.thread.timeoutinMilliseconds=lOOOO 3 hello-service.ribbon.ConnectTimeout=250 4 hello-service.ribbon.ReadTimeout=lOOO 5 hello-service.ribbon.OkToRetryOnAllOperations=true 6 hello-service.ribbon.MaxAutoRetriesNextServer=2 7 hello-service.ribbon.MaxAutoRetries=l
- spring.cloud.loadbalancer.retry.enabled: 该参数用来开启重试机制,它默认是关闭的
-
hystrix.command.default.execution.isolation. thread.timeoutInMilliseconds: 断路器的超时时间需要大于Ribbon的超时时间, 不然不会触发重试
- hello-service.ribbon.ConnectTimeout: 请求连接的超时时间
- hello-service.ribbon.ReadTimeout: 请求处理的超时时间
- hello-service.ribbon.OkToRetryOnAllOperations: 对所有操作请求都进行重试
-
hello-service.ribbon.MaxAutoRetriesNextServer: 切换实例的重试次数
-
hello-service.ribbon.MaxAutoRetries: 对当前实例的重试次数
根据如上配置, 当访问到故障请求的时候, 它会再尝试访问 一次当前实例(次数由MaxAutoRetries配置), 如果不行, 就换 一个实例进行访问, 如果还是不行,再换 一次实例访问(更换次数由MaxAutoRetriesNextServer配置),如果依然不行, 返回失败信息