建議看這篇文章是在學習了快速入門 dubbo 那篇文章的基礎上來學習
檔案地址 https://dubbo.apache.org/zh/index.html
關於 dubbo 的設定說明 在檔案中都有比較詳細的說明,下面舉例的都是較為常用的
- 啟動時會在註冊中心檢查依賴的服務是否可用,不可用時會丟擲異常
- 在消費方編寫初始化容器的 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.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秒,預設為1秒-->
<dubbo:provider timeout="2000"/>
@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 + "!!!";
}
}
錯誤程式碼: com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout.
- 說伺服器響應超時。
設定原則:
dubbo 推薦在Provider上儘量多設定Consumer端屬性
:
服務的提供者
,比服務使用方更清楚服務效能引數
,如呼叫的超時時間
,合理的重試在Provider設定後
,Consumer不設定
則會使用 Provider 的設定值,即 Provider 設定可
- 當出現失敗,自動切換並重試其它伺服器,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 次 第一次不算
並不是所有的方法都適合設定重試次數
我們需要單獨為某個方法設定重試次數
- 需要再新增一個方法,作對比
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";
}
}
public interface HelloService {
String sayHello(String name);
String no();
}
@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();
}
}
<dubbo:reference interface="service.HelloService" id="helloService">
<dubbo:method name="sayHello" retries="3"/>
<dubbo:method name="no" retries="0"/>
</dubbo:reference>
啟動專案,存取
可以看到,我們為每種方法設定的重試次數成功了
為 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
- 分別為每個實現類標識版本資訊
注意
:消費者的控制層要改為自動注入,因為@Reference 註解和 dubbo:reference在這裡衝突
- Resource 註解預設是根據變數名去 spring 容器中找對應的 bean 的
- 需要在直接引數中設定 bean 的名稱 和 上面圖中 id 對應
啟動測試
注意 每次修改組態檔 都需要重啟專案
當消費者的版本修改為 version="*",那麼就會隨機呼叫服務提供者的版本
這是存取多次 http://localhost:8002/no 控制檯輸出的資訊
為什麼要有本地存根?
- 目前我們的分散式架構搭建起來有一個嚴重的問題,就是所有的操作全都是 消費者發起,由服務
提供者執行- 消費者動動嘴皮子卻什麼活都不幹,這樣會讓提供者很累,例如簡單的引數驗證,消費者完全能夠
勝任,把合法的引數再傳送給提供者執行,效率高了,提供者也沒那麼累了- 例如:去房產局辦理房屋過戶,請帶好自己的證件和資料,如果什麼都不帶,那麼辦理過戶手續會
很麻煩,得先調查你有什麼貸款,有沒有抵押,不動產證是不是你本人,影印資料等操作。一天肯
定辦不完。明天還要來。如果你能提前將這些東西準備好,辦理過戶,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專案 然後打包啟動
- 因為我們只對 sayHello 方法進行了存根校驗,所以存取
- http://localhost:8002/hello/zhangsan
- 負載均衡(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 的內容
啟動一個消費者,三個提供者
- 底下我已經存取了一次,當我們存取多次,去控制檯檢視輸出資訊時,會發現他是隨機的去呼叫提供者
<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>
然後啟動測試即可
- zookeeper 註冊中心宕機,還可以消費 dubbo 暴露的服務
- 監控中心宕掉不影響使用,只是丟失部分取樣資料
資料庫宕掉後,註冊中心仍能通過快取提供服務列表查詢,但不能註冊新服務
註冊中心對等叢集,任意一臺宕掉後,將自動切換到另一臺
註冊中心全部宕掉後,服務提供者和服務消費者仍能通過本地快取通訊
服務提供者無狀態,任意一臺宕掉後,不影響使用
服務提供者全部宕掉後,服務消費者應用將無法使用,並無限次重連等待服務提供者恢復- 測試:
- 壁虎遇到危險會自動脫落尾巴,目的是損失不重要的東西,保住重要的
- 服務降級,就是根據實際的情況和流量,對一些服務有策略的停止或換種簡單的方式處理,從而釋
放伺服器的資源來保證核心業務的正常執行
- 而為什麼要使用服務降級,這是防止分散式服務發生雪崩效應
- 什麼是雪崩?就是蝴蝶效應,當一個請求發生超時,一直等待著服務響應,那麼在高並行情況下,
很多請求都是因為這樣一直等著響應,直到服務資源耗盡產生宕機,而宕機之後會導致分散式其他
服務呼叫該宕機的服務也會出現資源耗盡宕機,這樣下去將導致整個分散式服務都癱瘓,這就是雪
崩。
- 在 管理控制檯設定服務降級:遮蔽和容錯
- 遮蔽:mock=force:return+null 表示消費方對該服務的方法呼叫都 直接返回 null 值,不發起遠端
呼叫。用來遮蔽不重要服務不可用時對呼叫方的影響。- 容錯:mock=fail:return+null 表示消費方對該服務的方法呼叫在 失敗後,再返回 null 值,不拋異
常。用來容忍不重要服務不穩定時對呼叫方的影響。