1. 程式人生 > >【萬字長文】Dubbo 入門總結 ,一款高效能的 Java RPC 框架

【萬字長文】Dubbo 入門總結 ,一款高效能的 Java RPC 框架

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98b4ac71243740659266705e6e7135a5~tplv-k3u1fbpfcp-zoom-1.image) 這篇文章是我學習整理 Dubbo 的一篇文章,首先大部分內容參考了官網 + 某矽谷的視訊,內容講解進行了重新編排,40多張圖片,也都是我修改重製的,雖然一萬多字,但是其實也可以看出來,更多的內容集中在了概念或功能的介紹,具體環境的搭建,以及如何配置,快速上手上面,但是對於這樣一款優秀的框架,文章中提到的每一個點其實展開來講都能寫這樣篇幅的一篇文章,僅僅入門來看也沒必要,總得學會走,才可以去試著跑 - 文章提及了 SSM 和 Springboot 下的不同配置方式,但是 Zookeeper 環境搭在了 Windows 下,後面準備重新整理的 Redis 我考慮在 Linux 中來跑,會更貼近實際一些 - 說句題外話,設計模式也沒停,還有一篇存稿,不過想整理的東西太多,還有學校等等的事情,真有點自閉了 - 有什麼建議和意見歡迎在評論區和我交流哈,感謝各位大佬的支援~ 早安,學習人 & 打工人 ~ ~ ~ # 一 必備基礎知識 ## (一) 分散式基礎理論 在百度以及維基中的定義都相對專業且晦澀,大部分部落格或者教程經常會使用《分散式系統原理和範型》中的定義,即:**“分散式系統是若干獨立計算機的集合,這些計算機對於使用者來說就像是單個相關係統”** 下面我們用一些篇幅來通俗的解釋一下什麼叫做分散式 ### (1) 什麼是集中式系統 提到分散式,不得不提的就是 **“集中式系統”**,這個概念最好理解了,它就是將功能,程式等安裝在同一臺裝置上,就由這一臺主機裝置向外提供服務 舉個最簡單的例子:你拿一臺PC主機,將其改裝成了一臺簡單的伺服器,配置好各種內容後,你將MySQL,Web伺服器,FTP,Nginx 等等,全部安裝在其中,打包部署專案後,就可以對外提供服務了,但是一旦這臺機器無論是軟體還是硬體出現了問題,整個系統都會受到嚴重的牽連錯誤,雞蛋放在一個籃子裡,要打就全打了 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f63e1f1c70754bbdb2936284c8276b67~tplv-k3u1fbpfcp-zoom-1.image) ### (2) 什麼是分散式系統 既然集中式系統有這樣一種牽一髮而動全身的問題,那麼分散式的其中一個作用,自然是來解決這樣的問題了,正如定義中所知,分散式系統在使用者的體驗感官裡,就像傳統的單系統一樣,一些變化都是這個系統本身內部進行的,對於使用者並沒有什麼太大的感覺 例如:淘寶,京東這種大型電商平臺,它們的主機都是數以萬計的,否則根本沒法處理大量的資料和請求,具體其中有什麼劃分,以及操作,我們下面會說到,但是對於使用者的我們,我們不需要也不想關心這些,我們仍可以單純的認為,我們面對的就是 “淘寶” 這一臺 “主機” 所以分散式的一個相對專業一些的說法是這樣的(程序粒度)**兩個或者多個程式,分別執行在不同的主機程序上,它們互相配合協調,完成共同的功能,那麼這幾個程式之間構成的系統就可以叫做分散式系統** - 這幾者都是相同的程式 —— 分散式 - 這幾者都是不同的程式 —— 叢集 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/037e36280c074183ada2d709c059ec43~tplv-k3u1fbpfcp-zoom-1.image) ### (3) 發展過程 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c5271f70fb9b45aba51b3157cfc34551~tplv-k3u1fbpfcp-zoom-1.image) #### A:單一應用架構 當網站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節點和成本。此時,用於簡化增刪改查工作量的資料訪問框架(ORM)是關鍵 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e0db57f10eca4325bc65f4f95f1f95e2~tplv-k3u1fbpfcp-zoom-1.image) **優點:** - 所有功能集中在一起,開發以及後期維護均相對簡單 - 對於小型網站及管理系統,簡單易用 **缺點:** - 對於大型專案,升級維護困難 - 無法對於一個模組進行水平擴充套件或優化 - 模組之間耦合緊密,容錯率低 #### B:垂直應用架構 當訪問量逐漸增大,單一應用增加機器帶來的加速度越來越小,提升效率的方法之一是將應用拆成互不相干的幾個應用,以提升效率。此時,用於加速前端頁面開發的Web框架(MVC)是關鍵 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e477a1d9a6674ea9954336eb441abdb5~tplv-k3u1fbpfcp-zoom-1.image) **優點:** - 通過切分業務達到各模組獨立部署,維護和部署更加簡單 - 可以對一個模組進行水平擴充套件或優化 **缺點:** - 系統之間無法相互呼叫 - 具有重複程式碼,且公用模組無法重複利用 #### C:分散式服務架構 當垂直應用越來越多,應用之間互動不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提高業務複用及整合的分散式服務框架(RPC)是關鍵 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4c782df7d7e04da6ad335ce3e3f27cf3~tplv-k3u1fbpfcp-zoom-1.image) **優點:** - 抽取公共程式碼,程式碼複用性提高 **缺點:** - 呼叫關係複雜,維護很麻煩 #### D:流動計算架構 當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增加一個排程中心基於訪問壓力實時管理叢集容量,提高叢集利用率。此時,用於提高機器利用率的資源排程和治理中心(SOA)是關鍵 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b340aff886d047df8b705da72f6be03b~tplv-k3u1fbpfcp-zoom-1.image) **優點:** - 複雜的呼叫關係不再需要自行維護 **缺點:** - 服務具有依賴性,一個服務出現問題或許會導致其他系統不可用 ### (4) RPC 介紹 **RPC(Remote Procedure Call)是指遠端過程呼叫**,是一種程序間通訊方式,他是一種技術的思想,而不是規範。**它允許程式呼叫另一個地址空間(通常是共享網路的另一臺機器上)的過程或函式**,而不用程式設計師顯式編碼這個遠端呼叫的細節 這應該是很好理解的,呼叫本地 A 伺服器上的函式或者方法的時候很簡單,但是如果A 想要訪問 B 的方法時,兩者的記憶體空間壓根都不是同一個,只能通過網路傳輸呼叫的相關內容,關於傳輸協議亦或者傳輸方式,都由 RPC 幫我們背後實現 也就是說,即程式設計師無論是呼叫本地的還是遠端的函式,本質上編寫的呼叫程式碼基本相同 關於 RPC 更加細緻專業的解釋說明 > RPC是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通訊程式之間攜帶資訊資料。在OSI網路通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網路分散式多程式在內的應用程式更加容易 > RPC採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。首先,客戶機呼叫程序傳送一個有程序引數的呼叫資訊到服務程序,然後等待應答資訊。在伺服器端,程序保持睡眠狀態直到呼叫資訊到達為止。當一個呼叫資訊到達,伺服器獲得程序引數,計算結果,傳送答覆資訊,然後等待下一個呼叫資訊,最後,客戶端呼叫程序接收答覆資訊,獲得程序結果,然後呼叫執行繼續進行 > [某乎:誰能用通俗的語言解釋一下什麼是 RPC 框架?](https://www.zhihu.com/question/25536695) > > **本地過程呼叫** > > RPC就是要像呼叫本地的函式一樣去調遠端函式。在研究RPC前,我們先看看本地呼叫是怎麼調的。假設我們要呼叫函式Multiply來計算lvalue * rvalue的結果: > > ......省略,此篇可細細看一下 > > 問題答者:洪春濤 來看一下從 A 想要訪問 B 中方法的一個流程例子 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5357547d26dd4c85b00d6c692db1d906~tplv-k3u1fbpfcp-zoom-1.image) 順著執行路線來捋一下 - ① 客戶端 Client 發出一個呼叫遠端服務的訊號 - ② Client Stub 接收到呼叫,把方法引數進行序列化處理 - ③ 客戶端 Client 通過 Sockets 將訊息傳送到服務端 - ④ 服務端的 Server Stub,對收到的訊息進行解碼,也就是反序列化的過程 - ⑤ 服務端 Server Stub,根據解碼結果,呼叫服務端的本地服務 - ⑥ 服務端的本地服務執行,且將結果返回給自己的 Server Stub - ⑦ Server Stub 打包結果成訊息,即序列化結果訊息 - ⑧ 服務端通過 Sockets 將訊息傳送到客戶端 - ⑨ 客戶端的 Client Stub 接收到服務端傳來的結果訊息,進行解碼處理,即反序列化 - ⑩ 客戶端得到從服務端傳來的最後結果 > 第③點說明:RPC採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85ac2d0e90dd402dbc7965cfe04ccd09~tplv-k3u1fbpfcp-zoom-1.image) ## (二) Dubbo 概念 ### (1) 介紹 Dubbo官網:`http://dubbo.apache.org/zh/` Apache Dubbo 是一款高效能、輕量級的開源 Java 服務框架 Apache Dubbo |ˈdʌbəʊ| 提供了六大核心能力:面向介面代理的高效能RPC呼叫,智慧容錯和負載均衡,服務自動註冊和發現,高度可擴充套件能力,執行期流量排程,視覺化的服務治理與運維。 **面向介面代理的高效能RPC呼叫** - 提供高效能的基於代理的遠端呼叫能力,服務以介面為粒度,為開發者遮蔽遠端呼叫底層細節。 **智慧負載均衡** - 內建多種負載均衡策略,智慧感知下游節點健康狀況,顯著減少呼叫延遲,提高系統吞吐量。 **服務自動註冊與發現** - 支援多種註冊中心服務,服務例項上下線實時感知。 **高度可擴充套件能力** - 遵循微核心+外掛的設計原則,所有核心能力如Protocol、Transport、Serialization被設計為擴充套件點,平等對待內建實現和第三方實現。 **執行期流量排程** - 內建條件、指令碼等路由策略,通過配置不同的路由規則,輕鬆實現灰度釋出,同機房優先等功能。 **視覺化的服務治理與運維** - 提供豐富服務治理、運維工具:隨時查詢服務元資料、服務健康狀態及呼叫統計,實時下發路由策略、調整配置引數。 ### (2) 架構 這是Dubbo的架構圖 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0fd65e705af248b494b9e28940ad9426~tplv-k3u1fbpfcp-zoom-1.image) 首先介紹一下這五個節點的角色(五個圓角矩形框) - `Provider`:暴露服務的**服務提供方** - `Consume`:呼叫遠端服務的**服務消費方** - `Registry`:服務註冊與發現的**註冊中心** - `Monitor`:統計服務的呼叫次數和呼叫時間的**監控中心** - `Container`:服務**執行容器** 再來看一下呼叫的關係和流程: - ① 服務容器負責啟動,載入,執行服務提供者 - ② 服務提供者在啟動時,向註冊中心註冊自己提供的服務 - ③ 註冊中心返回服務提供者地址列表給消費者,如果有變更,註冊中心將基於長連線推送變更資料給消費者 - ④ 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行呼叫,如果呼叫失敗,再選另一臺呼叫 - ⑤ 服務消費者和提供者,在記憶體中累計呼叫次數和呼叫時間,定時每分鐘傳送一次統計資料到監控中心 ## (三) dubbo 環境搭建 說明:以下為學習演示方便,均為 Windows 系統搭建,真實 Linux 場景搭建會在後面出一篇,如何在Linux安裝配置常見的軟體 ### (1) 搭建zookeeper註冊中心環境 #### A:下載 zookeeper 首先先將 zookeeper 下載下來 官網(檢視文件,下載):`https://zookeeper.apache.org/` 嫌麻煩就直接去下載頁面:`https://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` ,首先可能會遇到第一個錯誤——**找不到或無法載入主類** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60932337a70f433da2e7a2cf31edb1ad~tplv-k3u1fbpfcp-zoom-1.image) 這個時候就需要解壓剛才下載的 apache-zookeeper-3.6.2-bin.tar.gz,然後在其目錄下,複製整個lib資料夾到剛才的 apache-zookeeper-3.6.2 資料夾下,這樣就解決了 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f7dbab466c754c8d8872226bb37fb1c6~tplv-k3u1fbpfcp-zoom-1.image) 再次執行,會有第二個問題:一個配置檔案的缺失 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d71f6e0e07704374b67d8ea7b3c2a48b~tplv-k3u1fbpfcp-zoom-1.image) 這個問題只需要在 `XXX\apache-zookeeper-3.6.2\conf` 中把 zoo_sample.cfg檔案複製一份,且名字改為zoo.cfg 可以開啟編輯這個新複製過來的 zoo.cfg檔案,其中就是一些配置內容,例如埠還有一個數據的儲存地址,因為它預設用的是 linux 的位置,所以我們如果想要修改,也可以將 `dataDir=/tmp/zookeeper` 修改為 `dataDir=../data` ,就是在根目錄下建立了一個 data 資料夾,也可以自己指定,也可以不管這個 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/144f9ca87dae4a01a8b813fa4c8d8f17~tplv-k3u1fbpfcp-zoom-1.image) #### C:執行服務端及客戶端 都解決好了,來執行 zkServer.cmd,就啟動成功了 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8c156aa1d80e477ba65056d99957398d~tplv-k3u1fbpfcp-zoom-1.image) 執行zkCli.cmd,可以成功連線到 zookeeper的伺服器 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3380d86a20c54c1585aa57d30b1dc10b~tplv-k3u1fbpfcp-zoom-1.image) 服務端客戶端都跑起來了,搭建zookeeper註冊中心環境到這裡就算 OK了 ### (2) 配置 zookeeper監控中心 > dubbo本身並不是一個服務軟體。它其實就是一個jar包能夠幫你的java程式連線到zookeeper,並利用zookeeper消費、提供服務。所以你不用在Linux上啟動什麼dubbo服務 但是為了讓使用者更好的管理監控眾多的dubbo服務,官方提供了一個視覺化的監控程式——**dubbo-admin**,不過這個監控即使不裝也不影響使用 #### A:下載 **dubbo-admin** 下載地址:https://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 ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d041875420d47a3a9b851706972a70e~tplv-k3u1fbpfcp-zoom-1.image) 打包成功後,target 資料夾中會多出一個 `dubbo-admin-0.0.1-SNAPSHOT.jar` ,我們把它複製到一個自己指定的位置,然後通過 cmd 執行這個 jar ```java java -jar dubbo-admin-0.0.1-SNAPSHOT.jar ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6252208e06e243f68db03b74bd3c4175~tplv-k3u1fbpfcp-zoom-1.image) 其實就是運行了一個 springboot 的專案,然後控制檯會有寫到是一個 7001 埠,來去訪問一下,使用者名稱,密碼都是 root,出現如下所圖就是成功了 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e890800294a415e88ddcb47564bdcb2~tplv-k3u1fbpfcp-zoom-1.image) 注意:要想訪問到這個頁面,請保證 zookeeper 註冊中心啟動了 ## (四) 第一個 Dubbo 專案 我們第一個例子就照著這個網路上參考的例子來做(程式碼並不重要,關鍵是理解呼叫關係和配置方式): 解釋一下,訂單生成前的時候,需要呼叫使用者服務,查詢到使用者所有的收貨地址,使用者選定地址後,再生成訂單,如果訂單服務和使用者服務分屬於不同的伺服器 A 和 B,而我們要解決的就是如何在 A 的訂單服務中,呼叫到 B 的使用者服務 順便提一句,很清楚的可以看出來,使用者服務是被呼叫的,所有它就是服務的提供者,而訂單服務作為消費者 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f09c566e242c4afeacd099caa071d1d9~tplv-k3u1fbpfcp-zoom-1.image) ### (1) 建立專案及各個模組 為了在一臺機器上演示,我首先建立了一個 Maven 的空專案,然後分別建立幾個子模組(程式很簡單,不需要Maven骨架) #### A:建立使用者服務(服務提供者) 在空專案中建立第一個模組 `user-service-provider` 首先創建出**實體**和**業務層介面**以及**實現類** 實體:UserAddress ```java 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 ```java public interface UserService { /** * 按照使用者id返回所有的收貨地址 * @param userId * @return */ public List getUserAddressList(String userId); } ``` 業務層實現類:UserServiceImpl ```java public class UserServiceImpl implements UserService { public List 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); } } ``` 程式很簡單,資料都是擬出來的 以下是這個模組的結構圖: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64b3b43bcb8f49b8b2b93fef9f98a3d0~tplv-k3u1fbpfcp-zoom-1.image) #### B:建立訂單服務(服務消費者) 在專案中建立第二個模組 `order-service-consumer` 業務層介面:OrderService ```java public interface OrderService { /** * 初始化訂單 * @param userID */ public void initOrder(String userID); } ``` 業務層實現類:OrderServiceImpl 現在還是一個空實現,後面會補充好 ```java public class OrderServiceImpl implements OrderService { public void initOrder(String userID) { //查詢使用者的收貨地址 } } ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c53419dc595a43f7886c11e0ec696432~tplv-k3u1fbpfcp-zoom-1.image) 如果我們想要在實現類中,呼叫使用者服務,拿到所有地址,但是呼叫方法肯定會報錯的,畢竟我們這個專案中並沒有拿到使用者地址的方法 有一種方式,就是將服務提供者,也就是使用者服務的實體類以及它的 UserService 介面,複製到我們這個訂單的模組中,但是總不能以後有一個地方呼叫就複製一次吧,這也太麻煩了 所以通用的做法是再建立一個模組,將服務介面,模型等等全放在一起,維護以及呼叫會更好 #### C:建立介面模組 在專案中建立:mall-interface 模組 將使用者模組(服務提供者)和訂單模組(服務消費者) 中的所有實體類和service介面複製到當前模組下 刪除原有的實體類包及service包,也就是將實體類及service放在了當前公共的專案中了 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c15ede48faed488395824e500dfb9595~tplv-k3u1fbpfcp-zoom-1.image) 既然原來兩個模組都刪掉了實體等內容,我們想要用到這個公共的介面模組,只需要引入依賴即可 ```xml cn.ideal.mall
mall-interface 1.0-SNAPSHOT
``` ### (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 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c1d9bb76aa34dd4abeafee5b7ba3c4e~tplv-k3u1fbpfcp-zoom-1.image) curator 用了 5.1.0 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b1c05a08fdf74b9086317c6991a110dc~tplv-k3u1fbpfcp-zoom-1.image) #### B:建立配置檔案 在 resource 檔案中建立 provider.xml 註釋中都寫的很清楚了,修改為自己的配置就好了 ```xml
``` #### C:建立一個啟動類 ```java 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 的管理頁面 `http://localhost:7001/`,在服務治理的提供者中,已經可以看到發現了這個提供者 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c619c8b45fe4424b223a3d040c14a20~tplv-k3u1fbpfcp-zoom-1.image) ### (3) 服務消費者配置及測試 #### A:引入依賴 ```xml com.alibaba
dubbo 2.6.9
org.apache.curator curator-framework 5.1.0 ``` #### B:建立配置檔案 ```xml ``` #### C:補充訂單業務實現類程式碼 這個實現類,剛才還空著,經過引入介面依賴,Dubbo 等依賴以及配置,已經可以呼叫了,呼叫一下這個方法,當然下面的輸出語句完全可以不寫,就是為了一個觀察方便 ```java @Service public class OrderServiceImpl implements OrderService { @Autowired public UserService userService; public void initOrder(String userID) { //查詢使用者的收貨地址 List 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:建立啟動類 ```java 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(); } } ``` 執行後,繼續去 檢視 `http://localhost:7001/` 可以看到,消費者也被發現了,同時控制檯也成功的輸出了內容 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b402d7ea77b54f549d57cdae06dc4d66~tplv-k3u1fbpfcp-zoom-1.image) 到這裡,其實一個簡單的呼叫過程已經完成了 ### (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` 出現以下內容即啟動成功 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e6a8ef7f60a4d178e41da1312ee1a59~tplv-k3u1fbpfcp-zoom-1.image) 在服務者和消費者的 XML 檔案中新增以下內容 ```xml ``` 然後啟動這兩個模組的啟動類 注:這時候要保證 zookeeper 服務客戶端等前面的內容保持開著 訪問localhost:8081,可以看到一個監控中心 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5f0c1c32bc854c838b25d78d31042341~tplv-k3u1fbpfcp-zoom-1.image) 點進去 Services 可以看到服務提供者和消費者的資訊 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/713401f2c17943d4bfb9e367213fd5b5~tplv-k3u1fbpfcp-zoom-1.image) 到這裡,這個監控中心也算安裝好了!!! ## (五) SpringBoot 整合 Dubbo 上面這個專案就是一個普通的 maven 專案,通過 XML 配置檔案進行配置,在 SSM 專案中就可以這樣使用,而 SpringBoot 作為現在開發的主力軍之一,自然也要再講一下它的一個配置執行方式 ### (1) 建立使用者服務(服務提供者) 建立一個 SpringBoot 專案,名為:boot-user-service-provider 匯入依賴 根據其 github 中的提示,因為我們選擇的是 2.6.9 所以我們選擇 0.2.1.RELEASE 這個版本就行了 https://github.com/apache/dubbo-spring-boot-project/blob/master/README_CN.md ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ea4f32511a2a479cb579a8023dc9a470~tplv-k3u1fbpfcp-zoom-1.image) https://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 的版本測試的時候卻發現並沒有(可能是我的問題),所以你也不能執行,可以考慮像我一樣,顯式的引入這些內容 記得別忘了引入這個我們自定義公共的介面模組喔 ```xml cn.ideal.mall mall-interface 1.0-SNAPSHOT com.alibaba.boot dubbo-spring-boot-starter 0.2.1.RELEASE com.alibaba dubbo 2.6.9 io.netty netty-all org.apache.curator curator-framework 5.1.0 ``` 在我們剛才的 `user-service-provider` 中將 service 的實現類按路徑複製過來 注意:這個 `@Service` 是 dubbo 的,而 `@Component` 是因為,如果仍使用 Spring 的 `@Service` 會使得 dubbo 的那個以全稱顯示,不是很好看,不過你非要的話,也可以哈 ```java 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 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` ```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 ``` 新增啟動類註解 ```java @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 同樣引入一樣的依賴 ```xml org.springframework.boot spring-boot-starter-web cn.ideal.mall mall-interface 1.0-SNAPSHOT com.alibaba.boot dubbo-spring-boot-starter 0.2.1.RELEASE com.alibaba dubbo 2.6.9 io.netty netty-all org.apache.curator curator-framework 5.1.0 ``` 把之前 `order-service-consumer` 專案中的 service 實現類按路徑複製過來 有兩點注意: - @Autowired ==> @Reference - 因為是一個 web 專案,要在瀏覽器檢視結果,所以將結果從 void 改成 List 返回回去,因此需要修改公共的介面模組中的此方法介面,將返回型別改為 List ```java 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 initOrder(String userID) { //查詢使用者的收貨地址 List 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 ```java @Controller public class OrderController { @Autowired OrderService orderService; @RequestMapping("/initOrder") @ResponseBody public List initOrder(@RequestParam("uid")String userId) { return orderService.initOrder(userId); } } ``` 配置 `application.properties` ```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 ``` 啟動類添加註解 ```java @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 ,執行成功後可以看一下效果 這是使用 `http://localhost:7001/` 訪問的結果 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7e18544274e34e06a5c02b16c318b15d~tplv-k3u1fbpfcp-zoom-1.image) 或者 `http://localhost:8001` (我的監控中心埠設定的是 8081) ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/744eb64b2e514d099cc01f327435ce17~tplv-k3u1fbpfcp-zoom-1.image) 以及根據自己設定的專案埠號去請求 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b84ea1eca3a64af08c2ead99f9ff7bc6~tplv-k3u1fbpfcp-zoom-1.image) 可以看到結果都是沒問題的,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 配置的時候也有用過哈 ```java @Configuration public class MyDubboConfig { @Bean public ApplicationConfig applicationConfig() { ApplicationConfig applicationConfig = new ApplicationConfig(); applicationConfig.setName("boot-user-service-provider"); return applicationConfig; } // @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setProtocol("zookeeper"); registryConfig.setAddress("127.0.0.1:2181"); return registryConfig; } // @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20882); return protocolConfig; } /** * */ @Bean public ServiceConfig userServiceConfig(UserService userService){ ServiceConfig 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 methods = new ArrayList<>(); methods.add(methodConfig); serviceConfig.setMethods(methods); //ProviderConfig //MonitorConfig return serviceConfig; } } ``` # 二 Dubbo 配置 ## (一) 重寫與配置優先順序 http://dubbo.apache.org/zh/docs/v2.7/user/configuration/properties/ ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a8e13d13be4d4a958d43efd2d40d6016~tplv-k3u1fbpfcp-zoom-1.image) 優先順序從高到低: - JVM -D 引數:當你部署或者啟動應用時,它可以輕易地重寫配置,比如,改變 dubbo 協議埠; - XML:XML 中的當前配置會重寫 dubbo.properties 中的; - Properties:預設配置,僅僅作用於以上兩者沒有配置時。 1. 如果在 classpath 下有超過一個 dubbo.properties 檔案,比如,兩個 jar 包都各自包含了 dubbo.properties,dubbo 將隨機選擇一個載入,並且列印錯誤日誌。 2. 如果 `id` 沒有在 `protocol` 中配置,將使用 `name` 作為預設屬性。 ## (二) 啟動時檢查 Dubbo 預設會在啟動時檢查依賴的服務是否可用,不可用時候就會丟擲異常,同時阻止 Spring 初始化完成,好處就是上線的時候可以及早的發現問題,注:預設 check=“true” - 可以通過 check=“false” 關閉檢查,比如,測試時,有些服務不關心,或者出現了迴圈依賴,必須有一方先啟動。 - 另外,如果你的 Spring 容器是懶載入的,或者通過 API 程式設計延遲引用服務,請關閉 check,否則服務臨時不可用時,會丟擲異常,拿到 null 引用,如果 check=“false”,總是會返回引用,當服務恢復時,能自動連上 比如在 `order-service-consumer` 消費者中,在 consumer.xml 中新增配置 ```xml ``` 也可以在每一個上面加 check ## (三) 全域性超時配置 由於網路或服務端不可靠,會導致調用出現一種不確定的中間狀態(超時),為了避免超時導致客戶端資源(執行緒)掛起耗盡,必須設定超時時間 ### (1) Dubbo消費端 ```xml ``` ### (2) Dubbo服務端 ```xml ``` 設定超時時間其實算蠻簡單的,但是最主要注意的問題就是優先順序問題,在上面不管是消費者還是服務者,我都配置了三種層次的超時配置,這幾者的優先級別簡單總結就是: - ① 更細,更精準的優先:1. 方法級別 <== 2. 介面級別 <== 3. 全域性級別 - ② 消費者設定優先:級別一致的情況下,消費者優先於提供者 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f4b7fba0337445b8bc8ddade2436167~tplv-k3u1fbpfcp-zoom-1.image) **補充:** - dubbo:consumer 超時預設值為 1000 http://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不可控的,並且往往是不合理的 ## (四) 多版本問題 https://dubbo.apache.org/zh/docs/v2.7/user/examples/multi-versions/#m-zhdocsv27userexamplesmulti-versions 當一個介面實現,出現不相容升級時,可以用版本號過渡,版本號不同的服務相互間不引用。 可以按照以下的步驟進行版本遷移: - 在低壓力時間段,先升級一半提供者為新版本 - 再將所有消費者升級為新版本 - 然後將剩下的一半提供者升級為新版本 老版本服務提供者配置: ```xml ``` 新版本服務提供者配置: ```xml ``` 老版本服務消費者配置: ```xml ``` 新版本服務消費者配置: ```xml ``` 如果不需要區分版本,可以按照以下的方式配置(2.2.0 以上版本支援) ```xml ``` # 三 Dubbo 高可用 這一塊的講解主要是針對,在一些突發的錯誤,或者大併發下等如何保證 Dubbo 仍為高可用狀態的一些概念,以及措施 注:高可用,即通過設計,減少系統不能提供服務的時間 ## (一) zookeeper 宕機 和 Dubbo 直連 ### (1) zookeeper 宕機 zookeeper 作為註冊中心,如果部署執行著它的伺服器出問題了,出現了 zookeeper 宕機,那麼這個時候消費者豈不是找不到被暴露的服務了 但是我們主動關掉 zookeeper 的服務,在dubbo-admin 監控中可以看到服務出現了錯誤,但是我們去請求介面,可以發現仍然能請求得到結果,即,還可以消費 dubbo 暴露的服務 這一點與 Dubbo 設計時實現的健壯性有關 - 監控中心宕掉不影響使用,只是丟失部分取樣資料 - 資料庫宕掉後,註冊中心仍能通過快取提供服務列表查詢,但不能註冊新服務 - 註冊中心對等叢集,任意一臺宕掉後,將自動切換到另一臺 - 註冊中心全部宕掉後,服務提供者和服務消費者仍能通過本地快取通訊 - 服務提供者無狀態,任意一臺宕掉後,不影響使用 - 服務提供者全部宕掉後,服務消費者應用將無法使用,並無限次重連等待服務提供者恢復 高可用:通過設計,減少系統不能提供服務的時間 > https://dubbo.apache.org/zh/docs/v2.7/dev/principals/robustness/#m-zhdocsv27devprincipalsrobustness > > ### Dubbo 的服務註冊中心 > > 目前服務註冊中心使用了資料庫來儲存服務提供者和消費者的資訊。註冊中心叢集不同註冊中心也通過資料庫來進行同步資料,以感知其它註冊中心上提供者的變化。註冊中心會在記憶體中儲存一份提供者和消費者資料,資料庫不可用時,註冊中心獨立對外提供服務以保證正常運轉,只是拿不到其它註冊中心的資料。當資料庫恢復時,重試邏輯會將記憶體中修改的資料寫回資料庫,並拿到資料庫中新資料。 > > ### 服務的消費者 > > 服務消費者從註冊中心拿到提供者列表後,會儲存提供者列表到記憶體和磁碟檔案中。這樣註冊中心宕機後消費者可以正常運轉,甚至可以在註冊中心宕機過程中重啟消費者。消費者啟動時,發現註冊中心不可用,會讀取儲存在磁碟檔案中提供者列表。重試邏輯保證註冊中心恢復後,更新資訊。 ### (2) Dubbo 直連 如果註冊中心現在有點問題,或者有意的不想訪問註冊中心上的服務,而是想要直接在本地上除錯 dubbo 介面,也可以使用 Dubbo 直連 - 第一種:通過 @Reference 的 url 屬性(Springboot) ```java @Reference(url = "127.0.0.1:20081") public UserService userService; ``` - 第二種:在 consumer.xml 中指定 url (SSM) ```xml ``` - 第三種:新增對映配置檔案 - 如果不想要修改工程內容,可以考慮使用這個方式 在本地電腦使用者下新建一個叫 dubbo-resolve.properties 的檔案路徑是${user.home}/dubbo-resolve.properties 然後就不需要修改本地工程的其他配置資訊,在檔案裡配置好需要直連的服務資訊即可 ```properties # 直連本地的服務 cn.ideal.mall.service.UserService=dubbo://localhost:20890 ``` ## (二) 叢集下 Dubbo 的負載均衡策略 這一塊,我們只對幾種負載均衡策略做一些說明和解釋,具體的演算法實現,不是幾句話能說的清楚地,需要再深入的去學習以及研究,以下是官網關於負載均衡詳細的說明 https://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** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a3715ea150e0425786f2a1b2d99495d6~tplv-k3u1fbpfcp-zoom-1.image) 隨機演算法,按權重設定隨機概率 在一個截面上碰撞的概率高,但呼叫量越大分佈越均勻,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重 **根據權重的這個比重來決定究竟用哪個** 注:weight 代表權重,即在所有份數中所佔的比例 #### B: RoundRobin LoadBalance **RoundRobin LoadBalance 基於權重的輪詢負載均衡機制** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7db6811a19a1470cbb1c17ff60e784d4~tplv-k3u1fbpfcp-zoom-1.image) 如果只考慮輪輪詢的意思就是,比如訪問 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最少活躍數負載均衡機制** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60ef395c3dac450581244e58c7a139bd~tplv-k3u1fbpfcp-zoom-1.image) 活躍數指呼叫前後計數差,計算活躍數使慢的提供者收到更少請求,因為越慢的提供者的呼叫前後計數差會越大 注:如果活躍數相同,就隨機 #### D:ConsistentHash LoadBalance **ConsistentHash LoadBalance一致性hash 負載均衡機制** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0d8c9131769f42d0a72f1b84b51ba02e~tplv-k3u1fbpfcp-zoom-1.image) 這個演算法會對,方法呼叫的第一個引數進行 Hash,例如就是對上面的 param 引數後面的 1、2、3 進行雜湊,一致性 Hash,相同引數的請求總是發到同一提供者 好處就是,當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動 ## (三) 整合hystrix,服務熔斷與降級處理 ### (1) 服務降級 **當伺服器壓力劇增的情況下,根據實際業務情況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放伺服器資源以保證核心交易正常運作或高效運作** 可以通過服務降級功能臨時遮蔽某個出錯的非關鍵服務,並定義降級後的返回策略 向註冊中心寫入動態配置覆蓋規則: ```java 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"` 來設定重試次數(不含第一次)。 重試次數配置如下: ```xml ``` 或 ```xml ``` 或 ```xml ``` **Failfast Cluster** 快速失敗,只發起一次呼叫,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。 **Failsafe Cluster** 失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。 **Failback Cluster** 失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於訊息通知操作。 **Forking Cluster** 並行呼叫多個伺服器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 `forks="2"` 來設定最大並行數。 **Broadcast Cluster** 廣播呼叫所有提供者,逐個呼叫,任意一臺報錯則報錯。通常用於通知所有提供者更新快取或日誌等本地資源資訊。 #### B:叢集模式配置(2.1.0 開始支援) 按照以下示例在服務提供方和消費方配置叢集模式,預設為 failover 重試 ```xml ``` 或 ```xml ``` ## (四) 整合 Hystrix > 在微服務架構中存在多個可直接呼叫的服務,這些服務若在呼叫時出現故障會導致連鎖效應,也就是可能會讓整個系統變得不可用,這種情況我們稱之為服務雪崩效應,在這種時候,就需要我們的**熔斷機制**來挽救整個系統 > > 在微服務架構下,很多服務都相互依賴,如果不能對依賴的服務進行隔離,那麼服務本身也有可能發生故障, Hystrix通過Hystrix Command對呼叫進行隔離, 這樣可以阻止故障的連鎖反應,能夠讓介面呼叫快速失敗並迅速恢復正常,或者回退並優雅降級 關於 Hystrix ,會在 SpringCloud 的學習文章整理中進行介紹,這裡只做一個簡單的使用 首先引入依賴 注意,注意!如果擬引入的 hystrix 版本相對較新,或許會報錯 Error creating bean with name 'configurationPropertiesBeans....... 一種情況就是你的 springboot 版本太新了,需要降低一下,spring 官網可以看到 現在的 cloud 對應能支援到哪個版本的 boot ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix 2.2.5.RELEASE ``` 啟動類新增 @EnableHystrix 註解 ```java @EnableDubbo // 開啟基於註解的dubbo功能 @EnableHystrix //開啟服務容錯功能 @SpringBootApplication public class BootUserServiceProviderApplication { public static void main(String[] args) { SpringApplication.run(BootUserServiceProviderApplication.class, args); } } ``` **在提供者中添加註解** 可以直接使用 @HystrixCommand,後面都是一些屬性設定,下面的隨機數判斷是為了模擬異常 ```java @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 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" ```java @Service public class OrderServiceImpl implements OrderService { @Reference public UserService userService; @HystrixCommand(fallbackMethod = "testError") public List initOrder(String userID) { //查詢使用者的收貨地址 List 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 testError(){ return Arrays.asList(new UserAddress(10,"錯誤測試地址", "1","測試BWH","15555555555","N")); } } ```