SpringCloud Alibaba——Ribbon负载均衡(学习记录)

SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第1张图片

负载均衡两种方式

服务器端负载均衡

为一个应用部署多个实例,然后由Nginx做反向代理,请求先请求到Nginx上,由Nginx通过负载均衡转发到某个实例。
SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第2张图片

客户端侧负载均衡

内容中心通获取到用户中心的实例,然后自己实现负载均衡算法来请求某个有用户中心的数据,这时候内容中心就是客户端了
SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第3张图片

加入了Ribbon之后的架构

SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第4张图片

整合Ribbon

在Nacos中已经引入了Ribbon,所以不需要再额外引入
需要在RestTemplate中加入注解@LoadBalanced

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

然后可以修改之前的请求代码,ribbon会自动把user-center这个名称从nacos上获取到,并通过负载均衡算法去请求。

        UserDTO userDTO = this.restTemplate.getForObject(
                "http://user-center/users/{userId}",
                UserDTO.class, userId
        );

Ribbon的组成

SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第5张图片

Ribbon 的负载均衡规则

SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第6张图片

Ribbon的配置

Java代码的方式进行配置


// 这里就是去请求user-center的实例
@Configuration
@RibbonClient(name = "user-center", configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
@Configuration
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule() {
    	// 实现随机的请求
        return new RandomRule();
    }
}

这里需要注意,RibbonConfiguration类的包需要在spring启动类的包之外,这是spring的一个bug,扫描到这个包会导致项目启动不来。
这是由于 @Configuration 注解里其实有一个 @Component 注解,在 @SpringBootApplication 注解里有一个 @ComponentScan,他会扫描启动类所在包下的所有 @Component 注解,也就是@Controller,@Service这些。
Spring是一个树状的上下文,@SpringBootApplication扫描的是一个主上下文,Ribbon也会有一个上下文,是一个子上下文,父子上下文扫描重叠就会导致各种问题父子上下文发生重叠就会导致事务不生效
如果将刚刚的配置放到@SpringBootApplication启动类所在包下那么就会将原本细粒度的配置变成了全局的配置(被所有的Ribbon共享),也就是任何的调用都会是采用配置的负载均衡规则。
SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第7张图片

属性配置

user-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

全局配置

@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}

代码配置VS属性配置

配置方式 优点 缺点
代码配置 基于代码,更灵活 有坑(父子上下文);线上修改需要打包
属性配置 易上有;配置更加直观,线上无需重新打包;优先级更高 极端场景下没有代码配置方式灵活

Ribbon支持的配置项

SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第8张图片

Java代码方式

自己需要什么就可以来挂载Bean

	@Bean
    public IPing ping() {
        return new PingUrl();
    }

属性配置

SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第9张图片

Ribbon饥饿加载

Ribbon默认是懒加载,也就是在第一次访问的时候请求特别慢,因为他要先去创建实例,而之后的速度就很快,这时候想要解决这个问题就可以开启Ribbon饥饿加载,具体配置如下。

ribbon:
  eager-load:
    enabled: true
    clients: user-center  #只有在这个配置中的微服务才会只用饥饿加载,多个可以用,分隔

扩展Ribbon,支持Nacos的权重

在Nacos的控制台可以给微服务的实例配置权重,值越大,权重越高,被请求的几率就越大。
SpringCloud Alibaba——Ribbon负载均衡(学习记录)_第10张图片
写一个自己的负载均衡规则,来实现权重

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

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        // 读取配置文件,并初始化NacosWeightedRule
    }

    @Override
    public Server choose(Object key) {
        try {
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            log.info("lb={}", loadBalancer);

            // 想要请求的微服务的名称
            String name = loadBalancer.getName();

            // 拿到服务发现相关的API
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();

            // nacos client 自动基于权重的负载均衡算法,给我们选择一个实例
            Instance instance = namingService.selectOneHealthyInstance(name);

            log.info("选择的实例是:port={},instantce={}", instance.getPort(), instance);

            return new NacosServer(instance);
        } catch (NacosException e) {
            e.printStackTrace();
            return null;
        }
    }
}

可以进行属性配置,也可以代码的全局配置,参考上面的Ribbon配置

Ribbon同集群优先

可以使用Nacos的Cluster来实现

手动实现

@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object key) {
        try {
            // 拿到配置文件中的集群名称
            String clusterName = nacosDiscoveryProperties.getClusterName();

            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();

            // 想要请求的微服务的名称
            String name = loadBalancer.getName();

            // 拿到服务器发现相关的API
            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> instancesToBeChosen = new ArrayList<>();
            if (CollectionUtils.isEmpty(sameClusterInstances)) {
                instancesToBeChosen = instances;
                log.warn("发生跨集群的调用,name={},clusterName={},instances={}", name, clusterName, instances);
            } else {
                instancesToBeChosen = sameClusterInstances;
            }

            // 基于负载均衡的算法,返回一个实例
            Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
            log.info("选择的实例是:端口={},实例是={}", instance.getPort(), instance);


            return new NacosServer(instance);
        } catch (NacosException e) {

            log.error("发生了异常={}", e);
            return null;
        }
    }
}

/**
 * 因为内部方法是 protected,所以可以继承一下调用需要的方法
 * 基于Nacos内部的负载均衡方法得到一个实例
 */
class ExtendBalancer extends Balancer {
    public static Instance getHostByRandomWeight2(List<Instance> hosts) {
        return getHostByRandomWeight(hosts);
    }
}

相关配置
微服务B和微服务A的cluster-name都是BJ,那么微服务B调用的接口都是服务器A cluster-name为BJ的程序,如果微服务A cluster-name为BJ的程序挂了,那么就会去调用其他cluster-name的微服务A

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: BJ
        namespace: 9b74b8d1-94ba-44ea-955b-2e174d42a14a

Ribbon 元数据控制版本

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: BJ
        namespace: 9b74b8d1-94ba-44ea-955b-2e174d42a14a
        metadata:
          # 自己这个实例的版本
          version: v1
          # 允许调用的提供者版本
          target-version: v1

优先选择同集群下,符合metadata的实例,如果没有,就选择所有集群下,符合metadata的实例

@Slf4j
public class NacosMetadataClusterRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object key) {
        try {
            // 拿到配置文件中的集群名称 和 调用版本号
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version");


            // 想要请求的微服务的名称
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
            String name = loadBalancer.getName();

            // 拿到服务器发现相关的API
            NamingService namingService = this.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);
                }
            }

            Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
            return new NacosServer(instance);
        } catch (Exception e) {
            log.warn("发生异常", e);
            return null;
        }
    }
}

你可能感兴趣的:(springcloud,微服务)