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