高並發壓測時,發現來自網關的消息出現粘包現象;分包就是勢在必行的
前置和處理平台(暫時)使用netty通話,由於都是服務器平台使用DelimiterBasedFrameDecoder來解決分包
和網關的通信,找出包長的字段,使用LengthFieldBasedFrameDecoder來解決分包;
這個類擁有很多構造器,對於底層的通信協議,只要上報的數據有字段標識了變長內容的長度,可以通過計算得到包長的,都可以通過該類解決;
剩下的兩種文中也有詳細說明
LineBasedFrameDecoder解碼器
LineBasedFrameDecoder是回車換行解碼器,如果用戶發送的消息以回車換行符作為消息結束的標識,則可以直接使用Netty的LineBasedFrameDecoder對消息進行解碼,只需要在初始化Netty服務端或者客戶端時將LineBasedFrameDecoder正確的添加到ChannelPipeline中即可,不需要自己重新實現一套換行解碼器。
FixedLengthFrameDecoder解碼器
FixedLengthFrameDecoder是固定長度解碼器,它能夠按照指定的長度對消息進行自動解碼,開發者不需要考慮TCP的粘包/拆包等問題,非常實用。
對於定長消息,如果消息實際長度小於定長,則往往會進行補位操作,它在一定程度上導致了空間和資源的浪費。但是它的優點也是非常明顯的,編解碼比較簡單,因此在實際項目中仍然有一定的應用場景。
下文對netty的粘包問題有比較詳細的說明
https://blog.csdn.net/a925907195/article/details/74942472
Netty是目前業界最流行的NIO框架之一,它的健壯性、高性能、可定制和可擴展性在同類框架中都是首屈一指。它已經得到了成百上千的商業項目的驗證,例如Hadoop的RPC框架Avro就使用了Netty作為底層通信框架,其他的業界主流RPC框架,例如:Dubbo、Google 開源的gRPC、新浪微博開源的Motan、Twitter 開源的 finagle也使用Netty來構建高性能的異步通信能力。另外,阿里巴巴開源的消息中間件RocketMQ也使用Netty作為底層通信框架。
TCP黏包/拆包
TCP是一個“流”協議,所謂流,就是沒有界限的一長串二進制數據。TCP作為傳輸層協議並不不了解上層業務數據的具體含義,它會根據TCP緩沖區的實際情況進行數據包的划分,所以在業務上認為是一個完整的包,可能會被TCP拆分成多個包進行發送,也有可能把多個小的包封裝成一個大的數據包發送,這就是所謂的TCP粘包和拆包問題。
粘包問題的解決策略
由於底層的TCP無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決。業界的主流協議的解決方案,可以歸納如下:
1. 消息定長,報文大小固定長度,例如每個報文的長度固定為200字節,如果不夠空位補空格;
2. 包尾添加特殊分隔符,例如每條報文結束都添加回車換行符(例如FTP協議)或者指定特殊字符作為報文分隔符,接收方通過特殊分隔符切分報文區分;
3. 將消息分為消息頭和消息體,消息頭中包含表示信息的總長度(或者消息體長度)的字段;
4. 更復雜的自定義應用層協議。
Netty粘包和拆包解決方案
Netty提供了多個解碼器,可以進行分包的操作,分別是:
* LineBasedFrameDecoder
* DelimiterBasedFrameDecoder(添加特殊分隔符報文來分包)
* FixedLengthFrameDecoder(使用定長的報文來分包)
* LengthFieldBasedFrameDecoder
第四種分包的參數比較復雜
轉自https://blog.csdn.net/thinking_fioa/article/details/80573483
LengthFieldBasedFrameDecoder - 參數說明
@author 魯偉林
網上諸多博客對於LengthFieldBasedFrameDecode解碼器的使用,翻譯和解釋過於死板,難於理解,特別是其構造函數的6個參數的解釋,過於字面化解釋。
該博客盡量保證通俗易懂,幫組讀者理解和使用。讀者可以選擇讀英文文檔。
工作量:
1. 詳細講解LengthFieldBasedFrameDecode中6個參數的作用和使用。maxFrameLength, lengthFieldOffset, lengthFieldLength,
lengthAdjustment, initialBytesToStrip, failFast。
2. 給出多個實例,幫助理解和使用LengthFieldBasedFrameDecode解碼器。
GitHub項目地址:
https://github.com/thinkingfioa/netty-learning/tree/master/netty-private-protocol
博客地址:
https://blog.csdn.net/thinking_fioa
Netty專欄地址:
https://blog.csdn.net/column/details/22861.html
1. LengthFieldBasedFrameDecoder作用
LengthFieldBasedFrameDecoder解碼器自定義長度解決TCP粘包黏包問題。所以LengthFieldBasedFrameDecoder又稱為: 自定義長度解碼器
1.1 TCP粘包和黏包現象
1. TCP粘包是指發送方發送的若干個數據包到接收方時粘成一個包。從接收緩沖區來看,后一個包數據的頭緊接着前一個數據的尾。
2. 當TCP連接建立后,Client發送多個報文給Server,TCP協議保證數據可靠性,但無法保證Client發了n個包,服務端也按照n個包接收。Client端發送n個數據包,Server端可能收到n-1或n+1個包。
1.2 為什么出現粘包現象
1. 發送方原因: TCP默認會使用Nagle算法。而Nagle算法主要做兩件事:1)只有上一個分組得到確認,才會發送下一個分組;2)收集多個小分組,在一個確認到來時一起發送。所以,正是Nagle算法造成了發送方有可能造成粘包現象。
2. 接收方原因: TCP接收方采用緩存方式讀取數據包,一次性讀取多個緩存中的數據包。自然出現前一個數據包的尾和后一個收據包的頭粘到一起。
1.3 如何解決粘包現象
1. 添加特殊符號,接收方通過這個特殊符號將接收到的數據包拆分開 - DelimiterBasedFrameDecoder特殊分隔符解碼器
2. 每次發送固定長度的數據包 - FixedLengthFrameDecoder定長編碼器
3. 在消息頭中定義長度字段,來標識消息的總長度 - LengthFieldBasedFrameDecoder自定義長度解碼器
2. LengthFieldBasedFrameDecoder怎么使用
1. LengthFieldBasedFrameDecoder本質上是ChannelHandler,一個處理入站事件的ChannelHandler
2. LengthFieldBasedFrameDecoder需要加入ChannelPipeline中,且位於鏈的頭部
---------------------
作者:thinking_fioa
來源:CSDN
原文:https://blog.csdn.net/thinking_fioa/article/details/80573483
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
3. LengthFieldBasedFrameDecoder - 6個參數解釋
LengthFieldBasedFrameDecoder是自定義長度解碼器,所以構造函數中6個參數,基本都圍繞那個定義長度域,進行的描述。
1. maxFrameLength - 發送的數據幀最大長度
2. lengthFieldOffset - 定義長度域位於發送的字節數組中的下標。換句話說:發送的字節數組中下標為${lengthFieldOffset}的地方是長度域的開始地方
3. lengthFieldLength - 用於描述定義的長度域的長度。換句話說:發送字節數組bytes時, 字節數組bytes[lengthFieldOffset, lengthFieldOffset+lengthFieldLength]域對應於的定義長度域部分
4. lengthAdjustment - 滿足公式: 發送的字節數組bytes.length - lengthFieldLength = bytes[lengthFieldOffset, lengthFieldOffset+lengthFieldLength] + lengthFieldOffset + lengthAdjustment
5. initialBytesToStrip - 接收到的發送數據包,去除前initialBytesToStrip位
6. failFast - true: 讀取到長度域超過maxFrameLength,就拋出一個 TooLongFrameException。false: 只有真正讀取完長度域的值表示的字節之后,才會拋出 TooLongFrameException,默認情況下設置為true,建議不要修改,否則可能會造成內存溢出
7. ByteOrder - 數據存儲采用大端模式或小端模式
代碼:
public LengthFieldBasedFrameDecoder(
ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset,
int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip,
boolean failFast) {
//...
}
划重點: 參照一個公式寫,肯定沒問題:
公式: 發送數據包長度 = 長度域的值 + lengthFieldOffset + lengthFieldLength + lengthAdjustment
4. 舉例解釋參數如何寫
客戶端多次發送"HELLO, WORLD"字符串給服務端。"HELLO, WORLD"共12字節(12B)。長度域中的內容是16進制的值,如下:
1. 0x000c -----> 12
2. 0x000e -----> 14
4.1 場景1
數據包大小: 14B = 長度域2B + "HELLO, WORLD"
解釋:
如上圖,長度域的值為12B(0x000c)。希望解碼后保持一樣,根據上面的公式,參數應該為:
1. lengthFieldOffset = 0
2. lengthFieldLength = 2
3. lengthAdjustment = 0 = 數據包長度(14) - lengthFieldOffset - lengthFieldLength - 長度域的值(12)
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
4.2 場景2
數據包大小: 14B = 長度域2B + "HELLO, WORLD"
解釋:
上圖中,解碼后,希望丟棄長度域2B字段,所以,只要initialBytesToStrip = 2即可。其他與場景1相同
1. lengthFieldOffset = 0
2. lengthFieldLength = 2
3. lengthAdjustment = 0 = 數據包長度(14) - lengthFieldOffset - lengthFieldLength - 長度域的值(12)
4. initialBytesToStrip = 2 解碼過程中,沒有丟棄2個字節的數據
4.3 場景3
數據包大小: 14B = 長度域2B + "HELLO, WORLD"。與場景1不同的是:場景3中長度域的值為14(0x000E)
解釋:
如上圖,長度域的值為14(0x000E)。希望解碼后保持一樣,根據上面的公式,參數應該為:
1. lengthFieldOffset = 0
2. lengthFieldLength = 2
3. lengthAdjustment = -2 = 數據包長度(14) - lengthFieldOffset - lengthFieldLength - 長度域的值(14)
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
4.4 場景4
場景4在長度域前添加2個字節的Header。長度域的值(0x00000C) = 12。總數據包長度: 17=Header(2B) + 長度域(3B) + "HELLO, WORLD"
解釋
如上圖。編碼解碼后,長度保持一致,所以initialBytesToStrip = 0。參數應該為:
1. lengthFieldOffset = 2
2. lengthFieldLength = 3
3. lengthAdjustment = 0 = 數據包長度(17) - lengthFieldOffset(2) - lengthFieldLength(3) - 長度域的值(12)
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
4.5 場景5
與場景4不同的地方是: Header與長度域的位置換了。總數據包長度: 17=長度域(3B) + Header(2B) + "HELLO, WORLD"
解釋
如上圖。編碼解碼后,長度保持一致,所以initialBytesToStrip = 0。參數應該為:
1. lengthFieldOffset = 0
2. lengthFieldLength = 3
3. lengthAdjustment = 2 = 數據包長度(17) - lengthFieldOffset(0) - lengthFieldLength(3) - 長度域的值(12)
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
4.6 場景6 - 終極復雜案例
如下圖,"HELLO, WORLD"域前有多個字段。總數據長度: 16 = HEADER1(1) + 長度域(2) + HEADER2(1) + "HELLO, WORLD"
1. lengthFieldOffset = 1
2. lengthFieldLength = 2
3. lengthAdjustment = 1 = 數據包長度(16) - lengthFieldOffset(1) - lengthFieldLength(2) - 長度域的值(12)
4. initialBytesToStrip = 0 - 解碼過程中,沒有丟棄任何數據
4.7 實際案例
我在項目Netty-private-protocol開發實際使用了LengthFieldBasedFrameDecoder自定義長度編碼器。在消息Header的前面添加4個字節(int型),用於表示整個數據包的長度,歡迎一起交流。所以,我的參數是:
1. lengthFieldOffset = 0
2. lengthFieldLength = 4
3. lengthAdjustment = -4 = 數據包長度(msgLen) - lengthFieldOffset(0) - lengthFieldLength(4) - msgLen
4. initialBytesToStrip = 0
5. 總結
請記住公式: 發送數據包長度 = 長度域的值 + lengthFieldOffset + lengthFieldLength + lengthAdjustment。
---------------------
作者:thinking_fioa
來源:CSDN
原文:https://blog.csdn.net/thinking_fioa/article/details/80573483
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!