Netty非同步Future原始碼解讀

LemonNan發表於2019-12-16

本文地址: juejin.im/post/5df771…

說在前面

本文的 Netty原始碼使用的是 4.1.31.Final 版本,不同版本會有一些差異.

JDK Future

在說JDK的非同步Future之前,先簡單介紹一下JDK自帶的Future機制.

首先先上一段程式碼

public class JDKFuture {

    static ExecutorService executors = new ThreadPoolExecutor(1,
            1,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(16));
    public static void main(String[] args) throws Exception{
        int cnt = 1;
        Future[] jdkFuture=new Future[cnt];
        Object jdkFutureResult;
        for(int i = 0;i < cnt; i++){
            jdkFuture[i] = executors.submit(new JDKCallable(i));
        }
        System.out.println(String.format("%s 在 %s 即將獲取任務執行結果", Thread.currentThread(), new Date()));
        jdkFutureResult = jdkFuture[0].get();
        System.out.println(String.format("%s 在 %s 任務結果獲取完畢 %s", Thread.currentThread(), new Date(), jdkFutureResult));
        executors.shutdown();
    }

    static class JDKCallable implements Callable{

        int index;

        JDKCallable(int ind){
            this.index = ind;
        }

        public Object call() throws Exception {
            try {
                System.out.println(String.format("執行緒 [%s] 提交任務[%s]", Thread.currentThread(), this.index));
              // 耗時2秒,模擬耗時操作
                Thread.sleep(2000);
                System.out.println(String.format("執行緒 [%s] 執行任務[%s]執行完畢", Thread.currentThread(), this.index));
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            return String.format("任務%s執行結果",this.index);
        }
    }
}
複製程式碼

輸出結果為:

執行緒 [Thread[pool-1-thread-1,5,main]] 提交任務[0]
Thread[main,5,main] 在 Mon Dec 16 16:40:38 CST 2019 即將獲取任務執行結果
執行緒 [Thread[pool-1-thread-1,5,main]] 執行任務[0]執行完畢
Thread[main,5,main] 在 Mon Dec 16 16:40:40 CST 2019 任務結果獲取完畢 任務0執行結果
複製程式碼

可以看到主執行緒在使用 future.get() 的時候,因為子執行緒還未處理完返回結果而導致主執行緒活生生的等了2秒鐘(耗時操作),這也是JDK自帶的Future機制不夠完善的地方.因為jdk自身的future機制不夠完善,所以Netty自實現了一套Future機制.

Netty 非同步Future/Promise

Netty的Future是非同步的,那他是怎麼實現的呢?接下來就從原始碼開始探究.

先看一下 Netty 的 FuturePromise 這兩個介面

Future

/**
 * The result of an asynchronous operation
 * 非同步操作的結果
 * 對狀態的判斷、新增listener、獲取結果
 */
public interface Future<V> extends java.util.concurrent.Future<V> {
    boolean isSuccess();
    boolean isCancellable();
    Throwable cause();
    Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
    Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
    Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
    Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
    Future<V> sync() throws InterruptedException;
    Future<V> syncUninterruptibly();
    Future<V> await() throws InterruptedException;
    Future<V> awaitUninterruptibly();
    boolean await(long timeout, TimeUnit unit) throws InterruptedException;
    boolean await(long timeoutMillis) throws InterruptedException;
    boolean awaitUninterruptibly(long timeout, TimeUnit unit);
    boolean awaitUninterruptibly(long timeoutMillis);
    V getNow();
  
    @Override
    boolean cancel(boolean mayInterruptIfRunning);
}
複製程式碼

Promise

Promise是一個特殊的Future,它可寫,可寫意味著可以修改裡面的結果.

/**
 * Special {@link Future} which is writable.
 * 一個可寫的特殊的Future
 * 繼承 Future, 繼承的方法就不列出
 */
public interface Promise<V> extends Future<V> {

    /**
     * Marks this future as a success and notifies all
     * listeners.
     * If it is success or failed already it will throw an {@link IllegalStateException}.
     * 將這個 future 標記為 success 並且通知所有的 listeners
     * 如果已經成功或者失敗將會丟擲異常
     */
    Promise<V> setSuccess(V result);

    /**
     * Marks this future as a success and notifies all
     * listeners.
     *
     * @return {@code true} if and only if successfully marked this future as
     *         a success. Otherwise {@code false} because this future is
     *         already marked as either a success or a failure.
     * 嘗試設定結果,成功返回true, 失敗 false, 上面的方法設定失敗會丟擲異常
     */
    boolean trySuccess(V result);

  	// 這2個跟上面的差不多
    Promise<V> setFailure(Throwable cause);
    boolean tryFailure(Throwable cause);

    /**
     * Make this future impossible to cancel.
     *
     * @return {@code true} if and only if successfully marked this future as uncancellable or it is already done
     *         without being cancelled.  {@code false} if this future has been cancelled already.
     */
    boolean setUncancellable();
}
複製程式碼

原始碼解讀

看到這裡都同學都預設是用netty寫過程式的~,還沒寫過的話可以看看官方文件或者我的另一篇Netty使用.

接下來就開始原始碼的解讀.

那麼從哪裡開始呢?

總所周知!,我們使用Netty開發的時候,寫出資料用的是 writeAndFlush(msg), 至於 write(msg) 嘛, 不就是少了個 flush (沒錯,是我比較懶).

開始

在大家知道 channel().writectx.write 的區別後, 我們就從 channel().write 開始講起.

不行,我感覺還是要說一下一些補充的,不然心裡不舒服.

Netty中有一個pipeline,也就是事件呼叫鏈,開發的時候在呼叫鏈裡面加入自己處理事件的handle,但是在這條 pipeline 中, Netty給我們加上了 Headtail 這兩個handle,方便Netty框架處理事件.

先看 DefaultChannelPipeline 的初始化,在初始化程式碼裡給我們新增了2個handle, head 和 tail, 這2個東西很有用,為什麼這麼說呢?詳情看後面解答

    protected DefaultChannelPipeline(Channel channel) {
        this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");
        this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);
        this.voidPromise = new VoidChannelPromise(channel, true);
        // ChannelInboundHandler
        this.tail = new DefaultChannelPipeline.TailContext(this);
	      // ChannelInboundHandler && ChannelOutboundHandler
        this.head = new DefaultChannelPipeline.HeadContext(this);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }
複製程式碼

Real 開始

沒錯,還是從 channel().write(msg) 開始說起(為什麼我要用還是).

跟蹤程式碼 channel().write(), 首先會呼叫到 DefaultChannelPipeline的 writeAndFlush 方法.

1.DefaultChannelPipeline#writeAndFlush

    public final ChannelFuture writeAndFlush(Object msg) {
        return this.tail.writeAndFlush(msg);
    }
複製程式碼

this.tail 就是上面建構函式裡面初始化的 tailHandle, 而 write 是出棧事件, 會從 tailHandle 開始往前傳遞,最後傳遞到 headHandle(怎麼感覺好像提前劇透了).

public ChannelFuture writeAndFlush(Object msg) {
  	// 這裡new了一個 promise, 然後這個promise將會一直傳遞,一直傳遞.....
    return this.writeAndFlush(msg, this.newPromise());
}
複製程式碼

接下來來到了AbstractChannelHandlerContext 的 writeAndFlush.

    /**
     * 執行 write and flush 操作
     * @param msg
     * @param promise
     */
    private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
        // 這個方法在 ChannelHandler#handlerAdded 呼叫後,才會返回 true
        if (invokeHandler()) {
            // write 繼續傳遞
            invokeWrite0(msg, promise);
            // flush data
            invokeFlush0();
        } else {
            writeAndFlush(msg, promise);
        }
    }

    private void write(Object msg, boolean flush, ChannelPromise promise) {
        // 查詢下一個 OutboundHandle, 因為是要輸出
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        // 下一個 OutboundHandle 所在的執行緒
        EventExecutor executor = next.executor();
        // 如果在是同一個執行緒(由於Netty的channel在一個ThreadPool中只繫結一個Thread, 不同執行緒的話也意味著是不同執行緒池)
        if (executor.inEventLoop()) {
            // 在同一個執行緒池(這裡意味著同一個執行緒)中,
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            // 在不同執行緒池(不同執行緒池那自然就是不同執行緒),需要建立一個任務,提交到下一個執行緒池
            final AbstractWriteTask task;
            if (flush) {
                // 提交給下一個執行緒池 && flush
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            // 因為是 write 事件, so 接下來提交任務到下一個 OutboundHandle(出棧) 所在的執行緒, 由它執行
            if (!safeExecute(executor, task, promise, m)) {
                // We failed to submit the AbstractWriteTask. We need to cancel it so we decrement the pending bytes
                // and put it back in the Recycler for re-use later.
                //
                // See https://github.com/netty/netty/issues/8343.
                // 任務提交失敗,取消任務
                task.cancel();
            }
        }
    }
複製程式碼

2.HeadContext#write、flush

接下來本篇文章最重要的地方了, HeadContext !

HeadContext的write和flush方法 實際上都是呼叫 unsafe的方法實現.

write

	// 如果是 writeAndFlush ,呼叫 write後會呼叫flush
	@Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            // 這個呼叫 AbstrachUnsafe.write
            unsafe.write(msg, promise);
        }

	// 這是 unsafe 的 write 方法
        @Override
        public final void write(Object msg, ChannelPromise promise) {
            assertEventLoop();

            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            // outboundBuffer = null 表明 channel已經關閉並且需要將 future 結果設定為 false
            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;
            }
            // 將 msg新增進 buffer 中
            outboundBuffer.addMessage(msg, size, promise);
        }
複製程式碼

flush

如果是WriteAndFlush, 則在呼叫write後,會呼叫Head的flush方法,同 write是呼叫AbstractUnsafe的flush

        /**
         * write 之後再呼叫這個 flush
         */
        @Override
        public final void flush() {
            assertEventLoop();

            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                return;
            }

            // buffer 標記為可以被 flush
            outboundBuffer.addFlush();
            // 接下來就是真正的 flush
            flush0();
        }
複製程式碼

ChannelOutboundBuffer 是個啥呢?

ChannelOutboundBuffer 簡單來說就是儲存當前channel寫出的資料, 並且在呼叫flush的時候將他們都寫出去.

跟著原始碼一直走,在 flush0 之後,最終會呼叫到 AbstractNioMessageChannel#doWrite 方法.(上面還有doRead方法,是接收資料的時候呼叫的)

    @Override
    protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        final SelectionKey key = selectionKey();
        final int interestOps = key.interestOps();

        for (;;) {
            //
            Object msg = in.current();
            if (msg == null) {
                // Wrote all messages.
                // 判斷寫事件
                if ((interestOps & SelectionKey.OP_WRITE) != 0) {
                    key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
                }
                break;
            }
            try {
                // 迴圈寫出資料
                boolean done = false;
                for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
                    // 真正的寫出資料
                    // 最終會呼叫 javaChannel().send(nioData, mi); 
                    // 很眼熟吧,這個是java nio的方法,註冊的時候也是javaChannel().register()
                    if (doWriteMessage(msg, in)) {
                        done = true;
                        break;
                    }
                }

                // 成功寫出,從 buffer 中移除剛才寫出的資料
                if (done) {
                    in.remove();
                } else {
                    // Did not write all messages.
                    // 寫出失敗
                    if ((interestOps & SelectionKey.OP_WRITE) == 0) {
                        key.interestOps(interestOps | SelectionKey.OP_WRITE);
                    }
                    break;
                }
            } catch (Exception e) {
                // 出錯後是否繼續寫出後面的資料
                if (continueOnWriteError()) {
                    in.remove(e);
                } else {
                    throw e;
                }
            }
        }
    }
複製程式碼

3.Promise

到上面位置,資料是寫出去了,那promise的相關作用呢?沒看出來啊?

說實話,這個藏得挺深,居然! 放在了 buffer.remove() 裡!

    public boolean remove() {
        // 剛寫出去資料的Entry
        Entry e = flushedEntry;
        if (e == null) {
            clearNioBuffers();
            return false;
        }
        Object msg = e.msg;

      	// 這個就是writeAndFlush 的時候 new 的 DefaultPromise()
        ChannelPromise promise = e.promise;
        int size = e.pendingSize;

	      // buffer 中移除
        removeEntry(e);

        if (!e.cancelled) {
            // only release message, notify and decrement if it was not canceled before.
            ReferenceCountUtil.safeRelease(msg);
	          // !!! 劃重點 !!!
						// 這裡設定了 promise 的結果, 呼叫了 trySuccess, 通知所有 listener
            // !!! 劃重點 !!!
            safeSuccess(promise);
            decrementPendingOutboundBytes(size, false, true);
        }

        // recycle the entry
	      // 重置Entry的資訊,方便重用.
        // 跟 Entry entry = Entry.newInstance(msg, size, total(msg), promise); 相對應, newInstance 是獲取一個快取的 Entry
        e.recycle();
        return true;
    }
複製程式碼

promise 通知所有 listener 是在寫資料成功,並且在 buffer.remove() 呼叫的時候在裡面 safeSuccess(promise) , 最終呼叫 Promise 的 trySuccess() 從而觸發 notifyListeners() 通知所有 listeners.

4.NotifyListener

這個是在 Promise#trySuccess的時候呼叫的,通知所有listeners操作已經完成.

    /**
     * 通知監聽者,任務已經完成
     */
    private void notifyListeners() {
        // 獲取future所屬執行緒(池)
        EventExecutor executor = executor();
        // 執行通知是當前執行緒 則直接回撥資訊
        // currentThread == this.executor
        if (executor.inEventLoop()) {
            // 獲取 ThreadLocal 變數
            final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
            // listen 的層級數
            final int stackDepth = threadLocals.futureListenerStackDepth();
            if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
                threadLocals.setFutureListenerStackDepth(stackDepth + 1);
                try {
                    // 通知所有的 listener
                    notifyListenersNow();
                } finally {
                    threadLocals.setFutureListenerStackDepth(stackDepth);
                }
                return;
            }
        }

        // 如果 executor 不是當前執行緒, 則交給 future 所屬 executor 去執行
        // 意思是新增通知的 executor 可能是前面的 executor , 然後到後面的 executor 也就是當前執行緒才執行通知
        // 此時將通知交回給之前的 executor
        // 執行通知的不是當前執行緒, 封裝成一個任務, 由之前提交的執行緒完成通知(回撥)
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                notifyListenersNow();
            }
        });
    }
複製程式碼

總結

Netty 的 Future 非同步機制是在操作完成後,將通知封裝成Task,由Promise所屬執行緒(Executors)執行(回撥).


最後

這次的內容到這裡就結束了,最後的最後,非常感謝你們能看到這裡!!你們的閱讀都是對作者的一次肯定!!!
覺得文章有幫助的看官順手點個贊再走唄(終於暴露了我就是來騙讚的(◒。◒)),你們的每個贊對作者來說都非常重要(異常真實),都是對作者寫作的一次肯定(double)!!!

相關文章