天天看點

手寫RPC-對RPC簡單的了解

RPC簡單的了解

其實RPC也是一種協定或者思想,在網絡環境中,他需要基于某種“網絡協定”,這種“網絡協定”如果在OSI中,需要有支援 “傳輸層” 的功能,比如經典的TCP/IP或者是Dubbo新出的Triple協定等這些在網絡中具有傳輸功能的協定做支撐;然後具有可以自己處理 編碼,序列化或者說是按指定格式交換資料的功能,也可以擴充新字段傳輸,這是差別傳統的傳輸層多出來的功能!!

RPC調用過程

手寫RPC-對RPC簡單的了解

Feign調用過程

手寫RPC-對RPC簡單的了解

設計一個高可用的RPC架構

  • 注冊中心:服務多了,我們需要有個東西去儲存服務的中繼資料資訊,比如redis、zookeeper、nacos等,都可以完成這個工作;
  • 序列化和反序列話:這個就是去解決遠端調用時傳輸資料的問題;這裡的雙向轉換也是一個會存在性能相關的問題,可以選擇protobuf之類的;
  • 負載均衡問題:類似于ribbon這種負載均衡架構,我們也可以用一些算法,寫自己的負載均衡政策;
  • 傳輸架構:一般我們考慮的是基于OSI裡面的傳輸層協定做選擇,比如TCP/IP;當然也可以http,但http方式傳輸效率肯定沒有基于TCP好,且http多了很多無用的封包資料;當然這裡也有很多公司自研的一些傳輸層協定架構Triple等;

    Tips:這裡的話有個面試經常問的問題,就是你自己的協定怎麼實作的?這裡一般指的是“傳輸架構 + 加上自己定義的編解碼”,也就是叫做“通信協定”;這裡就比如基于http的,http也有自己的消息協定,什麼header、cookie之類的;

  • 緩存:引入一個緩存也是有必要的,當服務已經注冊。那我們其實可以将這些中繼資料資訊緩存起來,這樣找到對應的函數調用會更快;
  • 異步調用:也就是請求過來不需要一直等待,而可以很快的去處理下一個請求,這裡異步調用我們可以選擇使用基于netty的NIO去實作;
  • 健康檢查:我們可以通過心跳得方式去做健康檢查;
  • SPI機制,可以引入序列化、壓縮等自己的實作;

關于TCP粘包/拆包的問題

TCP的粘包和拆包問題往往出現在基于TCP協定的通訊中,比如RPC架構、Netty等。如果你的履歷中寫了類似的技術或者你所面試的公司使用了相關的技術,被問到該面試的幾率會非常高。

今天這篇文章就帶大家詳細了解一下TCP的粘包和拆包以及解決方案。

什麼是粘包?

在學習粘包之前,先糾正一下讀音,很多視訊教程中将“粘”讀作“nián”。經過調研,個人更傾向于讀“zhān bāo”。

如果在百度百科上搜尋“粘包”,對應的讀音便是“zhān bāo”,語義解釋為:網絡技術術語。指TCP協定中,發送方發送的若幹包資料到接收方接收時粘成一包,從接收緩沖區看,後一包資料的頭緊接着前一包資料的尾。

TCP是面向位元組流的協定,就是沒有界限的一串資料,本沒有“包”的概念,“粘包”和“拆包”一說是為了有助于形象地了解這兩種現象。

為什麼UDP沒有粘包?

粘包拆包問題在資料鍊路層、網絡層以及傳輸層都有可能發生。日常的網絡應用開發大都在傳輸層進行,由于UDP有消息保護邊界,不會發生粘包拆包問題,是以粘包拆包問題隻發生在TCP協定中。

粘包拆包發生場景

因為TCP是面向流,沒有邊界,而作業系統在發送TCP資料時,會通過緩沖區來進行優化,例如緩沖區為1024個位元組大小。

如果一次請求發送的資料量比較小,沒達到緩沖區大小,TCP則會将多個請求合并為同一個請求進行發送,這就形成了粘包問題。

如果一次請求發送的資料量比較大,超過了緩沖區大小,TCP就會将其拆分為多次發送,這就是拆包。

關于粘包和拆包可以參考下圖的幾種情況:

手寫RPC-對RPC簡單的了解

上圖中示範了以下幾種情況:

  • 正常的理想情況,兩個包恰好滿足TCP緩沖區的大小或達到TCP等待時長,分别發送兩個包;
  • 粘包:兩個包較小,間隔時間短,發生粘包,合并成一個包發送;
  • 拆包:一個包過大,超過緩存區大小,拆分成兩個或多個包發送;
  • 拆包和粘包:Packet1過大,進行了拆包處理,而拆出去的一部分又與Packet2進行粘包處理。

常見的解決方案

對于粘包和拆包問題,常見的解決方案有四種:

  • 發送端将每個包都封裝成固定的長度,比如100位元組大小。如果不足100位元組可通過補0或空等進行填充到指定長度;
  • 發送端在每個包的末尾使用固定的分隔符,例如\r\n。如果發生拆包需等待多個包發送過來之後再找到其中的\r\n進行合并;例如,FTP協定;
  • 将消息分為頭部和消息體,頭部中儲存整個消息的長度,隻有讀取到足夠長度的消息之後才算是讀到了一個完整的消息;
  • 通過自定義協定進行粘包和拆包的處理。

Netty對粘包和拆包問題的處理

Netty對解決粘包和拆包的方案做了抽象,提供了一些解碼器(Decoder)來解決粘包和拆包的問題。如:

  • LineBasedFrameDecoder:以行為機關進行資料包的解碼;
  • DelimiterBasedFrameDecoder:以特殊的符号作為分隔來進行資料包的解碼;
  • FixedLengthFrameDecoder:以固定長度進行資料包的解碼;
  • LenghtFieldBasedFrameDecode:适用于消息頭包含消息長度的協定(最常用);

基于Netty進行網絡讀寫的程式,可以直接使用這些Decoder來完成資料包的解碼。對于高并發、大流量的系統來說,每個資料包都不應該傳輸多餘的資料(是以補齊的方式不可取),LenghtFieldBasedFrameDecode更适合這樣的場景。

可以看看具體的代碼:

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        /* 解碼器 */
        // 基于換行符号
        channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
        // 基于指定字元串【換行符,這樣功能等同于LineBasedFrameDecoder】
        // e.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, false, Delimiters.lineDelimiter()));
        // 基于最大長度
        // e.pipeline().addLast(new FixedLengthFrameDecoder(4));
        // 解碼轉String,注意調整自己的編碼格式GBK、UTF-8
        channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
        //在管道中添加我們自己的接收資料實作方法
        channel.pipeline().addLast(new MyServerHandler());
    }

}      

謝謝大家閱讀!!!