Spring Cloud Alibaba-Ribbon(十三)

简介

Ribbon is a Inter Process Communication (remote procedure calls) library with built in software load balancers. The primary usage model involves REST calls with various serialization scheme support.(客户端软负载)官方文档java

负载均衡

  • 服务端
    • nginx...
  • 客户端
    • ribbon...

本身实现

...
      private final DiscoveryClient discoveryClient;
...
       List<ServiceInstance> instances = discoveryClient.getInstances("lock-center");
        String url = instances.stream()
                .map(instance -> instance.getUri() + "/lock/test/{id}")
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("没有当前实例"));
        List<String> urls = instances.stream()
                .map(instance -> instance.getUri() + "/lock/test/{id}")
                .collect(Collectors.toList());
        int i = ThreadLocalRandom.current().nextInt(urls.size());
        log.info("请求的URl:{}",urls.get(i));
        TblCar tblCar = restTemplate.getForObject(urls.get(i), TblCar.class, 4000002L);
复制代码

ribbon实现

  • 加依赖
spring-cloud-starter-alibaba-nacos-discovery带了,不须要加
复制代码

  • 写注解
...
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
...
复制代码
  • 写配置
lock-center:
  ribbon:
    # 负载规则
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
# 饥饿加载
ribbon:
  eager-load:
    clients: '*'
    enabled: true
复制代码
  • 实现
TblCar tblCar = restTemplate.getForObject("http://lock-center/lock/test/{id}", TblCar.class, 4000002L);
复制代码

ribbon组成

接口 做用 默认值
IClientConfig 读取配置 DefaultClientConfigImpl
IRule 负载均衡规则,选则实例 ZoneAvoidanceRule
IPing 筛选ping不通的实例 DummyPing
ServerList 交给ribbon的实例列表 ConfigurationBasedServerList(ribbon)/NacosServerList(springcloudalibaba)
ServerListFilter 过滤不合规实例 ZonePreferenceServerListFilter
ILoadBalancer ribbon的入口 ZoneAwareLoadBalancer
ServerListUpdater 更新交给ribbon的List的策略 PollingServerListUpdater

内置负债均衡算法

名称 特色
RoundRobinRule 轮询
RandomRule 随机
AvailabilityFilteringRule 会优先过滤因为屡次访问故障而处于断路器跳闸状态的服务,还有并发的链接数量超过阀值的服务,而后对剩余的服务轮询
WeigthtedResponseTimeRule 根据平均响应时间计算全部服务的权重,响应时间越快权重越大被访问几率越高,刚开始统计信息不够使用轮询,以后切换
RetryRule 先按照轮询获取服务,若是失败则在指定的时间内重试,获取可用服务
BestAvailableRule 会过滤因为屡次访问故障而处于断路器跳闸状态的服务,而后选择一个并发量最小的服务
ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server可用性选择服务器,没有Zone的环境下相似轮询

配置负债均衡算法

  • 细粒度代码
package com.virgo.user.configuration;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Configuration;

/**
 * @author zhaozha
 * @date 2019/10/11 上午10:39
 */
@Configuration
@RibbonClient(name = "lock-center", configuration = RibbonConfiguration.class)
public class LockCenterRibbonConfiguration {
}
package com.virgo.user.configuration;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author zhaozha
 * @date 2019/10/11 上午10:47
 */
@Configuration
@ExcludeComponent
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule() {
        // 负载均衡规则改成随机
        return new NacosWeightedRule();
    }
}

// @ExcludeComponent解决父子上下文冲突问题,不解决变成全局配置
// 启动类@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
        value = {ExcludeComponent.class}))
package com.virgo.user.configuration;

/**
 * @author zhaozha
 * @date 2019/10/11 上午10:49
 */
public @interface ExcludeComponent {
}

复制代码
  • 细粒度配置(优先级高于代码)
lock-center:
  ribbon:
    # 负载规则
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
复制代码
  • 全局配置
package com.virgo.user.configuration;

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;

/**
 * @author zhaozha
 * @date 2019/10/11 上午10:39
 */
@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class LockCenterRibbonConfiguration {
}

复制代码

支持nacos权重

package com.virgo.user.configuration;

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.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;

import java.util.List;

/**
 * @author zhaozha
 * @date 2019/10/11 上午11:26
 */
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

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

    @Override
    public Server choose(Object o) {
        try {
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            // 想要请求的微服务的名称
            String name = loadBalancer.getName();
            // 拿到服务发现的相关API
            NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
            // nacos client自动经过基于权重的负载均衡算法,给咱们选择一个实例。
            Instance instance = namingService.selectOneHealthyInstance(name);
            log.info("选择的实例是:port = {}, instance = {}", instance.getPort(), instance);
            return new NacosServer(instance);
        } catch (NacosException e) {
            return null;
        }
    }
}
复制代码

同一Cluster下优先调用

  • yml
spring:
  cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: localhost:8848
        # 指定namespace
        namespace: 14c78c8e-c962-4d56-b5f3-0748830adc07
        # NJ
        # 指定集群名称
        cluster-name: BJ
        metadata:
          version: v1
复制代码
  • 本身写Rule
package com.virgo.user.configuration;

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.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author zhaozha
 * @date 2019/10/17 下午11:05
 */
@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();

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

            // 1. 找到指定服务的全部实例 A
            List<Instance> instances = namingService.selectInstances(name, true);

            // 2. 过滤出相同集群下的全部实例 B
            List<Instance> sameClusterInstances = instances.stream()
                    .filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
                    .collect(Collectors.toList());

            // 3. 若是B是空,就用A
            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);

            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error("发生异常了", e);
            return null;
        }
    }
}

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

复制代码
相关文章
相关标签/搜索