• 客户端负载均衡


    手写负载均衡

        private final DiscoveryClient discoveryClient;
    
        @GetMapping("/{id}")
        public ShareDto findById(@PathVariable Integer id){
            Share share = shareService.findById(id);
            List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
    //        String url = instances.stream()
    //                .map(instance->instance.getUri()+"/user/{id}")
    //                .findFirst()
    //                .orElseThrow(()->new IllegalArgumentException("当前没有实例!"));
    
            List<String> targetUrls = instances.stream()
                    .map(instance -> instance.getUri() + "/user/{id}")
                    .collect(toList());
            //随机获取指定一个,达到负载均衡的目的
            int i = ThreadLocalRandom.current().nextInt(targetUrls.size());
            String url = targetUrls.get(i);
    
            UserDto userDto = restTemplate.getForObject(
                    url,
                    UserDto.class,
                    share.getId());
            log.info("请求的目标地址{}",url);
            ShareDto shareDto = new ShareDto();
            BeanUtils.copyProperties(share,shareDto);
            shareDto.setWxNickname(userDto.getWxNickname());
            return shareDto;
        }
    

    使用Allow parallel run 启动多个应用,注意改server port

    使用Ribbon实现负载均衡

    RestTemplate 添加负载均衡注解

    @MapperScan("com.fly")
    @SpringBootApplication
    public class ContentApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ContentApplication.class, args);
        }
    
        @LoadBalanced  //负载均衡
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    
    

    使用,不需要discoveryClient了

     /**
         * 使用Ribbon 实现负载均衡
         * @param id
         * @return
         */
        @GetMapping("/{id}")
        public ShareDto findById(@PathVariable Integer id){
            Share share = shareService.findById(id);
            UserDto userDto = restTemplate.getForObject(
                    "http://user-center/user/{userId}",
                    UserDto.class,
                    share.getId());
            ShareDto shareDto = new ShareDto();
            BeanUtils.copyProperties(share,shareDto);
            shareDto.setWxNickname(userDto.getWxNickname());
            return shareDto;
        }
    

    Ribbon组成

    Ribbon内置的负载均衡规则

    默认为轮询RoundRobinRule

    自定义颗粒度配置java

    com.fly.contentcenter.configuration.UserCenterConfiguration

    @Configuration
    @RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
    public class UserCenterConfiguration {
    }
    
    

    com.fly.ribbonconfigration.RibbonConfiguration

    @Configuration
    public class RibbonConfiguration {
        @Bean
        public IRule randomRule(){
            //随机规则
            return new RandomRule();
        }
    }
    

    RibbonConfiguration 要放到SpringBootApplication启动类之外,防止被扫描到,出现父子上下文重叠问题,如果父子上下文扫描重叠就会导致事务不生效。

    一般我们在Spring的配置文件application.xml中对Service层代码配置事务管理,可以对Service的方法进行AOP增强或事务处理如事务回滚,但是遇到一个问题,在Controller类中调用Service层方法,配置的事务管理会失效,查询相关资料发现原因。其实Spring和SpringMVC俩个容器为父子关系,Spring为父容器,而SpringMVC为子容器。也就是说application.xml中应该负责扫描除@Controller的注解如@Service,而SpringMVC的配置文件应该只负责扫描@Controller,否则会产生重复扫描导致Spring容器中配置的事务失效。

    在Spring的applicationContext.xml文件中配置配上下面这段代码:

    <context:component-scan base-package="com.chuxin.platform,com.chuxin.core">
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    

    而在SpringMVC的dispatcher-servlet.xml配置文件中配置下面这段代码:

    
    <context:component-scan base-package="com.chuxin.platform.controller" use-default-filters="false">
          <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    

    自定义颗粒度配置 配置属性方式,推荐使用这种

    # <clientName>
    user-center:  
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    

    两种方式对比

    全局配置

    方式一:将UserCenterConfiguration放到springboot可以扫描的地方,不推荐
    方式二:

    @Configuration
    //@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
    @RibbonClients(defaultConfiguration = RibbonConfiguration.class) //全局配置
    public class UserCenterConfiguration {
    }
    
    

    其它配置

    ### 开启饥饿加载,因为懒加载第一次请求很慢 ``` # 默认是懒加载,开启饥饿加载 ribbon: eager-load: enabled: true clients: user-center # 多个用,分割 ``` ### 扩展Ribbon-支持Nacos权重 ``` /** * @see com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance * * 扩展Ribbon支持Nacos权重 */ @Slf4j public class NacosWeightRandomRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }
    
    @Override
    public Server choose(Object o) {
        DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) this.getLoadBalancer();
        String name = loadBalancer.getName();//想要请求的微服务的名称
        try {
            Instance instance = discoveryProperties.namingServiceInstance().selectOneHealthyInstance(name);
            log.info("选中的instance = {}", instance);
            return new NacosServer(instance);
        }catch (NacosException e) {
            log.error("发生异常", e);
            return null;
        }
    }
    

    }

    @Configuration
    public class RibbonConfiguration {
    @Bean
    public IRule randomRule(){
    //随机规则
    // return new RandomRule();
    return new NacosWeightRandomRule();
    }
    }

    ### 扩展Ribbon-同一集群优先调用
    

    /**

    • 扩展Ribbon-同一集群优先调用
      */
      @Slf4j
      public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
      @Autowired
      private NacosDiscoveryProperties nacosDiscoveryProperties;

      @Override
      public void initWithNiwsConfig(IClientConfig iClientConfig) {
      }

      @Override
      public Server choose(Object o) {
      try {
      // 配置文件的集群名称
      String clusterName = nacosDiscoveryProperties.getClusterName();
      BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
      // 想要请求的微服务的名称
      String name = loadBalancer.getName();

           NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
           // 找到指定服务的所有实例
           List<Instance> instances = namingService.selectInstances(name, true);
           //过滤出相同集群下的所有实例
           List<Instance> sameClusterInstances  = instances.stream()
                   .filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
                   .collect(Collectors.toList());
           List<Instance> instancesToBeChosen = new ArrayList<>();
           if (CollectionUtils.isEmpty(sameClusterInstances)){
               instancesToBeChosen = instances;
               log.warn("发生跨集群的调用, name = {}, clusterName = {}, instances = {}",
                       name,
                       clusterName,
                       instances
               );
           }else {
               instancesToBeChosen = sameClusterInstances;
           }
           // 4. 基于权重的负载均衡算法,返回1个实例
           Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
           log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance);
      
       } catch (NacosException e) {
           e.printStackTrace();
       }
       return null;
      

      }
      }
      class ExtendBalancer extends Balancer {
      public static Instance getHostByRandomWeight2(List hosts) {
      return getHostByRandomWeight(hosts);
      }
      }

    
    ### 扩展Ribbon支持基于元数据的版本管理
    

    /**

    • 扩展Ribbon支持基于元数据的版本管理
    • spring:
    • cloud:
    • nacos:
      
    •     metadata:
      
    •       # 自己这个实例的版本
      
    •       version: v1
      
    •       # 允许调用的提供者版本
      
    •       target-version: v1
      

    */

    @Slf4j
    public class NacosFinalRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }
    
    @Override
    public Server choose(Object o) {
        try {
            // 配置文件的集群名称
            String clusterName = nacosDiscoveryProperties.getClusterName();
            String targetVersion = nacosDiscoveryProperties.getMetadata().get("target-version");
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            // 想要请求的微服务的名称
            String name = loadBalancer.getName();
    
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
    
    
            // 找到指定服务的所有实例
            List<Instance> instances = namingService.selectInstances(name, true);
            List<Instance> metadataMatchInstances = instances;
            // 如果配置了版本映射,那么只调用元数据匹配的实例
            if (StringUtils.isNotBlank(targetVersion)){
                metadataMatchInstances = instances.stream()
                        .filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version")))
                        .collect(Collectors.toList());
                if (CollectionUtils.isEmpty(metadataMatchInstances)) {
                    log.warn("未找到元数据匹配的目标实例!请检查配置。targetVersion = {}, instance = {}", targetVersion, instances);
                    return null;
                }
            }
            List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
            // 如果配置了集群名称,需筛选同集群下元数据匹配的实例
            if (StringUtils.isNotBlank(clusterName)) {
                clusterMetadataMatchInstances = metadataMatchInstances.stream()
                        .filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
                        .collect(Collectors.toList());
                if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
                    clusterMetadataMatchInstances = metadataMatchInstances;
                    log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
                }
            }
    
            // 基于权重的负载均衡算法,返回1个实例
            Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
            log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance);
    
        } catch (NacosException e) {
            e.printStackTrace();
        }
        return null;
    }
    static class ExtendBalancer extends Balancer {
        public static Instance getHostByRandomWeight2(List<Instance> hosts) {
            return getHostByRandomWeight(hosts);
        }
    }
    

    }

    ### 不能跨namespace调用
  • 相关阅读:
    汇编中的字符串操作指令
    Scoket需要注意的地方
    判断是否为json对象
    offsetTop,offsetWidth,offsetParent
    ASP.net中页面事件的先后顺序
    opengl32.lib、glu32.lib、 glaux.lib、OpenGL32.lib的意思。
    递归中,方法中的变量值被改变的问题。
    中国数字认证网
    JSON中for in的使用
    (网上转载)JavaScript 跑马灯
  • 原文地址:https://www.cnblogs.com/fly-book/p/12818925.html
Copyright © 2020-2023  润新知