1. 程式人生 > 資訊 >抖音直播調整激勵方向,重點獎勵民族舞、古典舞、當代舞、民歌等七大內容品類

抖音直播調整激勵方向,重點獎勵民族舞、古典舞、當代舞、民歌等七大內容品類

學習本章需要先知道IO多路複用,不清楚的請移步:IO多路複用

網路通訊中,阻塞IO兩大阻塞的地方:socket連結阻塞等待讀取檔案阻塞。 本地檔案io就只有一個等待檔案阻塞

一.Reactor模型(Netty執行緒模型)

說Netty之前先說一下高效能網路模式Reactor。由於NIO是面向過程編寫,效率太低。大佬們基於面向物件的思想,對 I/O 多路複用作了一層封裝,讓使用者不用考慮底層網路 API 的細節,只需要關注應用程式碼的編寫。大佬們還為這種模式取了個讓人第一時間難以理解的名字:Reactor 模式。意思對事件的反應,來了一個事件(連線請求,讀寫請求),Reactor 就有相對應的反應/響應

Reactor有3種經典方案:


單Reactor單執行緒

  1. Reactor 物件通過 select (IO 多路複用介面) 監聽事件,收到事件後通過 dispatch 進行分發,具體分發給 Acceptor 物件還是 Handler 物件,還要看收到的事件型別;
  2. 如果是連線建立的事件,則交由 Acceptor 物件進行處理,Acceptor 物件會通過 accept 方法 獲取連線,並建立一個 Handler 物件來處理後續的響應事件;
  3. 如果不是連線建立事件, 則交由當前連線對應的 Handler 物件來進行響應;
  4. Handler 物件通過 read -> 業務處理 -> send 的流程來完成完整的業務流程。

缺點:

  • 因為只有一個程序,無法充分利用 多核 CPU 的效能;
  • Handler 物件在業務處理時,整個程序是無法處理其他連線的事件的,如果業務處理耗時比較長,那麼就造成響應的延遲;

適用場景:單 Reactor 單程序的方案不適用計算機密集型的場景,只適用於業務處理非常快速的場景。


單Reactor多執行緒

前面的三個步驟和單 Reactor 單執行緒方案是一樣的,我們只看不一樣的部分:

  1. Handler 物件不再負責業務處理,只負責資料的接收和傳送,Handler 物件通過 read 讀取到資料後,會將資料發給執行緒池中的執行緒進行業務處理;
  2. 處理完後,將結果發給主執行緒中的 Handler 物件,接著由 Handler 通過 send 方法將響應結果傳送給 client;

缺點:

  • 雖然充分了利用了多核CPU,但是會涉及共享資源的競爭。
  • 因為一個 Reactor 物件承擔所有事件的監聽和響應,而且只在主執行緒中執行,在面對瞬間高併發的場景時,容易成為效能的瓶頸的地方。

多Reactor多執行緒(主從Reactor多執行緒)

和單Reactor多執行緒唯一不同的是,將連線請求讀寫請求分別發給不同的Reactor進行處理。該模式應用於Netty,Memcache等專案中


那麼我們看看Netty到底是什麼樣的模型:

  • Netty將主Reactor和從Reactor抽象成倆個組:Boss Group專門負責連線請求,Worker Group專門負責讀寫請求。
  • 每一個組包含多個NioEventLoopGroup,這是一個事件迴圈組,這個組內有多個事件迴圈(NioEventLoop),每一個NioEventLoop都有一個selector,用於監聽網路的連線,讀寫請求。
  • 每一個連線請求被Boss Group的selector監測到,都會交給Worker Group中的一個事件迴圈組,由Worker Group的selector監測是否有讀寫操作。
  • 最後交給Pipeline的handler進行業務處理

具體細節下面詳細說明!

二.Netty概述

Netty 是什麼?

  • Netty 是一個基於 NIO的 client-server(客戶端伺服器)框架,使用它可以快速簡單地開發網路應用程式。
  • 它極大地簡化並優化了 TCP 和 UDP 套接字伺服器等網路程式設計,並且效能以及安全性等很多方面甚至都要更好。
  • 支援多種協議如 FTP,SMTP,HTTP 以及各種二進位制和基於文字的傳統協議。

為什麼要用 Netty?因為 Netty 具有下面這些優點,並且相比於直接使用 JDK 自帶的 NIO 相關的 API 來說更加易用。

  • 統一的 API,支援多種傳輸型別,阻塞和非阻塞的。
  • 簡單而強大的執行緒模型。
  • 自帶編解碼器解決 TCP 粘包/拆包問題。
  • 自帶各種協議棧。
  • 真正的無連線資料包套接字支援。
  • 比直接使用 Java 核心 API 有更高的吞吐量、更低的延遲、更低的資源消耗和更少的記憶體複製。
  • 安全性不錯,有完整的 SSL/TLS 以及 StartTLS 支援。
  • 社群活躍
  • 成熟穩定,經歷了大型專案的使用和考驗,而且很多開源專案都使用到了 Netty, 比如我們經常接觸的 Dubbo、RocketMQ 等等。
  • ......

Netty 應用場景瞭解麼?理論上來說,NIO 可以做的事情 ,使用 Netty 都可以做並且更好。Netty 主要用來做網路通訊:

  • 作為 RPC 框架的網路通訊工具
  • 實現一個自己的 HTTP 伺服器:說到 HTTP 伺服器的話,作為 Java 後端開發,我們一般使用 Tomcat 比較多。一個最基本的 HTTP 伺服器可要以處理常見的 HTTP Method 的請求,比如 POST 請求、GET 請求等等。
  • 實現一個即時通訊系統:使用 Netty 我們可以實現一個可以聊天類似微信的即時通訊系統,這方面的開源專案還蠻多的,可以自行去 Github 找一找。
  • 實現訊息推送系統:市面上有很多訊息推送系統都是基於 Netty 來做的。
  • ...
三.Netty 核心元件有哪些?分別有什麼作用?

Bootstrap,ServerBootstrap

引導,一個Netty通常又一個Bootstrap開始,主要作用是配置整個Netty程式,串聯各個元件

Bootstrap是客戶端的啟動引導類/輔助類,具體使用方法如下:

 EventLoopGroup group = new NioEventLoopGroup();
        try {
            //建立客戶端啟動引導/輔助類:Bootstrap
            Bootstrap b = new Bootstrap();
            //指定執行緒模型
            b.group(group).
                    ......
            // 嘗試建立連線
            ChannelFuture f = b.connect(host, port).sync();
            f.channel().closeFuture().sync();
        } finally {
            // 優雅關閉相關執行緒組資源
            group.shutdownGracefully();

ServerBootstrap客戶端的啟動引導類/輔助類,具體使用方法如下:

 // 1.bossGroup 用於接收連線,workerGroup 用於具體的處理
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //2.建立服務端啟動引導/輔助類:ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            //3.給引導類配置兩大執行緒組,確定了執行緒模型
            b.group(bossGroup, workerGroup).
                   ......
            // 6.繫結埠
            ChannelFuture f = b.bind(port).sync();
            // 等待連線關閉
            f.channel().closeFuture().sync();
        } finally {
            //7.優雅關閉相關執行緒組資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

說明:

  • Bootstrap通常使用connet()方法連線到遠端的主機和埠,作為一個 Netty TCP 協議通訊中的客戶端。另外,Bootstrap也可以通過bind()方法繫結本地的一個埠,作為 UDP 協議通訊中的一端。
  • ServerBootstrap通常使用bind()方法繫結本地的埠上,然後等待客戶端的連線。
  • Bootstrap只需要配置一個執行緒組—EventLoopGroup,而ServerBootstrap需要配置兩個執行緒組—EventLoopGroup,一個用於接收連線,一個用於具體的處理。

Channel(在程式碼層面,讀寫,連線操作看似交給BootStrap,其實是交給Channel)

Channel介面是 Netty 對網路操作抽象類,它除了包括基本的 I/O 操作,如bind()connect()read()write()等。

比較常用的Channel介面實現類是NioServerSocketChannel(服務端)和NioSocketChannel(客戶端),這兩個Channel可以和 BIO 程式設計模型中的ServerSocket以及Socket兩個概念對應上。


ChannelFuture

Netty 是非同步非阻塞的,所有的 I/O 操作都為非同步的。因此,我們不能立刻得到操作是否執行成功,但是,可通過下面2種方式知道:

  • 可以通過ChannelFuture介面的addListener()方法註冊一個ChannelFutureListener,當操作執行成功或者失敗時,監聽就會自動觸發返回結果。
  • 可以通過ChannelFuture介面的sync()方法讓非同步的操作變成同步的。

EventLoop 與 EventLoopGroup

  • EventLoopGroup 是一個 EventLoop 池,包含很多的 EventLoop。
  • EventLoop的主要作用實際就是負責監聽網路事件並呼叫事件處理器進行相關 I/O 操作的處理。因為每一個EventLoop有一個自己的selector
  • 每一個EventLoop都對應一個Thread
  • 每一個Channel都需要註冊到EventLoop上進行IO操作。
  • 對應到的實現類就是NioEventLoopNioEventLoopGroup

Selector和TaskQueue

  • 都屬於EventLoop 內的元件。
  • Selector就是NIO 的selector,這裡不多介紹,詳細請看NIO多路複用
  • 關於TaskQueue:當handler裡面有長時間的任務時候,可以把這個任務放到TaskQueue中,先響應客戶端,再執行長任務,,相當於非同步執行,但是其實還是這一個執行緒在執行。

ChannelPipeline和ChannelHandler

  • ChannelPipeline是一個雙向連結串列,裡面就是一些ChannelHandler,用來處理對應的Channel的業務。
  • ChannelPipeline有Channel的資訊,Channel中也有ChannelPipeline的資訊。
  • 我們可以在ChannelPipeline上通過addLast()方法新增一個或者多個ChannelHandler,因為一個數據或者事件可能會被多個 Handler 處理。當一個ChannelHandler處理完之後就將資料交給下一個ChannelHandler
四.Netty服務端和客戶端的啟動過程瞭解麼?
    // 1.bossGroup 用於接收連線,workerGroup 用於具體的處理
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //2.建立服務端啟動引導/輔助類:ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            //3.給引導類配置兩大執行緒組,確定了執行緒模型
            b.group(bossGroup, workerGroup)
                    // (非必備)列印日誌
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 4.指定 IO 模型
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {
                            ChannelPipeline p = ch.pipeline();
                            //5.可以自定義客戶端訊息的業務處理邏輯
                            p.addLast(new HelloServerHandler());
                        }
                    });
            // 6.繫結埠,呼叫 sync 方法阻塞直到繫結完成
            ChannelFuture f = b.bind(port).sync();
            // 7.阻塞等待直到伺服器Channel關閉(closeFuture()方法獲取Channel 的CloseFuture物件,然後呼叫sync()方法)
            f.channel().closeFuture().sync();
        } finally {
            //8.優雅關閉相關執行緒組資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
五.其他小問題

Netty 長連線?

  • 我們知道 TCP 在進行讀寫之前,server 與 client 之間必須提前建立一個連線。建立連線的過程,需要我們常說的三次握手,釋放/關閉連線的話需要四次揮手。這個過程是比較消耗網路資源並且有時間延遲的。
  • 所謂,短連線說的就是 server 端 與 client 端建立連線之後,讀寫完成之後就關閉掉連線,如果下一次再要互相傳送訊息,就要重新連線。短連線的有點很明顯,就是管理和實現都比較簡單,缺點也很明顯,每一次的讀寫都要建立連線必然會帶來大量網路資源的消耗,並且連線的建立也需要耗費時間。
  • 長連線說的就是 client 向 server 雙方建立連線之後,即使 client 與 server 完成一次讀寫,它們之間的連線並不會主動關閉,後續的讀寫操作會繼續使用這個連線。長連線的可以省去較多的 TCP 建立和關閉的操作,降低對網路資源的依賴,節約時間。對於頻繁請求資源的客戶來說,非常適用長連線。

Netty心跳機制瞭解麼?

  • 在 TCP 保持長連線的過程中,可能會出現斷網等網路異常出現,異常發生的時候, client 與 server 之間如果沒有互動的話,它們是無法發現對方已經掉線的。為了解決這個問題, 我們就需要引入心跳機制
  • 心跳機制的工作原理是: 在 client 與 server 之間在一定時間內沒有資料互動時, 即處於 idle 狀態時, 客戶端或伺服器就會發送一個特殊的資料包給對方, 當接收方收到這個資料報文後, 也立即傳送一個特殊的資料報文, 迴應傳送方, 此即一個 PING-PONG 互動。所以, 當某一端收到心跳訊息後, 就知道了對方仍然線上, 這就確保 TCP 連線的有效性.
  • TCP 實際上自帶的就有長連線選項,本身是也有心跳包機制,也就是 TCP 的選項:SO_KEEPALIVE。但是,TCP 協議層面的長連線靈活性不夠。所以,一般情況下我們都是在應用層協議上實現自定義心跳機制的,也就是在 Netty 層面通過編碼實現。通過 Netty 實現心跳機制的話,核心類是IdleStateHandler

Netty 的零拷貝瞭解麼?

在 OS 層面上的Zero-copy通常指避免在使用者態(User-space)核心態(Kernel-space)之間來回拷貝資料。而在 Netty 層面 ,零拷貝主要體現在對於資料操作的優化。

  • 使用 Netty 提供的CompositeByteBuf類, 可以將多個ByteBuf合併為一個邏輯上的ByteBuf, 避免了各個ByteBuf之間的拷貝。
  • ByteBuf支援 slice 操作, 因此可以將 ByteBuf 分解為多個共享同一個儲存區域的ByteBuf, 避免了記憶體的拷貝。
  • 通過FileRegion包裝的FileChannel.tranferTo實現檔案傳輸, 可以直接將檔案緩衝區的資料傳送到目標Channel, 避免了傳統通過迴圈 write 方式導致的記憶體拷貝問題。

NioEventLoopGroup 預設的建構函式會起多少執行緒?

  • 預設是cpu核數*2個子執行緒(也就是nioEventLoop)

寄語:當努力到一定程度,幸運自會與你不期而遇