2.【Spring Cloud Alibaba】實現負載均衡-Ribbon


負載均衡的兩種方式

如何實現負載均衡

目前已經實現讓A總能找到B,如何實現負載均衡

image

負載均衡的兩種方式

  • 服務器端負載均衡
  • 客戶端負載均衡

image

image

使用Ribbo實現負載均衡

Ribbon是什么

Netflix開源的客戶端側負載均衡器

引入Ribbon后的架構演進

image

整合Ribbon實現負載均衡

// 在spring容器中,創建一個對象,類型RestTemplate;名稱/ID是:restTemplate
// <bean id="restTemplate" class="xxx.RestTemplate"/>
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    RestTemplate template = new RestTemplate();
    return template;
}
@Autowired
private RestTemplate restTemplate;

@GetMapping("/test-rest-template-sentinel/{userId}")
public UserDTO test(@PathVariable Integer userId) {
    return this.restTemplate
        .getForObject(
            "http://user-center/users/{userId}",
            UserDTO.class, userId);
}

Ribbon的組成

image

Ribbon內置的負載均衡規則

image

細粒度配置自定義

  • Java代碼配置
  • 用配置屬性配置
  • 最佳實踐總結
Java代碼配置
@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration {
}
@Configuration
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule() {
        return new NacosSameClusterWeightedRule();
    }
}
用配置屬性配置

image

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

兩種方式對比

image

Ribbon全局配置

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

@Configuration
public class RibbonConfiguration {
    @Bean
    public IRule ribbonRule() {
        return new NacosSameClusterWeightedRule();
    }
}

Ribbon飢餓加載配置方式

ribbon:
  eager-load:
    enabled: true
    clients: user-center

擴展Ribbon-支持Nacos權重

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;

@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;
    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // 讀取配置文件,並初始化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 = {}, instance = {}", instance.getPort(), instance);
            return new NacosServer(instance);
        } catch (NacosException e) {
            return null;
        }
    }
}

// spring cloud commons --> 定義了標准
// spring cloud loadbalancer --> 沒有權重

擴展Ribbon-同一集群優先調用

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;

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

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    @Override
    public Server choose(Object key) {
        try {
            // 拿到配置文件中的集群名稱 BJ
            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);
    }
}

擴展Ribbon-基於元數據的版本控制

元數據

元數據就是一堆的描述信息,以map存儲。舉個例子:

spring:
  cloud:
    nacos:
        metadata: 
          # 自己這個實例的版本
          version: v1
          # 允許調用的提供者版本
          target-version: v1
代碼實現
@Slf4j
public class NacosFinalRule extends AbstractLoadBalancerRule {
    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Override
    public Server choose(Object key) {
        // 負載均衡規則:優先選擇同集群下,符合metadata的實例
        // 如果沒有,就選擇所有集群下,符合metadata的實例

        // 1. 查詢所有實例 A
        // 2. 篩選元數據匹配的實例 B
        // 3. 篩選出同cluster下元數據匹配的實例 C
        // 4. 如果C為空,就用B
        // 5. 隨機選擇實例
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version");

            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
            String name = loadBalancer.getName();

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

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }
}
public class ExtendBalancer extends Balancer {
    /**
     * 根據權重,隨機選擇實例
     *
     * @param instances 實例列表
     * @return 選擇的實例
     */
    public static Instance getHostByRandomWeight2(List<Instance> instances) {
        return getHostByRandomWeight(instances);
    }
}

深入理解Nacos的Namespace

服務調用不能跨namespace


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM