SpringCloud Ribbon 负载均衡

忘是亡心i 2023-02-14 02:44 171阅读 0赞

目录

      • 服务端负载均衡与客户端负载均衡
      • springcloud的服务调用流程
      • ribbon内置的负载均衡策略(7种)
      • 设置负载均衡策略
      • 自定义负载均衡策略
        • 通用示例
        • nacos做注册中心之优先调用当前集群中的服务
        • nacos做注册中心之调用指定版本的服务
      • ribbon的饥饿加载

服务端负载均衡与客户端负载均衡

假设user-server要调用order-server:user-server -> order-server

是user-server向order-server发起调用请求,user-server是客户端,order-server是服务端,提供服务。

  • 如果在客户端(消费者)获取服务端(提供者)的节点列表、选择一个节点来使用,即在客户端实现的负载均衡,称为客户端负载均衡;
  • 如果用nginx之类的反向代理服务端,客户端向nginx发起请求,nginx通过负载均衡算法转发给服务端的某个节点,即在服务端实现的负载均衡,称为服务端负载均衡。

eureka client、nacos client、zuul、gateway都内置了ribbon,以实现客户端的负载均衡。

springcloud的服务调用流程

eg. user-service的某个节点要调用order-service

1、向内置的ribbon发起调用order-service的负载均衡请求

2、ribbon查询缓存中有没有order-service的节点列表,有就直接使用,没有就从注册中心拉取

3、ribbon使用指定的负载均衡算法从节点列表中选取一个节点

4、user-service向返回的order-service节点发起调用

对于一个提供者服务,ribbon会在缓存中维护2个List:

  • 该服务所有节点的信息列表,从注册中心拉取,后续ribbon会轮询注册中心此节点列表是否有更新,如果有更新会从注册中心重新获取拉取。
  • 该服务有效节点的信息列表:ribbon会将拉取到的节点列表复制一份作为有效节点列表,定时检测有效节点列表中的节点是否还有效,若无效则从有效节点列表中删除该节点。

调用提供者时,ribbon先从本地缓存的该服务可用节点列表中获取一个节点,如果列表中没有可用的节点,再从注册中心更新该服务的节点列表。

ribbon内置的负载均衡策略(7种)

1、RoundRobinRule  轮询(默认策略)
轮询适合节点性能都差不多的情况。从前往后依次轮询节点列表中的每个节点,谁空闲就调用谁。

2、RetryRule  重试
先轮询,如果未获取到节点,则在指定的时间内(默认500ms)重试。

3、RandomRule  随机  

4、BestAvailableRule  最可用,选择负载最小的节点  

5、AvailabilityFilteringRule  可用过滤
先过滤掉处于断路状态(断路器打开)、负载很大的节点,再使用轮询。  

6、ZoneAvoidanceRule  根据大区性能、节点可用性综合筛选  

7、WeightedResponseTimeRule  权重响应时间
根据节点的平均响应时间计算权重,响应快的权重大,被选中的机率就越大。

设置负载均衡策略

eureka client的依赖中已经包含了ribbon的依赖,不用额外添加依赖。

负载均衡策略是在消费者中设置的,有2种方式

配置文件方式

  1. #设置调用order-service的负载均衡策略
  2. order-service:
  3. ribbon:
  4. NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

代码方式

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. public class UserServiceApplication {
  4. //如果直接使用RestTemplate进行服务调用,需要加上此方法
  5. @LoadBalanced
  6. @Bean
  7. public RestTemplate restTemplate() {
  8. return new RestTemplate();
  9. }
  10. //设置负载均衡策略
  11. @Bean
  12. public RandomRule getRule(){
  13. return new RandomRule();
  14. }
  15. public static void main(String[] args) {
  16. SpringApplication.run(UserServiceApplication.class, args);
  17. }
  18. }

配置文件方式设置的负载均衡策略只对指定的服务调用有效,代码设置方式对所有的服务调用都有效。

如果要对所有服务调用都设置相同的负载均衡策略,使用代码设置方式;如果要针对各个提供者设置不同的负载均衡策略,使用yml配置方式。

一般使用默认的轮询即可。

自定义负载均衡策略

通用示例

  1. import com.netflix.client.config.IClientConfig;
  2. import com.netflix.loadbalancer.AbstractLoadBalancerRule;
  3. import com.netflix.loadbalancer.ILoadBalancer;
  4. import com.netflix.loadbalancer.Server;
  5. import java.util.List;
  6. /** * 自定义的负载均衡策略 * 继承AbstractLoadBalancerRule,重写choose()方法 */
  7. public class MyRule extends AbstractLoadBalancerRule {
  8. @Override
  9. public void initWithNiwsConfig(IClientConfig iClientConfig) {
  10. }
  11. /** * @param o ribbon读取到的目标服务的所有节点的信息,会自动传入。很多时候都是ribbon缓存的数据,可能含有无效的节点 * @return 选择使用的节点 */
  12. @Override
  13. public Server choose(Object o) {
  14. //获取所要调用服务的负载均衡器
  15. ILoadBalancer loadBalancer = this.getLoadBalancer();
  16. //获取目标服务的所有节点,可能包含无效节点
  17. List<Server> allList = loadBalancer.getAllServers();
  18. //获取目标服务的所有可用节点
  19. List<Server> upList = loadBalancer.getReachableServers();
  20. for (Server server : upList) {
  21. //有效且空闲
  22. if (server.isAlive() && server.isReadyToServe()) {
  23. return server;
  24. }
  25. }
  26. return null;
  27. }
  28. }

配置方式和内置策略的配置方式相同。

相比于eureka,nacos提供了更多的属性、方法,用nacos做注册中心可以自定义更多的负载均衡策略,比如优先调用当前集群中的服务、调用指定版本的服务。

nacos做注册中心之优先调用当前集群中的服务

此处的集群可以理解为数据中心,比如按地域划分为华东、东北、西南…每个区域都建立全套服务器,eg. tomcat集群中包含多个user-server节点、order-server节点,user-server调用order-server时优先调用当前区域集群中的order集群节点。

  1. spring:
  2. application:
  3. name: user-server
  4. cloud:
  5. nacos:
  6. discovery:
  7. server-addr: 127.0.0.1:8848
  8. #部署服务时需要给节点指定所属的集群
  9. cluster-name: SW
  10. import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
  11. import com.alibaba.cloud.nacos.NacosServiceManager;
  12. import com.alibaba.cloud.nacos.ribbon.NacosServer;
  13. import com.alibaba.nacos.api.exception.NacosException;
  14. import com.alibaba.nacos.api.naming.NamingService;
  15. import com.alibaba.nacos.api.naming.pojo.Instance;
  16. import com.alibaba.nacos.client.naming.core.Balancer;
  17. import com.netflix.client.config.IClientConfig;
  18. import com.netflix.loadbalancer.AbstractLoadBalancerRule;
  19. import com.netflix.loadbalancer.BaseLoadBalancer;
  20. import com.netflix.loadbalancer.Server;
  21. import lombok.extern.slf4j.Slf4j;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.util.CollectionUtils;
  24. import java.util.List;
  25. import java.util.Objects;
  26. import java.util.stream.Collectors;
  27. /** * 自定义的负载均衡策略 * 继承AbstractLoadBalancerRule,重写choose()方法 */
  28. @Slf4j
  29. public class LocalClusterRule extends AbstractLoadBalancerRule {
  30. @Autowired
  31. private NacosDiscoveryProperties discoveryProperties;
  32. @Override
  33. public void initWithNiwsConfig(IClientConfig iClientConfig) {
  34. }
  35. /** * @param o ribbon读取到的目标服务的所有节点的信息,会自动传入。很多时候都是ribbon缓存的数据,可能含有无效的节点 * @return 选择使用的节点 */
  36. @Override
  37. public Server choose(Object o) {
  38. NacosServiceManager nacosServiceManager = new NacosServiceManager();
  39. NamingService namingService = nacosServiceManager.getNamingService(discoveryProperties.getNacosProperties());
  40. //获取当前服务节点的集群名称
  41. String clusterName = discoveryProperties.getClusterName();
  42. //获取被调服务的负载均衡器
  43. BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
  44. //被调服务的名称
  45. String name = loadBalancer.getName();
  46. try {
  47. //通过被调服务的名称筛选出所有健康的被调服务
  48. List<Instance> allInstance = namingService.selectInstances(name, true);
  49. if (CollectionUtils.isEmpty(allInstance)){
  50. log.error("注册中心没有可用的{}服务实例!",name);
  51. return null;
  52. }
  53. //过滤出与当前服务在同一集群中的被调服务
  54. List<Instance> localInstance = allInstance.stream().filter(instance ->
  55. Objects.equals(instance.getClusterName(), clusterName)
  56. ).collect(Collectors.toList());
  57. if (CollectionUtils.isEmpty(localInstance)){
  58. log.warn("当前集群{}中没有可用的{}服务,将跨集群调用该服务",clusterName,name);
  59. }else{
  60. allInstance = localInstance;
  61. }
  62. //使用nacos提供的方法随机选择一个节点
  63. Instance instance = ExtendBalancer.getHostByRandomWeight(allInstance);
  64. //可用直接输出instanceId,instanceId中包含了ip、port、所属集群、所属分组、服务名称
  65. log.info("选择使用的{}服务实例是{}集群的{}:{}",name,instance.getClusterName(),instance.getIp(),instance.getPort());
  66. return new NacosServer(instance);
  67. } catch (NacosException e) {
  68. log.error("ribbon使用自定义的负载均衡策略获取被调服务{}时发生异常",name);
  69. e.printStackTrace();
  70. }
  71. return null;
  72. }
  73. }
  74. /** * Balancer的getHostByRandomWeight()权限时protected,继承改为public暴露出去 */
  75. class ExtendBalancer extends Balancer {
  76. public static Instance getHostByRandomWeight(List<Instance> hosts){
  77. return Balancer.getHostByRandomWeight(hosts);
  78. }
  79. }

nacos做注册中心之调用指定版本的服务

  1. spring:
  2. application:
  3. name: user-server
  4. cloud:
  5. nacos:
  6. discovery:
  7. server-addr: 127.0.0.1:8848
  8. metadata:
  9. #指定当前服务版本
  10. version: 1.0
  11. #指定被调服务版本
  12. targetVersion: 1.0
  13. import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
  14. import com.alibaba.cloud.nacos.NacosServiceManager;
  15. import com.alibaba.cloud.nacos.ribbon.NacosServer;
  16. import com.alibaba.nacos.api.exception.NacosException;
  17. import com.alibaba.nacos.api.naming.NamingService;
  18. import com.alibaba.nacos.api.naming.pojo.Instance;
  19. import com.alibaba.nacos.client.naming.core.Balancer;
  20. import com.netflix.client.config.IClientConfig;
  21. import com.netflix.loadbalancer.AbstractLoadBalancerRule;
  22. import com.netflix.loadbalancer.BaseLoadBalancer;
  23. import com.netflix.loadbalancer.Server;
  24. import lombok.extern.slf4j.Slf4j;
  25. import org.springframework.beans.factory.annotation.Autowired;
  26. import org.springframework.util.CollectionUtils;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Objects;
  30. import java.util.stream.Collectors;
  31. /** * 自定义的负载均衡策略 * 继承AbstractLoadBalancerRule,重写choose()方法 */
  32. @Slf4j
  33. public class VersionRule extends AbstractLoadBalancerRule {
  34. @Autowired
  35. private NacosDiscoveryProperties discoveryProperties;
  36. @Override
  37. public void initWithNiwsConfig(IClientConfig iClientConfig) {
  38. }
  39. /** * @param o ribbon读取到的目标服务的所有节点的信息,会自动传入。很多时候都是ribbon缓存的数据,可能含有无效的节点 * @return 选择使用的节点 */
  40. @Override
  41. public Server choose(Object o) {
  42. NacosServiceManager nacosServiceManager = new NacosServiceManager();
  43. NamingService namingService = nacosServiceManager.getNamingService(discoveryProperties.getNacosProperties());
  44. //获取目标服务版本
  45. Map<String, String> metadata = discoveryProperties.getMetadata();
  46. String targetVersion = metadata.get("targetVersion");
  47. //获取被调服务的负载均衡器
  48. BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
  49. //被调服务的名称
  50. String name = loadBalancer.getName();
  51. try {
  52. //通过被调服务的名称筛选出所有健康的被调服务
  53. List<Instance> allInstance = namingService.selectInstances(name, true);
  54. if (CollectionUtils.isEmpty(allInstance)){
  55. log.error("注册中心没有可用的{}服务实例!",name);
  56. return null;
  57. }
  58. //过滤出指定版本的被调服务实例
  59. List<Instance> versionInstance = allInstance.stream().filter(instance ->
  60. Objects.equals(instance.getMetadata().get("version"), targetVersion)
  61. ).collect(Collectors.toList());
  62. if (CollectionUtils.isEmpty(versionInstance)){
  63. log.error("注册中心没有{}服务的{}版本实例)!", name, targetVersion);
  64. return null;
  65. }
  66. //使用nacos提供的方法随机选择一个节点
  67. Instance instance = ExtendBalancer.getHostByRandomWeight(versionInstance);
  68. //可用直接输出instanceId,instanceId中包含了ip、port、所属集群、所属分组、服务名称
  69. log.info("选择使用的{}服务实例是{}集群的{}:{}",name,instance.getClusterName(),instance.getIp(),instance.getPort());
  70. return new NacosServer(instance);
  71. } catch (NacosException e) {
  72. log.error("ribbon使用自定义的负载均衡策略获取被调服务{}时发生异常",name);
  73. e.printStackTrace();
  74. }
  75. return null;
  76. }
  77. }
  78. /** * Balancer的getHostByRandomWeight()权限时protected,继承改为public暴露出去 */
  79. class ExtendBalancer extends Balancer {
  80. public static Instance getHostByRandomWeight(List<Instance> hosts){
  81. return Balancer.getHostByRandomWeight(hosts);
  82. }
  83. }

ribbon的饥饿加载

ribbon从注册中心获取提供者节点列表时,默认使用懒加载:第一次调用服务提供者时,才会从注册中心加载该服务提供者的节点列表,初次调用某个服务有点慢。

可以设置为饥饿加载,在应用(消费者)启动时就加载服务提供者节点列表

  1. ribbon:
  2. eager-load:
  3. #开启饥饿加载。默认false
  4. enabled: true
  5. #指定要饥饿加载哪些服务提供者,其余提供者仍使用懒加载方式
  6. clients: order-server,msg-server

发表评论

表情:
评论列表 (有 0 条评论,171人围观)

还没有评论,来说两句吧...

相关阅读