1.3 自定義ribbon負載均衡策略


自定義Ribbon負載均衡

一. 按照權重實現負載均衡

ribbon本身是沒有權重的概念的, 那么如何才能實現代用權重的負載均衡呢?

 

我們在nacos中, 服務其的集群有一個權重的概念, 當給服務器設置了權重, 那么流量就可以根據權重比例分配到服務器上.

1. 先來看看如何自定義一個負載均衡策略. 

首先是繼承自AbstractLoadBalancerRule. 從下面這張圖可以看出, ribbon自定義的策略, 最終都繼承自這個類, 這個類封裝了負載均衡策略的公共方法.

 

 

在nacos中可以配置服務器的權重

在nacos中,有兩個重要的類, 一個是NameService, 一個是ConfigService

  • NameService: 注冊中心
  • ConfigService: 配置中心

二. 實現帶有權重的負載均衡器

 第一步: 自定義一個帶有權重的負載均衡器MyWeightRule

 

package com.lxl.www.gateway.myrule;

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.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 自定義一個權重負載均衡策略
 */
@Slf4j
public class MyWeightRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        // 讀取配置文件, 並且初始化, ribbon內部基本上用不上
    }

    /**
     * 這個方法是實現負載均衡策略的方法
     *
     * @param
     * @return
     */
    @Override
    public Server choose(Object key) {
        try {
            log.info("key:{}", key);
            // 調用父類方法, 獲取當前使用的負載均衡器
            BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            // 獲取當前服務的名稱
            String serviceName = baseLoadBalancer.getName();

            /**
             * namingService: 獲取nacos的服務發現API
             */
            NamingService namingService = discoveryProperties.namingServiceInstance();

            /**
             * 根據名稱獲取服務發現實例
             * 在selectOneHealthyInstance中, nacos實現了權重的負載均衡算法
             */
            Instance instance = namingService.selectOneHealthyInstance(serviceName);

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

        return null;
    }
}

第二步: 啟用自定義的負載均衡器應用

修改自定義的ribbon config.

package com.lxl.www.gateway.config;

import com.lxl.www.ribbonconfig.GlobalRibbonConfig;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;

@Configuration
/*@RibbonClients(value = {
        @RibbonClient(name="product", configuration = ProductConfiguration.class),
        @RibbonClient(name = "customer", configuration = CustomerConfiguration.class)
})*/
// 使用全局的配置
@RibbonClients(defaultConfiguration = GlobalRibbonConfig.class) public class CustomeRibbonConfig {

}

設置為全局配置GlobalRibbonConfig.class

然后在全局配置中,我們執行當前使用的負載均衡策略是自定義的權重負載均衡策略

package com.lxl.www.ribbonconfig;

import com.lxl.www.gateway.myrule.MyWeightRule;
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GlobalRibbonConfig {

    @Bean
    public IRule getRule() {
        // 實現帶有權重的負載均衡策略
        return new MyWeightRule();
    }
}

第三步:啟動服務, 並設置nacos權重

 

我們看到啟動了兩台product, 一台order. 接下來設置product兩台實例的權重

 

 我們看到一個設置為0.1, 另一個是0.9, 也就是說如果有10次請求, 基本上都是會打到8083端口上的. 

第四步: 瀏覽器訪問連接, 測試結果

http://localhost:8080/get/product

觸發了10次請求, 基本上都打到了8083服務器上.

 

三 實現同集群優先調用原則的負載均衡器

 盡量避免跨集群調用

比如, 南京集群的product優先調用南京集群的order . 北京集群的product優先調用北京集群的order.

 實現如上圖所示的功能

我們有product服務和order服務, 假設各有10台. product和order有5台部署在北京集群上, 另外5台部署在南京集群上. 

那么當有請求打到南京的product的時候, 那么他調用order要優先調用南京集群的, 南京集群沒有了, 在調用北京集群的. 

當有請求打到北京的product的是偶, 優先調用北京集群的order, 北京沒有找到, 再去調用南京的order.

第一步: 自定義一個同集群優先策略的負載均衡器

 

package com.lxl.www.gateway.myrule;

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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.omg.PortableInterceptor.INACTIVE;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 同集群優先調用--負載均衡策略
 */
@Slf4j
public class TheSameClusterPriorityRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    /**
     * 主要實現choose方法
     *
     * @param o
     * @return
     */
    @Override
    public Server choose(Object o) {
        try {
            // 第一步: 獲取服務所在的集群名稱
            String clusterName = discoveryProperties.getClusterName();
            // 第二步: 獲取當前負載均衡器
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            // 第三步: 獲取當前服務的實例名稱
            String serviceName = loadBalancer.getName();
            // 第四步: 獲取nacos client服務注冊發現api
            NamingService namingService = discoveryProperties.namingServiceInstance();
            // 第五步: 通過namingService獲取當前注冊的所有服務實例
            List<Instance> allInstances = namingService.getAllInstances(serviceName);
            List<Instance> instanceList = new ArrayList<>();
            // 第六步: 過濾篩選同集群下的服務實例
            for (Instance instance: allInstances) {
                if (StringUtils.endsWithIgnoreCase(instance.getClusterName(), clusterName)) {
                    instanceList.add(instance);
                }
            }
            Instance toBeChooseInstance;
            // 第七步: 選擇合適的服務實例
            if (instanceList == null || instanceList.size() == 0) {
                // 從其他集群中隨機選擇一個服務實例
                toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(allInstances);
                log.info("跨集群調用---{}", toBeChooseInstance.getPort());
            } else {
                // 從本集群中隨機選擇一個服務實例
                toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(instanceList);
                log.info("同集群調用---{}", toBeChooseInstance.getPort());
            }
            return new NacosServer(toBeChooseInstance);
        } catch (NacosException e) {
            e.printStackTrace();
        }
        return null;
    }
}

package com.lxl.www.ribbon.myrule;

import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;

import java.util.List;

/**
 * 根據權重隨機選擇一個實例
 */
public class WeightedBalancer extends Balancer {
    /**
     * 根據隨機權重策略, 從一群服務器中選擇一個
     * @return
     */
    public static Instance chooseInstanceByRandomWeight (List<Instance> instanceList) {
        // 這是父類Balancer自帶的根據隨機權重獲取服務的方法.
        return getHostByRandomWeight(instanceList);
    }
}

 

第二步: 設置當前的負載均衡策略為同集群優先策略

@Configuration
/*@RibbonClients(value = {
        @RibbonClient(name="product", configuration = ProductConfiguration.class),
        @RibbonClient(name = "customer", configuration = CustomerConfiguration.class)
})*/
// 使用全局的配置
@RibbonClients(defaultConfiguration = GlobalRibbonConfig.class)
public class CustomeRibbonConfig {

}
@Configuration
public class GlobalRibbonConfig {

    @Bean
    public IRule getRule() {
        // 實現帶有權重的負載均衡策略
        //return new MyWeightRule();
        // 實現同集群優先的服務實例
        return new TheSameClusterPriorityRule();
    }
}

第三步: 啟動服務

 

 可以看到order服務實例有1台, 所屬集群是BJ-CLUSTER, product服務有4台. BJ-CLUSTER和NJ-CLUSTER各兩台

 第四步: 訪問請求

http://localhost:8080/get/product

由於order服務是BJ-CLUSTER, 所以, 我們看到流量是隨機打到了ORDER的BJ-CLUSTER集群上. 

 

停止BJ-CLUSTER集群的兩台實例, 現在BJ-CLUSTER集群上沒有order的服務實例了, 這是在請求, 流量就會達到NJ-CLUSTER上.

  

四. 金絲雀發布--實現同版本同集群優先負載均衡策略

 

 

金絲雀發布, 也稱為灰度發布, 是什么意思呢?

首先, 我們的product服務實例有100台, order服務實例有100台. 現在都是在v1 版本上

然后新開發了一個變化很大的功能, 為了保證效果, 要進行灰度測試

在product-center上發布了5台, 在order-center上發布了5台

那么現在用戶的流量過來了, 我們要設定一部分用戶, 請求的是v1版本的流量, 而且全部都走v1版本, product-center, order-center都是v1版本

如果過來的用戶, 請求的v2版本的流量, 那么product和order都走v2版本.

下面我們要實現的功能描述如下:

 

 

1. 同集群,同版本優先調用, 

2. 沒有同集群的服務提供者, 進行跨集群,同版本調用

3. 不可以進行不同版本間的調用

 也就是說: 南京集群V1版本的product優先調用南京集群V1版本的order, 如果沒有南京集群V1版本的order, 那么就調用北京集群V1版本的order, 那么能調用v2版本的么? 當然不行. 

下面來具體實現一下

第一步: 實現帶有版本的集群優先策略的負載均衡算法

 

 

package com.lxl.www.gateway.myrule;

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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;

/**
 * 同集群優先帶版本的負載均衡策略
 */
@Slf4j
public class TheSameClusterPriorityWithVersionRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object o) {
        try {
            // 第一步: 獲取當前服務的集群名稱 和 服務的版本號
            String clusterName = discoveryProperties.getClusterName();
            String version = discoveryProperties.getMetadata().get("version");

            // 第二步: 獲取當前服務的負載均衡器
            BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            // 第三步: 獲取目標服務的服務名
            String serviceName = loadBalancer.getName();

            // 第四步: 獲取nacos提供的服務注冊api
            NamingService namingService = discoveryProperties.namingServiceInstance();
            // 第五步: 獲取所有服務名為serviceName的服務實例
            List<Instance> allInstances = namingService.getAllInstances(serviceName);

            // 第六步: 過濾相同版本的服務實例
            List<Instance> sameVersionInstance = new ArrayList<>();
            for (Instance instance : allInstances) {
                if (instance.getMetadata().get("version").equals(version)) {
                    sameVersionInstance.add(instance);
                }
            }

            // 第七步: 過濾有相同集群的服務實例
            List<Instance> sameClusterInstance = new ArrayList<>();
            for (Instance instance: sameVersionInstance) {
                if (instance.getClusterName().equals(clusterName)) {
                    sameClusterInstance.add(instance);
                }
            }
            // 第八步: 選擇合適的服務實例
            Instance toBeChooseInstanc;
            if (sameClusterInstance == null || sameClusterInstance.size() == 0) {
                toBeChooseInstanc = WeightedBalancer.chooseInstanceByRandomWeight(sameVersionInstance);
                log.info("同版本不同集群的服務實例--{}", toBeChooseInstanc.getPort());
            } else {
                toBeChooseInstanc = WeightedBalancer.chooseInstanceByRandomWeight(sameClusterInstance);
                log.info("同版本同集群服務實例--{}", toBeChooseInstanc.getPort());
            }
            return new NacosServer(toBeChooseInstanc);
        } catch (NacosException e) {
            e.printStackTrace();
        }
        return null;
    }
}

第二步: 啟用自定義負載均衡策略

 

 

 

@Configuration
/*@RibbonClients(value = {
        @RibbonClient(name="product", configuration = ProductConfiguration.class),
        @RibbonClient(name = "customer", configuration = CustomerConfiguration.class)
})*/
// 使用全局的配置
@RibbonClients(defaultConfiguration = GlobalRibbonConfig.class)
public class CustomeRibbonConfig {

}
@Configuration
public class GlobalRibbonConfig {

    @Bean
    public IRule getRule() {
        // 實現帶有權重的負載均衡策略
        //return new MyWeightRule();
        // 實現同集群優先的負載均衡策略
        // return new TheSameClusterPriorityRule();
        // 實現同版本集群優先的服務負載均衡策略
        return new TheSameClusterPriorityWithVersionRule();
    }
}

第三步:啟動服務

 

 如圖, 我們啟動了兩個服務, 一個order, 一個product, 其中order啟動了1個實例, product啟動了4個實例.

下面來看看order實例的詳情

 

 屬於北京集群, 版本號是v1版本

 

再來看看product集群的詳情

 

北京集群有兩台實例, 一個版本號是v1,另一個是v2

南京集群有兩台實例, 一個是v1, 另一個也是v2

 

第四步: 現在發起請求

http://localhost:8080/get/product

order集群是北京, 版本是version1. 那么服務應該達到哪台服務器呢? 應該打到北京集群的v1版本服務,端口號是8081的服務器.

 

 果然,請求達到了8081服務上, 

加入這時候, 北京集群的8081宕機了, 流量應該達到南京集群的v1版本的集群上, 端口號是8083

 

 果然是8083. 

那么如果8083也宕機了, 流量會打到v2服務器上么? 

沒錯,不會的, 報錯了, 沒有服務

 


免責聲明!

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



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