zoukankan      html  css  js  c++  java
  • Spring Cloud Alibaba学习笔记(3)

    1.手写一个客户端负载均衡器

      在了解什么是Ribbon之前,首先通过代码的方式手写一个负载均衡器

    RestTemplate restTemplate = new RestTemplate();
    
    // 获取请求示例
    List<ServiceInstance> instances = discoveryClient.getInstances("study02");
    List<String> collect = instances.stream()
            .map(instance -> instance.getUri().toString() + "/find")
            .collect(Collectors.toList());
    
    // 随机算法
    int i = ThreadLocalRandom.current().nextInt(collect.size());
    String targetURL =  collect.get(i);
    
    log.info("请求的目标地址: {}", targetURL);
    
    DemoComment forObject = restTemplate.getForObject(targetURL, DemoComment.class, 1);

    2.Ribbon是什么

      Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。

      简单来说,Ribbon就是简化了上面代码的组件,其中提供了更多的负载均衡算法。

    3.整合Ribbon

      依赖:因为spring-cloud-starter-alibaba-nacos-discovery中已经包含了Ribbon,所以不需要加依赖

      注解:

        在注册RestTemplate的地方添加注解

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

      配置:没有配置

      

      配置完成,此时,章节1中的手写负载均衡器代码可以简化为:

    DemoComment forObject = restTemplate.getForObject("http://study02/find", DemoComment.class, 1);

      当restTemplate组织请求的时候,Ribbon会自动把“study02”转换为该服务在Nacos上面的地址,并且进行负载均衡

      PS:默认情况下Ribbon是懒加载的。当服务起动好之后,第一次请求是非常慢的,第二次之后就快很多。

      解决方法:开启饥饿加载

    ribbon:
     eager-load:
      enabled: true #开启饥饿加载
      clients: server-1,server-2,server-3 #为哪些服务的名称开启饥饿加载,多个用逗号分隔

    4.Ribbon的组成

    接口 作用 默认值
    IclientConfig 读取配置 DefaultClientConfigImpl
    IRule 负载均衡规则,选择实例 ZoneAvoidanceRule
    IPing 筛选掉ping不通的实例 DumyPing(该类什么不干,认为每个实例都可用,都能ping通)
    ServerList 交给Ribbon的实例列表 Ribbon:ConfigurationBasedServerList
    Spring Cloud Alibaba:NacosServerList
    ServerListFilter 过滤掉不符合条件的实例 ZonePreferenceServerListFilter
    ILoadBalancer Ribbon的入口 ZoneAwareLoadBalancer
    ServerListUpdater 更新交给Ribbon的List的策略 PollingServerListUpdater

    4.1 Ribbon内置的负载均衡规则

    规则名称 特点
    AvailabilityFilteringRule 过滤掉一直连接失败的被标记为circuit tripped(电路跳闸)的后端Service,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤Server的逻辑,其实就是检查status的记录的各个Server的运行状态
    BestAvailableRule 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过
    RandomRule 随机选择一个Server
    ResponseTimeWeightedRule 已废弃,作用同WeightedResponseTimeRule
    RetryRule 对选定的负责均衡策略机上充值机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的Server
    RoundRobinRule 轮询选择,轮询index,选择index对应位置Server
    WeightedResponseTimeRule 根据相应时间加权,相应时间越长,权重越小,被选中的可能性越低
    ZoneAvoidanceRule (默认是这个)负责判断Server所Zone的性能和Server的可用性选择Server,在没有Zone的环境下,类似于轮询(RoundRobinRule)

    4.2 细粒度配置

      示例:调用服务A采用默认的负载均衡规则ZoneAvoidanceRule,调用服务B采用随即RandomRule规则

    4.2.1 Java代码

      新建一个ribbonConfiguration包(一定要在启动类所在的包以外),ribbonConfiguration包内新建RibbonConfiguration类

      ps:为什么ribbonConfiguration包一定要在启动类所在的包以外,RibbonConfiguration类引用了@Configuration注解,这个注解组合了@Component注解,可以理解为@Configuration是一个特殊的@Component。

      而在启动类中有一个@SpringBootApplication注解,其中组合了@ComponentScan注解,这个注解是用来扫猫@Component的,包括@Configuration注解,扫猫的分为当前启动类所在的包以及启动类所在包下面的所有的@Component。

      而Spring的上下文是树状的上下文,@SpringBootApplication所扫猫的上下文是主上下文;而ribbon也会有一个上下文,是子上下文。而父子上下文扫猫的包一旦重叠,会导致很多问题【在本文中会导致配置被共享】,所以ribbon的配置类一定不能被启动类扫猫到,ribbonConfiguration包一定要在启动类所在的包以外。

    package ribbonConfiguration;
    
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * java代码实现细粒度配置
     * 注意是单独的包
     * 父子上下文重复扫猫问题
     */
    @Configuration
    public class RibbonConfiguration {
        @Bean
        public IRule ribbonRule() {
            return new RandomRule();
        }
    }

      新建DemoRibbonConfiguration类,为服务B指定负载均衡算法

    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClients;
    import org.springframework.context.annotation.Configuration;
    import ribbonConfiguration.RibbonConfiguration;
    
    /**
     * 自定义负载均衡方式
     */
    @Configuration
    // 单个配置
    @RibbonClient(name = "B", configuration = RibbonConfiguration.class)
    public class DemoRibbonConfiguration {
    
    }

    4.2.2 配置属性方式

    # ribbon 配置方式实现细粒度配置
    demo-center:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

      其他参数:

         NFLoadBalancerClassName: #ILoadBalancer该接口实现类
         NFLoadBalancerRuleClassName: #IRule该接口实现类
         NFLoadBalancerPingClassName: #Iping该接口实现类
         NIWSServerListClassName: #ServerList该接口实现类
         NIWSServerListFilterClassName: #ServiceListFilter该接口实现类

      优先级:属性配置 > 代码配置

      尽量使用属性配置,属性配置解决不了问题的时候在考虑使用代码配置

      在同一个微服务内尽量保证配置单一性  

    4.3 全局配置

    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClients;
    import org.springframework.context.annotation.Configuration;
    import ribbonConfiguration.RibbonConfiguration;
    
    @Configuration
    // 全局配置
    @RibbonClients(defaultConfiguration = RibbonConfiguration.class)
    public class DemoRibbonConfiguration {
    
    }

      还有一种方法,4.2.1中提到的,让ComponentScan上下文重叠,从而实现全局配置,不建议使用这种方法。

    5.Ribbon扩展

    5.1 支持Nacos权重

      在Nacos的控制台,可以为每一个实例配置权重,取值在0~1之间,值越大,表示这个实例被调用的几率越大。而Ribbon内置的负载均衡的规则不支持权重,我们可以通过代码的方式让ribbon支持Nacos权重。

    import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
    import com.alibaba.cloud.nacos.ribbon.NacosServer;
    import com.alibaba.nacos.api.exception.NacosException;
    import com.alibaba.nacos.api.naming.NamingService;
    import com.alibaba.nacos.api.naming.pojo.Instance;
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.BaseLoadBalancer;
    import com.netflix.loadbalancer.Server;
    import org.springframework.beans.factory.annotation.Autowired;
    
    /**
     * 基于Nacos 权重的负载均衡算法
     */
    public class NacosWeightRule extends AbstractLoadBalancerRule {
    
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
    
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
            // 读取配置文件,并初始化NacosWeightRule
        }
    
        @Override
        public Server choose(Object o) {
            try {
                // ribbon入口
                BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
    
                //想要请求的微服务的名称
                String name = loadBalancer.getName();
    
                // 实现负载均衡算法
                // 拿到服务发现的相关api
                NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
                // nacos client自动通过基于权重的负载均衡算法,给我们一个示例
                Instance instance = namingService.selectOneHealthyInstance(name);
    
                return new NacosServer(instance);
            } catch (NacosException e) {
                return null;
            }
        }
    }

    5.2 同一集群优先调用

    import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
    import com.alibaba.cloud.nacos.ribbon.NacosServer;
    import com.alibaba.nacos.api.exception.NacosException;
    import com.alibaba.nacos.api.naming.NamingService;
    import com.alibaba.nacos.api.naming.pojo.Instance;
    import com.alibaba.nacos.client.naming.core.Balancer;
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.BaseLoadBalancer;
    import com.netflix.loadbalancer.Server;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.util.CollectionUtils;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;
    
    /**
     * 同一集群优先调用
     */
    @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();
                // 找到指定服务的所有示例 A
                List<Instance> instances = namingService.selectInstances(name, true);
                // 过滤出相同集群的所有示例 B
                List<Instance> sameClusterInstances = instances.stream()
                        .filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
                        .collect(Collectors.toList());
                // 如果B是空,就用A
                List<Instance> instancesToBeanChoose = new ArrayList<>();
                if (CollectionUtils.isEmpty(sameClusterInstances)) {
                    instancesToBeanChoose = instances;
                } else {
                    instancesToBeanChoose = sameClusterInstances;
                }
                // 基于权重的负载均衡算法返回示例
                Instance hostByRandomWeightExtend = ExtendBalancer.getHostByRandomWeightExtend(instancesToBeanChoose);
    
                return new NacosServer(hostByRandomWeightExtend);
            } catch (NacosException e) {
                log.error("发生异常了", e);
                return null;
            }
        }
    }
    
    class ExtendBalancer extends Balancer {
        public static Instance getHostByRandomWeightExtend(List<Instance> hosts) {
            return getHostByRandomWeight(hosts);
        }
    }

    5.3 基于元数据的版本控制

    import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
    import com.alibaba.cloud.nacos.ribbon.NacosServer;
    import com.alibaba.nacos.api.naming.NamingService;
    import com.alibaba.nacos.api.naming.pojo.Instance;
    import com.alibaba.nacos.client.naming.utils.CollectionUtils;
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.BaseLoadBalancer;
    import com.netflix.loadbalancer.Server;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;
    
    /**
     * 基于nacos元数据的版本控制
     */
    @Slf4j
    public class NacosFinalRule extends AbstractLoadBalancerRule {
        @Autowired
        private NacosDiscoveryProperties nacosDiscoveryProperties;
    
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
        }
    
        @Override
        public Server choose(Object o) {
            try {
                // 负载均衡规则:优先选择同集群下,符合metadata的实例
                // 如果没有,就选择所有集群下,符合metadata的实例
    
                // 1. 查询所有实例 A
                String clusterName = nacosDiscoveryProperties.getClusterName();
                String targetVersion = this.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);
    
                // 2. 筛选元数据匹配的实例 B
                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;
                    }
                }
    
                // 3. 筛选出同cluster下元数据匹配的实例 C
                // 4. 如果C为空,就用B
                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);
                    }
                }
    
                // 5. 随机选择实例
                Instance instance = ExtendBalancer.getHostByRandomWeightExtend(clusterMetadataMatchInstances);
                return new NacosServer(instance);
            } catch (Exception e) {
                return null;
            }
        }
    }
  • 相关阅读:
    Classic Source Code Collected
    Chapter 2 Build Caffe
    蓝屏代码大全 & 蓝屏全攻略
    AMD C1E SUPPORT
    DCU IP Prefether
    DCU Streamer Prefetcher
    adjacent cache line prefetch
    Hardware Prefetcher
    Execute Disable Bit
    Limit CPUID MAX
  • 原文地址:https://www.cnblogs.com/fx-blog/p/11713872.html
Copyright © 2011-2022 走看看