【萬字長文】Dubbo 入門總結 ,一款高性能的 Java RPC 框架
- 2020 年 12 月 7 日
- 筆記
這篇文章是我學習整理 Dubbo 的一篇文章,首先大部分內容參考了官網 + 某矽谷的影片,內容講解進行了重新編排,40多張圖片,也都是我修改重製的,雖然一萬多字,但是其實也可以看出來,更多的內容集中在了概念或功能的介紹,具體環境的搭建,以及如何配置,快速上手上面,但是對於這樣一款優秀的框架,文章中提到的每一個點其實展開來講都能寫這樣篇幅的一篇文章,僅僅入門來看也沒必要,總得學會走,才可以去試著跑
- 文章提及了 SSM 和 Springboot 下的不同配置方式,但是 Zookeeper 環境搭在了 Windows 下,後面準備重新整理的 Redis 我考慮在 Linux 中來跑,會更貼近實際一些
- 說句題外話,設計模式也沒停,還有一篇存稿,不過想整理的東西太多,還有學校等等的事情,真有點自閉了
- 有什麼建議和意見歡迎在評論區和我交流哈,感謝各位大佬的支援~
早安,學習人 & 打工人 ~ ~ ~
一 必備基礎知識
(一) 分散式基礎理論
在百度以及維基中的定義都相對專業且晦澀,大部分部落格或者教程經常會使用《分散式系統原理和范型》中的定義,即:「分散式系統是若干獨立電腦的集合,這些電腦對於用戶來說就像是單個相關係統」
下面我們用一些篇幅來通俗的解釋一下什麼叫做分散式
(1) 什麼是集中式系統
提到分散式,不得不提的就是 「集中式系統」,這個概念最好理解了,它就是將功能,程式等安裝在同一台設備上,就由這一台主機設備向外提供服務
舉個最簡單的例子:你拿一台PC主機,將其改裝成了一台簡單的伺服器,配置好各種內容後,你將MySQL,Web伺服器,FTP,Nginx 等等,全部安裝在其中,打包部署項目後,就可以對外提供服務了,但是一旦這台機器無論是軟體還是硬體出現了問題,整個系統都會受到嚴重的牽連錯誤,雞蛋放在一個籃子里,要打就全打了
(2) 什麼是分散式系統
既然集中式系統有這樣一種牽一髮而動全身的問題,那麼分散式的其中一個作用,自然是來解決這樣的問題了,正如定義中所知,分散式系統在用戶的體驗感官里,就像傳統的單系統一樣,一些變化都是這個系統本身內部進行的,對於用戶並沒有什麼太大的感覺
例如:淘寶,京東這種大型電商平台,它們的主機都是數以萬計的,否則根本沒法處理大量的數據和請求,具體其中有什麼劃分,以及操作,我們下面會說到,但是對於用戶的我們,我們不需要也不想關心這些,我們仍可以單純的認為,我們面對的就是 「淘寶」 這一台 「主機」
所以分散式的一個相對專業一些的說法是這樣的(進程粒度)兩個或者多個程式,分別運行在不同的主機進程上,它們互相配合協調,完成共同的功能,那麼這幾個程式之間構成的系統就可以叫做分散式系統
- 這幾者都是相同的程式 —— 分散式
- 這幾者都是不同的程式 —— 集群
(3) 發展過程
A:單一應用架構
當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本。此時,用於簡化增刪改查工作量的數據訪問框架(ORM)是關鍵
優點:
- 所有功能集中在一起,開發以及後期維護均相對簡單
- 對於小型網站及管理系統,簡單易用
缺點:
- 對於大型項目,升級維護困難
- 無法對於一個模組進行水平擴展或優化
- 模組之間耦合緊密,容錯率低
B:垂直應用架構
當訪問量逐漸增大,單一應用增加機器帶來的加速度越來越小,提升效率的方法之一是將應用拆成互不相干的幾個應用,以提升效率。此時,用於加速前端頁面開發的Web框架(MVC)是關鍵
優點:
- 通過切分業務達到各模組獨立部署,維護和部署更加簡單
- 可以對一個模組進行水平擴展或優化
缺點:
- 系統之間無法相互調用
- 具有重複程式碼,且公用模組無法重複利用
C:分散式服務架構
當垂直應用越來越多,應用之間交互不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提高業務復用及整合的分散式服務框架(RPC)是關鍵
優點:
- 抽取公共程式碼,程式碼復用性提高
缺點:
- 調用關係複雜,維護很麻煩
D:流動計算架構
當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個調度中心基於訪問壓力實時管理集群容量,提高集群利用率。此時,用於提高機器利用率的資源調度和治理中心(SOA)是關鍵
優點:
- 複雜的調用關係不再需要自行維護
缺點:
- 服務具有依賴性,一個服務出現問題或許會導致其他系統不可用
(4) RPC 介紹
RPC(Remote Procedure Call)是指遠程過程調用,是一種進程間通訊方式,他是一種技術的思想,而不是規範。它允許程式調用另一個地址空間(通常是共享網路的另一台機器上)的過程或函數,而不用程式設計師顯式編碼這個遠程調用的細節
這應該是很好理解的,調用本地 A 伺服器上的函數或者方法的時候很簡單,但是如果A 想要訪問 B 的方法時,兩者的記憶體空間壓根都不是同一個,只能通過網路傳輸調用的相關內容,關於傳輸協議亦或者傳輸方式,都由 RPC 幫我們背後實現
也就是說,即程式設計師無論是調用本地的還是遠程的函數,本質上編寫的調用程式碼基本相同
關於 RPC 更加細緻專業的解釋說明
RPC是一種通過網路從遠程電腦程式上請求服務,而不需要了解底層網路技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通訊程式之間攜帶資訊數據。在OSI網路通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網路分散式多程式在內的應用程式更加容易
RPC採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。首先,客戶機調用進程發送一個有進程參數的調用資訊到服務進程,然後等待應答資訊。在伺服器端,進程保持睡眠狀態直到調用資訊到達為止。當一個調用資訊到達,伺服器獲得進程參數,計算結果,發送答覆資訊,然後等待下一個調用資訊,最後,客戶端調用進程接收答覆資訊,獲得進程結果,然後調用執行繼續進行
本地過程調用
RPC就是要像調用本地的函數一樣去調遠程函數。在研究RPC前,我們先看看本地調用是怎麼調的。假設我們要調用函數Multiply來計算lvalue * rvalue的結果:
……省略,此篇可細細看一下
問題答者:洪春濤
來看一下從 A 想要訪問 B 中方法的一個流程例子
順著執行路線來捋一下
- ① 客戶端 Client 發出一個調用遠程服務的訊號
- ② Client Stub 接收到調用,把方法參數進行序列化處理
- ③ 客戶端 Client 通過 Sockets 將消息發送到服務端
- ④ 服務端的 Server Stub,對收到的消息進行解碼,也就是反序列化的過程
- ⑤ 服務端 Server Stub,根據解碼結果,調用服務端的本地服務
- ⑥ 服務端的本地服務執行,且將結果返回給自己的 Server Stub
- ⑦ Server Stub 打包結果成消息,即序列化結果消息
- ⑧ 服務端通過 Sockets 將消息發送到客戶端
- ⑨ 客戶端的 Client Stub 接收到服務端傳來的結果消息,進行解碼處理,即反序列化
- ⑩ 客戶端得到從服務端傳來的最後結果
第③點說明:RPC採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。
(二) Dubbo 概念
(1) 介紹
Dubbo官網://dubbo.apache.org/zh/
Apache Dubbo 是一款高性能、輕量級的開源 Java 服務框架
Apache Dubbo |ˈdʌbəʊ| 提供了六大核心能力:面向介面代理的高性能RPC調用,智慧容錯和負載均衡,服務自動註冊和發現,高度可擴展能力,運行期流量調度,可視化的服務治理與運維。
面向介面代理的高性能RPC調用
- 提供高性能的基於代理的遠程調用能力,服務以介面為粒度,為開發者屏蔽遠程調用底層細節。
智慧負載均衡
- 內置多種負載均衡策略,智慧感知下游節點健康狀況,顯著減少調用延遲,提高系統吞吐量。
服務自動註冊與發現
- 支援多種註冊中心服務,服務實例上下線實時感知。
高度可擴展能力
- 遵循微內核+插件的設計原則,所有核心能力如Protocol、Transport、Serialization被設計為擴展點,平等對待內置實現和第三方實現。
運行期流量調度
- 內置條件、腳本等路由策略,通過配置不同的路由規則,輕鬆實現灰度發布,同機房優先等功能。
可視化的服務治理與運維
- 提供豐富服務治理、運維工具:隨時查詢服務元數據、服務健康狀態及調用統計,實時下發路由策略、調整配置參數。
(2) 架構
這是Dubbo的架構圖
首先介紹一下這五個節點的角色(五個圓角矩形框)
-
Provider
:暴露服務的服務提供方 -
Consume
:調用遠程服務的服務消費方 -
Registry
:服務註冊與發現的註冊中心 -
Monitor
:統計服務的調用次數和調用時間的監控中心 -
Container
:服務運行容器
再來看一下調用的關係和流程:
-
① 服務容器負責啟動,載入,運行服務提供者
-
② 服務提供者在啟動時,向註冊中心註冊自己提供的服務
-
③ 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者
-
④ 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一台提供者進行調用,如果調用失敗,再選另一台調用
-
⑤ 服務消費者和提供者,在記憶體中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心
(三) dubbo 環境搭建
說明:以下為學習演示方便,均為 Windows 系統搭建,真實 Linux 場景搭建會在後面出一篇,如何在Linux安裝配置常見的軟體
(1) 搭建zookeeper註冊中心環境
A:下載 zookeeper
首先先將 zookeeper 下載下來
官網(查看文檔,下載)://zookeeper.apache.org/
嫌麻煩就直接去下載頁面://downloads.apache.org/zookeeper/
這裡下載的版本是:apache-zookeeper-3.6.2
注意:下載,apache-zookeeper-3.6.2.tar.gz ,apache-zookeeper-3.6.2-bin.tar.gz,雖然本質我們是用前者,但是啟動過程中遇到了報錯,需要後者部分文件來進行修復
B:配置及排錯
XXX\apache-zookeeper-3.6.2\bin
,下面的 zkServer.cmd
和 zkCli.cmd
就是我們想要運行的,可以在當前目錄下打開 cmd(什麼都行) 運行 zkServer.cmd
,首先可能會遇到第一個錯誤——找不到或無法載入主類
這個時候就需要解壓剛才下載的 apache-zookeeper-3.6.2-bin.tar.gz,然後在其目錄下,複製整個lib文件夾到剛才的 apache-zookeeper-3.6.2 文件夾下,這樣就解決了
再次運行,會有第二個問題:一個配置文件的缺失
這個問題只需要在 XXX\apache-zookeeper-3.6.2\conf
中把 zoo_sample.cfg文件複製一份,且名字改為zoo.cfg
可以打開編輯這個新複製過來的 zoo.cfg文件,其中就是一些配置內容,例如埠還有一個數據的存儲地址,因為它默認用的是 linux 的位置,所以我們如果想要修改,也可以將 dataDir=/tmp/zookeeper
修改為 dataDir=../data
,就是在根目錄下創建了一個 data 文件夾,也可以自己指定,也可以不管這個
C:運行服務端及客戶端
都解決好了,來運行 zkServer.cmd,就啟動成功了
運行zkCli.cmd,可以成功連接到 zookeeper的伺服器
服務端客戶端都跑起來了,搭建zookeeper註冊中心環境到這裡就算 OK了
(2) 配置 zookeeper監控中心
dubbo本身並不是一個服務軟體。它其實就是一個jar包能夠幫你的java程式連接到zookeeper,並利用zookeeper消費、提供服務。所以你不用在Linux上啟動什麼dubbo服務
但是為了讓用戶更好的管理監控眾多的dubbo服務,官方提供了一個可視化的監控程式——dubbo-admin,不過這個監控即使不裝也不影響使用
A:下載 dubbo-admin
下載地址://github.com/apache/dubbo-admin/tree/master
從 github 上 down 到本地 ,有 dubbo-admin-master 一個文件夾,其中含有三個主要文件夾
- dubbo-admin
- dubbo-monitor-simple
- dubbo-registry-simple
B:先確認配置正確
我們現在先只針對 dubbo-admin 操作
打開編輯配置文件:XXX\dubbo-admin-master\dubbo-admin\src\main\resources\application.properties
確認其中的配置,比如埠,密碼什麼等等都是正常的,例如服務埠7001,密碼 root,最關鍵的就是地址,因為我們測試是在本機,所以地址如下就可以了
dubbo.registry.address=zookeeper://127.0.0.1:2181
C:打包運行
既然沒問題了,就在 xx\dubbo-admin-master\dubbo-admin,這個文件夾下進行打包,用 cmd 也都一樣,命令如下:
mvn clean package
打包成功後,target 文件夾中會多出一個 dubbo-admin-0.0.1-SNAPSHOT.jar
,我們把它複製到一個自己指定的位置,然後通過 cmd 運行這個 jar
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
其實就是運行了一個 springboot 的項目,然後控制台會有寫到是一個 7001 埠,來去訪問一下,用戶名,密碼都是 root,出現如下所圖就是成功了
注意:要想訪問到這個頁面,請保證 zookeeper 註冊中心啟動了
(四) 第一個 Dubbo 項目
我們第一個例子就照著這個網路上參考的例子來做(程式碼並不重要,關鍵是理解調用關係和配置方式):
解釋一下,訂單生成前的時候,需要調用用戶服務,查詢到用戶所有的收貨地址,用戶選定地址後,再生成訂單,如果訂單服務和用戶服務分屬於不同的伺服器 A 和 B,而我們要解決的就是如何在 A 的訂單服務中,調用到 B 的用戶服務
順便提一句,很清楚的可以看出來,用戶服務是被調用的,所有它就是服務的提供者,而訂單服務作為消費者
(1) 創建項目及各個模組
為了在一台機器上演示,我首先創建了一個 Maven 的空項目,然後分別創建幾個子模組(程式很簡單,不需要Maven骨架)
A:創建用戶服務(服務提供者)
在空項目中創建第一個模組 user-service-provider
首先創建出實體和業務層介面以及實現類
實體:UserAddress
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; //用戶地址
private String userId; //用戶id
private String consignee; //收貨人
private String phoneNum; //電話號碼
private String isDefault; //是否為默認地址 Y-是 N-否
// 請自行補充 get set 構造方法 toString
}
業務層介面:UserService
public interface UserService {
/**
* 按照用戶id返回所有的收貨地址
* @param userId
* @return
*/
public List<UserAddress> getUserAddressList(String userId);
}
業務層實現類:UserServiceImpl
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "廣東省xxx市xxx區xxx路xxx小區24棟1單元801戶", "1", "阿文", "13999999999", "Y");
UserAddress address2 = new UserAddress(2, "北京市yyy區yyy路yyy小區19號3單元502戶", "1", "北方少女的夢", "13888888888", "N");
return Arrays.asList(address1,address2);
}
}
程式很簡單,數據都是擬出來的
以下是這個模組的結構圖:
B:創建訂單服務(服務消費者)
在項目中創建第二個模組 order-service-consumer
業務層介面:OrderService
public interface OrderService {
/**
* 初始化訂單
* @param userID
*/
public void initOrder(String userID);
}
業務層實現類:OrderServiceImpl
現在還是一個空實現,後面會補充好
public class OrderServiceImpl implements OrderService {
public void initOrder(String userID) {
//查詢用戶的收貨地址
}
}
如果我們想要在實現類中,調用用戶服務,拿到所有地址,但是調用方法肯定會報錯的,畢竟我們這個項目中並沒有拿到用戶地址的方法
有一種方式,就是將服務提供者,也就是用戶服務的實體類以及它的 UserService 介面,複製到我們這個訂單的模組中,但是總不能以後有一個地方調用就複製一次吧,這也太麻煩了
所以通用的做法是再創建一個模組,將服務介面,模型等等全放在一起,維護以及調用會更好
C:創建介面模組
在項目中創建:mall-interface 模組
將用戶模組(服務提供者)和訂單模組(服務消費者) 中的所有實體類和service介面複製到當前模組下
刪除原有的實體類包及service包,也就是將實體類及service放在了當前公共的項目中了
既然原來兩個模組都刪掉了實體等內容,我們想要用到這個公共的介面模組,只需要引入依賴即可
<dependency>
<groupId>cn.ideal.mall</groupId>
<artifactId>mall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
(2) 服務提供者配置及測試
A:引入依賴
我們先對用戶服務進行配置,首先要引入dubbo和zookeeper客戶端的依賴
至於版本,可以去maven中去查
由於我們使用 zookeeper 作為註冊中心,所以需要操作 zookeeper
dubbo 2.6 以前的版本引入 zkclient 操作 zookeeper
dubbo 2.6 及以後的版本引入 curator 操作 zookeeper
下面兩個 zk 客戶端根據 dubbo 版本 2 選 1 即可
Dubbo 用了 2.6.9
curator 用了 5.1.0
B:創建配置文件
在 resource 文件中創建 provider.xml
注釋中都寫的很清楚了,修改為自己的配置就好了
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="//code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd
//dubbo.apache.org/schema/dubbo //dubbo.apache.org/schema/dubbo/dubbo.xsd
//code.alibabatech.com/schema/dubbo //code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--1、指定當前服務/應用的名字(同樣的服務名字相同,不要和別的服務同名)-->
<dubbo:application name="user-service-provider"></dubbo:application>
<!--2、指定註冊中心的位置-->
<!--<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
<!--3、指定通訊規則(通訊協議? 服務埠)-->
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
<!--4、暴露服務 讓別人調用 ref 指向服務的真正實現對象,通過ref引用下面自定義的 bean-->
<dubbo:service interface="cn.ideal.mall.service.UserService" ref="userServiceImpl"></dubbo:service>
<!--服務的實現-->
<bean id="userServiceImpl" class="cn.ideal.mall.service.impl.UserServiceImpl"></bean>
</beans>
C:創建一個啟動類
public class MailApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:provider.xml");
applicationContext.start();
System.in.read();
}
}
如果除了 slf4j 以外沒有報出什麼警告或者異常,就是成功了
-
別忘了打開,zookeeper註冊中心的 zkServer.cmd、和zkCli.cmd服務
-
還有運行 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
然後繼續訪問 dubbo-admin 的管理頁面 //localhost:7001/
,在服務治理的提供者中,已經可以看到發現了這個提供者
(3) 服務消費者配置及測試
A:引入依賴
<!--dubbo-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.9</version>
</dependency>
<!--註冊中心是 zookeeper,引入zookeeper客戶端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.1.0</version>
</dependency>
B:創建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="//dubbo.apache.org/schema/dubbo"
xmlns:context="//www.springframework.org/schema/context"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd
//www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context-4.3.xsd
//dubbo.apache.org/schema/dubbo //dubbo.apache.org/schema/dubbo/dubbo.xsd
//code.alibabatech.com/schema/dubbo //code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--包掃描-->
<context:component-scan base-package="cn.ideal.mall.service.impl"/>
<!--指定當前服務/應用的名字(同樣的服務名字相同,不要和別的服務同名)-->
<dubbo:application name="order-service-consumer"></dubbo:application>
<!--指定註冊中心的位置-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
<!--調用遠程暴露的服務,生成遠程服務代理-->
<dubbo:reference interface="cn.ideal.mall.service.UserService" id="userService"></dubbo:reference>
<!--dubbo-monitor-simple監控中心發現的配置-->
<dubbo:monitor protocol="registry"></dubbo:monitor>
<!--<dubbo:monitor address="127.0.0.1:7070"></dubbo:monitor>-->
</beans>
C:補充訂單業務實現類程式碼
這個實現類,剛才還空著,經過引入介面依賴,Dubbo 等依賴以及配置,已經可以調用了,調用一下這個方法,當然下面的輸出語句完全可以不寫,就是為了一個觀察方便
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
public UserService userService;
public void initOrder(String userID) {
//查詢用戶的收貨地址
List<UserAddress> userAddressList = userService.getUserAddressList(userID);
//為了直觀的看到得到的數據,以下內容也可不寫
System.out.println("當前接收到的userId=> "+userID);
System.out.println("**********");
System.out.println("查詢到的所有地址為:");
for (UserAddress userAddress : userAddressList) {
//列印遠程服務地址的資訊
System.out.println(userAddress.getUserAddress());
}
}
}
C:創建啟動類
public class ConsumerApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = applicationContext.getBean(OrderService.class);
//調用方法查詢出數據
orderService.initOrder("1");
System.out.println("調用完成...");
System.in.read();
}
}
運行後,繼續去 查看 //localhost:7001/
可以看到,消費者也被發現了,同時控制台也成功的輸出了內容
到這裡,其實一個簡單的調用過程已經完成了
(4) 安裝簡易的監控中心
dubbo-monitor-simple——簡易監控中心
其實這個東西就是剛才 dubbo-admin-master 這個文件夾下除了dubbo-admin 的其中一個,本質也是一個圖形化的介面,方便查看服務提供和消費者的資訊
首先還是打包,然後 target 文件夾中會生成這個文件 dubbo-monitor-simple-2.0.0.jar
以及 dubbo-monitor-simple-2.0.0-assembly.tar.gz
將 dubbo-monitor-simple-2.0.0-assembly.tar.gz 解壓出來,解壓後config文件查看properties的配置是否是本地的zookeeper,配置文件的位置如下:
D:\develop\dubbo-monitor-simple-2.0.0\conf\dubbo.properties
- D:\develop 是我的路徑
因為前面可能運行的問題,我後面有一些埠佔用的問題,所以我把 dubbo.jetty.port=8080 修改成了 8081,這些可以根據需要自行修改
進入 assembly.bin
文件夾,然後雙擊運行 start.bat
出現以下內容即啟動成功
在服務者和消費者的 XML 文件中添加以下內容
<!--dubbo-monitor-simple監控中心發現的配置-->
<dubbo:monitor protocol="registry"></dubbo:monitor>
<!--<dubbo:monitor address="127.0.0.1:7070"></dubbo:monitor>-->
然後啟動這兩個模組的啟動類
註:這時候要保證 zookeeper 服務客戶端等前面的內容保持開著
訪問localhost:8081,可以看到一個監控中心
點進去 Services 可以看到服務提供者和消費者的資訊
到這裡,這個監控中心也算安裝好了!!!
(五) SpringBoot 整合 Dubbo
上面這個項目就是一個普通的 maven 項目,通過 XML 配置文件進行配置,在 SSM 項目中就可以這樣使用,而 SpringBoot 作為現在開發的主力軍之一,自然也要再講一下它的一個配置運行方式
(1) 創建用戶服務(服務提供者)
創建一個 SpringBoot 項目,名為:boot-user-service-provider
導入依賴
根據其 github 中的提示,因為我們選擇的是 2.6.9 所以我們選擇 0.2.1.RELEASE 這個版本就行了
//github.com/apache/dubbo-spring-boot-project/blob/master/README_CN.md
//github.com/apache/dubbo-spring-boot-project/blob/0.2.x/README_CN.md
點進去查看 0.2.1.RELEASE,根據其提示導入依賴
註:0.2.0 的版本中,導入 dubbo-spring-boot-starter 即同步背後幫你導入了 dubbo,curator等等,但是我拿 0.2.1 的版本測試的時候卻發現並沒有(可能是我的問題),所以你也不能運行,可以考慮像我一樣,顯式的引入這些內容
記得別忘了引入這個我們自定義公共的介面模組喔
<dependency>
<groupId>cn.ideal.mall</groupId>
<artifactId>mall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.9</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!--註冊中心是 zookeeper,引入zookeeper客戶端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.1.0</version>
</dependency>
在我們剛才的 user-service-provider
中將 service 的實現類按路徑複製過來
注意:這個 @Service
是 dubbo 的,而 @Component
是因為,如果仍使用 Spring 的 @Service
會使得 dubbo 的那個以全稱顯示,不是很好看,不過你非要的話,也可以哈
package cn.ideal.mall.service.impl;
import cn.ideal.mall.pojo.UserAddress;
import cn.ideal.mall.service.UserService;
import com.alibaba.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* @ClassName: UserServiceImpl
* @Author: BWH_Steven
* @Date: 2020/12/2 20:58
* @Version: 1.0
*/
@Service
@Component
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "廣東省xxx市xxx區xxx路xxx小區24棟1單元801戶", "1", "阿文", "13999999999", "Y");
UserAddress address2 = new UserAddress(2, "北京市yyy區yyy路yyy小區19號3單元502戶", "1", "北方少女的夢", "13888888888", "N");
return Arrays.asList(address1,address2);
}
}
配置 application.properties
dubbo.application.name=boot-user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#連接監控中心
dubbo.monitor.protocol=registry
spring.main.allow-bean-definition-overriding=true
server.port=8082
添加啟動類註解
@EnableDubbo // 開啟基於註解的dubbo功能
@SpringBootApplication
public class BootUserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceProviderApplication.class, args);
}
}
(2) 創建訂單服務(服務消費者)
創建 springboot 項目boot-order-service-consumer
,此項目應該是一個 web 項目,注意引入 web 的 starter
同樣引入一樣的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.ideal.mall</groupId>
<artifactId>mall-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.9</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!--註冊中心是 zookeeper,引入zookeeper客戶端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.1.0</version>
</dependency>
把之前 order-service-consumer
項目中的 service 實現類按路徑複製過來
有兩點注意:
- @Autowired ==> @Reference
- 因為是一個 web 項目,要在瀏覽器查看結果,所以將結果從 void 改成 List 返回回去,因此需要修改公共的介面模組中的此方法介面,將返回類型改為 List
package cn.ideal.mall.service.impl;
import cn.ideal.mall.pojo.UserAddress;
import cn.ideal.mall.service.OrderService;
import cn.ideal.mall.service.UserService;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @ClassName: OrderServiceImpl
* @Author: BWH_Steven
* @Date: 2020/12/2 22:38
* @Version: 1.0
*/
@Service
public class OrderServiceImpl implements OrderService {
@Reference
public UserService userService;
public List<UserAddress> initOrder(String userID) {
//查詢用戶的收貨地址
List<UserAddress> userAddressList = userService.getUserAddressList(userID);
//為了直觀的看到得到的數據,以下內容也可不寫
System.out.println("當前接收到的userId=> "+userID);
System.out.println("**********");
System.out.println("查詢到的所有地址為:");
for (UserAddress userAddress : userAddressList) {
//列印遠程服務地址的資訊
System.out.println(userAddress.getUserAddress());
}
return userAddressList;
}
}
編寫 controller
@Controller
public class OrderController {
@Autowired
OrderService orderService;
@RequestMapping("/initOrder")
@ResponseBody
public List<UserAddress> initOrder(@RequestParam("uid")String userId) {
return orderService.initOrder(userId);
}
}
配置 application.properties
dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
#連接監控中心 註冊中心協議
dubbo.monitor.protocol=registry
spring.main.allow-bean-definition-overriding=true
server.port=8083
啟動類添加註解
@EnableDubbo // 開啟基於註解的dubbo功能
@SpringBootApplication
public class BootOrderServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BootOrderServiceConsumerApplication.class, args);
}
}
(3) 測試
首先保證打開了,zookeeper註冊中心的 zkServer.cmd、和zkCli.cmd服務
還有運行 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar ,想開的話還可以打開監控中心 dubbo-monitor-simple-2.0.0
然後就可以運行服務提供者 boot-user-service-provider ,然後運行 服務消費者 boot-order-service-consumer ,運行成功後可以看一下效果
這是使用 //localhost:7001/
訪問的結果
或者 //localhost:8001
(我的監控中心埠設置的是 8081)
以及根據自己設置的項目埠號去請求
可以看到結果都是沒問題的,SpringBoot 整合 Dubbo 就是這樣一個方式
(4) 其他幾種方式
一 將服務提供者註冊到註冊中心(如何暴露服務)
- ① 導入Dubbo的依賴 和 zookeeper 客戶端
二 讓服務消費者去註冊中心訂閱服務提供者的服務地址
Springboot與Dubbo整合的三種方式
-
① 導入dubbo-starter。在application.properties配置屬性,使用@Service【暴露服務】,使用@Reference【引用服務】
-
② 保留Dubbo 相關的xml配置文件
- 導入dubbo-starter,使用@ImportResource導入Dubbo的xml配置文件
- 例如:在啟動類添加 @ImportResource(locations = “classpath:provider.xml”)
-
③ 使用 註解API 的方式
- 將每一個組件手動配置到容器中,讓dubbo來掃描其他的組件
例如創建一個 config,其本質就是為了放棄掉 xml 和配置文件,這種方式在學習 Spring 配置的時候也有用過哈
@Configuration
public class MyDubboConfig {
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("boot-user-service-provider");
return applicationConfig;
}
//<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("127.0.0.1:2181");
return registryConfig;
}
//<dubbo:protocol name="dubbo" port="20882"></dubbo:protocol>
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20882);
return protocolConfig;
}
/**
*<dubbo:service interface="com.atguigu.gmall.service.UserService"
ref="userServiceImpl01" timeout="1000" version="1.0.0">
<dubbo:method name="getUserAddressList" timeout="1000"></dubbo:method>
</dubbo:service>
*/
@Bean
public ServiceConfig<UserService> userServiceConfig(UserService userService){
ServiceConfig<UserService> serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(UserService.class);
serviceConfig.setRef(userService);
serviceConfig.setVersion("1.0.0");
//配置每一個method的資訊
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("getUserAddressList");
methodConfig.setTimeout(1000);
//將method的設置關聯到service配置中
List<MethodConfig> methods = new ArrayList<>();
methods.add(methodConfig);
serviceConfig.setMethods(methods);
//ProviderConfig
//MonitorConfig
return serviceConfig;
}
}
二 Dubbo 配置
(一) 重寫與配置優先順序
//dubbo.apache.org/zh/docs/v2.7/user/configuration/properties/
優先順序從高到低:
- JVM -D 參數:當你部署或者啟動應用時,它可以輕易地重寫配置,比如,改變 dubbo 協議埠;
- XML:XML 中的當前配置會重寫 dubbo.properties 中的;
- Properties:默認配置,僅僅作用於以上兩者沒有配置時。
- 如果在 classpath 下有超過一個 dubbo.properties 文件,比如,兩個 jar 包都各自包含了 dubbo.properties,dubbo 將隨機選擇一個載入,並且列印錯誤日誌。
- 如果
id
沒有在protocol
中配置,將使用name
作為默認屬性。
(二) 啟動時檢查
Dubbo 預設會在啟動時檢查依賴的服務是否可用,不可用時候就會拋出異常,同時阻止 Spring 初始化完成,好處就是上線的時候可以及早的發現問題,註:默認 check=「true」
-
可以通過 check=「false」 關閉檢查,比如,測試時,有些服務不關心,或者出現了循環依賴,必須有一方先啟動。
-
另外,如果你的 Spring 容器是懶載入的,或者通過 API 編程延遲引用服務,請關閉 check,否則服務臨時不可用時,會拋出異常,拿到 null 引用,如果 check=「false」,總是會返回引用,當服務恢復時,能自動連上
比如在 order-service-consumer
消費者中,在 consumer.xml 中添加配置
<!--配置當前消費者的統一規則,當前所有的服務都不啟動時檢查-->
<dubbo:consumer check="false"></dubbo:consumer>
也可以在每一個上面加 check
(三) 全局超時配置
由於網路或服務端不可靠,會導致調用出現一種不確定的中間狀態(超時),為了避免超時導致客戶端資源(執行緒)掛起耗盡,必須設置超時時間
(1) Dubbo消費端
<!--全局超時配置-->
<dubbo:consumer timeout="5000" />
<!--調用遠程暴露的服務,生成遠程服務代理-->
<dubbo:reference interface="cn.ideal.mall.service.UserService" id="userService" timeout="2000">
<dubbo:method name="getUserAddressList" timeout="3000"/>
</dubbo:reference>
(2) Dubbo服務端
<!--全局超時配置-->
<dubbo:consumer timeout="5000" />
<!--調用遠程暴露的服務,生成遠程服務代理-->
<dubbo:service interface="cn.ideal.mall.service.UserService" ref="userServiceImpl" timeout="2000">
<dubbo:method name="getUserAddressList" timeout="3000"/>
</dubbo:service>
設置超時時間其實算蠻簡單的,但是最主要注意的問題就是優先順序問題,在上面不管是消費者還是服務者,我都配置了三種層次的超時配置,這幾者的優先順序別簡單總結就是:
-
① 更細,更精準的優先:1. 方法級別 <== 2. 介面級別 <== 3. 全局級別
-
② 消費者設置優先:級別一致的情況下,消費者優先於提供者
補充:
- dubbo:consumer 超時默認值為 1000
//dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-consumer/
屬性 | 對應URL參數 | 類型 | 是否必填 | 預設值 | 作用 | 描述 | 兼容性 |
---|---|---|---|---|---|---|---|
timeout | default.timeout | int | 可選 | 1000 | 性能調優 | 遠程服務調用超時時間(毫秒) |
(3) 配置原則
dubbo 推薦在 Provider 上盡量多配置 Consumer 端屬性
- 作服務的提供者,比服務使用方更清楚服務性能參數,如調用的超時時間,合理的重試次數,等等
- 在Provider配置後,Consumer不配置則會使用Provider的配置值,即Provider配置可以作為Consumer的預設值。否則,Consumer會使用Consumer端的全局設置,這對於Provider不可控的,並且往往是不合理的
(四) 多版本問題
//dubbo.apache.org/zh/docs/v2.7/user/examples/multi-versions/#m-zhdocsv27userexamplesmulti-versions
當一個介面實現,出現不兼容升級時,可以用版本號過渡,版本號不同的服務相互間不引用。
可以按照以下的步驟進行版本遷移:
- 在低壓力時間段,先升級一半提供者為新版本
- 再將所有消費者升級為新版本
- 然後將剩下的一半提供者升級為新版本
老版本服務提供者配置:
<dubbo:service interface="cn.ideal.mall.service.UserService" ref="userServiceImpl" version="1.0.0"/>
<!--服務的實現-->
<bean id="userServiceImpl" class="cn.ideal.mall.service.impl.UserServiceImpl"/>
新版本服務提供者配置:
<dubbo:service interface="cn.ideal.mall.service.UserService" ref="userServiceImpl" version="2.0.0"/>
<!--服務的實現-->
<bean id="userServiceImpl" class="cn.ideal.mall.service.impl.UserServiceImpl"/>
老版本服務消費者配置:
<dubbo:reference id="userService" interface="cn.ideal.mall.service.UserService version="1.0.0" />
新版本服務消費者配置:
<dubbo:reference id="userService" interface="cn.ideal.mall.service.UserService version="2.0.0" />
如果不需要區分版本,可以按照以下的方式配置(2.2.0 以上版本支援)
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
三 Dubbo 高可用
這一塊的講解主要是針對,在一些突發的錯誤,或者大並發下等如何保證 Dubbo 仍為高可用狀態的一些概念,以及措施
註:高可用,即通過設計,減少系統不能提供服務的時間
(一) zookeeper 宕機 和 Dubbo 直連
(1) zookeeper 宕機
zookeeper 作為註冊中心,如果部署運行著它的伺服器出問題了,出現了 zookeeper 宕機,那麼這個時候消費者豈不是找不到被暴露的服務了
但是我們主動關掉 zookeeper 的服務,在dubbo-admin 監控中可以看到服務出現了錯誤,但是我們去請求介面,可以發現仍然能請求得到結果,即,還可以消費 dubbo 暴露的服務
這一點與 Dubbo 設計時實現的健壯性有關
- 監控中心宕掉不影響使用,只是丟失部分取樣數據
- 資料庫宕掉後,註冊中心仍能通過快取提供服務列表查詢,但不能註冊新服務
- 註冊中心對等集群,任意一台宕掉後,將自動切換到另一台
- 註冊中心全部宕掉後,服務提供者和服務消費者仍能通過本地快取通訊
- 服務提供者無狀態,任意一台宕掉後,不影響使用
- 服務提供者全部宕掉後,服務消費者應用將無法使用,並無限次重連等待服務提供者恢復
高可用:通過設計,減少系統不能提供服務的時間
//dubbo.apache.org/zh/docs/v2.7/dev/principals/robustness/#m-zhdocsv27devprincipalsrobustness
Dubbo 的服務註冊中心
目前服務註冊中心使用了資料庫來保存服務提供者和消費者的資訊。註冊中心集群不同註冊中心也通過資料庫來進行同步數據,以感知其它註冊中心上提供者的變化。註冊中心會在記憶體中保存一份提供者和消費者數據,資料庫不可用時,註冊中心獨立對外提供服務以保證正常運轉,只是拿不到其它註冊中心的數據。當資料庫恢復時,重試邏輯會將記憶體中修改的數據寫回資料庫,並拿到資料庫中新數據。
服務的消費者
服務消費者從註冊中心拿到提供者列表後,會保存提供者列表到記憶體和磁碟文件中。這樣註冊中心宕機後消費者可以正常運轉,甚至可以在註冊中心宕機過程中重啟消費者。消費者啟動時,發現註冊中心不可用,會讀取保存在磁碟文件中提供者列表。重試邏輯保證註冊中心恢復後,更新資訊。
(2) Dubbo 直連
如果註冊中心現在有點問題,或者有意的不想訪問註冊中心上的服務,而是想要直接在本地上調試 dubbo 介面,也可以使用 Dubbo 直連
- 第一種:通過 @Reference 的 url 屬性(Springboot)
@Reference(url = "127.0.0.1:20081")
public UserService userService;
- 第二種:在 consumer.xml 中指定 url (SSM)
<dubbo:reference interface="cn.ideal.mall.service.UserService" id="userService" url="127.0.0.1:20081">
-
第三種:添加映射配置文件
- 如果不想要修改工程內容,可以考慮使用這個方式
在本地電腦用戶下新建一個叫 dubbo-resolve.properties 的文件路徑是${user.home}/dubbo-resolve.properties
然後就不需要修改本地工程的其他配置資訊,在文件里配置好需要直連的服務資訊即可
# 直連本地的服務
cn.ideal.mall.service.UserService=dubbo://localhost:20890
(二) 集群下 Dubbo 的負載均衡策略
這一塊,我們只對幾種負載均衡策略做一些說明和解釋,具體的演算法實現,不是幾句話能說的清楚地,需要再深入的去學習以及研究,以下是官網關於負載均衡詳細的說明
//dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance
(1) 什麼是負載均衡
LoadBalance 中文意思為負載均衡,它的職責是將網路請求,或者其他形式的負載「均攤」到不同的機器上。避免集群中部分伺服器壓力過大,而另一些伺服器比較空閑的情況。
通過負載均衡,可以讓每台伺服器獲取到適合自己處理能力的負載。在為高負載伺服器分流的同時,還可以避免資源浪費,一舉兩得。
負載均衡可分為軟體負載均衡和硬體負載均衡。在我們日常開發中,一般很難接觸到硬體負載均衡。但軟體負載均衡還是可以接觸到的,比如 Nginx。
在 Dubbo 中,也有負載均衡的概念和相應的實現。Dubbo 需要對服務消費者的調用請求進行分配,避免少數服務提供者負載過大。服務提供者負載過大,會導致部分請求超時。因此將負載均衡到每個服務提供者上,是非常必要的。
Dubbo 提供了4種負載均衡實現,分別是
- 基於權重隨機演算法的 Random LoadBalance
- 以及基於加權輪詢演算法的 RoundRobin LoadBalance
- 基於最少活躍調用數演算法的 LeastActive LoadBalance
- 基於 hash 一致性的 ConsistentHash LoadBalance
(2) 四種負載均衡策略
A: Random LoadBalance
基於權重隨機演算法的 RandomLoadBalance
隨機演算法,按權重設置隨機概率
在一個截面上碰撞的概率高,但調用量越大分布越均勻,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重
根據權重的這個比重來決定究竟用哪個
註:weight 代表權重,即在所有份數中所佔的比例
B: RoundRobin LoadBalance
RoundRobin LoadBalance 基於權重的輪詢負載均衡機制
如果只考慮輪輪詢的意思就是,比如訪問 1 –> 2 –> 3,再一輪又是 1 –> 2 –> 3,但是如果還要基於權重,是這樣的,比如第一次是 1 –> 2 –> 3 的順序,然後第二輪 1 –> 2 ,當應該到 3 時候,按照這個權重比例,總共看成 7 份機會,1 和 2 調用了兩次了,各自佔據了 2份,但是 3 服務應該只能佔據 1份,所以只能跳過3了,再第三輪,1 已經兩份了,所以也不應該用了,所以考慮取去 2,所以這幾輪的順序就是 1 –> 2 –> 3 –>1 –> 2 –> 2 –> 2
缺點:存在慢的提供者累積請求的問題,比如:第二台機器很慢,但沒掛,當請求調到第二台時就卡在那,久而久之,所有請求都卡在調到第二台上
C:LeastActiveLoad Balance
LeastActive LoadBalance最少活躍數負載均衡機制
活躍數指調用前後計數差,計算活躍數使慢的提供者收到更少請求,因為越慢的提供者的調用前後計數差會越大
註:如果活躍數相同,就隨機
D:ConsistentHash LoadBalance
ConsistentHash LoadBalance一致性hash 負載均衡機制
這個演算法會對,方法調用的第一個參數進行 Hash,例如就是對上面的 param 參數後面的 1、2、3 進行哈希,一致性 Hash,相同參數的請求總是發到同一提供者
好處就是,當某一台提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動
(三) 整合hystrix,服務熔斷與降級處理
(1) 服務降級
當伺服器壓力劇增的情況下,根據實際業務情況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放伺服器資源以保證核心交易正常運作或高效運作
可以通過服務降級功能臨時屏蔽某個出錯的非關鍵服務,並定義降級後的返回策略
向註冊中心寫入動態配置覆蓋規則:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
其中:
mock=force:return+null
表示消費方對該服務的方法調用都直接返回 null 值,不發起遠程調用。用來屏蔽不重要服務不可用時對調用方的影響- 還可以改為
mock=fail:return+null
表示消費方對該服務的方法調用在失敗後,再返回 null 值,不拋異常。用來容忍不重要服務不穩定時對調用方的影響
(2) 集群容錯
在集群調用失敗時,Dubbo 提供了多種容錯方案,預設為 failover 重試
A:集群容錯模式
Failover Cluster
失敗自動切換,當出現失敗,重試其它伺服器。通常用於讀操作,但重試會帶來更長延遲。可通過 retries="2"
來設置重試次數(不含第一次)。
重試次數配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
Failfast Cluster
快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
Failsafe Cluster
失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。
Failback Cluster
失敗自動恢復,後台記錄失敗請求,定時重發。通常用於消息通知操作。
Forking Cluster
並行調用多個伺服器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2"
來設置最大並行數。
Broadcast Cluster
廣播調用所有提供者,逐個調用,任意一台報錯則報錯。通常用於通知所有提供者更新快取或日誌等本地資源資訊。
B:集群模式配置(2.1.0 開始支援)
按照以下示例在服務提供方和消費方配置集群模式,預設為 failover 重試
<dubbo:service cluster="模式名" />
或
<dubbo:reference cluster="模式名" />
(四) 整合 Hystrix
在微服務架構中存在多個可直接調用的服務,這些服務若在調用時出現故障會導致連鎖效應,也就是可能會讓整個系統變得不可用,這種情況我們稱之為服務雪崩效應,在這種時候,就需要我們的熔斷機制來挽救整個系統
在微服務架構下,很多服務都相互依賴,如果不能對依賴的服務進行隔離,那麼服務本身也有可能發生故障, Hystrix通過Hystrix Command對調用進行隔離, 這樣可以阻止故障的連鎖反應,能夠讓介面調用快速失敗並迅速恢復正常,或者回退並優雅降級
關於 Hystrix ,會在 SpringCloud 的學習文章整理中進行介紹,這裡只做一個簡單的使用
首先引入依賴
注意,注意!如果擬引入的 hystrix 版本相對較新,或許會報錯
Error creating bean with name ‘configurationPropertiesBeans…….
一種情況就是你的 springboot 版本太新了,需要降低一下,spring 官網可以看到 現在的 cloud 對應能支援到哪個版本的 boot
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
啟動類添加 @EnableHystrix 註解
@EnableDubbo // 開啟基於註解的dubbo功能
@EnableHystrix //開啟服務容錯功能
@SpringBootApplication
public class BootUserServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootUserServiceProviderApplication.class, args);
}
}
在提供者中添加註解
可以直接使用 @HystrixCommand,後面都是一些屬性設置,下面的隨機數判斷是為了模擬異常
@Service
@Component
public class UserServiceImpl implements UserService {
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
// @HystrixCommand
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "廣東省xxx市xxx區xxx路xxx小區24棟1單元801戶", "1", "阿文", "13999999999", "Y");
UserAddress address2 = new UserAddress(2, "北京市yyy區yyy路yyy小區19號3單元502戶", "1", "北方少女的夢", "13888888888", "N");
if (Math.random() > 0.5){
throw new RuntimeException();
}
return Arrays.asList(address1,address2);
}
}
在消費者中添加註解
method上配置@HystrixCommand。當調用出錯時,會走 fallbackMethod = “testError”
@Service
public class OrderServiceImpl implements OrderService {
@Reference
public UserService userService;
@HystrixCommand(fallbackMethod = "testError")
public List<UserAddress> initOrder(String userID) {
//查詢用戶的收貨地址
List<UserAddress> userAddressList = userService.getUserAddressList(userID);
//為了直觀的看到得到的數據,以下內容也可不寫
System.out.println("當前接收到的userId=> "+userID);
System.out.println("**********");
System.out.println("查詢到的所有地址為:");
for (UserAddress userAddress : userAddressList) {
//列印遠程服務地址的資訊
System.out.println(userAddress.getUserAddress());
}
return userAddressList;
}
public List<UserAddress> testError(){
return Arrays.asList(new UserAddress(10,"錯誤測試地址",
"1","測試BWH","15555555555","N"));
}
}