天天看點

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

前言

關于dubbo之前總結過基本的使用,但也隻是簡單總結了其入門使用——dubbo 簡單執行個體。這篇部落格打算總結一下dubbo中給我們提供的其他常見的一些功能。完整的内容可以參看dubbo官網——dubbo官網。

我們知道dubbo本身是一個微服務的治理架構,既然是面向微服務的,那麼dubbo自然為我們提供了一套微服務治理所需的服務注冊中心,負載均衡,叢集容錯,服務降級以及分布式鍊路追蹤等一系列強大的元件和功能。這篇部落格就簡單從負載均衡說開去。

準備工作(springboot內建dubbo)

為了對負載均衡有一個直覺的感受,這裡先通過springboot內建dubbo,然後記錄一下全部搭建過程。springboot內建dubbo比較簡單,無非就是引入dubbo對應的starter支援動态化配置,然後引入dubbo的引用jar包,支援類的動态注入。

服務提供端

服務提供端搭建一個多子產品的mvn應用,該應用基于springboot建構,包含一個api和一個service的簡單子產品

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

整體的pom檔案如下,這裡針對一些依賴用到了dependencyManagement,使得子子產品在使用對應依賴的時候,無需指定版本号。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <groupId>com.learn</groupId>
    <artifactId>dubbo-provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>dubbo服務提供者入門執行個體</description>

    <properties>
        <dubbo.starter.version>2.7.6</dubbo.starter.version>
        <dubbo.version>2.7.6</dubbo.version>
        <zookeeper.version>3.6.0</zookeeper.version>
        <curator.version>4.0.0</curator.version>
        <curator.recipes.version>4.0.0</curator.recipes.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- dubbo -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.starter.version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
            </dependency>

            <!-- zookeeper start -->
            <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>${zookeeper.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>slf4j-log4j12</artifactId>
                        <groupId>org.slf4j</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.apache.curator</groupId>
                <artifactId>curator-framework</artifactId>
                <version>${curator.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.curator</groupId>
                <artifactId>curator-recipes</artifactId>
                <version>${curator.recipes.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <modules>
        <module>dubbo-provider-api</module>
        <module>dubbo-provider-service</module>
    </modules>
</project>
           

服務端的service子產品

pom檔案如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo-provider</artifactId>
        <groupId>com.learn</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.learn</groupId>
    <artifactId>dubbo-provider-service</artifactId>


    <dependencies>
    	<!--這裡依賴api-->
        <dependency>
            <groupId>com.learn</groupId>
            <artifactId>dubbo-provider-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
        </dependency>
    </dependencies>

</project>
           

配置檔案中加入如下幾項

dubbo.scan.base-packages=com.learn.springboot.dubbo.provider
dubbo.application.name=dubbo-springboot-provider
## 這個必須加上,否則進行負載均衡實驗的時候,會抛錯
dubbo.application.id=liman-springboot-dubbo-provider
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
           

對外提供一個HelloService的服務

import org.apache.dubbo.config.annotation.Service;
/**
 * autor:liman
 * createtime:2020/8/25
 * comment:
 */
//這裡不是spring的service注解,而是dubbo的service注解
@Service
public class HelloServiceImpl implements IHelloService {
    @Override
    public String sayHello(String name) {
        System.out.printf("dubbo service is invoked param:%s\n",name);
        return "hello this is dubbo-sprintboot provider"+name;
    }
}
           

服務提供端api

api就沒啥好說的了,就一個接口

/**
 * autor:liman
 * createtime:2020/8/25
 * comment:
 */
public interface IHelloService {
    public String sayHello(String name);
}
           

消費端

也基于springboot建構一個應用,目錄結構比較單一,可以不用多子產品的。pom依賴于服務端差不多,簡單編寫一個controller友善我們測試

/**
 * autor:liman
 * createtime:2020/8/25
 * comment:
 */
@RestController
public class HelloController {

    //Reference是dubbo的注解,這裡的check=false,是讓用戶端啟動的時候不去檢測服務引用依賴。
    @Reference(check = false)
    IHelloService iHelloService;

    @RequestMapping(value="dubbohello",method=RequestMethod.GET)
    public String dubboHello(@RequestParam("name") String name){
        return iHelloService.sayHello(name);
    }

}
           

同時配置檔案中配置如下幾項

server.port=9909
dubbo.scan.base-packages=com.learn.dubboconsumer
dubbo.application.name=dubbo-springboot-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
           

服務端啟動的配置

我們為了試驗負載均衡,服務端需要同一套代碼在不同端口提供dubbo的服務。這就需要服務端存在兩套啟動的配置,在idea中可以通過配置啟動參數完成。這裡需要記錄一下,同一套代碼如果根據不同的啟動參數啟動多個應用。

1、進入啟動編輯頁面,idea中點選如下Edit Configurations進入

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

2、點選添加,然後選擇springboot應用(因為我們的應用是基于springboot)的

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

3、設定啟動類和啟動參數

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

4、啟動兩個應用配置

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

這樣就相當于我們服務端同一套代碼在同一個IDE中,分别在20880和20881兩個端口提供dubbo的應用服務。

看見服務正常啟動,表示準備工作完成。

負載均衡

dubbo通過內建zookeeper之後解決了服務注冊以及服務動态感覺的問題。但是其實實際中,我們服務端可能不止一個,以我所在的公司為例(分為災備和生産兩個環境)每個環境中至少需要兩台伺服器提供服務,避免出現單點的情況,從一定程度上確定了服務的高可用。同時資料中心還支援多地雙活。扯遠了。

當服務端存在多個應用提供服務的時候,對于用戶端而言,需要一種機制能實作目标服務的均衡調用,通過負載均衡,能讓服務端處理的請求均衡配置設定。dubbo官網中關于這部分的介紹不太多——dubbo官網負載均衡。但也能看到dubbo提供大緻以下幾種負載均衡政策,同時還支援我們自行擴充dubbo的負載均衡政策。

Random LoadBalance

随機算法。這個是dubbo的預設負載均衡政策,即使我們不配置什麼,預設的用戶端調用就是采用的這個政策完成。其底層原理就是類似産生一個随機數,根據這個随機數所在的區間,決定最終請求被分發至何處。dubbo中的配置值:random

RoundRobin LoadBalance

權重輪詢算法。還是通過執行個體來說明吧,官網上寥寥數筆,确實介紹的不多。簡單的輪詢比較容易了解,比如我們有三台伺服器——A,B,C。第一個請求發送給A,第二個請求發送給B,第三個請求發送給C,第四個請求發送給A…這就是輪詢,輪詢其實很簡單,就是一個比較公平的請求轉發。但是實際情況中,可能存在A,B,C三個伺服器性能存在差異,如果将等量的請求分發給性能較差的伺服器,這顯然是不合理的,是以我們需要給每個伺服器配置一定的權重比例,根據這個權重比例來進行請求的分發——這就是權重輪詢算法。dubbo中的配置值:roundrobin

LeastActive LoadBalance

**最小活躍調用數算法。**這個是一個比較科學的負載均衡算法,活躍調用數越小,表明這個服務端提供服務的能力越大,是以每次處理活躍調用數較小的服務端越有可能接收到用戶端請求。dubbo中的配置值:leastactive

ConsistentHash LoadBalance

一緻性hash算法。從名字也能看出來,這個就是和一緻性hash類似了,dubbo根據用戶端的調用參數等一系列内容,生成一個hash值,根據這個hash值決定調用的服務端。關于一緻性hash算法可以參看其他大牛的介紹,這裡不贅述。dubbo中的配置值:consistenthash

執行個體

在前面啟動了兩個基礎的應用的前提下,我們啟動消費端的應用程式,通過調用其中的controller,檢視負載均衡的日志執行個體。

1、在服務提供端的@Service注解中配置loadbalance,或者在服務消費端的@Reference中配置loadbalance政策

@Service(loadbalance="roundrobin")
@Reference(check = false,loadbalance = "roundrobin")
           

用戶端的loadbalance配置優先級高于服務端的配置

通過 http://localhost:9909/dubbohello?name=loadblancetest 不斷發送該url請求,然後看看對應控制台的輸出。

叢集容錯

所謂叢集容錯,其實也不是什麼高端的操作,隻是在微服務化之後,服務與服務之間的調用會出現第三種狀态,調用逾時或者出錯,在實際開發中,有一些第三方的查詢接口,其實本身不影響業務,但是我們不能因為在調用第三方接口出現問題時,直接讓應用程式中斷,這是不合理的,需要有一定的容錯機制。dubbo也為我們提供了相關的容錯政策,這個在用戶端@Reference中的cluster配置。dubbo官網叢集容錯。

Failover Cluster

失敗自動切換,這個是預設的配置,當調用出現失敗,則自動切換至其他提供服務的伺服器。通常用于讀操作,但重試會帶來更長延遲。預設的重試次數配置是

retries="2"

這個不包含第一次,是以調用次數其實是3,如果超過三次,這會抛出服務調用逾時(實際開發中已經碰到過N多次這種了)

Failfast Cluster

快速失敗,隻發起一次調用,失敗立即報錯。通常用于非幂等性的寫操作,比如新增記錄

Failsafe Cluster

失敗安全,出現異常時,直接忽略。通常用于寫入審計日志等操作。

Failback Cluster

失敗自動恢複,背景記錄失敗請求,定時重發。通常用于消息通知操作。

Forking Cluster

并行調用多個伺服器,隻要一個成功即傳回。通常用于實時性要求較高的讀操作,但需要浪費更多服務資源。可通過

forks="2"

來設定最大并行數。

Broadcast Cluster

廣播調用所有提供者,逐個調用,任意一台報錯則報錯。通常用于通知所有提供者更新緩存或日志等本地資源資訊。

配置如下:

@Reference(check = false, loadbalance = "roundrobin", cluster = "failover", retries = 5)
IHelloService iHelloService;
           

服務降級

當某個非關鍵的服務出現異常,可以通過降級該服務來臨時屏蔽這個功能,按照不同次元可以分為人工降級和自動降級,按照功能可以分為讀服務降級和寫服務降級。dubbo官網——服務降級示例。

常見的有故障降級和限流降級。故障降級——比如某個遠端服務出現了網絡異常,那麼可以直接通過傳回兜底的資料進行降級操作,限流降級——在突發通路量很大的情況下,可以通過設定相關門檻值,當請求數達到門檻值的時候,可以通過跳轉到錯誤頁面或者排隊頁面的方式進行降級。

Dubbo中提供了一個Mock的屬性配置,可以通過Mock的方式來實作服務降級。

執行個體

在上述執行個體的基礎上,服務端修改成如下代碼

@Service(loadbalance="random",timeout = 10000)
public class HelloServiceImpl implements IHelloService {
    @Override
    public String sayHello(String name) {
    	//加入線程等待5秒的操作
        try {
            Thread.sleep(50_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.printf("dubbo service is invoked param:%s\n",name);
        return "hello this is dubbo-sprintboot provider"+name;
    }
}
           

在消費端加入一個Mock的接口

/**
 * autor:liman
 * createtime:2020/8/25
 * comment:用戶端針對IHelloService調用的Mock操作。
 */
public class MockIHelloServiceImpl implements IHelloService {
    @Override
    public String sayHello(String name) {
        String errorMessage = "服務繁忙 , "+name+" 請稍後重試";
        return errorMessage;
    }
}
           

真正消費接口的配置

@RestController
public class HelloController {

	//這裡指定Mock的配置,mock指定為我們上面新增的mock的類
    @Reference(check = false, loadbalance = "roundrobin", cluster = "failfast", timeout = 100
            , mock = "com.learn.dubboconsumer.controller.MockIHelloServiceImpl"
            , retries = 5)
    IHelloService iHelloService;

    @RequestMapping(value = "dubbohello", method = RequestMethod.GET)
    public String dubboHello(@RequestParam("name") String name) {
        return iHelloService.sayHello(name);
    }
}
           

運作結果,用postman操作如下

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

dubbo-admin的一些功能

關于dubbo-admin的一些功能在官網中也有介紹——dubbo-admin官網。主要有标簽路由,應用級别的服務治理,配置管理以及中繼資料和服務測試等功能,這裡簡單介紹一下配置管理和中繼資料的功能。

dubbo-admin的GitHub位址——dubbo-admin GitHub位址。

配置中心

目前dubbo支援的配置中心有很多,Apollo,zookeeper,nacos等,所謂配置中心,其實就是将每一個應用的配置資訊管理起來,在工作中我接觸的是Apollo配置中心,這裡我們采用zookeeper來作為dubbo應用的配置中心。

準備工作

zookeeper啟動的時候,如果沒有配置服務端端口,則zookeeper的服務端端口為8080。這個端口與dubbo-admin的端口沖突,是以需要更改zookeeper服務端的端口配置,在zoo.cfg中加入如下配置

#zookeeper啟動預設會占用8080端口,與dubbo-admin沖突
admin.serverPort=9999
           

編譯啟動dubbo-admin

其實在dubbo-admin的GitHub文檔中都已經寫明了,clone下dubbo-admin的代碼,按照如下步驟操作即可

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

啟動之後,在浏覽器中輸入localhost:8080可以進入如下頁面。

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

給指定應用增加配置

選擇右側的配置管理,然後在點選主界面的建立,在彈出的對話框中輸入應用名以及配置内容,注意,這裡的應用名需要與dubbo.application.name配置的一緻。

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

啟動項目,會發現zookeeper中關于dubbo的配置,多了一個節點,如下圖所示

Dubbo拾遺(一)——負載均衡執行個體,服務降級與叢集容錯前言準備工作(springboot內建dubbo)負載均衡叢集容錯服務降級dubbo-admin的一些功能總結

應用中還是需要指定原來的配置,作為一個兜底政策,但是需要新增如下兩行配置

dubbo.config-center.address=zookeeper://127.0.0.1:2181
dubbo.config-center.app-name=dubbo-springboot-provider
## 配置資訊的優先級配置,配置為true,表示外部配置資訊的優先級更高(可選)
dubbo.config-center.highest-priority=true
           

中繼資料中心

dubbo本身是根據url驅動的一個RPC架構,本身關于服務端調用的一些配置資訊都寫在url裡頭,我們通過檢視zookeeper的服務端節點會發現如下資訊

dubbo%3A%2F%2F192.168.25.1%3A20880%2Fcom.learn.springboot.dubbo.provider.api.IHelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-springboot-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.learn.springboot.dubbo.provider.api.IHelloService%26methods%3DsayHello%26pid%3D5344%26release%3D2.7.6%26side%3Dprovider%26timeout%3D10000%26timestamp%3D1598412406232
           

這隻是一個服務,一個接口的配置資訊,如果有多個服務多個接口,zookeeper将這些資訊推送給消費端都是一個很占用網絡資源的操作。為此,dubbo給我們提供了中繼資料的功能。就是将這些配置資訊共有的部分提出去來,作為一個中繼資料進行配置,使得接口級别的配置資訊減少。dubbo的中繼資料可以采用zookeeper和Redis實作,官網推薦Redis。

我們需要在消費端配置如下屬性,這裡我們為了簡便,不再單獨啟用Redis,如果需要使用Redis,可以直接将

dubbo.metadata-report.address

設定成指定的Redis位址即可。

dubbo.metadata-report.address=zookeeper://127.0.0.1:2181
dubbo.registry.simplified=true
           

配置之後的服務消費方位址

dubbo%3A%2F%2F192.168.25.1%3A20880%2Fcom.learn.springboot.dubbo.provider.api.IHelloService%3Fapplication%3Ddubbo-springboot-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26release%3D2.7.6%26timeout%3D10000%26timestamp%3D1598412769476
           

可以明顯的看到,調用位址的長度明顯減少(其中的GBK的編碼,沒有轉換)

總結

本篇部落格簡單總結了一下dubbo中常用的幾個執行個體,在官網中都有指定的參考,隻是一個簡單的個人總結