什么是寫半包
寫半包:一份數(shù)據(jù),一次發(fā)送沒有把他全部發(fā)送,需要循環(huán)發(fā)送,那么第一次的操作稱為寫半包
什么情況下會出現(xiàn)寫半包:
發(fā)送方發(fā)送200byte,但是接收方只能接受100byte,因此發(fā)送方只會發(fā)送小于100byte的數(shù)據(jù)。
說到這里,機智的小伙伴已經想到了這跟TCP滑動窗口和消息中間件中常見的消息堆積是一個道理。
總的來說:接收方頂不住來自發(fā)送方的數(shù)據(jù)壓力。
對?.NETty來說就是,這個時刻TCP發(fā)送緩沖區(qū)滿了,無法再接收整包數(shù)據(jù),剩下的數(shù)據(jù)則會通過Channel去監(jiān)聽寫操作,當觸發(fā)寫操作的時候,再把這部分數(shù)據(jù)給帶上,那么這部分數(shù)據(jù)才完整地傳輸。
Netty中的寫半包處理
前提知識:Netty中的網絡數(shù)據(jù)讀寫,都先經過ByteBuf 
- 讀操作:從
ByteBuf中讀取數(shù)據(jù) - 寫操作:將數(shù)據(jù)寫入到
ByteBuf,然后再通過其他方式把ByteBuf的數(shù)據(jù)寫入(#doWrite)
Netty中的網路操作都是通過Channel和里面聚合的對象Unsafe對象進行操作,簡單介紹一下。
Channel
Channel的作用:給Netty用來進行網絡網絡
JDK 也有自己原生的Channel,但是為了方便框架擴展使用,Netty采用的是封裝了一層Facade(門面模式)。
最重要的是能夠支持Netty的自定義Channel來應對不同的業(yè)務場景。
Channel會被注冊到EventLoop上,在注冊的時候定義好感興趣的事件,他采用的是基于事件觸發(fā)的方式,當Channel上觸發(fā)相對應的事件時,就會主動回調通知,然后交給對應的ChannelHandler進行處理。
由于本篇講的是寫半包,因此不再過多解釋。

總的來說: Channel就是Netty用來處理網絡數(shù)據(jù)流的
回到本篇的主題:寫半包
AbstractNioByteChannel
主要負責處理寫半包
總的流程如圖:

ChannelOutboundBuffer:環(huán)形發(fā)送數(shù)組
-
不停地從ChannelOutboundBuffer讀取數(shù)據(jù),看是否有可以發(fā)送的數(shù)據(jù)
-
如果有,并且是ByteBuf類型的,可以選擇發(fā)送數(shù)據(jù)
- 如果一次發(fā)送沒有發(fā)送完,則采取一定次數(shù)的循環(huán)發(fā)送(寫半包)
-
數(shù)據(jù)最后還是沒有發(fā)送完,則會開一條新線程專門進行剩余數(shù)據(jù)的發(fā)送
-
在最后會去同步數(shù)據(jù)寫入進度
源碼解析 #doWrite

不停地去環(huán)形發(fā)送數(shù)組里面取數(shù)據(jù)出來
- 如果是空了,代表發(fā)送完了,把寫標志位置空(
clearOpWrite)

如果不是空數(shù)據(jù),則判斷是不是ByteBuf數(shù)據(jù)
- 對其進行強轉,若可讀字節(jié)數(shù)是0,代表消息不可讀(reidIndex >= writeIndex),則把他在環(huán)形發(fā)送數(shù)組中移除。
第一次讀的時候,會先去獲取循環(huán)發(fā)送次數(shù)writeSpinCount。循環(huán)發(fā)送次數(shù)就是指:第一次發(fā)送沒有完成時(寫半包)進行循環(huán)發(fā)送的次數(shù)。
給他設置一個閾值,為的就是當循環(huán)發(fā)送的時候,IO線程會一直嘗試寫操作,此時IO線程無法處理其他操作,相當于局部阻塞、死鎖、假死的情況。
像這種處理手法非常常見,比如一般我們會給分布式鎖設置一個鎖的超時時間,除此之外還需要設置一個客戶端的超時時間,避免客戶端在拿到鎖的時候,這把鎖已經過期了。客戶端的超時時間會比鎖的超時時間要短。

然后就是進行循環(huán)發(fā)送了

消息發(fā)送操作完成時候,會調用ChannelOutboundBuffer更新發(fā)送進度的消息,并且還會判斷是否需要寫半包處理

如果沒有發(fā)完,則設置寫半包標識位,啟動專門的寫半包線程繼續(xù)發(fā)后續(xù)的消息
總結
寫半包問題本質上是:對于接收方來說,來自發(fā)送方的數(shù)據(jù)壓力太大了,因此不得不采取的一種降保護措施
可以在發(fā)送方進行解決、也可以在接收方進行解決
Netty并沒有采取說,遇到TCP緩沖區(qū)滿了之后,這個數(shù)據(jù)包就等下一次再等發(fā),而是能發(fā)多少就發(fā)多少,不夠的 下次再發(fā),是一種追求性能的選擇。
像消息中間件遇到消息堆積問題,在消接收方(消費者)增大消費的速度,比如:加消費隊列或擴充消費者群組等。
又或者限制發(fā)送方(生產者)的發(fā)送速度,比如TCP的滑動窗口。
所以互聯(lián)想的技術都是有相關聯(lián)的,能看到互相的影子。






