這篇文章是我學習整理 Dubbo 的一篇文章,首先大部份內容參考了官網 + 某硅谷的視頻,內容講解進行了從新編排,40多張圖片,也都是我修改重製的,雖然一萬多字,可是其實也能夠看出來,更多的內容集中在了概念或功能的介紹,具體環境的搭建,以及如何配置,快速上手上面,可是對於這樣一款優秀的框架,文章中提到的每個點其實展開來說都能寫這樣篇幅的一篇文章,僅僅入門來看也不必,總得學會走,才能夠去試着跑前端
早安,學習人 & 打工人 ~ ~ ~java
在百度以及維基中的定義都相對專業且晦澀,大部分博客或者教程常常會使用《分佈式系統原理和範型》中的定義,即:「分佈式系統是若干獨立計算機的集合,這些計算機對於用戶來講就像是單個相關係統」linux
下面咱們用一些篇幅來通俗的解釋一下什麼叫作分佈式git
提到分佈式,不得不提的就是 「集中式系統」,這個概念最好理解了,它就是將功能,程序等安裝在同一臺設備上,就由這一臺主機設備向外提供服務程序員
舉個最簡單的例子:你拿一臺PC主機,將其改裝成了一臺簡單的服務器,配置好各類內容後,你將MySQL,Web服務器,FTP,Nginx 等等,所有安裝在其中,打包部署項目後,就能夠對外提供服務了,可是一旦這臺機器不管是軟件仍是硬件出現了問題,整個系統都會受到嚴重的牽連錯誤,雞蛋放在一個籃子裏,要打就全打了github
既然集中式系統有這樣一種牽一髮而動全身的問題,那麼分佈式的其中一個做用,天然是來解決這樣的問題了,正如定義中所知,分佈式系統在用戶的體驗感官裏,就像傳統的單系統同樣,一些變化都是這個系統自己內部進行的,對於用戶並無什麼太大的感受web
例如:淘寶,京東這種大型電商平臺,它們的主機都是數以萬計的,不然根本無法處理大量的數據和請求,具體其中有什麼劃分,以及操做,咱們下面會說到,可是對於用戶的咱們,咱們不須要也不想關心這些,咱們仍能夠單純的認爲,咱們面對的就是 「淘寶」 這一臺 「主機」算法
因此分佈式的一個相對專業一些的說法是這樣的(進程粒度)兩個或者多個程序,分別運行在不一樣的主機進程上,它們互相配合協調,完成共同的功能,那麼這幾個程序之間構成的系統就能夠叫作分佈式系統spring
當網站流量很小時,只需一個應用,將全部功能都部署在一塊兒,以減小部署節點和成本。此時,用於簡化增刪改查工做量的數據訪問框架(ORM)是關鍵數據庫
優勢:
缺點:
當訪問量逐漸增大,單一應用增長機器帶來的加速度愈來愈小,提高效率的方法之一是將應用拆成互不相干的幾個應用,以提高效率。此時,用於加速前端頁面開發的Web框架(MVC)是關鍵
優勢:
缺點:
當垂直應用愈來愈多,應用之間交互不可避免,將核心業務抽取出來,做爲獨立的服務,逐漸造成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用於提升業務複用及整合的分佈式服務框架(RPC)是關鍵
優勢:
缺點:
當服務愈來愈多,容量的評估,小服務資源的浪費等問題逐漸顯現,此時需增長一個調度中心基於訪問壓力實時管理集羣容量,提升集羣利用率。此時,用於提升機器利用率的資源調度和治理中心(SOA)是關鍵
優勢:
缺點:
RPC(Remote Procedure Call)是指遠程過程調用,是一種進程間通訊方式,他是一種技術的思想,而不是規範。它容許程序調用另外一個地址空間(一般是共享網絡的另外一臺機器上)的過程或函數,而不用程序員顯式編碼這個遠程調用的細節
這應該是很好理解的,調用本地 A 服務器上的函數或者方法的時候很簡單,可是若是A 想要訪問 B 的方法時,二者的內存空間壓根都不是同一個,只能經過網絡傳輸調用的相關內容,關於傳輸協議亦或者傳輸方式,都由 RPC 幫咱們背後實現
也就是說,即程序員不管是調用本地的仍是遠程的函數,本質上編寫的調用代碼基本相同
關於 RPC 更加細緻專業的解釋說明
RPC是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,爲通訊程序之間攜帶信息數據。在OSI網絡通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易
RPC採用客戶機/服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,而後等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息到達爲止。當一個調用信息到達,服務器得到進程參數,計算結果,發送答覆信息,而後等待下一個調用信息,最後,客戶端調用進程接收答覆信息,得到進程結果,而後調用執行繼續進行
本地過程調用
RPC就是要像調用本地的函數同樣去調遠程函數。在研究RPC前,咱們先看看本地調用是怎麼調的。假設咱們要調用函數Multiply來計算lvalue * rvalue的結果:
......省略,此篇可細細看一下
問題答者:洪春濤
來看一下從 A 想要訪問 B 中方法的一個流程例子
順着執行路線來捋一下
第③點說明:RPC採用客戶機/服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。
Dubbo官網:http://dubbo.apache.org/zh/
Apache Dubbo 是一款高性能、輕量級的開源 Java 服務框架
Apache Dubbo |ˈdʌbəʊ| 提供了六大核心能力:面向接口代理的高性能RPC調用,智能容錯和負載均衡,服務自動註冊和發現,高度可擴展能力,運行期流量調度,可視化的服務治理與運維。
面向接口代理的高性能RPC調用
智能負載均衡
服務自動註冊與發現
高度可擴展能力
運行期流量調度
可視化的服務治理與運維
這是Dubbo的架構圖
首先介紹一下這五個節點的角色(五個圓角矩形框)
Provider
:暴露服務的服務提供方
Consume
:調用遠程服務的服務消費方
Registry
:服務註冊與發現的註冊中心
Monitor
:統計服務的調用次數和調用時間的監控中心
Container
:服務運行容器
再來看一下調用的關係和流程:
① 服務容器負責啓動,加載,運行服務提供者
② 服務提供者在啓動時,向註冊中心註冊本身提供的服務
③ 註冊中心返回服務提供者地址列表給消費者,若是有變動,註冊中心將基於長鏈接推送變動數據給消費者
④ 服務消費者,從提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用,若是調用失敗,再選另外一臺調用
⑤ 服務消費者和提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心
說明:如下爲學習演示方便,均爲 Windows 系統搭建,真實 Linux 場景搭建會在後面出一篇,如何在Linux安裝配置常見的軟件
首先先將 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,雖然本質咱們是用前者,可是啓動過程當中遇到了報錯,須要後者部分文件來進行修復
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 文件夾,也能夠本身指定,也能夠無論這個
都解決好了,來運行 zkServer.cmd,就啓動成功了
運行zkCli.cmd,能夠成功鏈接到 zookeeper的服務器
服務端客戶端都跑起來了,搭建zookeeper註冊中心環境到這裏就算 OK了
dubbo自己並非一個服務軟件。它其實就是一個jar包可以幫你的java程序鏈接到zookeeper,並利用zookeeper消費、提供服務。因此你不用在Linux上啓動什麼dubbo服務
可是爲了讓用戶更好的管理監控衆多的dubbo服務,官方提供了一個可視化的監控程序——dubbo-admin,不過這個監控即便不裝也不影響使用
下載地址:https://github.com/apache/dubbo-admin/tree/master
從 github 上 down 到本地 ,有 dubbo-admin-master 一個文件夾,其中含有三個主要文件夾
咱們如今先只針對 dubbo-admin 操做
打開編輯配置文件:XXX\dubbo-admin-master\dubbo-admin\src\main\resources\application.properties
確認其中的配置,好比端口,密碼什麼等等都是正常的,例如服務端口7001,密碼 root,最關鍵的就是地址,由於咱們測試是在本機,因此地址以下就能夠了
dubbo.registry.address=zookeeper://127.0.0.1:2181
既然沒問題了,就在 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 註冊中心啓動了
咱們第一個例子就照着這個網絡上參考的例子來作(代碼並不重要,關鍵是理解調用關係和配置方式):
解釋一下,訂單生成前的時候,須要調用用戶服務,查詢到用戶全部的收貨地址,用戶選定地址後,再生成訂單,若是訂單服務和用戶服務分屬於不一樣的服務器 A 和 B,而咱們要解決的就是如何在 A 的訂單服務中,調用到 B 的用戶服務
順便提一句,很清楚的能夠看出來,用戶服務是被調用的,全部它就是服務的提供者,而訂單服務做爲消費者
爲了在一臺機器上演示,我首先建立了一個 Maven 的空項目,而後分別建立幾個子模塊(程序很簡單,不須要Maven骨架)
在空項目中建立第一個模塊 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); } }
程序很簡單,數據都是擬出來的
如下是這個模塊的結構圖:
在項目中建立第二個模塊 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 接口,複製到咱們這個訂單的模塊中,可是總不能之後有一個地方調用就複製一次吧,這也太麻煩了
因此通用的作法是再建立一個模塊,將服務接口,模型等等全放在一塊兒,維護以及調用會更好
在項目中建立:mall-interface 模塊
將用戶模塊(服務提供者)和訂單模塊(服務消費者) 中的全部實體類和service接口複製到當前模塊下
刪除原有的實體類包及service包,也就是將實體類及service放在了當前公共的項目中了
既然原來兩個模塊都刪掉了實體等內容,咱們想要用到這個公共的接口模塊,只須要引入依賴便可
<dependency> <groupId>cn.ideal.mall</groupId> <artifactId>mall-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
咱們先對用戶服務進行配置,首先要引入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
在 resource 文件中建立 provider.xml
註釋中都寫的很清楚了,修改成本身的配置就行了
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!--一、指定當前服務/應用的名字(一樣的服務名字相同,不要和別的服務同名)--> <dubbo:application name="user-service-provider"></dubbo:application> <!--二、指定註冊中心的位置--> <!--<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>--> <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry> <!--三、指定通訊規則(通訊協議? 服務端口)--> <dubbo:protocol name="dubbo" port="20880"></dubbo:protocol> <!--四、暴露服務 讓別人調用 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>
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/
,在服務治理的提供者中,已經能夠看到發現了這個提供者
<!--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>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://code.alibabatech.com/schema/dubbo http://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>
這個實現類,剛纔還空着,通過引入接口依賴,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()); } } }
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/
能夠看到,消費者也被發現了,同時控制檯也成功的輸出了內容
到這裏,其實一個簡單的調用過程已經完成了
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
由於前面可能運行的問題,我後面有一些端口占用的問題,因此我把 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 能夠看到服務提供者和消費者的信息
到這裏,這個監控中心也算安裝好了!!!
上面這個項目就是一個普通的 maven 項目,經過 XML 配置文件進行配置,在 SSM 項目中就能夠這樣使用,而 SpringBoot 做爲如今開發的主力軍之一,天然也要再講一下它的一個配置運行方式
建立一個 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://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); } }
建立 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 實現類按路徑複製過來
有兩點注意:
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); } }
首先保證打開了,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/
訪問的結果
或者 http://localhost:8001
(個人監控中心端口設置的是 8081)
以及根據本身設置的項目端口號去請求
能夠看到結果都是沒問題的,SpringBoot 整合 Dubbo 就是這樣一個方式
一 將服務提供者註冊到註冊中心(如何暴露服務)
二 讓服務消費者去註冊中心訂閱服務提供者的服務地址
Springboot與Dubbo整合的三種方式
① 導入dubbo-starter。在application.properties配置屬性,使用@Service【暴露服務】,使用@Reference【引用服務】
② 保留Dubbo 相關的xml配置文件
③ 使用 註解API 的方式
例如建立一個 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; } }
http://dubbo.apache.org/zh/docs/v2.7/user/configuration/properties/
優先級從高到低:
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
因爲網絡或服務端不可靠,會致使調用出現一種不肯定的中間狀態(超時),爲了不超時致使客戶端資源(線程)掛起耗盡,必須設置超時時間
<!--全局超時配置--> <dubbo:consumer timeout="5000" /> <!--調用遠程暴露的服務,生成遠程服務代理--> <dubbo:reference interface="cn.ideal.mall.service.UserService" id="userService" timeout="2000"> <dubbo:method name="getUserAddressList" timeout="3000"/> </dubbo:reference>
<!--全局超時配置--> <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. 全局級別
② 消費者設置優先:級別一致的狀況下,消費者優先於提供者
補充:
http://dubbo.apache.org/zh/docs/v2.7/user/references/xml/dubbo-consumer/
屬性 | 對應URL參數 | 類型 | 是否必填 | 缺省值 | 做用 | 描述 | 兼容性 |
---|---|---|---|---|---|---|---|
timeout | default.timeout | int | 可選 | 1000 | 性能調優 | 遠程服務調用超時時間(毫秒) |
dubbo 推薦在 Provider 上儘可能多配置 Consumer 端屬性
當一個接口實現,出現不兼容升級時,能夠用版本號過渡,版本號不一樣的服務相互間不引用。
能夠按照如下的步驟進行版本遷移:
老版本服務提供者配置:
<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 仍爲高可用狀態的一些概念,以及措施
注:高可用,即經過設計,減小系統不能提供服務的時間
zookeeper 做爲註冊中心,若是部署運行着它的服務器出問題了,出現了 zookeeper 宕機,那麼這個時候消費者豈不是找不到被暴露的服務了
可是咱們主動關掉 zookeeper 的服務,在dubbo-admin 監控中能夠看到服務出現了錯誤,可是咱們去請求接口,能夠發現仍然能請求獲得結果,即,還能夠消費 dubbo 暴露的服務
這一點與 Dubbo 設計時實現的健壯性有關
https://dubbo.apache.org/zh/docs/v2.7/dev/principals/robustness/#m-zhdocsv27devprincipalsrobustness
Dubbo 的服務註冊中心
目前服務註冊中心使用了數據庫來保存服務提供者和消費者的信息。註冊中心集羣不一樣註冊中心也經過數據庫來進行同步數據,以感知其它註冊中心上提供者的變化。註冊中心會在內存中保存一份提供者和消費者數據,數據庫不可用時,註冊中心獨立對外提供服務以保證正常運轉,只是拿不到其它註冊中心的數據。當數據庫恢復時,重試邏輯會將內存中修改的數據寫回數據庫,並拿到數據庫中新數據。
服務的消費者
服務消費者從註冊中心拿到提供者列表後,會保存提供者列表到內存和磁盤文件中。這樣註冊中心宕機後消費者能夠正常運轉,甚至能夠在註冊中心宕機過程當中重啓消費者。消費者啓動時,發現註冊中心不可用,會讀取保存在磁盤文件中提供者列表。重試邏輯保證註冊中心恢復後,更新信息。
若是註冊中心如今有點問題,或者有意的不想訪問註冊中心上的服務,而是想要直接在本地上調試 dubbo 接口,也可使用 Dubbo 直連
@Reference(url = "127.0.0.1:20081") public UserService userService;
<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
這一塊,咱們只對幾種負載均衡策略作一些說明和解釋,具體的算法實現,不是幾句話能說的清楚地,須要再深刻的去學習以及研究,如下是官網關於負載均衡詳細的說明
https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance
LoadBalance 中文意思爲負載均衡,它的職責是將網絡請求,或者其餘形式的負載「均攤」到不一樣的機器上。避免集羣中部分服務器壓力過大,而另外一些服務器比較空閒的狀況。
經過負載均衡,可讓每臺服務器獲取到適合本身處理能力的負載。在爲高負載服務器分流的同時,還能夠避免資源浪費,一箭雙鵰。
負載均衡可分爲軟件負載均衡和硬件負載均衡。在咱們平常開發中,通常很難接觸到硬件負載均衡。但軟件負載均衡仍是能夠接觸到的,好比 Nginx。
在 Dubbo 中,也有負載均衡的概念和相應的實現。Dubbo 須要對服務消費者的調用請求進行分配,避免少數服務提供者負載過大。服務提供者負載過大,會致使部分請求超時。所以將負載均衡到每一個服務提供者上,是很是必要的。
Dubbo 提供了4種負載均衡實現,分別是
基於權重隨機算法的 RandomLoadBalance
隨機算法,按權重設置隨機機率
在一個截面上碰撞的機率高,但調用量越大分佈越均勻,並且按機率使用權重後也比較均勻,有利於動態調整提供者權重
根據權重的這個比重來決定究竟用哪一個
注:weight 表明權重,即在全部份數中所佔的比例
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
缺點:存在慢的提供者累積請求的問題,好比:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,長此以往,全部請求都卡在調到第二臺上
LeastActive LoadBalance最少活躍數負載均衡機制
活躍數指調用先後計數差,計算活躍數使慢的提供者收到更少請求,由於越慢的提供者的調用先後計數差會越大
注:若是活躍數相同,就隨機
ConsistentHash LoadBalance一致性hash 負載均衡機制
這個算法會對,方法調用的第一個參數進行 Hash,例如就是對上面的 param 參數後面的 一、二、3 進行哈希,一致性 Hash,相同參數的請求老是發到同一提供者
好處就是,當某一臺提供者掛時,本來發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引發劇烈變更
當服務器壓力劇增的狀況下,根據實際業務狀況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務器資源以保證核心交易正常運做或高效運做
能夠經過服務降級功能臨時屏蔽某個出錯的非關鍵服務,並定義降級後的返回策略
向註冊中心寫入動態配置覆蓋規則:
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 值,不拋異常。用來容忍不重要服務不穩定時對調用方的影響在集羣調用失敗時,Dubbo 提供了多種容錯方案,缺省爲 failover 重試
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
廣播調用全部提供者,逐個調用,任意一臺報錯則報錯。一般用於通知全部提供者更新緩存或日誌等本地資源信息。
按照如下示例在服務提供方和消費方配置集羣模式,缺省爲 failover 重試
<dubbo:service cluster="模式名" />
或
<dubbo:reference cluster="模式名" />
在微服務架構中存在多個可直接調用的服務,這些服務若在調用時出現故障會致使連鎖效應,也就是可能會讓整個系統變得不可用,這種狀況咱們稱之爲服務雪崩效應,在這種時候,就須要咱們的熔斷機制來挽救整個系統
在微服務架構下,不少服務都相互依賴,若是不能對依賴的服務進行隔離,那麼服務自己也有可能發生故障, 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")); } }