java架構之路-(微服務專題)ribbon的基本使用和內部算法的自我實現


上次回歸:

  上次我們主要說了,我們的注冊中心nacos的使用,如我們的命名空間、分組、集群、版本等是如何使用的,如果是這樣呢?我們現在有三個用戶服務和三個訂單服務,我們應該如何分發這些請求呢?都請求到一個服務?輪詢?權重?這次我們就來看一下我們的如何解決這些問題的。

本次主要內容:

  本次我們主要來說ribbon的使用,還有ribbon是如何配置各個分發策略的,再就是我們怎么能自己實現我們的自己的分發策略。

客戶端負載均衡:

  nginx大家都很熟悉了,提到負載均衡,我們第一個想到的一定是nginx,他的反向代理很強大,內部可以實現輪詢、權重、IP哈希、URL哈希幾種算法來分發我們的請求,這個就是我們熟悉的負載均衡,也叫做服務端負載均衡。那么什么又是客戶端的負載均衡呢?舉個小栗子。我們去***高級會所,叫了一個按摩的,其實這個是由一個主管給你安排一個技師給你的,不是你自己來選擇的,由主管安排的,我們可以理解為服務端負載均衡。

  那么如果我們又去了***高級會所(由於常去,都熟悉哪個技師給力),又叫了一個按摩的,感覺上次的89號按的不錯,手法嫻熟,我們直接選取了我們的89號技師,並不是由主管安排的,是我們的一個自主選擇,是我們在我們的心中選擇了合適的人選給予按摩,這樣的操作,我們可以理解為客戶端負載均衡。

ribbon的使用:

  ribbon在中間給給予我們一個選擇的作用了,而且省去了很多的代碼(相比上次博客的代碼來說),我們先來看一個最簡單的示例代碼。

  一、最簡單的實現

  ①.加入我們的ribbon依賴包,我這直接放在的父工程下了。

<!-- 加入ribbon依賴包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

  ②.改用戶服務的config配置(調用方)。@LoadBalanced

package com.user.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ClientConfig {

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

  ③.改用戶服務(調用方)的調用方式,剛才我都提到了會簡單很多代碼,看一下到底簡單了多少代碼

package com.user.clintOrder;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/getOrder")
    public String getOrderData(){
        String forObject = restTemplate.getForObject("http://ribbon-order/getOrderData", String.class);
        System.out.println("forObject = " + forObject);
        return forObject;
    }
}

  ④.啟動訂單服務(被調用方),啟動用戶服務(調用方)

   OK,最簡單的示例完成了。

  二、內部算法

  剛才我說到了ribbon可以做到客戶端的負載均衡,現在我們只有一個服務體現不出來的啊,我們在多弄幾個被調用端。很多小伙伴可能還不會IDEA同時啟動多個服務,我這里順便說一句,會的請無視。我們先啟動一個訂單服務,然后點擊左上角的下拉選項,就是你運行程序,Debug程序,結束程序那里,然后點擊Edit Configurations...,點開以后,勾選Allow parenel run就可以了,記得自己該程序的端口,要不會端口沖突。有的IDEA版本是勾選share即可。

   這次我們啟動三個訂單服務,然后看一下訪問情況。第一次訪問,調用了我們的8082端口的訂單服務服務,第二次是我們的8083,第三次是我們的8081......可以看出來這個是一個輪詢的規則來調用的。

   嚴格意義來說也不算是輪詢的,如果你是亞馬遜的服務器,你就不會體驗的輪詢了,我們接下來看一下都有什么算法。

   我知道的有這7中算法。

  ①.RoundRobinRule 輪詢選擇, 輪詢index,選擇index對應位置的Server。輪詢

  ②.ZoneAvoidanceRule(默認是這個),復合判斷Server所在Zone的性能和Server的可用性選擇Server,在沒有Zone的情況下類是輪詢。你在國內可以理解為輪詢。輪詢

  ③.RandomRule,隨機選擇一個Server)。隨機

  ④.RetryRule,對選定的負載均衡策略機上重試機制,在一個配置時間段內當選擇Server不成功, 則一直嘗試使用subRule的方式選擇一個可用的server. 

  ⑤.AvailabilityFilteringRule,過濾掉一直連接失敗的被標記為circuit tripped的后端Server,並過濾掉那些高並發的后端Server或者使用一個AvailabilityPredicate來包含過濾server的邏輯,其實就就是檢查status里記錄的各個Server的運行狀態

  ⑥.BestAvailableRule,選擇一個最小的並發請求的Server,逐個考察Server,如果Server被tripped了,則跳過。並發

  ⑦.WeightedResponseTimeRule,根據響應時間加權,響應時間越長,權重越小,被選中的可能性越低。響應時間長短

  那么這么多的算法,我們來如何配置呢?我們來改一下我們的config配置

package com.user.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ClientConfig {

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

    /**
     * 選擇ribbon算法
     * @return
     */ @Bean public IRule chooseAlgorithm(){ return new RandomRule(); }
}

  這樣我們就更改了我們的默認算法,改為了隨機選擇的算法,我就不用上圖給你們看測試結果了,我這測過了,好用~!我這是8081->8081->8081->8083->8082->8082,完全沒有規律,是隨機的。

  三、細粒度配置

  說到這還沒完,我們來看一個需求,我們用戶服務需要輪詢調用積分服務,且需要隨機調用訂單服務,怎么來配置?我們下面來看一下如何細粒度的來配置。

  首先我們在用戶服務下面添加兩個類,注意不要被spring掃描到。

package com.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;public class IntegralConfig {

    /**
     * 選擇積分服務算法
     * @return
     */public IRule chooseAlgorithm(){
        return new RoundRobinRule();
    }
}
package com.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
public class OrderConfig {

    /**
     * 選擇訂單服務算法
     * @return
     */public IRule chooseAlgorithm(){
        return new RandomRule();
    }
}

  然后在我們的主配置類的加入注解,就是說我們的遇到ribbon-order服務調用OrderConfig類下面的算法,我們的遇到ribbon-logistics服務調用LogisticsConfig類下面的算法,

package com.user.config;

import com.config.IntegralConfig;
import com.config.OrderConfig;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
@RibbonClients(value={
        @RibbonClient(name="ribbon-order",configuration = OrderConfig.class),
        @RibbonClient(name="ribbon-integral",configuration = IntegralConfig.class)
})
public class ClientConfig {

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

  這是其中的一種配置,下面還有一種配置比較簡單,在配置文件中加入算法就可以了,但是切記,不要兩種同時使用,容易亂。

ribbon-order:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ribbon-integral:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule

  我們先來實測一下我們的配置是否真的可用,先測試第一種注解+外部配置文件的,輪詢積分服務,隨機訂單服務,我們先驗證積分。第一次訪問積分服務

第二次訪問積分服務

第三次訪問積分服務

第四次訪問積分服務

積分服務是輪詢的,說明積分服務算法配置正確,我們再來看一下訂單服務。第一訪問訂單服務

 

第二次訪問訂單服務

第三次訪問訂單服務

第四和第五次訪問訂單服務

嚇死我了,以為配置失敗了呢,害得我第四次和第五次同時訪問的,可以看出來12311這個順序是一個隨機的,我們再來驗證一下我們的配置文件方式。

我們的訂單服務的結果是:8083->8083->8083->8082->8082->8082->8082,可以看出來我們的訂單是一個隨機數。

我們的積分服務的結果是:9081->9082->9083->9081->9082->9083->9081,可以看出來我們的積分是一個輪詢的算法。

   這里我說到了兩種配置,再次強調一次,強烈不建議兩種配置同時使用,你會亂的,建議使用配置文件的方式(不需要考慮文件是否被spring掃描到的問題),其次使用外部配置文件,不讓spring掃描到的方式。

  四、ribbon參數解析

ribbon.MaxAutoRetries=1 #每一台服務器重試的次數,不包含首次調用的那一次 
ribbon.MaxAutoRetriesNextServer=2 #重試的服務器的個數,不包含首次調用的那一台實例 
ribbon.OkToRetryOnAllOperations=true #是否對所以的操作進行重試(True 的話,會對pos、 put操作進行重試,存在服務冪等問題) 沒事別配置這個玩意,是個坑
ribbon.ConnectTimeout=3000 # 建立連接超時 
ribbon.ReadTimeout=3000 # 讀取數據超時

  也可以為每個Ribbon客戶端設置不同的超時時間, 通過服務名稱進行指定:

  ribbon-order.ribbon.ConnectTimeout=2000

  ribbon-integral.ribbon.ReadTimeout=5000

  詳細地址http://c.biancheng.net/view/5356.html

  開啟飢餓加載,用來解決Ribbon第一次調用耗時高的配置

ribbon.eager-load.enabled=true #開啟Ribbon的飢餓加載模式
ribbon.eager-load.clients=ribbon-order #指定需要飢餓加載的服務名

   五、自定義規則策略(權重)

   我們的ribbon並沒有我們平時用的權重算法,所以我們還是需要自己來實現權重算法的,首先新建一個規則類,然后集成我們的AbstractLoadBalancerRule,重寫我們的choose方法。

package com.user.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 org.springframework.beans.factory.annotation.Autowired;

public class WeightedRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;


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

    @Override
    public Server choose(Object key) {
        try {
            BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
            //獲取微服務的名稱
            String serviceName = baseLoadBalancer.getName();

            //獲取Nocas服務發現的相關組件API
            NamingService namingService =  discoveryProperties.namingServiceInstance();

            //獲取 一個基於nacos client 實現權重的負載均衡算法
            Instance instance = namingService.selectOneHealthyInstance(serviceName);

            //返回一個server
            return new NacosServer(instance);
        } catch (NacosException e) {
            System.out.println("自定義負載均衡算法錯誤");
            e.printStackTrace();
        }
        return null;
    }
}

  然后按照我們配置算法的方式來配置一下我們的自定義算法,首先寫一個不會被spring掃描的類,在我們的配置中加入我們的注解,這次我們設置所有的服務都用我們的自定義算法

package com.config;

import com.netflix.loadbalancer.IRule;
import com.user.myRule.WeightedRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class WeightedConfig {

    /**
     * 選擇權重算法
     * @return
     */public IRule chooseAlgorithm(){
        return new WeightedRule();
    }
}
@RibbonClients(defaultConfiguration = WeightedConfig.class)

啟動我們的訂單服務,這次我啟動了兩個訂單服務,打開我們的nacos頁面,設置權重

   從這個配置來看我們是幾乎都是訪問的8081這個服務的,我們來測試一下。我獲取了10次,實際上也是有9次落到我們的8081的服務上

   六、自定義算法,同集群優先

  上次nacos我們留下了一個小問題,就是同一個集群的優先調用,需要我們自己來實現,上次沒有說,今天說了ribbon,可以去說如何同集群優先調用了

package com.user.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class TheSameClusterPriorityRule extends AbstractLoadBalancerRule {

    public static final Logger log = LoggerFactory.getLogger(TheSameClusterPriorityRule.class);

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {

    }

    @Override
    public Server choose(Object key) {
        try {
            //第一步:獲取當前服務所在的集群
            String currentClusterName = discoveryProperties.getClusterName();

            //第二步:獲取一個負載均衡對象
            BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();

            //第三步:獲取當前調用的微服務的名稱
            String invokedSerivceName = baseLoadBalancer.getName();

            //第四步:獲取nacos clinet的服務注冊發現組件的api
            NamingService namingService = discoveryProperties.namingServiceInstance();

            //第五步:獲取所有的服務實例
            List<Instance> allInstance =  namingService.getAllInstances(invokedSerivceName);

            List<Instance> theSameClusterNameInstList = new ArrayList<>();

            //第六步:過濾篩選同集群下的所有實例
            for(Instance instance : allInstance) {
                if(StringUtils.endsWithIgnoreCase(instance.getClusterName(),currentClusterName)) {
                    theSameClusterNameInstList.add(instance);
                }
            }

            Instance toBeChooseInstance ;

            //第七步:選擇合適的一個實例調用
            if(theSameClusterNameInstList.isEmpty()) {

                toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(allInstance);
                System.out.println("發生跨集群調用");
                log.info("發生跨集群調用--->當前微服務所在集群:{},被調用微服務所在集群:{},Host:{},Port:{}",
                        currentClusterName,toBeChooseInstance.getClusterName(),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());
            }else {
                toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(theSameClusterNameInstList);
                System.out.println("同集群調用");
                log.info("同集群調用--->當前微服務所在集群:{},被調用微服務所在集群:{},Host:{},Port:{}",
                        currentClusterName,toBeChooseInstance.getClusterName(),toBeChooseInstance.getIp(),toBeChooseInstance.getPort());

            }

            return new NacosServer(toBeChooseInstance);

        } catch (NacosException e) {
            log.error("同集群優先權重負載均衡算法選擇異常:{}",e);
        }
        return null;
    }
}

總結:

  本次博客主要說了我們的ribbon的使用,以及我們的內部算法也簡單說了一遍,后面的源碼博客會具體去說內部的實現,我們還定義了我們自己的算法。

最進弄了一個公眾號,小菜技術,歡迎大家的加入


免責聲明!

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



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