zoukankan      html  css  js  c++  java
  • Java 客户端负载均衡

    • 客户端侧负载均衡

    在下图中,负载均衡能力算法是由内容中心提供,内容中心相对于用户中心来说,是用户中心的客户端,所以又被称为客户端侧负载均衡

    图片名称

    自定义实现Client Random负载均衡

    1. 获取所有的服务list
    2. 随机获取需要访问的服务信息
    				// 自定义客户端负载均衡能力
            // 获取所有用户中心服务的实例列表
            List<String> targetUris = instances.stream().map(i -> i.getUri().toString() + "/users/{id}").collect(Collectors.toList());
    
            //获取随机实例
            int i = ThreadLocalRandom.current().nextInt(targetUris.size());
    
            //调用用户微服务 /users/{userId}
            log.info("请求的目标地址:{}", targetUris.get(i));
            ResponseEntity<UserDTO> userEntity = restTemplate.getForEntity(
                    targetUris.get(i),
                    UserDTO.class, userId
            );
    

    Ribbon

    什么是Ribbon

    Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法。

    Github: Ribbon 源码

    组成接口

    ![image-20190713132523310](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713132523310.png)

    内置负载均衡规则

    ![image-20190713133911384](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713133911384.png)

    配置方式

    • Java 代码配置
    /**
     * RibbonConfiguration for TODO
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     * @since 2019/7/13
     */
    @Configuration
    public class RibbonConfiguration {
        @Bean
        public IRule ribbonRule(){
            return new RandomRule();
        }
    }
    /*------------------------------------------------------------*/
    /**
     * UserCenterRibbonConfiguration for 自定义实现User-center service ribbon client
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     * @since 2019/7/13
     */
    @Configuration
    @RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)
    public class UserCenterRibbonConfiguration {
        
    }
    /*------------------------------------------------------------*/
    @Configuration
    //@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class) //作用域为 user-center
    @RibbonClients(defaultConfiguration = RibbonConfiguration.class) //作用域为全局
    public class UserCenterRibbonConfiguration {
    
    }
    
    • 使用配置文件
    user-center: # service name
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 规则类的全路径名称
    
    图片名称
    • 对比

    ![image-20190713142916635](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713142916635.png)

    • 最佳使用
      • 尽量使用属性配置
      • 在同一个微服务中尽量保持单一配置,不要混合使用,增加定位复杂性

    Tip

    在使用Ribbon的时候,配置class一定不能处于启动类的同级目录及其子目录,否则会导致父子上下文重叠问题,带来的结果就是,Ribbon规则会被配置称全局配置规则,从而被所有微服务应用。

    ![image-20190713141743025](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713141743025.png)

    The CustomConfiguration class must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).

    Ribbon 饥饿加载

    默认情况下,Ribbon是懒加载,在第一次请求的时候才会创建客户端。

    ribbon:
      eager-load:
        enabled: true # 饥饿加载激活
        clients: user-center,xxx,xxx # 为哪些clients开启
    

    使用Ribbon 替代自定义实现

    1. 添加依赖(Spring-Cloud-Alibaba-Nacos-Discovery已经依赖了Ribbon,因此不需要额外依赖)

    2. 添加注解(只需要在RestTemplate IOC添加 @LoadBalance)

      /**
           * 在Spring 容器中,创建一个对象,类型是{@link RestTemplate}
           * 名称/ID 为 restTemplate
           * <bean id ="restTemplate" class="XXX.RestTemplate" />
           * {@link LoadBalanced} 为RestTemplate整合Ribbon调用
           *
           * @return restTemplate
           */
          @Bean
          @LoadBalanced
          public RestTemplate restTemplate() {
              return new RestTemplate();
          }
      
    3. 添加配置,直接使用

              ResponseEntity<UserDTO> userEntity = restTemplate.getForEntity(
                      "http://user-center/users/{userId}",
                      UserDTO.class, userId
              );
      

    扩展Ribbon - 支持Nacos权重

    • 实现接口com.netflix.loadbalancer.IRule
    • 实现抽象类 com.netflix.loadbalancer.AbstractLoadBalancerRule

    ![image-20190713150041397](/Users/zhangpan/Library/Application Support/typora-user-images/image-20190713150041397.png)

    @Slf4j
    @RequiredArgsConstructor(onConstructor = @__(@Autowired))
    public class NacosWeightRule4Ribbon extends AbstractLoadBalancerRule {
    
        private final NacosDiscoveryProperties nacosDiscoveryProperties;
    
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
            // 读取配置文件,并初始化 NacosWeightRule4Ribbon
        }
    
        @Override
        public Server choose(Object key) {
    
            try {
                // ILoadBalancer 是Ribbon的入口,基本上我们想要的元素都可以在这个对象中找到
                BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
                log.info("NacosWeightRule4Ribbon lb = {}", loadBalancer);
                // 想要请求的微服务名称
                String name = loadBalancer.getName();
    
                // 实现负载均衡算法
                // 可得到服务发现相关的API(nacos内部实现)
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
    
                // nacos client 通过基于权重的负载均衡算法,选择一个实例
                Instance instance = namingService.selectOneHealthyInstance(name);
                log.info("port = {}, weight = {}, instance = {}", instance.getPort(), instance.getWeight(), instance);
                return new NacosServer(instance);
            } catch (NacosException e) {
                log.error("NacosWeightRule4Ribbon {}", e.getMessage());
            }
            return null;
        }
    }
    
    
    @Configuration
    @RibbonClients(defaultConfiguration = NacosWeightRule4Ribbon.class) //全局配置
    public class UserCenterRibbonConfiguration {
    }
    

    拓展Ribbon - 同集群优先

    public Server choose(Object key) {
    
            try {
                // 获取到配置文件中的集群名称 BJ
                String clusterName = nacosDiscoveryProperties.getClusterName();
    
                BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
                String serviceName = loadBalancer.getName();
    
                //获取服务发现的相关API
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
                // 1. 找到指定服务的所有实例 A
                List<Instance> instances = namingService.selectInstances(serviceName, true);
                // 2. 过滤出相同集群下的所有实例 B
                List<Instance> sameClusterInstances = instances.stream()
                                                               .filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
                                                               .collect(Collectors.toList());
                // 3. 如果B为空,则使用 A
                List<Instance> instancesChoosen = new ArrayList<>();
                if (CollectionUtils.isEmpty(sameClusterInstances)) {
                    instancesChoosen = instances;
                    log.warn("发生跨集群调用,name = {},clusterName = {}", serviceName, clusterName);
                } else {
                    instancesChoosen = sameClusterInstances;
                }
    
                // 4. 基于权重的负载均衡算法,返回一个实例 A
                Instance instance = ExtendBalancer.getHostByRandomWeightOverride(instancesChoosen);
                log.info("choose instance is : port = {}, instance = {}", instance.getPort(), instance);
                return new NacosServer(instance);
            } catch (NacosException e) {
                e.printStackTrace();
                log.error(e.getErrMsg());
            }
            return null;
        }
    
    /**
    * 调用Nacos内部方法,进行一次包装
    */
    class ExtendBalancer extends Balancer {
        public static Instance getHostByRandomWeightOverride(List<Instance> hosts) {
            return getHostByRandomWeight(hosts);
        }
    }
    

    扩展Ribbon - 基于元数据的版本控制

  • 相关阅读:
    LeetCode 842. Split Array into Fibonacci Sequence
    LeetCode 1087. Brace Expansion
    LeetCode 1219. Path with Maximum Gold
    LeetCode 1079. Letter Tile Possibilities
    LeetCode 1049. Last Stone Weight II
    LeetCode 1046. Last Stone Weight
    LeetCode 1139. Largest 1-Bordered Square
    LeetCode 764. Largest Plus Sign
    LeetCode 1105. Filling Bookcase Shelves
    LeetCode 1027. Longest Arithmetic Sequence
  • 原文地址:https://www.cnblogs.com/zhangpan1244/p/11203754.html
Copyright © 2011-2022 走看看