我與Dubbo的二三事
我是2016年畢業的,在我畢業之前,我在學校裡面學到的架構都是SSH,即struts+spring+hibernate,是的你沒有看錯,在大學裡面的課本裡面學的是strusts,這個還沒畢業就被基本抛棄的架構。然而我大四出去實習,用的技術是SSM,即Spring,SpringMVC,Mybatis。實習的時候做的項目都是外包項目,非常傳統的單體大項目,和學校裡面做課程設計一樣,所有的功能包括前後端都糅合在一個項目裡面,根本不知道什麼是分布式架構,不誇張的說,那個時候我對分布式這一塊的知識無限趨近于零。
第一次接觸到分布式的概念是我正式參加工作後,第一家公司屬于一家網際網路公司,做第三方支付。我甚至現在還記得加入這個公司之後,第一次在同僚的幫助下,分别把支付服務和賬務服務的Demo,兩個項目,在兩個IDEA中運作起來,然後我在賬務服務打了一個斷點,運作支付服務的測試用例,最後程式在賬務服務的斷點處停了下來!程式停下來的時候,我仿佛感覺看到了"神迹",颠覆了我前4年的大學學習中的固有印象!那個時候,我才知道了還有分布式這麼一回事,才第一次接觸到Dubbo,那個時候,程式猿的大門才向我徐徐打開,那個時候,我才知道,我一直在新手村待了4年。
Dubbo的坎坷一生
你的一生中總是會碰到幾個十分神秘的人,他們看起來或者仙風道骨,或者平平無奇,他們總是問你一些終極的問題,總是會引起你的思考,你也會無情的拒絕,因為你知道,這事,不靠譜,這些人就是“算命先生”,總是問你:"你從哪裡來?你往哪裡去?你60歲的時候會有一道坎,了解一下?"
我不知道我的前世,也無法預知自己今生還沒發生的坎,但是我知道Dubbo從哪裡來,往哪裡去,了解一下?
出生豪門:
2011 年 10 月 27 日,阿裡巴巴開源了自己服務化治理方案的核心架構 Dubbo,服務治理的設計理念開始逐漸在國内軟體行業中落地,并被廣泛應用。自開源後,許多非阿裡系公司選擇使用 Dubbo。
半路夭折:
2012 年 10 月 23 日,Dubbo 2.5.3 釋出後,在 Dubbo 開源将滿一周年之際,阿裡基本停止了對 Dubbo 的主要更新。
2012 年 10 月 23 日,Dubbo 2.5.3 釋出後,在 Dubbo 開源将滿一周年之際,阿裡基本停止了對 Dubbo 的主要更新。2013 年,2014 年,更新了 2 次 Dubbo 2.4 的維護版本,然後停止了所有維護工作。至此,Dubbo 對 Spring 的支援也停留在了 Spring 2.5.6 版本上。
同行續命:
阿裡停止維護和更新 Dubbo 期間,當當網開始維護自己的 Dubbo 分支版本 Dubbox,新增支援了新版本的 Spring,支援了 Rest 協定等,并對外開源了 Dubbox。同時,網易考拉也維護了自己的獨立分支 Dubbok,可惜并未對外開源。
起死回生:
2017 年 9 月 7 日,Dubbo 悄悄在 GitHub 釋出了 2.5.4 版本。随後,又迅速釋出了 2.5.5、2.5.6、2.5.7 等版本。在 10 月舉行的雲栖大會上,阿裡宣布 Dubbo 被列入集團重點維護開源項目,這也就意味着 Dubbo 起死回生,開始重新進入快車道。
回歸正統:
2018 年 1 月 8 日,Dubbo 2.6.0 版本釋出,新版本将之前當當網開源的 Dubbox 進行了合并,實作了 Dubbo 版本的統一整合。
走向巅峰:
2018年2月,阿裡巴巴宣布将Dubbo捐獻給apache,進入apache孵化器。
2019 年 1 月,2.7.0 release 版本釋出,這個即将畢業的 apache 版本支援了豐富的新特性,全新的 Dubbo Ops 控制台。時至 5 月,Dubbo 來到了 2.7.2 版本,期間積極引入了新的特性,支援 consul,nacos,etcd 等注冊中心。
2019 年 5 月 21 号,經過了漫長的孵化期,Dubbo 迎來了畢業。成為Apache基金會頂級項目。
從Dubbo的曆程可以看出,Dubbo的一生是坎坷的一生,雖然半路夭折,但是最後還是走向了巅峰。不知道為什麼,這個時候我想起了馬雲爸爸說的一句話:

阿裡說:我對Dubbo沒有興趣。因為我最快樂的時候,是當當網,幫我續命的時候!
很有馬雲爸爸的氣質,一脈相承,厲害厲害!
Dubbo的異步化改造
Dubbo2.7新特性包括但不限于如下幾點:
1.異步化改造
2.三大中心改造
3.服務治理增強
本文主要分享Dubbo2.7新特征之一,異步化改造相關的内容。
Dubbo的四種調用方式:
此圖是本文的核心,本文分享的内容基本上都是對于此圖深入到源碼解級别的解析:
1.oneway --- 有去無回
oneway 指的是用戶端發送消息後,不需要接受響應。對于那些不關心服務端響應的請求,比較适合使用 oneway 通信。但是請注意,傳回值定義為void的并不是oneway的調用方式,void表示的程式上不需要關心傳回值,但是對Dubbo架構而言,還是需要建構傳回資料的。
仔細看oneway調用方式的圖,可以看出:從用戶端到服務端,隻有req,沒有resp;是以用戶端不需要阻塞等待。
2.sync --- 同步調用
2.sync是最常用的通信方式,也是Dubbo預設的通信方法。
還是仔細看sync調用方式的圖,再想一想你自己寫的Dubbo應用,或者公司其他的Dubbo應用,是不是就是你們現在正在使用的通信方式。客服端發起req請求到A服務端,然後在設定的逾時時間内,一直等待A伺服器的響應resp,這個時候,我們說用戶端處于阻塞的狀态。當A伺服器傳回resp後,用戶端才會繼續運作。
3.future和callback --- 異步調用
future 和 callback 都屬于異步調用的範疇,我們放在一起讨論。
繼續仔細看future和callback調用方式的圖,可以看出他們的差別是:在接收響應時,future.get() 會導緻線程的阻塞;callback 通常會設定一個回調線程,當接收到服務端響應時,自動執行,不會對目前線程造成阻塞。
源碼之下無秘密
1.Dubbo 2.6.0中展現調用方式的關鍵代碼
有了前面的四種調用方式的簡單介紹鋪墊。我們深入到源碼中一探究竟:
Dubbo2.6.0 --- DubboInvoke.doInvoke()方法
上圖是Dubbo2.6.0版本DubboInvoke.doInvoke()方法的截圖,先看個全局的代碼。
其中的箭頭解釋一下:
箭頭1:表明這段代碼的版本,Dubbo2.6.0版本。
箭頭2:判斷調用方式是否是oneway模式,即有去無回調用。
箭頭3:判斷調用是否是否是async模式,即異步調用。
箭頭4:既不是有去無回(oneway),也不是 異步調用(async),那麼就是sync模式,即同步調用。
對于紅框中的代碼,放大如下:
Dubbo2.6.0 --- DubboInvoke.doInvoke()方法關鍵代碼
接下來對關鍵代碼進行解讀:
Dubbo2.6.0 --- DubboInvoke.doInvoke()方法關鍵代碼解讀
1.首先,Dubbo是怎麼判斷調用方式是前面說的4種調用方式(對于Dubbo2.6.x來說,其實是3種,2.7.0之後才支援了callback的調用方式)的哪一種的呢?
可以看到這兩行代碼:
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
可以對着關鍵代碼解讀的圖來看,這兩行代碼的用途,就是判斷你的配置檔案(注解的方式或者dubbo.xml)中有沒有配置async=true或者return=true。
2.接下來我們重點看一下我說的“最騷”的這一行代碼:
"騷"在哪裡?
是的,當是異步調用的時候,Dubbo把future放到RpcContext的上下文中,然後構造一個空的RpcResult給調用方,調用方再從上下文中把future取出來,需要用傳回的值的時候調用一下future.get()方法。完成異步調用的操作。
同步調用的時候,dubbo也有拿到了這個future,但是并沒有傳回,而是直接調用了future.get()方法,這就是同步調用。
綜上:我認為同步調用和異步調用的差別就是誰去調用future.get()方法。如果是Dubbo調用則是同步調用,如果是用戶端調用者是異步方法。
2.Dubbo 2.7.0中展現調用方式的關鍵代碼
接下來,我們看一下Alibaba提供給Apache的初始版本,即2.7.0版本中展現調用方式的關鍵代碼。
Dubbo2.7.0 --- DubboInvoke.doInvoke()方法關鍵代碼
朋友們可以先看左上角,确實是Dubbo2.7.0版本的代碼。然後紅框中圈起來的代碼,看起來和Dubbo2.6.0版本中的差不多,那我們就對比着看。
Dubbo2.6.0與Dubbo2.7.0 DubboInvoke.doInvoke()關鍵代碼對比
看到這個地方的時候我曾經走了一點彎路,甚至走上了歧途,一度質疑Dubbo的這個地方的源碼是有問題的,畢竟我們搞技術的,就是一個大膽假設,小心求證吧。是以我給Dubbo提了一個issue.如下:
Dubbo issus#4421
這裡就不講我走上歧途的過程了,後面有機會再分享。連接配接在這裡,大家可以去看看,會不會被固化思維給帶偏了。Dubbo issus#4421
這裡的兩個回答,第一個解答了我的問題,我看了後恍然大悟,這題屬于當局者迷旁觀者清。
第二個回答,建議我看一下最新版本的代碼,當時的最新版本的代碼是2.7.3。是以我把2.7.3版本的代碼拉了下來。
3.Dubbo 2.7.3中展現調用方式的關鍵代碼
接下來,我們就看看2.7.3中展現調用方式的關鍵代碼,請各位朋友坐穩扶好,這裡變化較大,車速較快,非常優秀。
Dubbo 2.7.3中展現調用方式的關鍵代碼
首先我們可以看到isOneway的判斷還是我們熟悉的代碼。但是這裡隻有一個if-else了。Dubbo調用有四種方式,if判斷了isOneway,那麼剩下的三種都在這個else裡面啦。
看到這裡,筆者冷靜的思考了一下,剩下的三種調用方式,sync調用,future調用,callback調用。其中sync調用是預設的方式,沒有在這個地方展現出來,那麼直覺告訴我在某個地方一定有一個異步轉同步的調用。于是乎,我發現了這樣一個神奇的類:
AsyncToSyncInvoker方法調用
AsyncToSyncInvoker方法中的54行asyncResult.get(),其中asyncResult繼承自Future,用源碼說話:
Result繼承了Future
接着我們說說AsyncToSyncInvoker方法中的53行,getInvokeMode().
getInvokeMode()是RpcInvocation裡InvokeMode的get方法。而且2.6.0裡面RpcInvocation是沒有invokeMode這個成員變量的。是2.7.0版本後新加的。
剩下的三種調用方式在InvokeMode裡面
至此,基本圓滿了。感謝大神指引我看最新版本的代碼。
然後在上一個對比圖:
Dubbo2.6.0/2.7.0/2.7.3 DubboInvoke.doInvoke()關鍵代碼對比
Show me the code
大佬的微笑
Dubbo 2.6.0的異步化實作:
1.dubbo.xml配置,加入async="true"
<dubbo:reference id="asyncService" inter async="true"/>
2.dubbo接口定義:
public interface AsyncService{
String sayHello(String name);
}
3.異步調用,從RpcContext上下文中取出future,然後調用這個最"騷"的future.get()方法。還記得之前說的嘛:同步調用和異步調用的差別就是誰去調用future.get()方法。這裡是用戶端調用,是以是異步調用。
AsyncService.sayHello("hello");
Future<String> fooFuture=RpcContext.getContext().getFuture();
fooFuture.get();
有幾個弊端:
1.不太符合異步程式設計的習慣,需要從一個上下文類中擷取到 Future
2.如果多個異步調用,使用不當很容易造成上下文污染
3.Future 并不支援 callback 的調用方式
Dubbo 2.7.x的異步化實作:
無需相關配置中進行特殊配置,顯示聲明異步接口即可:
public interface AsyncService{
String sayHello(String name);
default CompletableFuture<String> sayHiAsync(String name){
return CompletableFuture.completedFuture(sayHello(name));
}
}
使用callback方式處理傳回值
CompletableFuture<String> future = asyncService.sayHiAsync("hi");
future.whenComplete((retValue, exception) ->{
if (exception == null) {
System.out.println(retValue);
} else {
exception.printStackTrace();
}
});
那麼為什麼Dubbo2.7.0這樣簡單的幾行代碼就能實作異步化了呢?記住,源碼之下無秘密:
源碼之下無秘密
完結撒花,謝謝大家!歡迎大家關注我的公衆号。
why技術