【原創】Dubbo 2.7新特性之異步化改造

 我與Dubbo的二三事spring

我是2016年畢業的,在我畢業以前,我在學校裏面學到的框架都是SSH,即struts+spring+hibernate,是的你沒有看錯,在大學裏面的課本里面學的是strusts,這個還沒畢業就被基本拋棄的框架。然而我大四出去實習,用的技術是SSM,即Spring,SpringMVC,Mybatis。實習的時候作的項目都是外包項目,很是傳統的單體大項目,和學校裏面作課程設計同樣,全部的功能包括先後端都糅合在一個項目裏面,根本不知道什麼是分佈式架構,不誇張的說,那個時候我對分佈式這一塊的知識無限趨近於零。apache

第一次接觸到分佈式的概念是我正式參加工做後,第一家公司屬於一家互聯網公司,作第三方支付。我甚至如今還記得加入這個公司以後,第一次在同事的幫助下,分別把支付服務和帳務服務的Demo,兩個項目,在兩個IDEA中運行起來,而後我在帳務服務打了一個斷點,運行支付服務的測試用例,最後程序在帳務服務的斷點處停了下來!程序停下來的時候,我彷彿感受看到了"神蹟",顛覆了我前4年的大學學習中的固有印象!那個時候,我才知道了還有分佈式這麼一回事,才第一次接觸到Dubbo,那個時候,程序猿的大門才向我徐徐打開,那個時候,我才知道,我一直在新手村待了4年。編程


Dubbo的坎坷一輩子

你的一輩子中老是會碰到幾個十分神祕的人,他們看起來或者仙風道骨,或者平平無奇,他們老是問你一些終極的問題,老是會引發你的思考,你也會無情的拒絕,由於你知道,這事,不靠譜,這些人就是「算命先生」,老是問你:"你從哪裏來?你往哪裏去?你60歲的時候會有一道坎,瞭解一下?"後端

我不知道個人前世,也沒法預知本身此生還沒發生的坎,可是我知道Dubbo從哪裏來,往哪裏去,瞭解一下?api

出生豪門:服務器

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 版本上。async

同行續命:

阿里中止維護和升級 Dubbo 期間,噹噹網開始維護本身的 Dubbo 分支版本 Dubbox,新增支持了新版本的 Spring,支持了 Rest 協議等,並對外開源了 Dubbox。同時,網易考拉也維護了本身的獨立分支 Dubbok,惋惜並未對外開源。

起死回生:

2017 年 9 月 7 日,Dubbo 悄悄在 GitHub 發佈了 2.5.4 版本。隨後,又迅速發佈了 2.5.五、2.5.六、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 --- 同步調用

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()方法的截圖,先看個全局的代碼。

其中的箭頭解釋一下:

箭頭1:代表這段代碼的版本,Dubbo2.6.0版本。

箭頭2:判斷調用方式是不是oneway模式,即有去無回調用。

箭頭3:判斷調用是不是否是async模式,即異步調用。

箭頭4:既不是有去無回(oneway),也不是 異步調用(async),那麼就是sync模式,即同步調用。

對於紅框中的代碼,放大以下:

接下來對關鍵代碼進行解讀:

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版本的代碼。而後紅框中圈起來的代碼,看起來和Dubbo2.6.0版本中的差很少,那咱們就對比着看。

看到這個地方的時候我曾經走了一點彎路,甚至走上了歧途,一度質疑Dubbo的這個地方的源碼是有問題的,畢竟咱們搞技術的,就是一個大膽假設,當心求證吧。因此我給Dubbo提了一個issus.以下:

這裏就不講我走上歧途的過程了,後面有機會再分享。你們能夠去看看,會不會被固化思惟給帶偏了。

這裏的兩個回答,第一個解答了個人問題,我看了後恍然大悟,這題屬於當局者迷旁觀者清。

第二個回答,建議我看一下最新版本的代碼,當時的最新版本的代碼是2.7.3。因此我把2.7.3版本的代碼拉了下來。

3.Dubbo 2.7.3中體現調用方式的關鍵代碼

接下來,咱們就看看2.7.3中體現調用方式的關鍵代碼,請各位朋友坐穩扶好,這裏變化較大,車速較快,很是優秀。

 

首先咱們能夠看到isOneway的判斷仍是咱們熟悉的代碼。可是這裏只有一個if-else了。Dubbo調用有四種方式,if判斷了isOneway,那麼剩下的三種都在這個else裏面啦。

看到這裏,筆者冷靜的思考了一下,剩下的三種調用方式,sync調用,future調用,callback調用。其中sync調用是默認的方式,沒有在這個地方體現出來,那麼直覺告訴我在某個地方必定有一個異步轉同步的調用。因而乎,我發現了這樣一個神奇的類:

AsyncToSyncInvoker方法中的54行asyncResult.get(),其中asyncResult繼承自Future,用源碼說話:

接着咱們說說AsyncToSyncInvoker方法中的53行,getInvokeMode().

getInvokeMode()是RpcInvocation裏InvokeMode的get方法。並且2.6.0裏面RpcInvocation是沒有invokeMode這個成員變量的。是2.7.0版本後新加的。

至此,基本圓滿了。感謝大神指引我看最新版本的代碼。

而後在上一個對比圖:

Show me the code

Dubbo 2.6.0的異步化實現:

1.dubbo.xml配置,加入async="true"

<dubbo:reference id="asyncService" interface="org.apache.dubbo.demo.api.AsyncService" 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這樣簡單的幾行代碼就能實現異步化了呢?記住,源碼之下無祕密:

完結撒花,關注我吧。下期再見,謝謝你們!

再推銷一下我公衆號:對於寫文章,其實想到寫什麼內容並不難,難的是你對內容的把控。關於技術性的語言,我是反覆推敲,查閱大量文章來進行證僞,總之慎言慎言再慎言,畢竟作技術,我認爲是一件很是嚴謹的事情,我經常想象本身就是在故宮修文物的工匠,在工匠精神的認知上,目前我可能和他們還差的有點遠,可是我時常以工匠精神要求本身。就像我以前表達的:對於技術文章(由於我偶爾也會荒腔走板的聊一聊生活,寫一寫書評,影評),我儘可能保證周推,全力保證質量。堅持輸出原創。

才疏學淺,不免會有紕漏,若是你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。
以上。
謝謝您的閱讀,感謝您的關注。

相關文章
相關標籤/搜索