Dubbo源碼分析:小白入門篇
關(guān)注公眾號(hào)“java后端技術(shù)全棧”
回復(fù)“000”獲取優(yōu)質(zhì)面試資料
大家好,我是老田
答應(yīng)了小伙伴的Dubbo源碼分析系列,今天終于來(lái)了,希望不是很晚。
主要也是現(xiàn)在Spring CLoud Alibaba實(shí)在是太火了,很多小伙伴都想好好搞搞。
既然如此,我們就選擇從Dubbo開始吧。
如果你已經(jīng)對(duì)Dubbo熟練使用了,那這篇文章不太適合你,但如果想了解Dubbo,想學(xué)習(xí)Dubbo,那就非常適合你。

什么是Dubbo?
Dubbo一開始是由阿里巴巴開發(fā),后面貢獻(xiàn)給了Apache,所以后面我們稱之為Apache Dubbo或者直接叫Dubbo。
Dubbo 是一款高性能、輕量級(jí)的開源服務(wù)框架 。
先糾正讀法:
錯(cuò)誤讀法:diubo、dubo
正確讀法:|?d?b??|
Dubbo的六大核心能力
面向接口代理的高性能 RPC調(diào)用智能容錯(cuò)和負(fù)載均衡 服務(wù)自動(dòng)注冊(cè)和發(fā)現(xiàn) 高度可擴(kuò)展能力 運(yùn)行期流量調(diào)度 可視化的服務(wù)治理與運(yùn)維。
開發(fā)中,我們都喜歡把Dubbo簡(jiǎn)稱為RPC開源框架。
什么是RPC?
RPC是Remote Procedure Call的簡(jiǎn)稱,翻譯過(guò)來(lái)就是:遠(yuǎn)程過(guò)程調(diào)用。
簡(jiǎn)單的理解是一個(gè)節(jié)點(diǎn)請(qǐng)求另一個(gè)節(jié)點(diǎn)提供的服務(wù) 。
通俗點(diǎn)講:
兩臺(tái)服務(wù)器A和B,在服務(wù)器A上部署一個(gè)應(yīng)用程序serverA,在服務(wù)器B上部署一個(gè)應(yīng)用程序serverB。此時(shí),serverA想調(diào)用serverB里的某個(gè)方法,由于不在同一服務(wù)器內(nèi),不能直接調(diào)用,需要通過(guò)網(wǎng)絡(luò)來(lái)表達(dá)調(diào)用的語(yǔ)義和傳導(dǎo)調(diào)用的數(shù)據(jù)。
調(diào)用遠(yuǎn)程的方法就像調(diào)用本地的方法一樣。

我們開發(fā)中,通常兩個(gè)服務(wù)(不同服務(wù)器上的服務(wù))之間的調(diào)用,通常都是用HTTP REST。
RPC框架
其實(shí)關(guān)于RPC框架,市面上有很多了,Dubbo只是其中之一。比如說(shuō)還有:
gRPC 是 Google 開發(fā)的高性能、通用的開源 RPC 框架,gRPC 使用 ProtoBuf 來(lái)定義服務(wù),ProtoBuf 是 Google 開發(fā)的一種數(shù)據(jù)序列化協(xié)議,性能比較高,壓縮和傳輸效率高,語(yǔ)法也比較簡(jiǎn)單。另外,gRPC 支持多種語(yǔ)言,并能夠基于語(yǔ)言自動(dòng)生成客戶端和服務(wù)端功能庫(kù)。 Thrift 起源于 Facebook,和 Dubbo 一樣,后來(lái)被提交 Apache 基金會(huì)將 Thrift 作為一個(gè)開源項(xiàng)目。Facebook 創(chuàng)造 Thrift 的目的是為了解決 Facebook 各系統(tǒng)間大數(shù)據(jù)量的傳輸通信,以及系統(tǒng)間語(yǔ)言環(huán)境不同需要跨平臺(tái)的問(wèn)題。 Motan 是新浪微博開源的一個(gè) Java RPC 框架,官方文檔對(duì)外宣傳在微博平臺(tái)已經(jīng)廣泛應(yīng)用,每天為數(shù)百個(gè)服務(wù)完成近千億次的調(diào)用。 ...
Dubbo核心角色
我們來(lái)看看Dubbo架構(gòu)中的核心角色:

該圖來(lái)自于官網(wǎng),下面我們對(duì)圖做一個(gè)簡(jiǎn)單介紹:
Registry
注冊(cè)中心。負(fù)責(zé)服務(wù)地址的注冊(cè)與查找,服務(wù)的 Provider 和 Consumer 只在啟動(dòng)時(shí)與注冊(cè)中心交互。注冊(cè)中心通過(guò)長(zhǎng)連接感知 Provider 的存在,在 Provider 出現(xiàn)宕機(jī)的時(shí)候,注冊(cè)中心會(huì)立即推送相關(guān)事件通知 Consumer。
Provider
服務(wù)提供者。在它啟動(dòng)的時(shí)候,會(huì)向 Registry 進(jìn)行注冊(cè)操作,將自己服務(wù)的地址和相關(guān)配置信息封裝成 URL 添加到 ZooKeeper 中。
Consumer
服務(wù)消費(fèi)者。在它啟動(dòng)的時(shí)候,會(huì)向 Registry 進(jìn)行訂閱操作。訂閱操作會(huì)從 ZooKeeper 中獲取 Provider 注冊(cè)的 URL,并在 ZooKeeper 中添加相應(yīng)的監(jiān)聽器。獲取到 Provider URL 之后,Consumer 會(huì)根據(jù)負(fù)載均衡算法從多個(gè) Provider 中選擇一個(gè) Provider 并與其建立連接,最后發(fā)起對(duì) Provider 的 RPC 調(diào)用。如果 Provider URL 發(fā)生變更,Consumer 將會(huì)通過(guò)之前訂閱過(guò)程中在注冊(cè)中心添加的監(jiān)聽器,獲取到最新的 Provider URL 信息,進(jìn)行相應(yīng)的調(diào)整,比如斷開與宕機(jī) Provider 的連接,并與新的 Provider 建立連接。Consumer 與 Provider 建立的是長(zhǎng)連接,且 Consumer 會(huì)緩存 Provider 信息,所以一旦連接建立,即使注冊(cè)中心宕機(jī),也不會(huì)影響已運(yùn)行的 Provider 和 Consumer。
Monitor
監(jiān)控中心。用于統(tǒng)計(jì)服務(wù)的調(diào)用次數(shù)和調(diào)用時(shí)間。Provider 和 Consumer 在運(yùn)行過(guò)程中,會(huì)在內(nèi)存中統(tǒng)計(jì)調(diào)用次數(shù)和調(diào)用時(shí)間,定時(shí)每分鐘發(fā)送一次統(tǒng)計(jì)數(shù)據(jù)到監(jiān)控中心。監(jiān)控中心在上面的架構(gòu)圖中并不是必要角色,監(jiān)控中心宕機(jī)不會(huì)影響 Provider、Consumer 以及 Registry 的功能,只會(huì)丟失監(jiān)控?cái)?shù)據(jù)而已。
猥瑣發(fā)育,后期爆發(fā),(前期可能關(guān)注的不多,但是后期它特別香)
Container:
服務(wù)運(yùn)行容器。是一個(gè)獨(dú)立的容器,因?yàn)榉?wù)通常不需要Tomcat、JBoss等Web容器的特性,沒必要用Web容器去加載服務(wù)。服務(wù)容器只是一個(gè)簡(jiǎn)單的main方法,并加載一個(gè)簡(jiǎn)單的Spring容器,用于暴露服務(wù)。
流程說(shuō)明
在上面這張圖中,有幾個(gè)角色,并且還畫了很多線條,下面我們對(duì)此做一個(gè)簡(jiǎn)單說(shuō)明。
服務(wù)容器負(fù)責(zé)啟動(dòng),加載,運(yùn)行服務(wù)提供者。 服務(wù)提供者在啟動(dòng)時(shí),向注冊(cè)中心注冊(cè)自己提供的服務(wù)。 服務(wù)消費(fèi)者在啟動(dòng)時(shí),向注冊(cè)中心訂閱自己所需的服務(wù)。 注冊(cè)中心返回服務(wù)提供者地址列表給消費(fèi)者,如果有變更,注冊(cè)中心將基于長(zhǎng)連接推送變更數(shù)據(jù)給消費(fèi)者。 服務(wù)消費(fèi)者,從提供者地址列表中,基于軟負(fù)載均衡算法,選一臺(tái)提供者進(jìn)行調(diào)用,如果調(diào)用失敗,再選另一臺(tái)調(diào)用。 服務(wù)消費(fèi)者和提供者,在內(nèi)存中累計(jì)調(diào)用次數(shù)和調(diào)用時(shí)間,定時(shí)每分鐘發(fā)送一次統(tǒng)計(jì)數(shù)據(jù)到監(jiān)控中心。
Dubbo官網(wǎng)
Dubbo的官網(wǎng):https://dubbo.apache.org/

由于Dubbo是由阿里巴巴技術(shù)團(tuán)隊(duì)開發(fā)的,所以,文檔方面對(duì)于咱們中國(guó)人來(lái)說(shuō)那是相當(dāng)?shù)挠押?,一個(gè)字:爽!
另外,Dubbo官網(wǎng)上很多東西,我們就不在這里一一介紹了。
建議大家都去官網(wǎng)逛逛。
話不多說(shuō),咱們先來(lái)嗨一把!
demo案例1
我們先來(lái)搞一個(gè)沒有注冊(cè)中心的案例。
我們搭建一個(gè)項(xiàng)目,并創(chuàng)建三個(gè)module:
dubbo-demodubbo-demo-apidubbo-demo-providerdubbo-demo-consumer
項(xiàng)目整體結(jié)構(gòu)如下:

下面,我們來(lái)代碼做一個(gè)簡(jiǎn)單說(shuō)明。
首先是pom依賴:
<dependency>
?????<groupId>org.apache.dubbogroupId>
?????<artifactId>dubboartifactId>
?????<version>3.0.4version>
dependency>
<dependency>
?????<groupId>com.tian.dubbogroupId>
?????<artifactId>dubbo-demo-apiartifactId>
?????<version>1.0-SNAPSHOTversion>
dependency>
consumer和provider項(xiàng)目都需要添加這兩個(gè)依賴。
api
api主要是定義服務(wù)接口以及一些工具類,主要是供consumer和provider共用。
在api中我們只定義了一個(gè)服務(wù)接口:DemoService
package?com.tian.dubbo.service;
public?interface?DemoService?{
????String?sayHello(String?msg);
}
然后打成jar,在consumer和provider項(xiàng)目中添加到pom.xml依賴?yán)?,最后兩遍都可以使用了?/p>
provider
在resources目錄下創(chuàng)建一個(gè)目錄META-INF.spring,然后在目錄下創(chuàng)建一個(gè)application.xml,內(nèi)容如下:
<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://code.alibabatech.com/schema/dubbo????????http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
?????
????<dubbo:application?name="dubbo-provider"/>
?????
????<dubbo:registry?address="N/A"/>
?????
????<dubbo:protocol?name="dubbo"?port="20880"/>
?????
????<dubbo:service?interface="com.tian.dubbo.service.DemoService"?ref="demoService"/>
????<bean?id="demoService"?class="com.tian.dubbo.service.DemoServiceImpl"/>
beans>
再在resources目錄下創(chuàng)建一個(gè)日志打印的配置文件:log4j.properties
###set log levels###
log4j.rootLogger=debug, stdout
###output to the console###
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{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n
在定義一個(gè)業(yè)務(wù)實(shí)現(xiàn)類:DemoServiceImpl
package?com.tian.dubbo.service;
public?class?DemoServiceImpl?implements?DemoService?{
????public?String?sayHello(String?msg)?{
????????System.out.println("msg=?"?+?msg);
????????return?"SUCCESS";
????}
}
再就是定義一個(gè)provider的啟動(dòng)類:ProviderMain
package?com.tian.dubbo;
import?org.apache.dubbo.container.Main;
public?class?ProviderMain?{
????public?static?void?main(String[]?args)?{
????????Main.main(args);
????}
}
注意:這里的Main類是Dubbo
最后,我們啟動(dòng)ProviderMain類,日志輸出:

好了,已經(jīng)啟動(dòng)成功了。
我們繼續(xù)來(lái)看看consumer項(xiàng)目,在項(xiàng)目中,也就是調(diào)用我們服務(wù)的項(xiàng)目。
consumer
在consumer項(xiàng)目中application.xml配置文件和provider有所區(qū)別。
<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://code.alibabatech.com/schema/dubbo????????http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
????
????<dubbo:application?name="dubbo-consumer"/>
?????
????
????<dubbo:reference?id="demoService"
?????????????????????interface="com.tian.dubbo.service.DemoService"
?????url="dubbo://127.0.0.1:20880/com.tian.dubbo.service.DemoService"/>
beans>
這個(gè)url地址,我們?cè)趐rovider啟動(dòng)的時(shí)候,可以從日志中找到。
日志文件和provider一樣,然后就是ConsumerMain啟動(dòng)類了。
package?com.tian.dubbo;
import?com.tian.dubbo.service.DemoService;
import?org.springframework.context.ApplicationContext;
import?org.springframework.context.support.ClassPathXmlApplicationContext;
public?class?ConsumerMain?{
????public?static?void?main(String[]?args)?{
????????DemoService?demoService?=?null;
????????ApplicationContext?context?=?new?ClassPathXmlApplicationContext
????????????????("classpath:META-INF/spring/application.xml");
????????demoService?=?context.getBean(DemoService.class);
????????//調(diào)用服務(wù)
????????System.out.println(demoService.sayHello("tian"));
????}
}
前面,我們已經(jīng)把provider成功啟動(dòng)了,下面我們就來(lái)啟動(dòng)ConsumerMain。

從日志可以看出我們已經(jīng)成功調(diào)用了provider,我們?cè)賮?lái)看看provider的日志輸出:

也成功的輸出了我們想要的。
到此,一個(gè)簡(jiǎn)單的入門無(wú)注冊(cè)中心(通過(guò)url直接調(diào)用)的方式就完成了。
url在開發(fā)聯(lián)調(diào)的時(shí)候還是很擁有的哦,因?yàn)樗鼣[脫了對(duì)注冊(cè)中心的依賴。
demo案例2
前面我們已經(jīng)演示完了無(wú)注冊(cè)中心,下面我們來(lái)演示有注冊(cè)中心。
Dubbo目前差不多能支持市面上所有的注冊(cè)中心:
consul zookeeper eureka redis etcd nacos ....
我們?cè)趯?shí)際開發(fā)中,Dubbo注冊(cè)中心大部分都是使用Zookeeper和Nacos。
下面?zhèn)兓?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Zookeeper來(lái)演示(nacos類似,下面會(huì)說(shuō)到)。
代碼層面
我基于前面的案例進(jìn)行改造。改造只需要調(diào)整兩個(gè)地方:
consumer和provider中添加 pom依賴application.xml中添加注冊(cè)中心
pom依賴
我們需要在前面demo中consumer和provider的pom.xml中添加Zookeeper的依賴:
<dependency>
?????<groupId>org.apache.dubbogroupId>
?????<artifactId>dubbo-dependencies-zookeeperartifactId>
?????<version>3.0.4version>
?????<type>pomtype>
dependency>
provider端
在provider項(xiàng)目中我們需要調(diào)整:
<dubbo:registry?address="N/A"/>
改成:
<dubbo:registry?address="zookeeper://127.0.0.1:2181"?timeout="10000"/>
這個(gè)timeout建議配上,我這里其實(shí)沒必要配,因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">dubbo服務(wù)和
Zookeeper都在我本地。
然后我們啟動(dòng)provider項(xiàng)目:

看到我們的項(xiàng)目已經(jīng)啟動(dòng)成功,并且已經(jīng)注冊(cè)到Zookeeper上了。
我們可以使用Zookeeper的可視化工具,看看注冊(cè)上去的信息。

我們?cè)倏纯碿onsumer端的調(diào)整。
consumer端
我們需要在application.xml中添加
<dubbo:registry?address="zookeeper://127.0.0.1:2181"?timeout="10000"/>
同時(shí),去掉reference中的url:
<dubbo:reference?id="demoService"
?????????????????????interface="com.tian.dubbo.service.DemoService"
?????url="dubbo://127.0.0.1:20880/com.tian.dubbo.service.DemoService"/>
因?yàn)槭峭ㄟ^(guò)Zookeeper注冊(cè)中心拿到地址,所以這里的url就可以去掉了。
最后,啟動(dòng)ConsumerMain類:

可以看到我們也成功調(diào)用服務(wù),另外也有大量的Zookeeper日志。
到此,說(shuō)明,我們的Zookeeper為注冊(cè)中心的demo案例也成功了。
注意:provider和consumer項(xiàng)目都需要依賴相關(guān)jar包(api、zookeeper、dubbo)
其他
關(guān)于Nacos,我們這里就不演示了,因?yàn)樘?jiǎn)單了,如果你把Nacos搭建好了后,直接配置就好了。
<dubbo:registry?address="nacos://127.0.0.1:8848"?timeout="10000"/>
就是把a(bǔ)ddress地址改一下就可以了。
Nacos 的演示,我們下一篇文章中見。
總結(jié)
本文分享了Dubbo入門案例的兩個(gè)版本:無(wú)注冊(cè)中心和Zookeeper注冊(cè)中心。
也啰嗦了一些Dubbo廢話(其實(shí)官網(wǎng)都有的)。
好了,今天咱們就分享到這里!
下一篇:
Dubbo源碼分析:Dubbo集成Spring Boot爽歪歪!
喜歡的朋友,我們下一期見!
參考:Dubbo官網(wǎng)和楊正四《Dubbo源碼解讀與實(shí)戰(zhàn)》
推薦閱讀:
Redis為什么變慢,一文講透如何排查Redis性能問(wèn)題
7張圖揭曉RocketMQ存儲(chǔ)設(shè)計(jì)的精髓
歡迎關(guān)注微信公眾號(hào):互聯(lián)網(wǎng)全棧架構(gòu),收取更多有價(jià)值的信息。
