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);
    }
}

複製代碼
相關文章
相關標籤/搜索