摘要:而編碼器是講應(yīng)用程序的數(shù)據(jù)轉(zhuǎn)化為網(wǎng)絡(luò)格式,解碼器則是講網(wǎng)絡(luò)格式轉(zhuǎn)化為應(yīng)用程序,同時(shí)具備這兩種功能的單一組件就叫編解碼器。在中是老的編解碼器接口,而是新的編解碼器接口,并且已經(jīng)用把適配成了。
遠(yuǎn)程通訊——開篇
目標(biāo):介紹之后解讀遠(yuǎn)程通訊模塊的內(nèi)容如何編排、介紹dubbo-remoting-api中的包結(jié)構(gòu)設(shè)計(jì)以及最外層的的源碼解析。前言
服務(wù)治理框架中可以大致分為服務(wù)通信和服務(wù)管理兩個(gè)部分,前面我先講到有關(guān)注冊中心的內(nèi)容,也就是服務(wù)管理,當(dāng)然dubbo的服務(wù)管理還包括監(jiān)控中心、 telnet 命令,它們起到的是人工的服務(wù)管理作用,這個(gè)后續(xù)再介紹。接下來我要講解的就是跟服務(wù)通信有關(guān)的部分,也就是遠(yuǎn)程通訊模塊。我在《dubbo源碼解析(一)Hello,Dubbo》的"(六)dubbo-remoting——遠(yuǎn)程通信模塊“中提到過一些內(nèi)容。該模塊中提供了多種客戶端和服務(wù)端通信的功能,而在對NIO框架選型上,dubbo交由用戶選擇,它集成了mina、netty、grizzly等各類NIO框架來搭建NIO服務(wù)器和客戶端,并且利用dubbo的SPI擴(kuò)展機(jī)制可以讓用戶自定義選擇。如果對SPI不太了解的朋友可以查看《dubbo源碼解析(二)Dubbo擴(kuò)展機(jī)制SPI》。
接下來我們先來看看dubbo-remoting的包結(jié)構(gòu):
我接下來解讀遠(yuǎn)程通訊模塊的內(nèi)容并不是按照一個(gè)包一篇文章的編排,先來看看dubbo-remoting-api的包結(jié)構(gòu):
可以看到,大篇幅的邏輯在dubbo-remoting-api中,所以我對于dubbo-remoting-api的解讀會(huì)分為下面五個(gè)部分來說明,其中第五點(diǎn)會(huì)在本文介紹,其他四點(diǎn)會(huì)分別用四篇文章來介紹:
buffer包:緩沖在NIO框架中是很重要的存在,各個(gè)NIO框架都實(shí)現(xiàn)了自己相應(yīng)的緩存操作。這個(gè)buffer包下包括了緩沖區(qū)的接口以及抽象
exchange包:信息交換層,其中封裝了請求響應(yīng)模式,在傳輸層之上重新封裝了 Request-Response 語義,為了滿足RPC的需求。這層可以認(rèn)為專注在Request和Response攜帶的信息上。該層是RPC調(diào)用的通訊基礎(chǔ)之一。
telnet包:dubbo支持通過telnet命令來進(jìn)行服務(wù)治理,該包下就封裝了這些通用指令的邏輯實(shí)現(xiàn)。
transport包:網(wǎng)絡(luò)傳輸層,它只負(fù)責(zé)單向消息傳輸,是對 Mina, Netty, Grizzly 的抽象,它也可以擴(kuò)展 UDP 傳輸。該層是RPC調(diào)用的通訊基礎(chǔ)之一。
最外層的源碼:該部分我會(huì)在下面之間給出介紹。
為什么我要把一個(gè)api分成這么多文章來講解,我們先來看看下面的圖:
我們可以看到紅框內(nèi)的是遠(yuǎn)程通訊的框架,序列化我會(huì)在后面的主題中介紹,而Exchange層和Transport層在框架設(shè)計(jì)中起到了很重要的作用,也是支撐Remoting的核心,所以我要分開來介紹。
除了上述的五點(diǎn)外,根據(jù)慣例,我還是會(huì)分別介紹dubbo支持的實(shí)現(xiàn)客戶端和服務(wù)端通信的七種方案,也就是說該遠(yuǎn)程通訊模塊我會(huì)用12篇文章詳細(xì)的講解。
最外層源碼解析 (一)接口Endpointdubbo抽象出一個(gè)端的概念,也就是Endpoint接口,這個(gè)端就是一個(gè)點(diǎn),而點(diǎn)對點(diǎn)之間是可以雙向傳輸。在端的基礎(chǔ)上在衍生出通道、客戶端以及服務(wù)端的概念,也就是下面要介紹的Channel、Client、Server三個(gè)接口。在傳輸層,其實(shí)Client和Server的區(qū)別只是在語義上區(qū)別,并不區(qū)分請求和應(yīng)答職責(zé),在交換層客戶端和服務(wù)端也是一個(gè)點(diǎn),但是已經(jīng)是有方向的點(diǎn),所以區(qū)分了明確的請求和應(yīng)答職責(zé)。兩者都具備發(fā)送的能力,只是客戶端和服務(wù)端所關(guān)注的事情不一樣,這個(gè)在后面會(huì)分開介紹,而Endpoint接口抽象的方法就是它們共同擁有的方法。這也就是它們都能被抽象成端的原因。
來看一下它的源碼:
public interface Endpoint { // 獲得該端的url URL getUrl(); // 獲得該端的通道處理器 ChannelHandler getChannelHandler(); // 獲得該端的本地地址 InetSocketAddress getLocalAddress(); // 發(fā)送消息 void send(Object message) throws RemotingException; // 發(fā)送消息,sent是是否已經(jīng)發(fā)送的標(biāo)記 void send(Object message, boolean sent) throws RemotingException; // 關(guān)閉 void close(); // 優(yōu)雅的關(guān)閉,也就是加入了等待時(shí)間 void close(int timeout); // 開始關(guān)閉 void startClose(); // 判斷是否已經(jīng)關(guān)閉 boolean isClosed(); }
前三個(gè)方法是獲得該端本身的一些屬性,
兩個(gè)send方法是發(fā)送消息,其中第二個(gè)方法多了一個(gè)sent的參數(shù),為了區(qū)分是否是第一次發(fā)送消息。
后面幾個(gè)方法是提供了關(guān)閉通道的操作以及判斷通道是否關(guān)閉的操作。
(二)接口Channel該接口是通道接口,通道是通訊的載體。還是用自動(dòng)販賣機(jī)的例子,自動(dòng)販賣機(jī)就好比是一個(gè)通道,消息發(fā)送端會(huì)往通道輸入消息,而接收端會(huì)從通道讀消息。并且接收端發(fā)現(xiàn)通道沒有消息,就去做其他事情了,不會(huì)造成阻塞。所以channel可以讀也可以寫,并且可以異步讀寫。channel是client和server的傳輸橋梁。channel和client是一一對應(yīng)的,也就是一個(gè)client對應(yīng)一個(gè)channel,但是channel和server是多對一對關(guān)系,也就是一個(gè)server可以對應(yīng)多個(gè)channel。
public interface Channel extends Endpoint { // 獲得遠(yuǎn)程地址 InetSocketAddress getRemoteAddress(); // 判斷通道是否連接 boolean isConnected(); // 判斷是否有該key的值 boolean hasAttribute(String key); // 獲得該key對應(yīng)的值 Object getAttribute(String key); // 添加屬性 void setAttribute(String key, Object value); // 移除屬性 void removeAttribute(String key); }
可以看到Channel繼承了Endpoint,也就是端抽象出來的方法也同樣是channel所需要的。上面的幾個(gè)方法很好理解,我就不多介紹了。
(三)接口ChannelHandler@SPI public interface ChannelHandler { // 連接該通道 void connected(Channel channel) throws RemotingException; // 斷開該通道 void disconnected(Channel channel) throws RemotingException; // 發(fā)送給這個(gè)通道消息 void sent(Channel channel, Object message) throws RemotingException; // 從這個(gè)通道內(nèi)接收消息 void received(Channel channel, Object message) throws RemotingException; // 從這個(gè)通道內(nèi)捕獲異常 void caught(Channel channel, Throwable exception) throws RemotingException; }
該接口是負(fù)責(zé)channel中的邏輯處理,并且可以看到這個(gè)接口有注解@SPI,是個(gè)可擴(kuò)展接口,到時(shí)候都會(huì)在下面介紹各類NIO框架的時(shí)候會(huì)具體講到它的實(shí)現(xiàn)類。
(四)接口Clientpublic interface Client extends Endpoint, Channel, Resetable { // 重連 void reconnect() throws RemotingException; // 重置,不推薦使用 @Deprecated void reset(com.alibaba.dubbo.common.Parameters parameters); }
客戶端接口,可以看到它繼承了Endpoint、Channel和Resetable接口,繼承Endpoint的原因上面我已經(jīng)提到過了,客戶端和服務(wù)端其實(shí)只是語義上的不同,客戶端就是一個(gè)點(diǎn)。繼承Channel是因?yàn)榭蛻舳烁ǖ朗且灰粚?yīng)的,所以做了這樣的設(shè)計(jì),還繼承了Resetable接口是為了實(shí)現(xiàn)reset方法,該方法,不過已經(jīng)打上@Deprecated注解,不推薦使用。除了這些客戶端就只需要關(guān)注一個(gè)重連的操作。
這里插播一個(gè)公共模塊下的接口Resetable:
public interface Resetable { // 用于根據(jù)新傳入的 url 屬性,重置自己內(nèi)部的一些屬性 void reset(URL url); }
該方法就是根據(jù)新的url來重置內(nèi)部的屬性。
(五)接口Serverpublic interface Server extends Endpoint, Resetable { // 判斷是否綁定到本地端口,也就是該服務(wù)器是否啟動(dòng)成功,能夠連接、接收消息,提供服務(wù)。 boolean isBound(); // 獲得連接該服務(wù)器的通道 CollectiongetChannels(); // 通過遠(yuǎn)程地址獲得該地址對應(yīng)的通道 Channel getChannel(InetSocketAddress remoteAddress); @Deprecated void reset(com.alibaba.dubbo.common.Parameters parameters); }
該接口是服務(wù)端接口,繼承了Endpoint和Resetable,繼承Endpoint是因?yàn)榉?wù)端也是一個(gè)點(diǎn),繼承Resetable接口是為了繼承reset方法。除了這些以外,服務(wù)端獨(dú)有的是檢測是否啟動(dòng)成功,還有事獲得連接該服務(wù)器上所有通道,這里獲得所有通道其實(shí)就意味著獲得了所有連接該服務(wù)器的客戶端,因?yàn)榭蛻舳撕屯ǖ朗且灰粚?yīng)的。
(六)接口Codec && Codec2這兩個(gè)都是編解碼器,那么什么叫做編解碼器,在網(wǎng)絡(luò)中只是講數(shù)據(jù)看成是原始的字節(jié)序列,但是我們的應(yīng)用程序會(huì)把這些字節(jié)組織成有意義的信息,那么網(wǎng)絡(luò)字節(jié)流和數(shù)據(jù)間的轉(zhuǎn)化就是很常見的任務(wù)。而編碼器是講應(yīng)用程序的數(shù)據(jù)轉(zhuǎn)化為網(wǎng)絡(luò)格式,解碼器則是講網(wǎng)絡(luò)格式轉(zhuǎn)化為應(yīng)用程序,同時(shí)具備這兩種功能的單一組件就叫編解碼器。在dubbo中Codec是老的編解碼器接口,而Codec2是新的編解碼器接口,并且dubbo已經(jīng)用CodecAdapter把Codec適配成Codec2了。所以在這里我就介紹Codec2接口,畢竟人總要往前看。
@SPI public interface Codec2 { //編碼 @Adaptive({Constants.CODEC_KEY}) void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException; //解碼 @Adaptive({Constants.CODEC_KEY}) Object decode(Channel channel, ChannelBuffer buffer) throws IOException; enum DecodeResult { // 需要更多輸入和忽略一些輸入 NEED_MORE_INPUT, SKIP_SOME_INPUT } }
因?yàn)槭蔷幗獯a器,所以有兩個(gè)方法分別是編碼和解碼,上述有以下幾個(gè)關(guān)注的:
Codec2是一個(gè)可擴(kuò)展的接口,因?yàn)橛蠤SPI注解。
用到了Adaptive機(jī)制,首先去url中尋找key為codec的value,來加載url攜帶的配置中指定的codec的實(shí)現(xiàn)。
該接口中有個(gè)枚舉類型DecodeResult,因?yàn)榻獯a過程中,需要解決 TCP 拆包、粘包的場景,所以增加了這兩種解碼結(jié)果,關(guān)于TCP 拆包、粘包的場景我就不多解釋,不懂得朋友可以google一下。
(七)接口Decodeablepublic interface Decodeable { //解碼 public void decode() throws Exception; }
該接口是可解碼的接口,該接口有兩個(gè)作用,第一個(gè)是在調(diào)用真正的decode方法實(shí)現(xiàn)的時(shí)候會(huì)有一些校驗(yàn),判斷是否可以解碼,并且對解碼失敗會(huì)有一些消息設(shè)置;第二個(gè)是被用來message核對用的。后面看具體的實(shí)現(xiàn)會(huì)更了解該接口的作用。
(八)接口Dispatcher@SPI(AllDispatcher.NAME) public interface Dispatcher { // 調(diào)度 @Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"}) // The last two parameters are reserved for compatibility with the old configuration ChannelHandler dispatch(ChannelHandler handler, URL url); }
該接口是調(diào)度器接口,dispatch是線程池的調(diào)度方法,這邊有幾個(gè)注意點(diǎn):
該接口是一個(gè)可擴(kuò)展接口,并且默認(rèn)實(shí)現(xiàn)AllDispatcher,也就是所有消息都派發(fā)到線程池,包括請求,響應(yīng),連接事件,斷開事件,心跳等。
用了Adaptive注解,也就是按照URL中配置來加載實(shí)現(xiàn)類,后面兩個(gè)參數(shù)是為了兼容老版本,如果這是三個(gè)key對應(yīng)的值都為空,就選擇AllDispatcher來實(shí)現(xiàn)。
(九)接口Transporter@SPI("netty") public interface Transporter { // 綁定一個(gè)服務(wù)器 @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) Server bind(URL url, ChannelHandler handler) throws RemotingException; // 連接一個(gè)服務(wù)器,即創(chuàng)建一個(gè)客戶端 @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) Client connect(URL url, ChannelHandler handler) throws RemotingException; }
該接口是網(wǎng)絡(luò)傳輸接口,有以下幾個(gè)注意點(diǎn):
該接口是一個(gè)可擴(kuò)展的接口,并且默認(rèn)實(shí)現(xiàn)NettyTransporter。
用了dubbo SPI擴(kuò)展機(jī)制中的Adaptive注解,加載對應(yīng)的bind方法,使用url攜帶的server或者transporter屬性值,加載對應(yīng)的connect方法,使用url攜帶的client或者transporter屬性值,不了解SPI擴(kuò)展機(jī)制的可以查看《dubbo源碼解析(二)Dubbo擴(kuò)展機(jī)制SPI》。
(十)Transporterspublic class Transporters { static { // check duplicate jar package // 檢查重復(fù)的 jar 包 Version.checkDuplicate(Transporters.class); Version.checkDuplicate(RemotingException.class); } private Transporters() { } public static Server bind(String url, ChannelHandler... handler) throws RemotingException { return bind(URL.valueOf(url), handler); } public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException { if (url == null) { throw new IllegalArgumentException("url == null"); } if (handlers == null || handlers.length == 0) { throw new IllegalArgumentException("handlers == null"); } ChannelHandler handler; // 創(chuàng)建handler if (handlers.length == 1) { handler = handlers[0]; } else { handler = new ChannelHandlerDispatcher(handlers); } // 調(diào)用Transporter的實(shí)現(xiàn)類對象的bind方法。 // 例如實(shí)現(xiàn)NettyTransporter,則調(diào)用NettyTransporter的connect,并且返回相應(yīng)的server return getTransporter().bind(url, handler); } public static Client connect(String url, ChannelHandler... handler) throws RemotingException { return connect(URL.valueOf(url), handler); } public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException { if (url == null) { throw new IllegalArgumentException("url == null"); } ChannelHandler handler; if (handlers == null || handlers.length == 0) { handler = new ChannelHandlerAdapter(); } else if (handlers.length == 1) { handler = handlers[0]; } else { handler = new ChannelHandlerDispatcher(handlers); } // 調(diào)用Transporter的實(shí)現(xiàn)類對象的connect方法。 // 例如實(shí)現(xiàn)NettyTransporter,則調(diào)用NettyTransporter的connect,并且返回相應(yīng)的client return getTransporter().connect(url, handler); } public static Transporter getTransporter() { return ExtensionLoader.getExtensionLoader(Transporter.class).getAdaptiveExtension(); } }
該類用到了設(shè)計(jì)模式的外觀模式,通過該類的包裝,我們就不會(huì)看到內(nèi)部具體的實(shí)現(xiàn)細(xì)節(jié),這樣降低了程序的復(fù)雜度,也提高了程序的可維護(hù)性。比如這個(gè)類,包裝了調(diào)用各種實(shí)現(xiàn)Transporter接口的方法,通過getTransporter來獲得Transporter的實(shí)現(xiàn)對象,具體實(shí)現(xiàn)哪個(gè)實(shí)現(xiàn)類,取決于url中攜帶的配置信息,如果url中沒有相應(yīng)的配置,則默認(rèn)選擇@SPI中的默認(rèn)值netty。
bind和connect方法分別有兩個(gè)重載方法,其中的操作只是把把字符串的url轉(zhuǎn)化為URL對象。
靜態(tài)代碼塊中檢測了一下jar包是否有重復(fù)。
(十一)RemotingException && ExecutionException && TimeoutException這三個(gè)類是遠(yuǎn)程通信的異常類:
RemotingException繼承了Exception類,是遠(yuǎn)程通信的基礎(chǔ)異常。
ExecutionException繼承了RemotingException類,ExecutionException是遠(yuǎn)程通信的執(zhí)行異常。
TimeoutException繼承了RemotingException類,TimeoutException是超時(shí)異常。
為了不影響篇幅,這三個(gè)類源碼我就不介紹了,因?yàn)楸容^簡單。
后記該部分相關(guān)的源碼解析地址:https://github.com/CrazyHZM/i...
該文章講解了dubbo-remoting-api中的包結(jié)構(gòu)設(shè)計(jì)以及最外層的的源碼解析,其中關(guān)鍵的是理解端的概念,明白在哪一層才區(qū)分了發(fā)送和接收的職責(zé),后續(xù)文章會(huì)按照我上面的編排去寫。如果我在哪一部分寫的不夠到位或者寫錯(cuò)了,歡迎給我提意見,我的私人微信號(hào)碼:HUA799695226。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/72579.html
摘要:遠(yuǎn)程調(diào)用開篇目標(biāo)介紹之后解讀遠(yuǎn)程調(diào)用模塊的內(nèi)容如何編排介紹中的包結(jié)構(gòu)設(shè)計(jì)以及最外層的的源碼解析。十該類就是遠(yuǎn)程調(diào)用的上下文,貫穿著整個(gè)調(diào)用,例如調(diào)用,然后調(diào)用。十五該類是系統(tǒng)上下文,僅供內(nèi)部使用。 遠(yuǎn)程調(diào)用——開篇 目標(biāo):介紹之后解讀遠(yuǎn)程調(diào)用模塊的內(nèi)容如何編排、介紹dubbo-rpc-api中的包結(jié)構(gòu)設(shè)計(jì)以及最外層的的源碼解析。 前言 最近我面臨著一個(gè)選擇,因?yàn)閐ubbo 2.7.0-...
摘要:而存在的意義就是保證請求或響應(yīng)對象可在線程池中被解碼,解碼完成后,就會(huì)分發(fā)到的。 2.7大揭秘——服務(wù)端處理請求過程 目標(biāo):從源碼的角度分析服務(wù)端接收到請求后的一系列操作,最終把客戶端需要的值返回。 前言 上一篇講到了消費(fèi)端發(fā)送請求的過程,該篇就要將服務(wù)端處理請求的過程。也就是當(dāng)服務(wù)端收到請求數(shù)據(jù)包后的一系列處理以及如何返回最終結(jié)果。我們也知道消費(fèi)端在發(fā)送請求的時(shí)候已經(jīng)做了編碼,所以我...
摘要:可以參考源碼解析二十四遠(yuǎn)程調(diào)用協(xié)議的八。十六的該類也是用了適配器模式,該類主要的作用就是增加了心跳功能,可以參考源碼解析十遠(yuǎn)程通信層的四。二十的可以參考源碼解析十七遠(yuǎn)程通信的一。 2.7大揭秘——消費(fèi)端發(fā)送請求過程 目標(biāo):從源碼的角度分析一個(gè)服務(wù)方法調(diào)用經(jīng)歷怎么樣的磨難以后到達(dá)服務(wù)端。 前言 前一篇文章講到的是引用服務(wù)的過程,引用服務(wù)無非就是創(chuàng)建出一個(gè)代理。供消費(fèi)者調(diào)用服務(wù)的相關(guān)方法。...
摘要:大揭秘異步化改造目標(biāo)從源碼的角度分析的新特性中對于異步化的改造原理。看源碼解析四十六消費(fèi)端發(fā)送請求過程講到的十四的,在以前的邏輯會(huì)直接在方法中根據(jù)配置區(qū)分同步異步單向調(diào)用。改為關(guān)于可以參考源碼解析十遠(yuǎn)程通信層的六。 2.7大揭秘——異步化改造 目標(biāo):從源碼的角度分析2.7的新特性中對于異步化的改造原理。 前言 dubbo中提供了很多類型的協(xié)議,關(guān)于協(xié)議的系列可以查看下面的文章: du...
摘要:服務(wù)引用過程目標(biāo)從源碼的角度分析服務(wù)引用過程。并保留服務(wù)提供者的部分配置,比如版本,,時(shí)間戳等最后將合并后的配置設(shè)置為查詢字符串中。的可以參考源碼解析二十三遠(yuǎn)程調(diào)用的一的源碼分析。 dubbo服務(wù)引用過程 目標(biāo):從源碼的角度分析服務(wù)引用過程。 前言 前面服務(wù)暴露過程的文章講解到,服務(wù)引用有兩種方式,一種就是直連,也就是直接指定服務(wù)的地址來進(jìn)行引用,這種方式更多的時(shí)候被用來做服務(wù)測試,不...
閱讀 2882·2021-11-24 09:39
閱讀 2454·2019-08-30 15:53
閱讀 3024·2019-08-30 13:47
閱讀 1295·2019-08-30 12:50
閱讀 1480·2019-08-29 16:31
閱讀 2641·2019-08-29 13:14
閱讀 1558·2019-08-29 10:55
閱讀 789·2019-08-26 13:32