Netty 之 ChannelOutboundBuffer 源碼分析

每個 ChannelSocket 的 Unsafe 都有一個綁定的 ChannelOutboundBuffer , Netty 向站外輸出數據的過程統一通過 ChannelOutboundBuffer 類進行封裝,目的是爲了提高網絡的吞吐量,在外面調用 write 的時候,數據並沒有寫到 Socket,而是寫到了 ChannelOutboundBuffer 這裏,當調用 flush 的時候,才真正的向 Socket 寫出。

ChannelOutboundBuffer 類屬性

public final class ChannelOutboundBuffer {

    // Assuming a 64-bit JVM:
    //  - 16 bytes object header
    //  - 8 reference fields
    //  - 2 long fields
    //  - 2 int fields
    //  - 1 boolean field
    //  - padding
    static final int CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD =
            SystemPropertyUtil.getInt("io.netty.transport.outboundBufferEntrySizeOverhead", 96);


    private final Channel channel;

    // Entry(flushedEntry) --> ... Entry(unflushedEntry) --> ... Entry(tailEntry)
    //
    // 緩存鏈表中被刷新的第一個元素
    private Entry flushedEntry;
    // 緩存鏈表中中第一個未刷新的元素
    private Entry unflushedEntry;
    // 緩存鏈表中的尾元素
    private Entry tailEntry;
    // 刷新但還沒有寫入到 socket 中的數量
    private int flushed;

從類的屬性中可以看出 ChannelOutboundBuffer 定義了一個鏈表進行存儲寫入的數據。

1、ChannelOutboundBuffer 添加了N個 Entry


2、當 flush 後的效果


3、flush 後把所有的數據都寫入 Socket 中


4、當再次寫入 Entry 後


下面針對操作這些操作進行分析,添加、刷新、刪除等操作。

addMessage() 添加操作

public void addMessage(Object msg, int size, ChannelPromise promise) {
    Entry entry = Entry.newInstance(msg, size, total(msg), promise);
    if (tailEntry == null) {
        flushedEntry = null;
    } else {
        Entry tail = tailEntry;
        tail.next = entry;
    }
    tailEntry = entry;
    if (unflushedEntry == null) {
        unflushedEntry = entry;
    }

    // increment pending bytes after adding message to the unflushed arrays.
    // See https://github.com/netty/netty/issues/1619
    incrementPendingOutboundBytes(entry.pendingSize, false);
}

1、創建一個 新的Entry。
2、判斷 tailEntry 是否爲 null,如果爲 null 說明鏈表爲空。則把 flushedEntry 置爲null。
3、如果 tailEntry 不爲空,則把新添加的 Entry 添加到 tailEntry 後面 。
4、 將新添加的 Entry 設置爲 鏈表的 tailEntry。
5、如果 unflushedEntry 爲null,說明沒有未被刷新的元素。新添加的Entry 肯定是未被刷新的,則把當前 Entry 設置爲 unflushedEntry 。
6、統計未被刷新的元素的總大小。

addFlush() 刷新操作

當 addMessage 成功添加進 ChannelOutboundBuffer 後,就需要 flush 刷新到 Socket 中去。但是這個方法並不是做刷新到 Socket 的操作。而是將 unflushedEntry 的引用轉移到 flushedEntry 引用中,表示即將刷新這個 flushedEntry,至於爲什麼這麼做?
答:因爲 Netty 提供了 promise,這個對象可以做取消操作,例如,不發送這個 ByteBuf 了,所以,在 write 之後,flush 之前需要告訴 promise 不能做取消操作了。

public void addFlush() {
    // There is no need to process all entries if there was already a flush before and no new messages
    // where added in the meantime.
    //
    // See https://github.com/netty/netty/issues/2577
    Entry entry = unflushedEntry;
    if (entry != null) {
        if (flushedEntry == null) {
            // there is no flushedEntry yet, so start with the entry
            flushedEntry = entry;
        }
        do {
            flushed ++;
            if (!entry.promise.setUncancellable()) {
                // Was cancelled so make sure we free up memory and notify about the freed bytes
                int pending = entry.cancel();
                decrementPendingOutboundBytes(pending, false, true);
            }
            entry = entry.next;
        } while (entry != null);

        // All flushed so reset unflushedEntry
        unflushedEntry = null;
    }
}

1、通過 unflushedEntry 獲取未被刷新元素 entry。
2、如果 entry 爲null 說明沒有待刷新的元素,不執行任何操作
3、如果 entry 不爲 null,說明有需要被刷新的元素
4、如果 flushedEntry == null 說明當前沒有正在刷新的任務,則把 entry 設置爲 flushedEntry 刷新的起點。
5、循環設置 entry, 設置這些 entry 狀態設置爲非取消狀態,如果設置失敗,則把這些entry 節點取消並使 totalPendingSize 減去這個節點的字節大小。

在調用完 outboundBuffer.addFlush() 方法後,Channel 會調用 flush0 方法做真正的刷新。代碼參見 AbstractUnsafe.flush() 方法。

remove() 刪除操作

public boolean remove() {
    Entry e = flushedEntry;
    if (e == null) {
        clearNioBuffers();
        return false;
    }
    Object msg = e.msg;

    ChannelPromise promise = e.promise;
    int size = e.pendingSize;

    removeEntry(e);

    if (!e.cancelled) {
        // only release message, notify and decrement if it was not canceled before.
        ReferenceCountUtil.safeRelease(msg);
        safeSuccess(promise);
        decrementPendingOutboundBytes(size, false, true);
    }

    // recycle the entry
    e.recycle();

    return true;
}

1、獲取 flushedEntry 節點,鏈表的頭結點。如果獲取不到清空 ByteBuf 緩存。
2、在鏈表上移除該 Entry。如果之前沒有取消,只釋放消息、通知和遞減。
3、回收 Entry 對象。

removeEntry() 鏈表移除節點

private void removeEntry(Entry e) {
    if (-- flushed == 0) {
        // processed everything
        flushedEntry = null;
        if (e == tailEntry) {
            tailEntry = null;
            unflushedEntry = null;
        }
    } else {
        flushedEntry = e.next;
    }
}

1、如果 flushed ==0 說明,鏈表中所有 flush 的數據都已經發送到 Socket 中。把 flushedEntry 置位 null。此時鏈表可能還有 unflushedEntry 數據。
如果此時 e == tailEntry 說明鏈表爲空,則把 tailEntry 和 unflushedEntry 都置爲空。

flush==0,可能是這種狀態,如下圖:


2、把 flushedEntry 置爲下一個節點(flushedEntry 此時是頭結點)。

Entry 對象池使用

由於 Entry 使用比較頻繁,會頻繁的創建和銷燬,這裏使用了 Entry 的對象池,創建的時候從緩存中獲取,銷燬時回收。

下面看下 Entry 的創建過程

Entry 創建

public void addMessage(Object msg, int size, ChannelPromise promise) {
    Entry entry = Entry.newInstance(msg, size, total(msg), promise);
    ..... 

使用 Entry.newInstance 方法進行創建 Entry 對象。

static final class Entry {
    private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
        @Override
        protected Entry newObject(Handle<Entry> handle) {
            return new Entry(handle);
        }
    };

    static Entry newInstance(Object msg, int size, long total, ChannelPromise promise) {
        Entry entry = RECYCLER.get();
        entry.msg = msg;
        entry.pendingSize = size + CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD;
        entry.total = total;
        entry.promise = promise;
        return entry;
    }

Entry.newInstance() 方法收下從 Recycler 中獲取 Entry 對象,如果獲取不到則使用 Recycler.newObject() 方法創建一個 Entry 對象,調用 newObject() 方法在 Recycler.get() 方法中,代碼如下:

public abstract class Recycler<T> {
    public final T get() {
        if (maxCapacityPerThread == 0) {
            return newObject((Handle<T>) NOOP_HANDLE);
        }
        Stack<T> stack = threadLocal.get();
        DefaultHandle<T> handle = stack.pop();
        if (handle == null) {
            handle = stack.newHandle();
            handle.value = newObject(handle);
        }
        return (T) handle.value;
    }

從 Recycler 中可以看出,Entry 對象是存儲在 Stack 中。如果 Stack 中沒有可用的 Stack,則調用 newObject() 方法創建。

Entry 對象回收

public boolean remove() {
    Entry e = flushedEntry;
    ...
    e.recycle();

    return true;
}

當 Entry 從鏈表中移除的時候回調用 e.recycle() 方法。

static final class Entry {
    void recycle() {
        next = null;
        bufs = null;
        buf = null;
        msg = null;
        promise = null;
        progress = 0;
        total = 0;
        pendingSize = 0;
        count = -1;
        cancelled = false;
        handle.recycle(this);
    }

Entry.recycle() 方法會把 Entry 中成員變量全部初始化。然後在調用 handle.recycle() 方法。

public abstract class Recycler<T> {
    static final class DefaultHandle<T> implements Handle<T> {
        @Override
        public void recycle(Object object) {
            if (object != value) {
                throw new IllegalArgumentException("object does not belong to handle");
            }

            Stack<?> stack = this.stack;
            if (lastRecycledId != recycleId || stack == null) {
                throw new IllegalStateException("recycled already");
            }

            stack.push(this);
        }
    }

Recycler.recycle() 方法又把 Entry 對象壓進 stack 中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章