springcloud入门实战:Feign+Ribbon实现客户端负载均衡

Feign+Ribbon实现客户端负载均衡

理论上,如果服务端同一个服务提供者存在多个运行实例,一般的负载均衡方案分为以下两种:

1.服务端负载均衡

在消费者和服务提供者中间使用独立的反向代理服务进行负载均衡。可以通过硬件的方式提供反向代理服务,比如F5专业设备;也可以通过软件的方式提供反向代理服务,比如Nginx反向代理服务器;更多的情况是两种方式结合,并且有多个层级的反向代理。

2.客户端负载均衡

客户端自己维护一份从注册中心获取的Provider列表清单,根据自己配置的Provider负载均衡选择算法在客户端进行请求的分发。

Ribbon就是一个客户端的负载均衡开源组件,是Netflix发布的开源项目。

Feign组件自身不具备负载均衡能力,Spring Cloud Feign是通过集成Ribbon组件实现客户端的负载均衡。Ribbon在客户端以轮询、随机、权重等多种方式实现负载均衡。由于在微服务架构中同一个微服务Provider经常被部署多个运行实例,因此客户端的负载均衡可以说是基础能力。

Spring Cloud Ribbon基础

Spring Cloud Ribbon是Spring Cloud集成Ribbon开源组件的一个模块,它不像服务注册中心Eureka Server、配置中心Spring CloudConfig那样独立部署,而是作为基础设施模块,几乎存在于每个SpringCloud微服务提供者中。微服务间的RPC调用以及API网关的代理请求的RPC转发调用,实际上都需要通过Ribbon来实现负载均衡。

有关Ribbon的详细资料可参考其官方网站(
https://github.com/Netflix/ribbon),本文只对其基本的使用进行介绍。

虽然Spring Cloud集成了Ribbon组件,但是要在Provider微服务中开启Ribbon负载均衡组件,还需要在Maven的pom文件中增加以下SpringCloud Ribbon集成模块的依赖:

<!--导入Spring Cloud Ribbon -->
 <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
 </dependency>

打开该依赖模块的配置文件
spring-cloud-starter-netflixribbon-{version}.pom(这里的version版本号为2.0.0.RELEASE),发现Spring Cloud Ribbon集成模块主要依赖表2-1所示的Ribbon组件模块。

springcloud入门实战:Feign+Ribbon实现客户端负载均衡

表2-1 Ribbon组件模块

在Spring Cloud的Provider中使用Ribbon,只需要导入SpringCloud Ribbon依赖,Ribbon在RPC调用时就会生效。下面以Cray-SpringCloud微服务脚手架为例演示Ribbon的执行过程,整体的演示需要启动3个微服务提供者,如图2-13所示。

springcloud入门实战:Feign+Ribbon实现客户端负载均衡

图2-13 Feign+Ribbon客户端负载均衡演示Provider实例示意图

演示过程大致如下:

(1)demo-provider模块在增加Spring Cloud Ribbon依赖后,Feign+Ribbon的客户端负载均衡将自动生效。演示还是使用“
/api/call/uaa/user/detail/v2”REST接口,这一次其将以负载均衡的方式访问uaa-provider的“/api/user/detail/v1”REST接口。

(2)启动两个uaa-provider服务提供者:可以在IDEA调试环境(localhost)中启动一个,在另一台主机(如虚拟机)上启动一个。

在Eureka上查看uaa-provider实例清单,确保两个uaa-provider提供者实例都成功启动。

(3)在IDEA调试环境启动demo-provider实例,在demo-provider的swagger-ui界面上发起对uaa-provider服务提供者的RPC调用。这里为了演示客户端的负载均衡,可以在提供者uaa-provider的swagger-ui界面上多次访问“
/api/call/uaa/user/detail/v2”REST接口。

(4)在demo-provider重要的源码处打上断点,通过断点可以查看每次RPC实际访问的目标提供者uaa-provider的实例。

断点之一设置在ribbon-loadbalancer组件LoadBalancerContext类的getServerFromLoadBalancer方法的某行代码上(见图2-14),该方法的功能是获取目标Provider实例。每次RPC请求调用到来时,可以查看Ribbon负载均衡计算出来的Provider,它放置在类型为Server的svc变量中。

springcloud入门实战:Feign+Ribbon实现客户端负载均衡

图2-14 Ribbon计算出来的Provider值示意图

断点之二可以设置在ribbon-loadbalancer组件的
AbstractLoadBalancerAwareClient类的方法executeWithLoadBalancer的某行代码上(见图2-15),可以查看到每次RPC调用的最终URL地址保存在finalUri变量中。

springcloud入门实战:Feign+Ribbon实现客户端负载均衡

图2-15 Ribbon计算出来的最终URL地址值示意图

多次执行并观察断点处的变量值可以发现uaa-provider的两个实例轮番被RPC访问到。

本小节的演示过程可参见疯狂创客圈社群网盘小视频:“SpringCloud实战视频:Feign+Ribbon实现客户端负载均衡.mp4”。

Spring Cloud Ribbon的负载均衡策略

Ribbon负载均衡的原理是:从Eureka Client实例获取Provider服务列表清单,并且定期通过IPing实例判断清单中Provider服务实例的可用性。每次RPC调用到来时,在Provider服务列表清单中根据IRule策略类的Bean计算出每次RPC要访问的最终Provider。

Ribbon内部有一个负载均衡器接口ILoadBalance,定义了添加Provider、获取所有的Provider列表、获取可用的Provider列表等基础的操作。该接口的核心实现类
DynamicServerListLoadBalancer会通过EurekaClient(实现类为DiscoveryClient)获取Provider清单,并且通过IPing实例定期(如每10秒)向每个Provider实例发送“ping”,并且根据Provider是否有响应来判断该Provider实例是否可用。如果该Provider的可用性发生了改变,或者Provider清单中的数量和之前的不一致,就从注册中心更新或者重新拉取Provider服务实例清单。

每次RPC请求到来时,由Ribbon的IRule负载均衡策略接口的某个实现类来进行负载均衡。主要的负载均衡策略实现类如下:

1.随机策略(RandomRule)

RandomRule实现类从Provider服务列表清单中随机选择一个Provider服务实例,作为RPC请求的目标Provider。

2.线性轮询策略(RoundRobinRule)

RoundRobinRule和RandomRule相似,只是每次都取下一个Provider服务器。假设一共有5台Provider服务节点,使用线性轮询策略,第1次取第1台,第2次取第2台,第3次取第3台,以此类推。

3.响应时间权重策略(WeightedResponseTimeRule)

WeightedResponseTimeRule为每一个Provider服务维护一个权重值,它的规则简单概括为Provider服务响应时间越长,其权重就越小。在进行服务器选择时,权重值越小,被选择的机会就越少。

WeightedResponseTimeRule继承了RoundRobinRule,开始时每一个Provider都没有权重值,每当RPC请求过来时,由其父类的轮询算法完成负载均衡方式。该策略类有一个默认的每30秒执行一次的权重更新定时任务,该定时任务会根据Provider实例的响应时间更新Provider权重列表。后续有RPC过来时,将根据权重值进行负载均衡。

4.最少连接策略(BestAvailableRule)

在进行服务器选择时,该策略类遍历Provider清单,选出可用的且连接数最少的一个Provider。该策略类里面有一个LoadBalancerStats类型的成员变量,会存储所有Provider的运行状况和连接数。在进行负载均衡计算时,如果选取到的Provider为null,就会调用线性轮询策略重新选取。

如果第一次RPC请求时LoadBalancerStats成员为null,就会使用线性轮询策略来获取符合要求的实例,后续的RPC在选择的时候,才能选择连接数最少的服务。每次RPC请求时,BestAvailableRule都会统计LoadBalancerStats,作为后续请求负载均衡计算的输入。

5.重试策略(RetryRule)

该类会在一定的时限内进行Provider循环重试。RetryRule会在每次选取之后对选举的Provider进行判断,如果为null或者not alive,就会在一定的时限内(如500毫秒)不停地选取和判断。

6.可用过滤策略(AvailabilityFilteringRule)

该类扩展了线性轮询策略,会先通过默认的线性轮询策略选取一

个Provider,再去判断该Provider是否超时可用,当前连接数是否超过限制,如果都符合要求,就成功返回。

简单来说,AvailabilityFilteringRule将对候选的Provider进行可用性过滤,会先过滤掉因多次访问故障而处于熔断器跳闸状态的Provider服务,还会过滤掉并发的连接数超过阈值的Provider服务,然后对剩余的服务列表进行线性轮询。

7.区域过滤策略(ZoneAvoidanceRule)

该类扩展了线性轮询策略,除了过滤超时和连接数过多的Provider之外,还会过滤掉不符合要求的Zone区域中的所有节点。

Ribbon实现的负载均衡策略不止以上7种,还可以实现自定义的策略类。本文使用的Spring Cloud Ribbon版本中默认使用了ZoneAvoidanceRule负载均衡策略,可以通过Provider配置文件的
ribbon.NFLoadBalancerRuleClassName配置项更改实际的负载均衡策略。2.4.1小节的演示中,微服务demo-provider对uaa-provider的RPC调用使用RetryRule负载均衡策略,demo-provider的具体配置如下:

uaa-provider:
 ribbon:
 NFLoadBalancerRuleClassName: com.netflix.loadbalancer. RetryRule #重试+线性轮询
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule
#最少连接策略
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机选择

如果要配置全局的、针对所有Provider都使用的负载均衡策略,就可以在配置文件中直接使用
ribbon.NFLoadBalancerRuleClassName配置项进行配置,具体如下:

ribbon:
 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #重试+线性轮询
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule
#最少连接策略
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机选择

 Spring Cloud Ribbon的常用配置

上节介绍了负载均衡的配置,本小节介绍Ribbon的一些常用配置选项,以及对这些选项进行配置的两种方式:代码方式和配置文件方式。

1.手工配置Provider实例清单

如果Ribbon没有和Eureka集成,Ribbon消费者客户端就不能从Eureka(或者其他的注册中心)拉取到Provider清单。如果不需要和Eureka集成,那么可以使用如下方式手工配置Provider清单:

ribbon:

eureka:

enabled: false #禁用Eureka

uaa-provider:

ribbon:


listOfServers:192.168.142.1:7702,192.168.233.128:7702#手工配置Provider清单这个配置是针对uaa-provider服务的,配置项的前缀就是RPC目标服务名称。配置完之后,demo-provider服务就可以通过目标服务名称uaaprovider来调用其接口。

无论是在开发环境还是在测试环境,手工配置Provider清单的方式都用得很少,之所以在此介绍该方式,仅仅是为了让大家更加明白Ribbon的工作方式。

2.RPC请求超时配置

Ribbon中有两种和时间相关的设置,分别是请求连接的超时时间ConnectTimeout和请求处理的超时时间ReadTimeout。大家都知道,HTTP请求的3个阶段:建立连接阶段、数据传送阶段、断开连接阶段。ConnectTimeout指的是第一个阶段建立连接所能用的最长时间。第一个阶段需要进行3次握手,ConnectTimeout用于设置3次握手完成的最长时间。如果在ConnectTimeout设置的时间内消费端连接不上目标Provider服务,连接就会超时。这个超时也许是目标Provider宕机所导致的,也许是网络延迟所导致的。

ReadTimeout指的是连接成功之后,从服务器读取到可用数据所花费的最长时间。如果在ReadTimeout设置的时间内目标Provider没有及时返回数据,就会导致读超时,也常常被称为请求处理超时。Ribbon设置RPC请求超时的规则如下:

ribbon:
 ConnectTimeout: 30000 #连接超时时间,单位为毫秒
 ReadTimeout: 30000 #读取超时时间,单位为毫秒

在实际场景中,每个目标Provider的性能要求也许是不一样的,可以单独为某些Provider目标服务设置特定的超时时间,只要通过服务名称进行指定即可:

uaa-provider:
 ribbon:
 ConnectTimeout: 30000 #连接超时时间,单位为毫秒
 ReadTimeout: 30000 #读取超时时间,单位为毫秒

3.重试机制配置

在有很多Provider实例同时运行的集群环境中难免会有某个Provider节点出现故障。如果某个目标Provider节点已经挂掉,但其信息还是缓存在消费者的Ribbon实例清单,就会导致RPC时发生请求失败。要解决上述问题,简单的方法就是利用Ribbon自带的重试策略进行重试,此时只需要指定消费者的负载策略为重试策略,并且配置适当的重试参数即可。

为进行具体的演示,demo-provider微服务的重试策略和参数配置如下:

ribbon:

MaxAutoRetries: 1 #同一台实例的最大重试次数,但是不包括首次调用,默认为1次 MaxAutoRetriesNextServer: 1 #重试其他实例的最大重试次数,不包括首次调用,默认为0次 OkToRetryOnAllOperations: true #是否对所有操作都进行重试,默认为false 
ServerListRefreshInterval: 2000 #从注册中心刷新Provider的时间间隔,默认为2000毫秒,即2秒 retryableStatusCodes: 400,401,403,404,500,502,504 NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RetryRule #负载均衡配置为重试策略在上面的配置中,选项retryableStatusCodes用于配置对特定的HTTP响应码进行重试,常见的HTTP请求的状态码如下:

(1)2xx(成功):这类状态码标识客户端的请求被成功接收、理解并接受,常见的如200(OK)、204(NoContent)。

(2)3xx(重定向):这类状态码标识请求发起端或请求代理要做出进一步的动作来完成请求,常见的如301(MovedPermanently)、302(MovedTemprarily)。

(3)4xx(客户端错误):这类状态码是客户端出错时使用的,常见的如400(BadRequest)、401(Unauthorized)、403(Forbidden)、404(NotFound)。

(4)5xx(服务器错误):这类状态码表示服务器知道自己出错或者没有能力执行请求,常见的如500(InternalServer Error)、502(BadGateway)、504(GatewayTimeout)。如果一个消费者依赖很多Provider,就可以使用上面的重试策略与参数针对特定的目标Provider进行单独配置。只要在配置时通过微服务名称进行指定即可:

uaa-provider:
 ribbon:
 MaxAutoRetries: 1
 MaxAutoRetriesNextServer: 1
 OkToRetryOnAllOperations: true
 ServerListRefreshInterval: 2000
 retryableStatusCodes: 400,401,403,404,500,502,504
 NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RetryRule

4.代码配置Ribbon

配置Ribbon比较简单的方式是使用配置文件,除此之外,还可以通过代码的方式进行配置。

一个常见的场景为:实际的RPC往往需要传递一些特定请求头,比如认证令牌,这时可以通过代码配置的方式对Ribbon的请求模板template进行请求头设置,完成请求头的传递。参考的代码如下:

package com.crazymaker.springcloud.standard.config;
...
/**
 *通过代码配置Ribbon
 */
@Configuration
public class FeignConfiguration implements RequestInterceptor
{
 /**
 *配置RPC时的请求头与参数,将来自用户的令牌传递给目标Provider
 *@param template请求模板
 */
 @Override
 public void apply(RequestTemplate template)
 {
 /**
 *从用户请求的上下文属性获取用户令牌
 */
 ServletRequestAttributes attributes =
 (ServletRequestAttributes)
 RequestContextHolder.getRequestAttributes();
 if (null == attributes)
 {
 return;
 }
 HttpServletRequest request = attributes.getRequest(); /**
 *获取令牌
 */
 String token = request.getHeader
 (SessionConstants.AUTHORIZATION_HEAD);
 if (null != token)
 {
 token = StringUtils.removeStart(token, "Bearer ");
 /**
 *设置令牌
 */
 template.header("token ", new String[]{token});
 }
 ...
 }
 /**
 *配置负载均衡策略
 */
 @Bean
 public IRule ribbonRule()
 {
 /**
 *配置为线性轮询策略
 */
 return new RoundRobinRule();
 }
...
}
在以上配置代码的apply()方法中,为Ribbon的RPC请求模板template增加了一个叫作token的请求头,用于在RPC调用时进行用户令牌的传递;另外,在以上代码的ribbonRule()方法中,通过程序的方式配置了Ribbon的负载均衡策略为线性轮询。

如何使以上自定义的配置程序生效呢?如果需要对所有的Feign客户端生效,就可以在启动类上进行配置,将自定义的Ribbon配置类赋值给@EnableFeignClients注解的defaultConfiguration属性,示例如下:

...
@EnableFeignClients(
 basePackages = "com.crazymaker.springcloud.user.info.remote.client",
 defaultConfiguration = FeignConfiguration.class)
public class DemoCloudApplication
{
 public static void main(String[] args)
 {
 SpringApplication.run(DemoCloudApplication.class, args);
 ...
 }
}

如果需要对某个特定的(部分的)Feign客户端生效,就可以在特定Feign客户端接口上进行配置,将自定义的Ribbon配置类赋值给@FeignClient注解的configuration属性,示例如下:

package com.crazymaker.springcloud.user.info.remote.client;
...
/**
 *@description:用户信息 远程调用接口
 *create by尼恩 @ 疯狂创客圈
 */
@FeignClient(value = "uaa-provider",
 configuration = FeignConfiguration.class,
 fallback = UserClientFailBack.class,
 path = "/uaa-provider/api/user")
public interface UserClient
{
...
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值