10 Dubbo 設定實戰

2022-07-20 12:00:31

Dubbo 設定實戰

快速入門 dubbo

建議看這篇文章是在學習了快速入門 dubbo 那篇文章的基礎上來學習

設定說明

檔案地址 https://dubbo.apache.org/zh/index.html

關於 dubbo 的設定說明 在檔案中都有比較詳細的說明,下面舉例的都是較為常用的

1 啟動時檢查

  • 啟動時會在註冊中心檢查依賴的服務是否可用,不可用時會丟擲異常
  • 在消費方編寫初始化容器的 main 方法啟動(tomcat 啟動方式,必須存取一次 action 才能初始化
    spring)
  • 想想為什麼要有這個設定呢?
    • 可以提前發現服務提供方是否可用
範例程式碼

直接啟動這個測試類,注意 spring 組態檔的位置

  • 我這裡測試,現在是沒有啟動提供者
  • 因為我們測試的目的就是讓他沒有提供者,會不會有報錯提示
/**
 * @author : look-word
 * 2022-07-19 09:44
 **/
public class TestCheckException {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring.xml");
        // 讓程式一直讀取, 目的是不讓他停止
        System.in.read();
    }
}

當我們啟動後會發現,誒,怎麼沒有錯誤呢,是下面 log4j 的提示呢?

  • 這裡沒有錯誤提示的原因呢,就是說我們沒有正確的去設定 log4j,的確我們也沒有去設定

  • 系統級別紀錄檔,需要配合 log4j 才輸出,在 resources 下新增 log4j.properties,內容如下:
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=dubbo.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n
log4j.rootLogger=error, stdout,file

再次啟動,會發現。如我們所願它出錯了。

錯誤資訊

  • 翻譯的意思:說在 zookeeper 中沒有找到可用的服務

java.lang.IllegalStateException: Failed to check the status of the service service.HelloService. No provider available for the service service.HelloService from the url zookeeper:

關閉檢查

在 spring.xml 組態檔中加上就不會有異常提示了

  • 可以看到,我這裡的這個設定是註釋掉的,在實際開發中我們是需要這個異常提示的,不推薦關閉
<!--預設是true:拋異常;false:不拋異常-->
<dubbo:consumer check="false" />

然後啟動測試檔案即可,這裡不做演示了


2 超時時間

  • 由於網路或伺服器端不可靠,會導致呼叫過程中出現不確定的阻塞狀態(超時)
  • 為了避免超時導致使用者端資源(執行緒)掛起耗盡,必須設定超時時間
  • 在服務提供者新增如下設定:
<!--設定超時時間為2秒,預設為1秒-->
<dubbo:provider timeout="2000"/>
  • 可以將服務實現 HelloServiceImpl.java 中加入模擬的網路延遲進行測試:
@com.alibaba.dubbo.config.annotation.Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Hello," + name + "!!!";
    }
}
  • 超時設定 2 秒,而模擬的網路延遲有 3 秒,超出時限,報錯!

錯誤程式碼: com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout.

  • 說伺服器響應超時。

設定原則:

dubbo 推薦在Provider上儘量多設定Consumer端屬性

  1. 服務的提供者,比服務使用方更清楚服務效能引數,如呼叫的超時時間,合理的重試
    次數,等等
  2. 在Provider設定後,Consumer不設定則會使用 Provider 的設定值,即 Provider 設定可
    以作消費者的預設值

3 重試次數

  • 當出現失敗,自動切換並重試其它伺服器,dubbo 重試的預設值是 2 次,我們可以自行設定
  • 在 provider 提供方設定:
<!-- 消費方連線第1次不算,再來重試3次,總共重試4次 -->
<dubbo:provider timeout="2000" retries="3"/>

修改實現類程式碼: 增加次數

@com.alibaba.dubbo.config.annotation.Service
public class HelloServiceImpl implements HelloService {
    int a;
    @Override
    public String sayHello(String name) {
        System.out.println("被呼叫第"+(++a)+"次");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Hello," + name + "!!!";
    }
}

可以看到 重試了 3 次 第一次不算

引入問題

並不是所有的方法都適合設定重試次數

  • 冪等方法:適合(當引數一樣,無論執行多少次,結果是一樣的,例如:查詢,修改)
  • 非冪等方法:不適合(當引數一樣,執行結果不一樣,例如:刪除,新增)

我們需要單獨為某個方法設定重試次數

  • 需要再新增一個方法,作對比
  1. 提供方介面新增 sayNo()方法並實現
public interface HelloService {
    String sayHello(String name);
    String no();
}
@com.alibaba.dubbo.config.annotation.Service
public class HelloServiceImpl implements HelloService {
    int a,b;
    @Override
    public String sayHello(String name) {
        System.out.println("sayHello被呼叫第"+(++a)+"次");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Hello," + name + "!!!";
    }

    @Override
    public String no() {
        System.out.println("no被呼叫第"+(++b)+"次");
        return "no";
    }
}
  1. 消費方介面新增 sayNo()方法宣告
public interface HelloService {
    String sayHello(String name);
    String no();
}
  1. 消費方 controller
@RestController
public class HelloAction {
	// Resource 註解 指定名稱注入
    @Resource(name = "helloService")
    private HelloService hs;

    @RequestMapping("hello/{name}")
    @ResponseBody
    public String hello(@PathVariable String name) {
        return hs.sayHello(name);
    }

    @RequestMapping("no")
    @ResponseBody
    public String no() {
        return hs.no();
    }
}
  1. 消費方設定方法重試次數
    <dubbo:reference interface="service.HelloService" id="helloService">
        <dubbo:method name="sayHello" retries="3"/>
        <dubbo:method name="no" retries="0"/>
    </dubbo:reference>

啟動專案,存取

可以看到,我們為每種方法設定的重試次數成功了


4 多版本

  • 一個介面,多個(版本的)實現類,可以使用定義版本的方式引入
  • 為 HelloService 介面定義兩個實現類,提供者修改設定:
組態檔

為 HelloService 定義了兩個版本

    <dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImpl1" version="1.0.0">
    </dubbo:service>
    <dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImpl2" version="2.0.0">
    </dubbo:service>
修改實現類
  • 複製 HelloServiceImpl 重新命名為 1 和 2
  • 分別為每個實現類標識版本資訊

  • 因為提供者定義了版本所以消費者就可以根據 version 的版本,選擇具體的服務版本 這裡是消費者組態檔

注意:消費者的控制層要改為自動注入,因為@Reference 註解和 dubbo:reference在這裡衝突

  • Resource 註解預設是根據變數名去 spring 容器中找對應的 bean 的
  • 需要在直接引數中設定 bean 的名稱 和 上面圖中 id 對應

啟動測試

注意 每次修改組態檔 都需要重啟專案

存取: http://localhost:8002/no

  • 當消費者的版本修改為 version="*",那麼就會隨機呼叫服務提供者的版本

    這是存取多次 http://localhost:8002/no 控制檯輸出的資訊

5 本地存根

為什麼要有本地存根?

  • 目前我們的分散式架構搭建起來有一個嚴重的問題,就是所有的操作全都是 消費者發起,由服務
    提供者執行
  • 消費者動動嘴皮子卻什麼活都不幹,這樣會讓提供者很累,例如簡單的引數驗證,消費者完全能夠
    勝任,把合法的引數再傳送給提供者執行,效率高了,提供者也沒那麼累了
  • 例如:去房產局辦理房屋過戶,請帶好自己的證件和資料,如果什麼都不帶,那麼辦理過戶手續會
    很麻煩,得先調查你有什麼貸款,有沒有抵押,不動產證是不是你本人,影印資料等操作。一天肯
    定辦不完。明天還要來。如果你能提前將這些東西準備好,辦理過戶,1 個小時足矣,這就是「房產
    中介辦事效率高的原因」
  • 話不多說,先在消費者處理一些業務邏輯,再呼叫提供者的過程,就是「本地存根」
範例程式碼

程式碼實現肯定在 消費者,建立一個 HelloServiceStub 類並且實現 HelloService 介面

注意:必須使用構造方法的方式注入

public class HelloServiceStub implements HelloService {
    private HelloService helloService;
    // 注入HelloService
    public HelloServiceStub(HelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public String sayHello(String name) {
        System.out.println("本地存根資料驗證。。。");
        if(!StringUtils.isEmpty(name)){
            return helloService.sayHello(name);
        }
        return "i am sorry!";
    }

    @Override
    public String no() {
        return helloService.no();
    }
}
修改消費者組態檔
  • 新增的是紅框位置的引數

    <dubbo:reference interface="service.HelloService" id="helloService" version="*" stub="service.impl.HelloServiceStub">
        <dubbo:method name="sayHello" retries="3"/>
        <dubbo:method name="no" retries="0"/>
    </dubbo:reference>

老樣子,clean專案 然後打包啟動

負載均衡策略

  • 負載均衡(Load Balance), 其實就是將請求分攤到多個操作單元上進行執行,從而共同完成工作
    任務。
  • 簡單的說,好多臺伺服器,不能總是讓一臺伺服器幹活,應該「雨露均沾」
  • dubbo 一共提供 4 種策略,預設為 random 隨機分配呼叫

範例程式碼
  • 修改提供者設定並啟動 3 個提供者,讓消費者對其進行存取
    • tomcat 埠 8001,8002,8003
    • provider 埠 20881,20882,20883
<dubbo:provider timeout="2000" retries="3" port="20881"/>

HelloServiceImpl2 類,伺服器 1,伺服器 2,伺服器 3

  • 在每次修改 tomcat 埠號 和 provider 埠是 修改 HelloServiceImpl2 的內容
  • 因為我這裡用的是 2.0.0 的版本,所以修改的是 HelloServiceImpl2 的內容

啟動 consumer 進行測試

啟動一個消費者,三個提供者

  • 底下我已經存取了一次,當我們存取多次,去控制檯檢視輸出資訊時,會發現他是隨機的去呼叫提供者

消費方修改權重

loadbalance 取值文章

    <dubbo:reference loadbalance="roundrobin" interface="service.HelloService" id="helloService" version="2.0.0" stub="service.impl.HelloServiceStub">
        <dubbo:method name="sayHello" retries="3"/>
        <dubbo:method name="no" retries="0"/>
    </dubbo:reference>

  • 最好使用管理端修改權重

然後啟動測試即可

高可用

1 zookeeper 宕機

  • zookeeper 註冊中心宕機,還可以消費 dubbo 暴露的服務
    • 監控中心宕掉不影響使用,只是丟失部分取樣資料
      資料庫宕掉後,註冊中心仍能通過快取提供服務列表查詢,但不能註冊新服務
      註冊中心對等叢集,任意一臺宕掉後,將自動切換到另一臺
      註冊中心全部宕掉後,服務提供者和服務消費者仍能通過本地快取通訊
      服務提供者無狀態,任意一臺宕掉後,不影響使用
      服務提供者全部宕掉後,服務消費者應用將無法使用,並無限次重連等待服務提供者恢復
  • 測試:
  • 正常發出請求
  • 關閉 zookeeper:./zkServer.sh stop
  • 消費者仍然可以正常消費

服務降級

  • 壁虎遇到危險會自動脫落尾巴,目的是損失不重要的東西,保住重要的
  • 服務降級,就是根據實際的情況和流量,對一些服務有策略的停止或換種簡單的方式處理,從而釋
    放伺服器的資源來保證核心業務的正常執行
1 為什麼要服務降級
  • 而為什麼要使用服務降級,這是防止分散式服務發生雪崩效應
  • 什麼是雪崩?就是蝴蝶效應,當一個請求發生超時,一直等待著服務響應,那麼在高並行情況下,
    很多請求都是因為這樣一直等著響應,直到服務資源耗盡產生宕機,而宕機之後會導致分散式其他
    服務呼叫該宕機的服務也會出現資源耗盡宕機,這樣下去將導致整個分散式服務都癱瘓,這就是雪
    崩。
2 服務降級實現方式
  • 在 管理控制檯設定服務降級:遮蔽和容錯
  • 遮蔽:mock=force:return+null 表示消費方對該服務的方法呼叫都 直接返回 null 值,不發起遠端
    呼叫。用來遮蔽不重要服務不可用時對呼叫方的影響。
  • 容錯:mock=fail:return+null 表示消費方對該服務的方法呼叫在 失敗後,再返回 null 值,不拋異
    常。用來容忍不重要服務不穩定時對呼叫方的影響。