<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Netty 多線程模型

          共 9554字,需瀏覽 20分鐘

           ·

          2021-11-29 23:27

          點擊上方“程序員大白”,選擇“星標”公眾號

          重磅干貨,第一時間送達


          0x01: 背景

          Java線程模型的演進

          • 單線程

          ? ? ? ? 時間回到十幾年前,那時主流的CPU都還是單核(除了商用高性能的小機),CPU的核心頻率是機器最重要的指標之一

          在Java領(lǐng)域當時比較流行的是單線程編程,對于CPU密集型的應(yīng)用程序而言,?頻繁的通過多線程進行協(xié)作和搶占時間片反而會降低性能

          • 多線程

          ? ? ? ?隨著硬件性能的提升,CPU的核數(shù)越來越越多,很多服務(wù)器標配已經(jīng)達到32或64核

          ? ? ? ?通過多線程并發(fā)編程,可以充分利用多核CPU的處理能力,提升系統(tǒng)的處理效率和并發(fā)性能

          從2005年開始,隨著多核處理器的逐步普及,java的多線程并發(fā)編程也逐漸流行起來,當時商用主流的JDK版本是1.4,用戶可以通過?new Thread() 的方式創(chuàng)建新的線程

          由于JDK1.4并沒有提供類似線程池這樣的線程管理容器,多線程之間的同步、協(xié)作、創(chuàng)建和銷毀等工作都需要用戶自己實現(xiàn)。由于?創(chuàng)建和銷毀線程是個相對比較重量級的操作,因此,這種原始的多線程編程效率和性能都不高

          • 線程池

          ? ? 為了提升Java多線程編程的效率和性能,降低用戶開發(fā)難度。JDK1.5推出了 java.util.concurrent 并發(fā)編程包

          ? ? ? 在并發(fā)編程類庫中,提供了 線程池、線程安全容器、原子類等新的類庫,極大的提升了Java多線程編程的效率,降低了開發(fā)難度

          從JDK1.5開始,?基于線程池的并發(fā)編程已經(jīng)成為Java多核編程的主流

          Reactor模型

          無論是C++還是Java編寫的網(wǎng)絡(luò)框架,大多數(shù)都是基于Reactor模式進行設(shè)計和開發(fā)

          Reactor?模式 基于?事件驅(qū)動?,特別適合處理?海量的I/O事件

          單線程模型

          Reactor?單線程?模型,?所有的IO操作?都在?同一個NIO線程?上面完成,NIO線程的職責(zé)如下:

          1. 作為NIO服務(wù)端,接收客戶端的TCP連接

          2. 作為NIO客戶端,向服務(wù)端發(fā)起TCP連接

          3. 讀取通信對端的請求或者應(yīng)答消息

          4. 向通信對端發(fā)送消息請求或者應(yīng)答消息

          Reactor單線程模型示意圖如下所示:

          由于Reactor模式使用的是?異步非阻塞IO?,所有的IO操作都不會導(dǎo)致阻塞,理論上一個線程可以獨立處理所有IO相關(guān)的操作。從架構(gòu)層面看,一個NIO線程確實可以完成其承擔的職責(zé)。例如:

          • 通過?Acceptor?類:?接收?客戶端的TCP連接請求?消息

          • 鏈路建立成功之后

          • 通過?Dispatch?:?對應(yīng)的ByteBuffer?派發(fā)?到指定的?Handler?上進行?消息解碼

          • 用戶線程?可以把?消息編碼?通過?NIO線程?將消息?發(fā)送給客戶端

          對于一些?小容量應(yīng)用場景?,可以使用單線程模型

          ? ? ? 對于高負載、大并發(fā)的應(yīng)用場景卻不合適,主要原因如下:

          ? ? ? 一個NIO線程同時處理成百上千的鏈路,性能上無法支撐

          ? ? ? 即便NIO線程的CPU負荷達到100%,也無法滿足海量消息的編碼、解碼、讀取和發(fā)送

          ? ? ? 當NIO線程負載過重之后,處理速度將變慢,這會導(dǎo)致大量客戶端連接超時

          ? ? ? 超時之后往往會進行重發(fā),這更加重了NIO線程的負載

          ? ? ? 最終會導(dǎo)致大量消息積壓和處理超時,成為系統(tǒng)的性能瓶頸

          ? ? ? ?一旦NIO線程意外跑飛,或者進入死循環(huán)

          ? ? ? 會導(dǎo)致整個系統(tǒng)通信模塊不可用,不能接收和處理外部消息,造成節(jié)點故障

          為了解決這些問題,演進出了 Reactor多線程模型

          • 多線程模型

          Rector?多線程?模型與單線程模型最大的區(qū)別就是有?一組NIO線程?處理IO操作?,它的原理圖如下:


          Reactor多線程模型的特點

          • 有專門一個NIO線程?Acceptor線程?用于監(jiān)聽服務(wù)端,?接收?客戶端的TCP連接請求

          • 網(wǎng)絡(luò)IO操作讀、寫等由?一個NIO線程池?負責(zé)

            • 一個任務(wù)隊列

            • N個可用的線程

            • 線程池可以采用?標準的JDK線程池?實現(xiàn)

            • 這些NIO線程負責(zé)?消息的讀取?、?解碼?、?編碼?和?發(fā)送

          • 1個NIO線程可以同時處理N條鏈路,但是1個鏈路只對應(yīng)1個NIO線程?,防止發(fā)生并發(fā)操作問題

          在?絕大多數(shù)場景?下,Reactor多線程模型都可以滿足性能需求

          ? ? ? 在極個別特殊場景中, 一個NIO線程負責(zé)監(jiān)聽和處理所有的客戶端連接可能會存在性能問題

          ? ? ? 例如并發(fā)百萬客戶端連接,或者服務(wù)端需要對客戶端握手進行安全認證,但是認證本身非常損耗性能

          ? ? ? 在這類場景下,單獨一個Acceptor線程可能會存在性能不足問題

          ? ? ? 為了解決性能問題,產(chǎn)生了第三種Reactor線程模型 主從Reactor多線程模型

          主從Reactor多線程模型

          主從Reactor線程模型?的特點是:

          • 服務(wù)端用于?接收客戶端連接?的不再是個?1個單獨的NIO線程?,而是一個?獨立的NIO線程池

          • Acceptor?接收?到?客戶端TCP連接請求?處理完成?后(可能包含接入?認證?等)

            • 由它負責(zé)?SocketChannel?的?讀寫?和?編解碼?工作

            • 將?新創(chuàng)建?的?SocketChannel?注冊?到?IO線程池?的?某個IO線程?上

          ? ? ? Acceptor線程池僅僅只用于 客戶端的登陸 、 握手和安全認證

          ? ? ? 一旦鏈路建立成功,就將鏈路注冊到后端subReactor線程池的IO線程上,由IO線程負責(zé)后續(xù)的IO操作

          它的線程模型如下圖所示:

          利用主從NIO線程模型,可以解決1個服務(wù)端監(jiān)聽線程無法有效處理所有客戶端連接的性能不足問題

          它的工作流程總結(jié)如下:

          1. 從主線程池中隨機選擇一個Reactor線程作為Acceptor線程,用于綁定監(jiān)聽端口,接收客戶端連接

          2. Acceptor線程接收客戶端連接請求之后創(chuàng)建新的SocketChannel,將其注冊到主線程池的其它Reactor線程上,由其負責(zé)接入認證、IP黑白名單過濾、握手等操作

          3. 業(yè)務(wù)層的鏈路正式建立,將?SocketChannel?從?主線程池的Reactor線程?的?多路復(fù)用器?上摘除,?重新注冊?到?Sub線程池的線程?上,用于處理I/O的讀寫操作


          0x02: Netty線程模型

          事實上,Netty的線程模型與上面介紹的三種Reactor線程模型相似

          下面通過Netty服務(wù)端和客戶端的線程處理流程圖來介紹Netty的線程模型

          • 服務(wù)端線程模型

          一種比較流行的做法是服務(wù)端監(jiān)聽線程和IO線程分離,類似于Reactor的多線程模型,它的工作原理圖如下:

          下面結(jié)合Netty的源碼,對服務(wù)端創(chuàng)建線程工作流程進行介紹:

          從用戶線程發(fā)起創(chuàng)建服務(wù)端

          第一步,從用戶線程發(fā)起創(chuàng)建服務(wù)端操作,代碼如下:

          通常情況下,服務(wù)端的創(chuàng)建是在用戶進程啟動的時候進行,因此一般由Main函數(shù)或者啟動類負責(zé)創(chuàng)建, 服務(wù)端的創(chuàng)建由業(yè)務(wù)線程負責(zé)完成

          在創(chuàng)建服務(wù)端的時候?qū)嵗?個EventLoopGroup:

          1個EventLoopGroup實際就是一個EventLoop線程組,負責(zé)管理EventLoop的申請和釋放

          • EventLoopGroup管理的線程數(shù):可以通過構(gòu)造函數(shù)設(shè)置,如果沒有設(shè)置,默認取?-Dio.netty.eventLoopThreads?,如果該系統(tǒng)參數(shù)也沒有指定,則為可用的CPU內(nèi)核數(shù) * 2

          • bossGroup線程組::實際就是Acceptor線程池,負責(zé)處理客戶端的TCP連接請求,如果系統(tǒng)只有一個服務(wù)端端口需要監(jiān)聽,則建議bossGroup線程組線程數(shù)設(shè)置為1

          • workerGroup:是真正負責(zé)I/O讀寫操作的線程組,通過ServerBootstrap的group方法進行設(shè)置,用于后續(xù)的Channel綁定

          Acceptor線程綁定監(jiān)聽端口,啟動NIO服務(wù)端

          第二步,Acceptor線程綁定監(jiān)聽端口,啟動NIO服務(wù)端

          從bossGroup中選擇一個Acceptor線程監(jiān)聽服務(wù)端,相關(guān)代碼如下:

          其中g(shù)roup()返回的就是bossGroup,next方法用于從線程組中獲取可用線程來選擇Acceptor線程,代碼如下:

          服務(wù)端Channel創(chuàng)建完成之后,將其注冊到多路復(fù)用器Selector上,用于接收客戶端的TCP連接,核心代碼如下:

          監(jiān)聽客戶端連接

          如果監(jiān)聽到客戶端連接,則創(chuàng)建客戶端SocketChannel連接,重新注冊到workerGroup的IO線程上

          首先看Acceptor如何處理客戶端的接入:

          調(diào)用unsafe的read()方法,對于NioServerSocketChannel,它調(diào)用了NioMessageUnsafe的read()方法,代碼如下:


          最終它會調(diào)用NioServerSocketChannel的doReadMessages方法,代碼如下:

          其中childEventLoopGroup就是之前的workerGroup, 從中選擇一個I/O線程負責(zé)網(wǎng)絡(luò)消息的讀寫

          監(jiān)聽網(wǎng)絡(luò)讀事件

          第四步,選擇IO線程之后,將SocketChannel注冊到多路復(fù)用器上,監(jiān)聽READ操作:

          處理讀寫事件

          處理網(wǎng)絡(luò)的I/O讀寫事件,核心代碼如下:

          • 客戶端線程模型

          相比于服務(wù)端,客戶端的線程模型簡單一些,它的工作原理如下:

          建立客戶端連接

          第一步,由用戶線程發(fā)起客戶端連接,示例代碼如下:


          相比于服務(wù)端,客戶端只需要創(chuàng)建一個EventLoopGroup,因為它不需要獨立的線程去監(jiān)聽客戶端連接,也沒必要通過一個單獨的客戶端線程去連接服務(wù)端。Netty是?異步事件驅(qū)動的NIO框架,它的連接和所有IO操作都是異步的,因此不需要創(chuàng)建單獨的連接線程?。相關(guān)代碼如下:

          當前的group()就是之前傳入的EventLoopGroup,從中獲取可用的IO線程EventLoop,然后作為參數(shù)設(shè)置到新創(chuàng)建的NioSocketChannel中

          發(fā)起連接操作

          第二步,發(fā)起連接操作,判斷連接結(jié)果,代碼如下:

          @Override
          protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
          if (localAddress != null) {
          javaChannel().socket().bind(localAddress);
          }

          boolean success = false;
          try {
          boolean connected = javaChannel().connect(remoteAddress);
          if (!connected) {
          selectionKey().interestOps(SelectionKey.OP_CONNECT);
          }
          success = true;
          return connected;
          } finally {
          if (!success) {
          doClose();
          }
          }
          }

          判斷連接結(jié)果:

          • 如果沒有連接成功:則監(jiān)聽連接網(wǎng)絡(luò)操作位SelectionKey.OP_CONNECT

          • 如果連接成功:則調(diào)用pipeline().fireChannelActive()將監(jiān)聽位修改為READ

          Selector發(fā)起輪詢操作

          第三步,由NioEventLoop的多路復(fù)用器輪詢連接操作結(jié)果,代碼如下:

          if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
          // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
          // See https://github.com/netty/netty/issues/924
          int ops = k.interestOps();
          ops &= ~SelectionKey.OP_CONNECT;
          k.interestOps(ops);

          unsafe.finishConnect();
          }

          判斷連接結(jié)果:

          public final void finishConnect() {
          // Note this method is invoked by the event loop only if the connection attempt was
          // neither cancelled nor timed out.

          assert eventLoop().inEventLoop();

          try {
          boolean wasActive = isActive();
          doFinishConnect();
          fulfillConnectPromise(connectPromise, wasActive);

          如果或連接成功,重新設(shè)置監(jiān)聽位為READ:

          @Override
          protected void doBeginRead() throws Exception {
          // Channel.read() or ChannelHandlerContext.read() was called
          if (inputShutdown) {
          return;
          }

          final SelectionKey selectionKey = this.selectionKey;
          if (!selectionKey.isValid()) {
          return;
          }

          readPending = true;

          final int interestOps = selectionKey.interestOps();
          if ((interestOps & readInterestOp) == 0) {
          selectionKey.interestOps(interestOps | readInterestOp);
          }
          }

          I/O讀寫

          第四步,由NioEventLoop線程負責(zé)I/O讀寫,同服務(wù)端

          總結(jié)

          客戶端線程模型如下:

          1. 由用戶線程負責(zé)初始化客戶端資源,發(fā)起連接操作

          2. 如果連接成功,將SocketChannel注冊到IO線程組的NioEventLoop線程中,監(jiān)聽讀操作位

          3. 如果沒有立即連接成功,將SocketChannel注冊到IO線程組的NioEventLoop線程中,監(jiān)聽連接操作位

          4. 連接成功之后,修改監(jiān)聽位為READ,但是不需要切換線程

          • Reactor線程NioEventLoop

          NioEventLoop介紹

          NioEventLoop是Netty的Reactor線程,它的職責(zé)如下:

          • 服務(wù)端Acceptor線程:負責(zé)處理客戶端的請求接入

          • 客戶端Connecor線程:負責(zé)注冊監(jiān)聽連接操作位,用于判斷異步連接結(jié)果

          • IO線程:監(jiān)聽網(wǎng)絡(luò)讀操作位,負責(zé)從SocketChannel中讀取報文

          • IO線程:負責(zé)向SocketChannel寫入報文發(fā)送給對方,如果發(fā)生寫半包,會自動注冊監(jiān)聽寫事件,用于后續(xù)繼續(xù)發(fā)送半包數(shù)據(jù),直到數(shù)據(jù)全部發(fā)送完成

          • 定時任務(wù)線程:執(zhí)行定時任務(wù),例如鏈路空閑檢測和發(fā)送心跳消息等

          • 線程執(zhí)行器:執(zhí)行普通的任務(wù)線程(Runnable)

          在服務(wù)端和客戶端線程模型章節(jié)我們已經(jīng)詳細介紹了NioEventLoop如何處理網(wǎng)絡(luò)IO事件,下面簡單看下它是如何處理定時任務(wù)和執(zhí)行普通的Runnable的

          執(zhí)行用戶自定義Task

          首先NioEventLoop繼承SingleThreadEventExecutor,這就意味著它實際上是一個線程個數(shù)為1的線程池,類繼承關(guān)系如下所示:

          其中,線程池和任務(wù)隊列定義如下:

          private final EventExecutorGroup parent;
          private final Queue<Runnable> taskQueue;
          private final Thread thread;
          private final Semaphore threadLock = new Semaphore(0);
          private final Set<Runnable> shutdownHooks = new LinkedHashSet<Runnable>();

          對于用戶而言,直接調(diào)用NioEventLoop的execute(Runnable task)方法即可執(zhí)行自定義的Task,代碼實現(xiàn)如下:

          @Override
          public void execute(Runnable task) {
          if (task == null) {
          throw new NullPointerException("task");
          }

          boolean inEventLoop = inEventLoop();
          if (inEventLoop) {
          addTask(task);
          } else {
          startThread();
          addTask(task);
          if (isShutdown() && removeTask(task)) {
          reject();
          }
          }

          if (!addTaskWakesUp && wakesUpForTask(task)) {
          wakeup(inEventLoop);
          }
          }
          實現(xiàn)定時任務(wù)

          NioEventLoop實現(xiàn)ScheduledExecutorService:

          通過調(diào)用SingleThreadEventExecutor的schedule系列方法,可以在NioEventLoop中執(zhí)行Netty或者用戶自定義的定時任務(wù),接口定義如下:

          • NioEventLoop設(shè)計原理

          串行化設(shè)計避免線程競爭

          當系統(tǒng)在運行過程中,如果頻繁的進行線程上下文切換,會帶來額外的性能損耗。多線程并發(fā)執(zhí)行某個業(yè)務(wù)流程,業(yè)務(wù)開發(fā)者還需要時刻對線程安全保持警惕,哪些數(shù)據(jù)可能會被并發(fā)修改,如何保護?這不僅降低了開發(fā)效率,也會帶來額外的性能損耗

          串行執(zhí)行Handler鏈

          為了解決上述問題,Netty采用了串行化設(shè)計理念,從消息的讀取、編碼以及后續(xù)Handler的執(zhí)行,始終都由IO線程NioEventLoop負責(zé),這就意外著整個流程不會進行線程上下文的切換,數(shù)據(jù)也不會面臨被并發(fā)修改的風(fēng)險,對于用戶而言,甚至不需要了解Netty的線程細節(jié),這確實是個非常好的設(shè)計理念,它的工作原理圖如下:

          • 一個NioEventLoop聚合了一個多路復(fù)用器Selector,因此?可以處理成百上千的客戶端連接

          • Netty的處理策略是每當有一個新的客戶端接入,則從NioEventLoop線程組中順序獲取一個可用的NioEventLoop,當?shù)竭_數(shù)組上限之后,重新返回到0,通過這種方式,可以?基本保證各個NioEventLoop的負載均衡

          • 一個客戶端連接只注冊到一個NioEventLoop上,這樣就?避免了多個IO線程?去并發(fā)操作它

          Netty通過串行化設(shè)計理念降低了用戶的開發(fā)難度,提升了處理性能。利用線程組實現(xiàn)了多個串行化線程水平并行執(zhí)行,線程之間并沒有交集,這樣既可以充分利用多核提升并行處理能力,同時避免了線程上下文的切換和并發(fā)保護帶來的額外性能損耗

          定時任務(wù)與時間輪算法

          在Netty中,有很多功能依賴定時任務(wù),比較典型的有兩種:

          1. 客戶端連接超時控制

          2. 鏈路空閑檢測

          一種比較常用的設(shè)計理念是在NioEventLoop中聚合JDK的定時任務(wù)線程池ScheduledExecutorService,通過它來執(zhí)行定時任務(wù)。這樣做單純從性能角度看不是最優(yōu),原因有如下三點:

          • 在IO線程中聚合了一個獨立的定時任務(wù)線程池,這樣在處理過程中會存在線程上下文切換問題,這就打破了Netty的串行化設(shè)計理念

          • 存在多線程并發(fā)操作問題,因為定時任務(wù)Task和IO線程NioEventLoop可能同時訪問并修改同一份數(shù)據(jù)

          • JDK的ScheduledExecutorService從性能角度看,存在性能優(yōu)化空間

          ? ? ? ?最早面臨上述問題的是操作系統(tǒng)和協(xié)議棧,例如TCP協(xié)議棧,其可靠傳輸依賴超時重傳機制,因此每個通過TCP傳輸?shù)?packet 都需要一個 timer來調(diào)度 timeout 事件

          ? ? ? 這類超時可能是海量的,如果為每個超時都創(chuàng)建一個定時器,從性能和資源消耗角度看都是不合理的

          定時輪

          Netty的定時任務(wù)調(diào)度基于時間輪算法調(diào)度: ? ?

          根據(jù)George Varghese和Tony Lauck 1996年的論文提出了一種定時輪的方式來管理和維護大量的timer調(diào)度

          定時輪是一種數(shù)據(jù)結(jié)構(gòu),其主體是一個循環(huán)列表,每個列表中包含一個稱之為slot的結(jié)構(gòu),它的原理圖如下:

          定時輪的工作原理可以類比于時鐘,如上圖箭頭(指針)按某一個方向按固定頻率輪動,每一次跳動稱為一個tick。這樣可以看出定時輪有3個重要的屬性參數(shù):

          • ticksPerWheel: 一輪的tick數(shù)

          • tickDuration: 一個tick的持續(xù)時間

          • timeUnit: 時間單位

          例如當ticksPerWheel=60,tickDuration=1,timeUnit=秒,這就和時鐘的秒針走動完全類似了

          時間輪的執(zhí)行由NioEventLoop來負責(zé)檢測,首先看任務(wù)隊列中是否有超時的定時任務(wù)和普通任務(wù),如果有則按照比例循環(huán)執(zhí)行這些任務(wù),代碼如下:

          @Override
          protected void run() {
          for (;;) {
          boolean oldWakenUp = wakenUp.getAndSet(false);
          try {
          if (hasTasks()) {
          selectNow();
          } else {

          如果沒有需要理解執(zhí)行的任務(wù),則調(diào)用Selector的select方法進行等待,等待的時間為定時任務(wù)隊列中第一個超時的定時任務(wù)時延,代碼如下:

          private void select(boolean oldWakenUp) throws IOException {
          Selector selector = this.selector;
          try {
          int selectCnt = 0;
          long currentTimeNanos = System.nanoTime();
          long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
          for (;;) {
          long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
          if (timeoutMillis <= 0) {
          if (selectCnt == 0) {
          selector.selectNow();
          selectCnt = 1;
          }
          break;
          }

          int selectedKeys = selector.select(timeoutMillis);

          從定時任務(wù)Task隊列中彈出delay最小的Task,計算超時時間,代碼如下:

          protected long delayNanos(long currentTimeNanos) {
          ScheduledFutureTask scheduledTask = peekScheduledTask();
          if (scheduledTask == null) {
          return SCHEDULE_PURGE_INTERVAL;
          }

          return scheduledTask.delayNanos(currentTimeNanos);
          }

          經(jīng)過周期tick之后,掃描定時任務(wù)列表,將超時的定時任務(wù)移除到普通任務(wù)隊列中,等待執(zhí)行,相關(guān)代碼如下:

          private void fetchFromScheduledTaskQueue() {
          if (hasScheduledTasks()) {
          long nanoTime = AbstractScheduledEventExecutor.nanoTime();
          for (;;) {
          Runnable scheduledTask = pollScheduledTask(nanoTime);
          if (scheduledTask == null) {
          break;
          }
          taskQueue.add(scheduledTask);
          }
          }
          }

          檢測和拷貝任務(wù)完成之后,就執(zhí)行超時的定時任務(wù),代碼如下:

          protected boolean runAllTasks() {
          fetchFromScheduledTaskQueue();
          Runnable task = pollTask();
          if (task == null) {
          return false;
          }

          for (;;) {
          try {
          task.run();
          } catch (Throwable t) {
          logger.warn("A task raised an exception.", t);
          }

          task = pollTask();
          if (task == null) {
          lastExecutionTime = ScheduledFutureTask.nanoTime();
          return true;
          }
          }
          }

          為了保證定時任務(wù)的執(zhí)行不會因為過度擠占IO事件的處理,Netty提供了IO執(zhí)行比例供用戶設(shè)置,用戶可以設(shè)置分配給IO的執(zhí)行比例,?防止因為海量定時任務(wù)的執(zhí)行導(dǎo)致IO處理超時或者積壓

          因為獲取系統(tǒng)的納秒時間是件耗時的操作,所以Netty每執(zhí)行64個定時任務(wù)檢測一次是否達到執(zhí)行的上限時間,達到則退出。如果沒有執(zhí)行完,放到下次Selector輪詢時再處理,給IO事件的處理提供機會,代碼如下:

          // Check timeout every 64 tasks because nanoTime() is relatively expensive.
          // XXX: Hard-coded value - will make it configurable if it is really a problem.
          if ((runTasks & 0x3F) == 0) {
          lastExecutionTime = ScheduledFutureTask.nanoTime();
          if (lastExecutionTime >= deadline) {
          break;
          }
          }

          task = pollTask();
          if (task == null) {
          lastExecutionTime = ScheduledFutureTask.nanoTime();
          break;
          }

          聚焦而不是膨脹

          Netty是個異步高性能的NIO框架,它并不是個業(yè)務(wù)運行容器,因此它?不需要也不應(yīng)該提供業(yè)務(wù)容器和業(yè)務(wù)線程?。合理的設(shè)計模式是Netty 只負責(zé)提供和管理NIO線程?,其它的業(yè)務(wù)層線程模型由用戶自己集成,Netty不應(yīng)該提供此類功能,只要將分層劃分清楚,就會更有利于用戶集成和擴展

          令人遺憾的是在Netty 3系列版本中,Netty提供了類似Mina異步Filter的ExecutionHandler,它聚合了JDK的線程池java.util.concurrent.Executor,用戶異步執(zhí)行后續(xù)的Handler

          ExecutionHandler是為了解決部分用戶Handler可能存在執(zhí)行時間不確定而導(dǎo)致IO線程被意外阻塞或者掛住,從需求合理性角度分析這類需求本身是合理的,但是Netty提供該功能卻并不合適。原因總結(jié)如下:

          1. 它打破了Netty堅持的串行化設(shè)計理念,在消息的接收和處理過程中發(fā)生了線程切換并引入新的線程池,打破了自身架構(gòu)堅守的設(shè)計原則,實際是一種架構(gòu)妥協(xié)

          2. 潛在的線程并發(fā)安全問題,如果異步Handler也操作它前面的用戶Handler,而用戶Handler又沒有進行線程安全保護,這就會導(dǎo)致隱蔽和致命的線程安全問題

          3. 用戶開發(fā)的復(fù)雜性,引入ExecutionHandler,打破了原來的ChannelPipeline串行執(zhí)行模式,用戶需要理解Netty底層的實現(xiàn)細節(jié),關(guān)心線程安全等問題,這會導(dǎo)致得不償失

          鑒于上述原因,Netty的后續(xù)版本徹底刪除了ExecutionHandler,而且也沒有提供類似的相關(guān)功能類,把精力聚焦在Netty的IO線程NioEventLoop上,這無疑是一種巨大的進步,Netty重新開始聚焦在IO線程本身,而不是提供用戶相關(guān)的業(yè)務(wù)線程模型

          • Netty線程開發(fā)最佳實踐

          時間可控的簡單業(yè)務(wù)

          如果業(yè)務(wù)非常簡單,執(zhí)行時間非常短,不需要與外部網(wǎng)元交互、訪問數(shù)據(jù)庫和磁盤,不需要等待其它資源,則建議?直接在業(yè)務(wù)ChannelHandler?中執(zhí)行,不需要再啟業(yè)務(wù)的線程或者線程池。避免線程上下文切換,也不存在線程并發(fā)問題

          復(fù)雜和時間不可控業(yè)務(wù)

          對于此類業(yè)務(wù),不建議直接在業(yè)務(wù)ChannelHandler中啟動線程或者線程池處理,建議?將不同的業(yè)務(wù)統(tǒng)一封裝成Task,統(tǒng)一投遞到后端的業(yè)務(wù)線程池中進行處理

          過多的業(yè)務(wù)ChannelHandler會帶來開發(fā)效率和可維護性問題,不要把Netty當作業(yè)務(wù)容器,對于大多數(shù)復(fù)雜的業(yè)務(wù)產(chǎn)品,仍然需要集成或者開發(fā)自己的業(yè)務(wù)容器,做好和Netty的架構(gòu)分層

          業(yè)務(wù)線程避免直接操作ChannelHandler

          對于ChannelHandler,IO線程和業(yè)務(wù)線程都可能會操作,因為業(yè)務(wù)通常是多線程模型,這樣就會存在多線程操作ChannelHandler。為了盡量避免多線程并發(fā)問題,建議按照Netty自身的做法,通過將?操作封裝成獨立的Task由NioEventLoop統(tǒng)一執(zhí)行?,而不是業(yè)務(wù)線程直接操作,相關(guān)代碼如下所示:

          如果你確認并發(fā)訪問的數(shù)據(jù)或者并發(fā)操作是安全的,則無需多此一舉,這個需要根據(jù)具體的業(yè)務(wù)場景進行判斷,靈活處理


          0x03: 總結(jié)

          盡管Netty的線程模型并不復(fù)雜,但是如何合理利用Netty開發(fā)出高性能、高并發(fā)的業(yè)務(wù)產(chǎn)品,仍然是個有挑戰(zhàn)的工作。只有充分理解了Netty的線程模型和設(shè)計原理,才能開發(fā)出高質(zhì)量的產(chǎn)品


          source: //klose911.github.io/html/netty/thread.html


          13個你一定要知道的PyTorch特性

          解讀:為什么要做特征歸一化/標準化?

          一文搞懂 PyTorch 內(nèi)部機制

          張一鳴:每個逆襲的年輕人,都具備的底層能力


          關(guān)


          ,學(xué),西學(xué)學(xué),質(zhì),結(jié),關(guān)[],學(xué)習(xí)!


          瀏覽 27
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲无码高清视频 | 国产宴妇精品久久久久久 | 夜夜撸夜夜| 亚洲在线资源 | 日本人体视频 |