Netty裏write數據的時候,是有水位線處理的,只要我們設置了。
Netty中write的時候,其實並沒有真正將數據寫到網絡上,而是寫到ChannelOutboundBuffer裏面緩存起來的,writeAndFlush的時候則會寫入網絡。如果我們設置了高低水位線,比如低水位線L、高水位線H,如果ChannelOutboundBuffer裏面的緩存size大小超過了高水位線H,那麼channel.isWritable()是false,即邏輯上的不可寫入;而ChannelOutboundBuffer裏面緩存的size低於L則channel.isWritable()變爲true,即邏輯上可寫入。爲什麼說邏輯上,因爲如果我們在寫入之前不判斷channel.isWritable,其實是可以寫入的。
如果不設置高低水位線限制,可能會有問題,比如網絡慢,但是程序往寫入數據快,導致很多數據堆積在ChannelOutboundBuffer中,使得內存爆炸。
Abstract.AbstractUnsafe中,如下:
- AbstraceUnsafe中有個ChannelOutboundBuffer,write()時就是存在這個buffer裏面,數據結構是一維數組
- filterOutboundMessage裏面,AbstractNioByteChannel裏面,會把ByteBuffer轉換爲DirectByteBuffer或者FileRegion,因爲ByteBuffer是JVM內存,轉換爲DirectByteBuffer或者FileRegion後,減輕JVM GC的壓力,同時可以使用zero copy的優點
- estimatorHandle.size()裏面返還ByteBuffer中的byte個數,即發送的數據是多少byte
- addMessage()裏面,如圖1所示,在一維鏈表的最後添加Entry,這個新的Entry就是我們新write的數據
- addMessage()裏面,如List-2所示,TOTAL_PENDING_SIZE_UPDATER是一個AtomicLongFieldUpdater,把ChannelOutboundBuffer裏面的totalPendingSize加上新write的數據大小,如果大小highWaterMark值,則設置unwritabble
List-1
public final void write(Object msg, ChannelPromise promise) {
assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
// If the outboundBuffer is null we know the channel was closed and so
// need to fail the future right away. If it is not null the handling of the rest
// will be done in flush0()
// See https://github.com/netty/netty/issues/2362
safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
// release message now to prevent resource-leak
ReferenceCountUtil.release(msg);
return;
}
int size;
try {
msg = filterOutboundMessage(msg);
size = pipeline.estimatorHandle().size(msg);
if (size < 0) {
size = 0;
}
} catch (Throwable t) {
safeSetFailure(promise, t);
ReferenceCountUtil.release(msg);
return;
}
outboundBuffer.addMessage(msg, size, promise);
}
圖1
List-2
private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
if (size == 0) {
return;
}
long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
setUnwritable(invokeLater);
}
}
AnstractUnsafe裏面flush時就是把ChannelOutboundBuffer裏面的Entry鏈表中未flush的Entry寫入到網絡中,即返回給調用方,所以writeAndFlush是寫入得到Entry鏈表中後,又觸發了flush操作,flush時寫入網絡後,會把totalPendingSize減去相應的大小,如果totalPengingSize的值小於lowWaterMark則將unwritable設置爲false,適合及時響應的場景。