1. 常见的负载均衡方案
1.1 服务端负载均衡
- 在消费者和服务提供者中间使用独立的反向代理服务进行负载均衡。
- 可以通过硬件的方式提供反向代理服务,比如F5专业设备;也可以通过软件的方式提供反向代理服务,比如Nginx反向代理服务器;
- 更多的情况是两种方式结合,并且有多个层级的反向代理。
1.2 客户端负载均衡
- 客户端自己维护一份从注册中心获取的Provider列表清单,根据自己配置的Provider负载均衡选择算法在客户端进行请求的分发。
- Ribbon就是一个客户端的负载均衡开源组件,是Netflix发布的开源项目。
- Feign组件自身不具备负载均衡能力,Spring Cloud Feign是通过集成Ribbon组件实现客户端的负载均衡。
- Ribbon在客户端以轮询、随机、权重等多种方式实现负载均衡。
- 由于在微服务架构中同一个微服务Provider经常被部署多个运行实例,因此客户端的负载均衡可以说是基础能力。
2. Spring Cloud Ribbon基础
- Spring Cloud Ribbon是Spring Cloud集成Ribbon开源组件的一个模块,它不像服务注册中心Eureka Server、配置中心Spring Cloud Config那样独立部署,而是作为基础设施模块,几乎存在于每个Spring Cloud微服务提供者中。
- 微服务间的RPC调用以及API网关的代理请求的RPC转发调用,实际上都需要通过Ribbon来实现负载均衡。
- 有关Ribbon的详细资料可参考其官方网站(https://github.com/Netflix/ribbon)。
- 虽然Spring Cloud集成了Ribbon组件,但是要在Provider微服务中开启Ribbon负载均衡组件,还需要在Maven的pom文件中增加Spring Cloud Ribbon集成模块的依赖。
3. Spring Cloud Ribbon的负载均衡策略
3.1 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负载均衡策略接口的某个实现类来进行负载均衡。
3.2 主要的负载均衡策略实现类
- 随机策略(RandomRule):RandomRule实现类从Provider服务列表清单中随机选择一个Provider服务实例,作为RPC请求的目标Provider。
- 线性轮询策略(RoundRobinRule):RoundRobinRule和RandomRule相似,只是每次都取下一个Provider服务器。假设一共有5台Provider服务节点,使用线性轮询策略,第1次取第1台,第2次取第2台,第3次取第3台,以此类推。
- 响应时间权重策略(WeightedResponseTimeRule):WeightedResponseTimeRule为每一个Provider服务维护一个权重值,它的规则简单概括为Provider服务响应时间越长,其权重就越小。在进行服务器选择时,权重值越小,被选择的机会就越少。WeightedResponseTimeRule继承了RoundRobinRule,开始时每一个Provider都没有权重值,每当RPC请求过来时,由其父类的轮询算法完成负载均衡方式。该策略类有一个默认的每30秒执行一次的权重更新定时任务,该定时任务会根据Provider实例的响应时间更新Provider权重列表。后续有RPC过来时,将根据权重值进行负载均衡。
- 最少连接策略(BestAvailableRule):在进行服务器选择时,该策略类遍历Provider清单,选出可用的且连接数最少的一个Provider。该策略类里面有一个LoadBalancerStats类型的成员变量,会存储所有Provider的运行状况和连接数。在进行负载均衡计算时,如果选取到的Provider为null,就会调用线性轮询策略重新选取。如果第一次RPC请求时LoadBalancerStats成员为null,就会使用线性轮询策略来获取符合要求的实例,后续的RPC在选择的时候,才能选择连接数最少的服务。每次RPC请求时,BestAvailableRule都会统计LoadBalancerStats,作为后续请求负载均衡计算的输入。
- 重试策略(RetryRule):该类会在一定的时限内进行Provider循环重试。RetryRule会在每次选取之后对选举的Provider进行判断,如果为null或者not alive,就会在一定的时限内(如500毫秒)不停地选取和判断。
- 可用过滤策略(AvailabilityFilteringRule):该类扩展了线性轮询策略,会先通过默认的线性轮询策略选取一个Provider,再去判断该Provider是否超时可用,当前连接数是否超过限制,如果都符合要求,就成功返回。简单来说,AvailabilityFilteringRule将对候选的Provider进行可用性过滤,会先过滤掉因多次访问故障而处于熔断器跳闸状态的Provider服务,还会过滤掉并发的连接数超过阈值的Provider服务,然后对剩余的服务列表进行线性轮询。
- 区域过滤策略(ZoneAvoidanceRule)该类扩展了线性轮询策略,除了过滤超时和连接数过多的Provider之外,还会过滤掉不符合要求的Zone区域中的所有节点。
- Ribbon实现的负载均衡策略不止以上7种,还可以实现自定义的策略类。
3.3 通过配置更改负载均衡策略
- 可以通过Provider配置文件的ribbon.NFLoadBalancerRuleClassName配置项更改实际的负载均衡策略:
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 #随机选择
4. Spring Cloud Ribbon的常用配置
4.1 手工配置Provider实例清单
1.如果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服务就可以通过目标服务名称uaa-provider来调用其接口。
- 无论是在开发环境还是在测试环境,手工配置Provider清单的方式都用得很少。
4.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 #读取超时时间,单位为毫秒
4.3 重试机制配置
- 在有很多Provider实例同时运行的集群环境中,难免会有某个Provider节点出现故障。如果某个目标Provider节点已经挂掉,但其信息还是缓存在消费者的Ribbon实例清单,就会导致RPC时发生请求失败。
- 要解决上述问题,简单的方法就是利用Ribbon自带的重试策略进行重试,此时只需要指定消费者的负载策略为重试策略,并且配置适当的重试参数即可。重试策略和参数配置如下:
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请求的状态码如下:
- 2xx(成功):这类状态码标识客户端的请求被成功接收、理解并接受,常见的如200(OK)、204(NoContent)。
- 3xx(重定向):这类状态码标识请求发起端或请求代理要做出进一步的动作来完成请求,常见的如301(MovedPermanently)、302(MovedTemprarily)。
- 4xx(客户端错误):这类状态码是客户端出错时使用的,常见的如400(BadRequest)、401(Unauthorized)、403(Forbidden)、404(NotFound)。
- 5xx(服务器错误):这类状态码表示服务器知道自己出错或者没有能力执行请求,常见的如500(InternalServerError)、502(BadGateway)、504(GatewayTimeout)。
5.如果一个消费者依赖很多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.4 代码配置Ribbon
1.配置Ribbon比较简单的方式是使用配置文件,除此之外,还可以通过代码的方式进行配置。
2.一个常见的场景为:实际的RPC往往需要传递一些特定请求头,比如认证令牌,这时可以通过代码配置的方式对Ribbon的请求模板template进行请求头设置,完成请求头的传递。