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 - 基于元数据的版本控制

  • 相关阅读:
    HashMap 的实现原理(1.8)
    HashMap 的实现原理(1.7)
    Java面试总结 -2018(补录)
    在java中写出完美的单例模式
    ArrayList实现原理分析
    Ngigx+Tomcat配置动静分离,负载均衡
    微信小程序——常用快捷键【四】
    Linux服务器下安装vmware虚拟机
    微信小程序——部署云函数【三】
    微信小程序——安装开发工具和环境【二】
  • 原文地址:https://www.cnblogs.com/zhangpan1244/p/11203754.html
Copyright © 2011-2022 走看看