精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Netty-Reactor 模型常見知識點小結

開發
Netty作為一款強大的高性能網絡編程框架,其底層Reactor的設計理念和實現都是非常值得我們研究和學習了解的,本文將從幾個問題為導向,帶讀者深入理解netty reactor線程模型。

Netty作為一款強大的高性能網絡編程框架,其底層Reactor的設計理念和實現都是非常值得我們研究和學習了解的,本文將從以下幾個問題為導向,帶讀者深入理解netty reactor線程模型。

1. Netty有Reactor線程模型

Reactor模型的用戶層面的IO模型,按照結構它可分為:

  • Reactor單線程
  • Reactor多線程
  • 主從Reactor模型

先來說說單Reactor單線程模型,每個客戶端與服務端建立連接時,所有的請求建立、讀寫事件分發都由這個Reactor線程處理。很明顯,所有的連接建立、讀寫和業務邏輯處理等工作都分配到一個線程上,對于現如今多核的服務器場景,這種方案未能很好的利用CPU資源,對應高并發場景表現也不算特別出色(會比傳統的BIO好一些):

于是就有了單Reactor多線程模型,與前者相比,Reactor監聽到就緒的IO連接并建立連接后,它會將所有的讀寫請求交給一個業務線程池進行處理。 該模型較好利用了CPU資源,提升的程序執行效率,但是面對大量的并發連接請求時,因為只有一個Reactor處理IO請求,系統的吞吐量還是沒有提升。

最后就是主從Reactor模型,也就是如今主流的Reactor模型,該模型為用分為主Reactor和從Reactor,各自都是以線程池的形式存在,由主Reactor專門處理連接事件,隨后將每個建立連接的客戶端socket讀寫事件注冊到從Reactor中,由從Reactor負責處理這些讀寫以及業務邏輯。主從Reactor模型是一種改進的事件驅動編程模型,相比于單Reactor單線程模型,它具有以下幾個優勢:

  • 多線程并發處理:主從Reactor模型允許多個線程同時處理事件,每個線程都有一個獨立的Reactor負責事件分發。這樣可以充分利用多核處理器的優勢,提高系統的并發處理能力和性能。
  • 高吞吐量:由于使用了多線程并發處理,主從Reactor模型能夠同時處理多個事件,從而提高系統的吞吐量。每個線程都可以獨立處理事件,不會被其他事件的處理阻塞。
  • 負載均衡:主從Reactor模型中,主Reactor負責監聽和接收連接請求,然后將連接分配給從Reactor進行具體的事件處理。這種分配方式可以實現負載均衡,將連接均勻地分配給多個Reactor,避免某個Reactor的負載過重。
  • 異步IO支持:主從Reactor模型可以結合異步IO技術,充分利用操作系統提供的異步IO接口。這樣可以在進行IO操作時立即返回,不會阻塞線程,提高系統的并發性和響應性能。
  • 容錯能力:通過使用多個Reactor和線程,主從Reactor模型具有更好的容錯能力。如果某個Reactor或線程出現錯誤或崩潰,其他Reactor和線程仍然可以繼續處理事件,保證系統的正常運行。

主從Reactor模型通過多線程并發處理、負載均衡、異步IO支持和容錯能力的提升,能夠更好地滿足高并發、高性能的網絡應用程序的需求。

2. Netty如何實現Reactor模式

通過上文我們大體了解了幾種常見的Reactor模式,實際上Netty已經將這三種Reactor模式都封裝好了,假設我們需要單Reactor服務端,只需指明NioEventLoopGroup的線程數為1即可:

ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(1);
serverBootstrap.group(nioEventLoopGroup);

同理多Reactor則將線程數設置為大于1即可,當然我們也可以設置NioEventLoopGroup參數為空,因為如果NioEventLoopGroup不設置參數時,該分發內部會創建CPU核心數2倍的線程:

ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();
serverBootstrap.group(nioEventLoopGroup);

這一點我們直接步入NioEventLoopGroup內部只需流程即可看到,默認情況下我們傳入的thread為0,它就取DEFAULT_EVENT_LOOP_THREADS 的值,而這個值初始情況下回去CPU核心數2倍:

//不傳參時nThreads值為0,super即MultithreadEventLoopGroup
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
                             final SelectStrategyFactory selectStrategyFactory) {
        super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
    }

//MultithreadEventLoopGroup看到nThreads為0則取DEFAULT_EVENT_LOOP_THREADS 
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

//DEFAULT_EVENT_LOOP_THREADS 取CPU核心數的2倍
privatestaticfinalint DEFAULT_EVENT_LOOP_THREADS;

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

3. 為什么 main Reactor大部分場景只用到一個線程

上文介紹Reactor模式時已經介紹到了,它主要負責處理新連接,而Netty服務端初始化時只會綁定一個ip和端口號然后生成serverSocketChannel,而每個channel只能和一個線程綁定,這就導致了main Reactor主服務端連接大部分場景(連接沒有斷開)只會用到一個線程:

我們不妨通過代碼的方式進行印證,我們服務端初始化時都是通過這個bind方法完成連接建立:

// Start the server.
 ChannelFuture f = serverBootstrap.bind(PORT).sync();

查看bind內部的調用doBind即可看到它通過異步任務完成服務端serverSocketChannel創建之后,就會調用doBind0完成ip和端口號綁定:

private ChannelFuture doBind(final SocketAddress localAddress) {
        //生成創建服務端serverSocketChannel的regFuture
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }
        //如果regFuture完成了則將channel和ip端口號即localAddress綁定
        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
          //......
        }
    }

隨后我們查看doBind0(一般帶有do+0的方法都是執行核心邏輯的方法)方法,即可看到它會從當前channel的eventLoopGroup找到一個線程真正執行ip和端口綁定,這也就是我們所說的為什么main Reactor大部分場景只用到一個線程:

private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {

        
        //從channel的eventLoopGroup中找到一個線程執行bind
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (regFuture.isSuccess()) {
                    channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                    promise.setFailure(regFuture.cause());
                }
            }
        });
    }

有讀者可能會問,為什么時大部分呢?那么小部分是什么情況?

答案是連接失敗的情況,一旦綁定ip端口失敗,Netty內部會拋出異常,如果服務端有斷線重連機制,進行重新綁定時,channel可能會綁定eventGroup中的另一個線程:

這里筆者也給出斷線重連的服務端實現,可以看到我們通過channelInactive監聽到斷線后會重新創建channel進行綁定ip端口生成新的socket,此時我們就可以用到線程組中別的線程了:

@Override
        public void channelInactive(ChannelHandlerContext ctx) {
            ctx.channel().eventLoop().execute(()->{
             //創建新的引導類
               ServerBootstrap serverBootstrap =......;
               //在地調用bind
               serverBootstrap.bind("127.0.0.1",8080);
            });
            
        }

4. Netty線程分配策略是什么

線程分配的負載均衡策略,也是在這里完成初始化的,chooserFactory會根據我們傳入的線程數給定一個負載均衡算法。對于負載均衡算法Netty也做了很多的優化。我們查看chooserFactory創建策略可以看到,如果當前線程數的2的次冪則返回PowerOfTowEventExecutorChooser改選擇使用位運算替代取模,反之返回GenericEventExecutorChooser這就是常規的取模運算。

@Override
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
     //如果是2的次冪則用PowerOfTwoEventExecutorChooser選擇器
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
         //反之取常規的取模運算選擇器
            return new GenericEventExecutorChooser(executors);
        }
    }

先來說說PowerOfTowEventExecutorChooser ,其實它們的本質就是基于一個索引idx 通過原子自增并取模得到線程索引,只不過若線程數為2的次冪則可以通過位運算完成取模的工作,這么做的原因也是因為計算機對于位運算的執行效率遠遠高于算術運算。

這種算法通過位運算的方式提升計算效率,那么是否存在索引越界問題呢?假設線程數組長度為8,也就是2的3次方,那么實際進行與運算的值就是7,這個值也正是線程數組索引的最大值。筆者分別帶入索引0、5、8,進行與運算時,真正參與的二進制永遠是和永遠是7以內的進制,得出的結果分別是0、5、0,永遠不會越界,并且運算性能還能得到保證。

對此我們給出PowerOfTowEventExecutorChooser 選擇器的實現,思路正如上文所說,通過按位與一個線程索引范圍的最大值得到executors線程組索引范圍以內的線程:

private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {
        //......
  //原子類自增并和線程索引最大值進行按位與運算得到線程
        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }

而GenericEventExecutorChooser 則是原子自增和線程數組長度進行取模%運算得到線程,實現比較簡單,這里筆者就直接給出代碼了:

private static final class GenericEventExecutorChooser implements EventExecutorChooser {
       //......

        @Override
        public EventExecutor next() {
            //原子類自增和線程長度進行取模
            return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
        }
    }

5. Netty中的IO多路復用的概念

默認情況,Netty是通過JDK的NIO的selector組件實現IO多路復用的,其實現的特點為:

  • 功能上:輪詢其是多路復用的,它支持將多個客戶端(channel)的讀寫事件注冊到一個selector上,由單個selector進行輪詢。
  • 非阻塞:selector進行輪詢是采用非阻塞輪詢,即非阻塞的到內核態查看注冊的讀寫事件是否就緒,如果沒就緒則直接返回未就緒,而不是阻塞等待。
  • 事件驅動:輪詢到就緒事件后selector就會將結果返回給對應的EventLoop,交由其chanel pipeline上的處理器進行處理。

6. Netty基于那幾個組件搭配實現IO多路復用

我們以最經典的reactor模型來探討這個問題,整體來說,Netty是通過以下幾個組件完成IO多路復用的方案落地:

  • 聲明boss group線程組作為main reactor,它本質是Selector(對于Linux系統下是EpollEventLoop 的封裝),對于接收新連接的客戶端socket,并通過acceptHandler分發連接請求。
  • 通過work group為分發過過來的連接分配一個線程,對應的它們都會被抽象為一個Channel對象,后續該socket的讀寫時間都在work group的線程上的處理,而這個線程內部也會有一個EventLoop通過selector針對這幾個socket的讀寫事件進行io輪詢查看是否就緒。
  • 而每個一個客戶端channel注冊到從reactor后續的讀寫事件都會通過對應的channel pipeline上的handler處理器進行處理。

對此我們也將這些組件的協作流程進行總結:

  • 服務端初始化所有線程,各自都綁定一個selector。
  • BossGroup 初始化連接,綁定ip和端口,其底層selector輪詢器會監聽當前ServerSocketChannel 對應的客戶端新接入的連接事件。
  • 客戶端連接到達時,BossGroup將就緒的客戶端channel件分發到worker group的某個線程的EventLoop上。
  • work group為該channel分配處理器并將其讀寫事件注冊到自己的selector上,同時監聽其讀寫事件。
  • 后續讀寫事件就緒時,EventLoop就會觸發ChannelPipeline 中的處理器處理事件。

7. Netty如何實現通用NIO多路復用器

實際上Netty對于JDK NIO SelectorProvider 做了一些靈活的處理,它可以讓用戶通過JVM參數或者SPI文件配置等方式讓用戶直接JDK NIO提供的selector。

我們配置引導類的時候,通常會聲明b.channel(NioServerSocketChannel.class);,一旦我們通過引導類進行初始化的時候,其底層就會按照如下順序執行:

  • 首先會通過loadProviderFromProperty查看用戶是否有通過系統配置指定創建,即通過JVM參數-D java.nio.channels.spi.SelectorProvider指定selectorProvider的全限定名稱,若存在則通過應用程序加載器即(Application Classloader)完成反射創建。
  • 若步驟1明確沒有配置,則查看SPI是否有配置,即查看工廠目錄META-INF/services下是否有定義名為SelectorProvider的SPI文件,若存在則會拿著第一個SelectorProvider的全限定名稱進行反射創建。
  • 若都沒有則是創建DefaultSelectorProvider這個DefaultSelectorProvider會根據操作系統內核版本決定提供那個DefaultSelectorProvider,以筆者為例是Windows操作系統所以提供的Provider是WindowsSelectorProvider,同理如果是Linux內核2.6以上則是EpollSelectorProvider。

這就是Netty如何保證NIO多路復用器通用的原因:

我們直接查看NioServerSocketChannel,可以看到其默認構造方法內部使用默認DEFAULT_SELECTOR_PROVIDER 進行NioServerSocketChannel創建:

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
 //......
 //使用DEFAULT_SELECTOR_PROVIDER創建server socket channel
    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

我們查看DEFAULT_SELECTOR_PROVIDER 的實現,即SelectorProvider.provider()內部邏輯,可以正如我們上文所說的順序,這里筆者就不多做贅述了:

public static SelectorProvider provider() {
//臨界加載上個鎖
        synchronized (lock) {
            //......
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                      //使用jvm方式嘗試反射創建
                            if (loadProviderFromProperty())
                                return provider;
                            //使用spi的方式進行反射創建    
                            if (loadProviderAsService())
                                return provider;
                            //返回通過系統平臺jdk系統的DefaultSelectorProvider 
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

8. Netty如何優化工作線程調度平衡

Netty設計者為了提升單個NIO線程的利用率,對每一個線程調度分配都做了極致的壓榨,其工作流程為先查看定時任務隊列scheduledTaskQueue中查看是否有就緒的任務,若有則查看它的到期時間距今的時差,并基于這個時差進行非阻塞輪詢查看是否存在就緒的任務。當然如果定時隊列中沒有就緒的任務,那么輪詢IO任務的方法select就會阻塞輪詢,直到被移步任務喚醒或者select有就緒事件。

得到就緒的IO事件后,Netty會調用processSelectedKeys進行處理,然后基于這個IO事件的處理時長,按照同等執行比例從taskQueue和tailTasks中獲取任務并執行,可以看出Netty中的節點針對每一個時間點都做好了很好的安排,并完成相對公平的調度:

對應的我們給出Netty每一個線程NioEventLoop的run方法,邏輯和筆者上文描述一致,讀者可自行參閱:

@Override
    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                   //......

                    case SelectStrategy.SELECT:
                     //查看是否有就緒的定時任務,如果有則設置到期時間curDeadlineNanos 
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        //如果沒有任務,則基于curDeadlineNanos進行定長時阻塞輪詢就緒IO事件
                        try {
                            if (!hasTasks()) {
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                           //......
                        }
                      
                    default:
                    }
                } catch (IOException e) {
                 //......
                }

               //......
               //默認情況下ioRatio 為50,我們直接看else邏輯
                if (ioRatio == 100) {
                   //......
                } elseif (strategy > 0) {
                    finallong ioStartTime = System.nanoTime();
                    try {
                     //處理IO事件
                        processSelectedKeys();
                    } finally {
      //基于IO事件處理的耗時繼續處理其他異步任務
                        finallong ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }

                
             //......
        }
    }

9. Netty如何解決CPU 100% 即空輪詢問題

JDK的NIO底層由Epoll實現,在部分Linux的2.6的kernel中,poll和epoll對于突然中斷的連接socket會對返回的eventSet事件集合置為POLLHUP或POLLERR,進而導致eventSet事件集合發生了變化,這就可能導致selector會被喚醒,由此引發CPU 100%.問題。

關于這個問題的bug,感興趣的讀者可移步下面這個鏈接查看bug詳情:

JDK-6670302:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6670302

而Netty的NIO線程解決方案則比較簡單了,每一次循環它都會查看本次是否有執行任務,如果有則不做處理,反之它會累加一個selectCnt,一旦selectCnt值大于或者等于512(默認值)時,就會調用rebuildSelector重新構建選擇器從而解決這個問題:

對應的源碼仍然在NioEventLoop的run方法,當我們執行了異步任務則ranTasks 為true,如果有輪詢到IO事件則strategy 大于0,在后續邏輯中selectCnt(這個變量代表空輪詢次數) 會被重置,反之selectCnt會不斷被累加直到超過512次,通過執行rebuildSelector重新構建輪詢器避免CPU100%問題:

@Override
    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                   //輪詢并處理任務
                   //......
                //累加一次selectCnt
    selectCnt++;
    //如果有執行任務則重置selectCnt 
                if (ranTasks || strategy > 0) {
                    //......
                    selectCnt = 0;
                } elseif (unexpectedSelectorWakeup(selectCnt)) { //反之視為異常喚醒,執行unexpectedSelectorWakeup
                    selectCnt = 0;
                }
            } catch (CancelledKeyException e) {
              //......
            } //......
        }
    }

步入unexpectedSelectorWakeup即可印證筆者所說的,當空輪詢大于或者等于512次之后就會重新構建輪詢器:

private boolean unexpectedSelectorWakeup(int selectCnt) {
       //......
       //如果selectCnt 大于SELECTOR_AUTO_REBUILD_THRESHOLD(512)則執行rebuildSelector重新構建當前eventLoop的輪詢器
        if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
            //......
            rebuildSelector();
            return true;
        }
        return false;
    }

10. Netty對于事件輪詢器做了哪些優化

默認情況下JDK的DefaultSelectorProvider在Windows系統下創建的是WindowsSelectorImpl,而Linux則是EpollSelectorImpl,它們都繼承自SelectorImpl,查看SelectorImpl的源碼可以發現它如下幾個核心參數:

  • selectedKeys :存放就緒IO事件集。
  • publicSelectedKeys:和上述概念一致,只不過是selectedKeys 的一個視圖,給用戶讀取就緒IO事件時用的,且外部線程對于這個publicSelectedKeys只能做刪除操作。
  • keys :我們都知道對于socket感興趣的IO事件都會注冊到keys上。
  • publicKeys:和上述概念類似,只不過是keys 一個對外的視圖,不可增加元素,只能讀取和刪除。
public abstractclass SelectorImpl extends AbstractSelector {
//存儲感興趣的IO事件
protected HashSet<SelectionKey> keys = new HashSet();
//keys的只讀視圖層
private Set<SelectionKey> publicKeys;
//存放就緒IO事件的集合
     protected Set<SelectionKey> selectedKeys = new HashSet();
     //上一個集合的視圖層
    private Set<SelectionKey> publicSelectedKeys;

//......
protected SelectorImpl(SelectorProvider var1) {
        super(var1);
        if (Util.atBugLevel("1.4")) {
            //......
        } else {
         
           //......
           //使用ungrowableSet封裝selectedKeys作為視圖
            this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);
        }

    }

}

應用程序從內核獲取就緒的IO事件也就是添加到selectedKeys 上,以服務端接收客戶端讀寫請求為例,我們的主reactor為了拿到客戶端的連接請求,就會將自己的channel依附即attach到SelectionKeyImpl上,一旦這個輪循到就緒連接事件繼續后就會調用attachment方法通知這個channel處理連接。很明顯遍歷就緒的key用HashSet效率不是很高效(無需的哈希集):

所以Netty為了提高處理時遍歷的效率,對存儲就緒事件的集合進行了優化,它會判斷創建的selector 是否是默認的selector ,且DISABLE_KEYSET_OPTIMIZATION 這個變量是否為false(默認為false),如果符合這兩個條件,則初始化時會通過反射將selectedKeys改為數組,通過數組的連續性保證CPU緩存可以一次性加載盡可能多的key以及提升迭代效率:

對此我們給出NioEventLoop的構造方法,可以看到NioEventLoop初始化時回調用openSelector完成selector創建,其內部就存在我們上述所說的如果是原生jdk的selector且DISABLE_KEYSET_OPTIMIZATION為false(即允許key優化)則通過反射修改集合類型:

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                 EventLoopTaskQueueFactory queueFactory) {
        super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
                rejectedExecutionHandler);
        //......
        //創建selector,如果是原生jdk的selector且DISABLE_KEYSET_OPTIMIZATION為false(即允許key優化)則通過反射修改集合類型
        final SelectorTuple selectorTuple = openSelector();
        this.selector = selectorTuple.selector;
       //......
    }

最終步入openSelector即可看到我們所說的條件判斷和反射修改集合的邏輯:

private SelectorTuple openSelector() {
       
        //......
        //反射獲取當前selector類型
        Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    return Class.forName(
                            "sun.nio.ch.SelectorImpl",
                            false,
                            PlatformDependent.getSystemClassLoader());
                } catch (Throwable cause) {
                    return cause;
                }
            }
        });
        //非平臺提供的selector則直接封裝返回
        if (!(maybeSelectorImplClass instanceof Class) ||
            //......
            returnnew SelectorTuple(unwrappedSelector);
        }

        final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
        //創建一個1024長度的SelectionKey數組存放事件
        final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();

        Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    //反射獲取當前selector字段
                    Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                    Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");

                    //......
                    //通過反射將selector設置為數組類型的selector
                    selectedKeysField.set(unwrappedSelector, selectedKeySet);
                    publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                    returnnull;
                } catch (NoSuchFieldException e) {
                  //......
                }
            }
        });

        //......
    }

我們不妨看看SelectedSelectionKeySet做了那些優化,首先從定義來看它的SelectionKey是一個數組,很明顯數組的添加和遍歷效率都是順序的所以處理效率相較于HashSet會高效需多。而且因為數組內存空間是連續的,可以更好的利用CPU緩存行從而一次性讀取并遍歷更多的key進行高效處理,所以每次CPU都可以加載對應元素和其鄰接元素,所以處理效率相較于不規則的HashSet要高效許多。

11. Netty無鎖化的串行設計理念

為盡可能提升NioEventLoop的執行效率,出了上述提到的空閑等待、基于定時任務定長時輪詢以及IO和計算任務平衡配比等設計以外,在提交任務時,Netty采用MpscChunkedArrayQueue作為任務隊列,這是一個無鎖的多生產者單消費者的任務隊列,提交任務時,該隊列就會基于CAS得到這個隊列的索引位置,然后將任務提交到隊列中,然后我們的NioEventLoop一樣通過原子操作或者可以消費的索引位置進行任務消費:

這一點我們可以直接查看NioEventLoopGroup 的構造函數即可看到,初始化時其內部會調用newTaskQueue創建MpscChunkedArrayQueue來管理任務:

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                 EventLoopTaskQueueFactory queueFactory) {
         //......
         //指明創建隊列為mpscQueue        
        super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
                rejectedExecutionHandler);
      
    }

addTask本質上就是調用MpscChunkedArrayQueue的offer方法,其本質就是通過CAS操作獲得可以添加元素的索引位置pIdex,然后基于這個pIndex得到物理地址并完成賦值:

@Override
    public boolean offer(final E e)
    {
        if (null == e)
        {
            thrownew NullPointerException();
        }

        long mask;
        E[] buffer;
        long pIndex;

        while (true)
        {
           //各種索引位計算
   //cas獲取生產者索引位置
            if (casProducerIndex(pIndex, pIndex + 2))
            {
                break;
            }
        }
        // 獲取cas之后得到的pIndex的位置然后賦值
        finallong offset = modifiedCalcCircularRefElementOffset(pIndex, mask);
        soRefElement(buffer, offset, e); // release element e
        returntrue;
    }

而數據消費也是同理,Netty的NIO線程通過poll進行獲取,其內部通過lpConsumerIndex進行CAS獲得消費者的消費端索引,然后通過原子操作拿到元素值,如果e不存在則繼續CAS自旋直到可以得到這個值為止:

@Override
    public E poll()
    {
        //CAS獲取索引位置
        finallong index = lpConsumerIndex();
       //......
//定位到索引偏移量
        finallong offset = modifiedCalcCircularRefElementOffset(index, mask);
        Object e = lvRefElement(buffer, offset);
        //如果元素為空,不斷自旋拿到值為止
        if (e == null)
        {
            if (index != lvProducerIndex())
            {
                // poll() == null iff queue is empty, null element is not strong enough indicator, so we must
                // check the producer index. If the queue is indeed not empty we spin until element is
                // visible.
                do
                {
                    e = lvRefElement(buffer, offset);
                }
                while (e == null);
            }
            //......
        }

        //......
        //返回元素
        return (E) e;
    }

關于網絡IO框架的一些展望——網絡IO模型io_uring

文章補充更新:近期和業界的一些大牛進行深入交流時了解到一個除了epoll以外更強大的io模型——io_uring,相較于epoll和其它io模型,它有著如下優點:

  • 用戶態和內存態進行IO操作時共享一塊內存區域,由此避免切態開銷。
  • 發起IO調用無需內核態調用,在SQPOLL模式下,sq線程會自行從提交隊列中獲取IO事件并處理,完成后會將結果寫入共享區域的完成隊列告知用戶。
  • 用戶態的應用程序可可直接通過完成隊列這個環形緩沖區獲得完成的IO事件并進行進一步操作。

這里我們以一個簡單的到磁盤中讀取數據的流程為例看看io_uring的整體流程:

  • 用戶發起IO請求,希望從/tmp目錄下讀取某個文本文件的內容
  • 發起IO請求,該調用會在io_uring的提交隊列(它是一個環形緩沖區)中追加該事件,用tail指針指向該事件。
  • 底層的sq線程輪詢提交隊列中待完成的事件指針,拿到這個IO事件,發起磁盤IO調用。
  • 完成數據讀寫之后,將該事件和結果寫入完成隊列。
  • 應用程序直接從完成隊列中讀取該事件結果并進行業務處理。

用戶態從完成隊列中獲取到我們的磁盤讀取事件的指針地址,從而拿到數據。可以看到,整個流程用戶態在完成一次IO期間完全沒有進行切態和數據拷貝的開銷,相較于epoll來說性能損耗小了很多。

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2025-05-07 08:55:00

2025-07-09 09:05:00

2025-05-19 10:00:00

MySQL數據庫InnoDB

2022-07-20 00:15:48

SQL數據庫編程語言

2021-04-19 08:35:44

PythonPython語言Python基礎

2021-06-16 14:18:37

NettyReactor線程模型

2010-08-17 14:56:00

HCNE認證

2011-04-15 12:25:21

BGP路由

2016-05-30 17:31:34

Spring框架

2022-08-16 15:17:37

機器學習算法模型

2021-07-10 08:04:07

Reactor模式Netty

2025-05-13 08:10:00

MySQL二進制日志binlog

2022-09-29 15:39:10

服務器NettyReactor

2024-11-25 11:00:00

模型訓練

2010-08-18 10:52:46

Linux筆試

2020-10-07 15:15:41

Python

2010-09-02 10:11:11

華為認證

2010-06-17 16:42:04

UML

2010-07-27 15:49:28

Flex

2009-12-18 17:34:38

Ruby線程
點贊
收藏

51CTO技術棧公眾號

日韩免费av片在线观看| 欧美精品久久久久久久多人混战| 精品一区二区视频| 中文字幕+乱码+中文| 婷婷伊人综合| 亚洲开心激情网| 国产精欧美一区二区三区白种人| 99久久精品免费看国产小宝寻花 | 成人中心免费视频| 中文字幕激情小说| 亚洲最新色图| 一本大道亚洲视频| 日韩黄色一区二区| 六九午夜精品视频| 日韩欧美国产激情| 在线观看三级网站| 粉嫩一区二区三区国产精品| 成人教育av在线| 国产日韩中文字幕| 毛片在线免费视频| 国产一区清纯| 久久的精品视频| 性欧美一区二区| 精品av导航| 日韩欧美电影一二三| 国产精品拍拍拍| 性欧美xxx69hd高清| 亚洲在线成人精品| 欧美三级午夜理伦三级老人| a天堂中文在线| 久久免费的精品国产v∧| 国产91色在线|亚洲| 亚洲中文一区二区三区| 久久久精品日韩| 久久人91精品久久久久久不卡| 任我爽在线视频| 日韩成人综合| 国产一区二区三区在线| 加勒比一区二区| 欧美天堂社区| 亚洲а∨天堂久久精品喷水| 绯色av蜜臀vs少妇| 亚洲大奶少妇| 日韩欧美国产高清| 欧美一级大片免费看| 九九99久久精品在免费线bt| 欧美美女黄视频| 91亚洲精品久久久蜜桃借种| 色呦呦在线观看视频| 亚洲视频网在线直播| 超碰97免费观看| a在线免费观看| 亚洲精品国产无天堂网2021| 在线观看污视频| 欧美aaaaaaa| 亚洲制服欧美中文字幕中文字幕| 青青青在线观看视频| 第四色日韩影片| 狠狠做深爱婷婷久久综合一区| 97xxxxx| 国产日韩另类视频一区| 91福利小视频| 一道本在线免费视频| 国产精品麻豆| 亚洲缚视频在线观看| jlzzjizz在线播放观看| 国产传媒欧美日韩成人精品大片| 在线观看亚洲视频| 一级黄色片日本| 欧美日韩免费观看一区=区三区| 欧美—级高清免费播放| 国产精品第5页| 琪琪一区二区三区| 91免费在线观看网站| 懂色av成人一区二区三区| www.亚洲免费av| 日本一区二区三区四区在线观看 | 久久爱www成人| 中文字幕精品在线| 久久久久99精品成人片试看| 亚洲精品影视| 国产精品成人播放| 国产精品久久久久久久免费看| 国产成人啪免费观看软件| 国产亚洲二区| 成人在线免费视频| 亚洲男人天堂av| 国产原创中文在线观看| 日韩欧美2区| 日韩欧美黄色影院| 久久久久久久久久久久| 亚洲综合色网| 日本欧美精品在线| 国产v在线观看| 国产人成亚洲第一网站在线播放 | 日韩一区二区三区免费观看| 香港三日本8a三级少妇三级99| 欧美色蜜桃97| 国内自拍欧美激情| 国产又粗又猛又色又| av电影在线观看一区| 伊人婷婷久久| 亚洲天堂免费电影| 日韩一卡二卡三卡四卡| 午夜精产品一区二区在线观看的| 黄色av成人| 国产欧美中文字幕| 日本成人一区| 亚洲一区自拍偷拍| 911av视频| 国产探花一区| 2019精品视频| 精品国产av 无码一区二区三区| 久久久久久电影| 久久久久久人妻一区二区三区| 日韩国产一二三区| 亚洲午夜国产成人av电影男同| 国语对白一区二区| 国产又粗又猛又爽又黄91精品| 欧美一区亚洲二区| 波多野结衣视频一区二区| 欧美一区二区网站| 女性裸体视频网站| 琪琪一区二区三区| 欧美重口乱码一区二区| ririsao久久精品一区| 日韩一卡二卡三卡四卡| www.xx日本| 日本不卡不码高清免费观看| 欧美黑人xxxxx| 免费一二一二在线视频| 欧美成人福利视频| 国产一区二区视频在线观看免费| 蜜臀av性久久久久蜜臀av麻豆| 欧美精品久久| 亚洲黄色网址| 亚洲欧美在线x视频| 日韩免费不卡视频| 成人午夜电影网站| 欧美黄网在线观看| 日韩激情综合| 久久久久久久国产精品视频| 精品区在线观看| 亚洲欧美日韩小说| 青娱乐精品在线| 一个色综合网| 97av影视网在线观看| 在线观看男女av免费网址| 91精品国产欧美日韩| 欧美丰满艳妇bbwbbw| 国产 日韩 欧美大片| wwwwww欧美| 豆花视频一区二区| 欧美在线性视频| 国产人成在线观看| 欧美午夜免费电影| 2014亚洲天堂| 国产美女精品人人做人人爽| 91大学生片黄在线观看| 成人在线视频你懂的| 97久久精品视频| 日本电影一区二区在线观看 | 99久久精品无免国产免费| 亚洲色图制服丝袜| 在线观看一区二区三区视频| 欧美精品观看| 久久人人九九| 国产成人精选| 欧美激情欧美激情在线五月| 亚洲av成人无码久久精品老人| 91久久精品一区二区| 亚洲av无一区二区三区| 国产成人自拍网| 国产最新免费视频| 日韩在线视频精品| 国产精品免费观看高清| 亚洲美女尤物影院| 日韩亚洲欧美成人| 欧美一区,二区| 欧洲一区在线观看| 黑人巨大精品一区二区在线| av不卡免费在线观看| 亚洲欧美激情网| 欧美成人69av| 欧美日韩另类丝袜其他| 97久久精品一区二区三区的观看方式| 欧美日韩福利视频| 黄色片在线免费观看| 欧美一区二区精品在线| 在线免费黄色av| 亚洲女人小视频在线观看| 一级特级黄色片| 久久99久久久欧美国产| 成年人午夜视频在线观看| 第九色区aⅴ天堂久久香| av一区二区在线看| 在线成人视屏| 高清欧美性猛交| 免费观看成人高潮| 精品视频一区在线视频| 99草在线视频| 一本高清dvd不卡在线观看| 欧产日产国产v| 日本一区二区高清| 女同性恋一区二区三区| 韩国精品一区二区| 日本精品久久久久中文字幕| 欧美黄色精品| 亚洲综合av一区| 亚洲最好看的视频| 国产精品久久亚洲7777| 9999精品| 国产精品海角社区在线观看| 91超碰在线播放| 久热精品视频在线免费观看| 黄色美女网站在线观看| 亚洲成人久久电影| 国产ts变态重口人妖hd| 欧美裸体bbwbbwbbw| www.亚洲激情| 色偷偷88欧美精品久久久| 国产精品九九九九九九| 亚洲人成在线观看一区二区| 免费看裸体网站| 久久一夜天堂av一区二区三区| 亚洲精品久久久久久| 韩国女主播成人在线观看| 日日噜噜噜噜久久久精品毛片| 欧美亚洲在线| 乱妇乱女熟妇熟女网站| 99精品99| www一区二区www免费| 亚洲第一区色| 久久成人福利视频| 伊人久久大香线蕉av超碰演员| 8x8x华人在线| 一级欧洲+日本+国产| 欧美性受xxxx黑人猛交88| 91偷拍一区二区三区精品| 亚洲精品国产精品国自产观看| 国精一区二区| 午夜精品一区二区三区在线观看| 国产欧美一区| 亚洲高清123| 欧美大人香蕉在线| 午夜啪啪免费视频| 亚洲色图88| 男女裸体影院高潮| 亚洲一级黄色| 欧美日韩性生活片| 国产精品亚洲综合色区韩国| 日韩av资源在线| 日本成人中文字幕在线视频| www.久久久精品| 国产真实乱偷精品视频免| 污污视频在线免费| 成人午夜碰碰视频| 亚洲一级av无码毛片精品| 91女厕偷拍女厕偷拍高清| 一级片手机在线观看| 国产精品免费视频观看| 婷婷久久综合网| 亚洲在线成人精品| 超碰超碰超碰超碰| 欧美日韩视频一区二区| 国产女人高潮时对白| 精品女同一区二区| 欧美欧美欧美| 日韩最新中文字幕电影免费看| а√天堂8资源在线官网| 久久久久久av| 日本综合字幕| 91精品视频播放| 欧美一级二级三级视频| 视频在线一区二区三区| 午夜天堂精品久久久久| 欧美日韩亚洲一| 免费成人av在线播放| 能看毛片的网站| 91在线视频免费观看| 成人一级黄色大片| 天天综合天天做天天综合| 黄色网址中文字幕| 日韩一区二区视频| 久草视频在线看| 久久综合88中文色鬼| 无码小电影在线观看网站免费| 国产精品久久久久久久久久久久久| av一级久久| 精品在线观看一区二区| 香港欧美日韩三级黄色一级电影网站| 久久99久久久久久| 欧美a一区二区| 成人做爰www看视频软件| 日本一区二区成人| 日本少妇吞精囗交| 51午夜精品国产| 男人天堂综合| 久久青草精品视频免费观看| 亚洲欧美久久精品| 久久久国产精品一区二区三区| 国产精品成人a在线观看| 成人免费观看视频在线观看| 国产精品自产自拍| 欧美熟妇激情一区二区三区| 亚洲成av人影院| 国产欧美一级片| 尤物yw午夜国产精品视频| 九九精品调教| 国产日韩换脸av一区在线观看| 天天操综合520| 青青在线视频免费观看| 久久精品久久99精品久久| 51调教丨国产调教视频| 亚洲综合清纯丝袜自拍| 伊人免费在线观看| 国产丝袜一区二区三区| aa级大片免费在线观看| 99国精产品一二二线| 国产精品videosex性欧美| 538在线视频观看| 91看片淫黄大片一级在线观看| 久久免费播放视频| 欧美大片拔萝卜| 污影院在线观看| 91亚洲精品视频| 久久精品亚洲人成影院 | 久久先锋影音av鲁色资源网| 国产一卡二卡在线播放| 91精品国产高清一区二区三区蜜臀| 都市激情一区| 国产精品91视频| 国产aⅴ精品一区二区三区久久| 国自产拍偷拍精品啪啪一区二区 | 欧美精品久久久久久久多人混战| 成人在线免费观看| 国产精品久久综合av爱欲tv| 精品国产精品| 精品国产成人av在线免| 久久久久久毛片| 懂色av中文字幕| 一本一本久久a久久精品综合小说| 成人天堂yy6080亚洲高清 | 午夜精品剧场| 亚洲热在线视频| 一级日本不卡的影视| 91精品久久久久久久久久久久久久| 天堂在线免费av| 韩国三级日本三级少妇99| 超碰在线亚洲| 日韩中文字幕在线免费| 99视频有精品| 好吊色在线视频| 日韩在线观看视频免费| 伊人亚洲精品| 久久香蕉视频网站| 成人爱爱电影网址| 日本中文字幕在线| 中文字幕亚洲一区二区三区五十路| 欧美亚洲综合视频| www成人免费| 99久久久国产精品| 69av视频在线观看| 久久精品国产99国产精品澳门| 日韩精品视频在线看| 国产免费黄色小视频| 国产亚洲综合在线| 国产精品久久久久久久免费| 欧美激情手机在线视频 | 丁香婷婷综合网| 在线观看精品国产| 一区二区三区国产视频| 欧美aaa级| www.日本少妇| 久久亚洲综合色一区二区三区| 国产裸体美女永久免费无遮挡| 久久精品免费播放| 精品日产乱码久久久久久仙踪林| 99999精品视频| 最新高清无码专区| 三级小视频在线观看| 国产精品视频地址| 欧美亚洲不卡| 久久美女免费视频| 日韩亚洲欧美高清| 亚洲wwww| 蜜臀精品一区二区| 国产欧美精品一区二区色综合 | 国产毛片精品国产一区二区三区| 全部毛片永久免费看| 色777狠狠综合秋免鲁丝| caoporn成人| 国产喷水theporn| 午夜国产不卡在线观看视频| 99青草视频在线播放视| 国产精品一区二区三区在线| 日本91福利区| 亚州国产精品视频| 久久精品国产欧美激情| 免费看日本一区二区|