Netty 学习和进阶策略
https://www.infoq.cn/article/xt9*7K4fJktiuWTLYrZS
背景
Netty 框架的特點(diǎn)
Netty 的一個(gè)特點(diǎn)就是入門相對(duì)比較容易,但是真正掌握并精通是非常困難的,原因有如下幾個(gè):
初學(xué)者常見問題
對(duì)于很多初學(xué)者,在學(xué)習(xí)過程中經(jīng)常會(huì)遇到如下幾個(gè)問題:
Netty 學(xué)習(xí)策略
Netty 入門相對(duì)簡(jiǎn)單,但是要在實(shí)際項(xiàng)目中用好它,出了問題能夠快速定位和解決,卻并非易事。只有在入門階段扎實(shí)的學(xué)好 Netty,后面使用才能夠得心應(yīng)手。
入門知識(shí)準(zhǔn)備
Java NIO 類庫
需要熟悉和掌握的類庫主要包括:
- 緩沖區(qū) Buffer。
- 通道 Channel。
- 多路復(fù)用器 Selector。
首先介紹緩沖區(qū)(Buffer)的概念,Buffer 是一個(gè)對(duì)象,它包含一些要寫入或者要讀出的數(shù)據(jù)。在 NIO 類庫中加入 Buffer 對(duì)象,體現(xiàn)了新庫與原 I/O 的一個(gè)重要區(qū)別。在面向流的 I/O 中,可以將數(shù)據(jù)直接寫入或者將數(shù)據(jù)直接讀到 Stream 對(duì)象中。在 NIO 庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的;在寫入數(shù)據(jù)時(shí),寫入到緩沖區(qū)中。任何時(shí)候訪問 NIO 中的數(shù)據(jù),都是通過緩沖區(qū)進(jìn)行操作。
緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組。通常它是一個(gè)字節(jié)數(shù)組(ByteBuffer),也可以使用其他種類的數(shù)組。但是一個(gè)緩沖區(qū)不僅僅是一個(gè)數(shù)組,緩沖區(qū)提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪問以及維護(hù)讀寫位置(limit)等信息。
最常用的緩沖區(qū)是 ByteBuffer,一個(gè) ByteBuffer 提供了一組功能用于操作 byte 數(shù)組。比較常用的就是 get 和 put 系列方法,如下所示:
圖 1 ByteBuffer 常用接口定義Channel 是一個(gè)通道,可以通過它讀取和寫入數(shù)據(jù),它就像自來水管一樣,網(wǎng)絡(luò)數(shù)據(jù)通過 Channel 讀取和寫入。通道與流的不同之處在于通道是雙向的,流只是在一個(gè)方向上移動(dòng)(一個(gè)流必須是 InputStream 或者 OutputStream 的子類),而且通道可以用于讀、寫或者同時(shí)用于讀寫。因?yàn)?Channel 是全雙工的,所以它可以比流更好地映射底層操作系統(tǒng)的 API。特別是在 UNIX 網(wǎng)絡(luò)編程模型中,底層操作系統(tǒng)的通道都是全雙工的,同時(shí)支持讀寫操作。
比較常用的 Channel 是 SocketChannel 和 ServerSocketChannel,其中 SocketChannel 的繼承關(guān)系如下圖所示:
圖 2 SocketChannel 繼承關(guān)系Selector 是 Java NIO 編程的基礎(chǔ),熟練地掌握 Selector 對(duì)于掌握 NIO 編程至關(guān)重要。多路復(fù)用器提供選擇已經(jīng)就緒的任務(wù)的能力。簡(jiǎn)單來講,Selector 會(huì)不斷地輪詢注冊(cè)在其上的 Channel,如果某個(gè) Channel 上面有新的 TCP 連接接入、讀和寫事件,這個(gè) Channel 就處于就緒狀態(tài),會(huì)被 Selector 輪詢出來,然后通過 SelectionKey 可以獲取就緒 Channel 的集合,進(jìn)行后續(xù)的 I/O 操作。
Java 多線程編程
作為異步事件驅(qū)動(dòng)、高性能的 NIO 框架,Netty 代碼中大量運(yùn)用了 Java 多線程編程技巧,熟練掌握多線程編程是掌握 Netty 的必備條件。
需要掌握的多線程編程相關(guān)知識(shí)包括:
- Java 內(nèi)存模型。
- 關(guān)鍵字 synchronized。
- 讀寫鎖。
- volatile 的正確使用。
- CAS 指令和原子類。
- JDK 線程池以及各種默認(rèn)實(shí)現(xiàn)。
以關(guān)鍵字 synchronized 為例,它可以保證在同一時(shí)刻,只有一個(gè)線程可以執(zhí)行某一個(gè)方法或者代碼塊。同步的作用不僅僅是互斥,它的另一個(gè)作用就是共享可變性,當(dāng)某個(gè)線程修改了可變數(shù)據(jù)并釋放鎖后,其它的線程可以獲取被修改變量的最新值。如果沒有正確的同步,這種修改對(duì)其它線程是不可見的。
下面我們就通過對(duì) Netty 的源碼進(jìn)行分析,看看 Netty 是如何對(duì)并發(fā)可變數(shù)據(jù)進(jìn)行正確同步的。以 AbstractBootstrap 為例進(jìn)行分析,首先看它的 option 方法:
這個(gè)方法的作用是設(shè)置 ServerBootstrap 或 Bootstrap 的 Socket 屬性,它的屬性集定義如下:
復(fù)制代碼| ? | private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>(); |
由于是非線程安全的 LinkedHashMap, 所以如果多線程創(chuàng)建、訪問和修改 LinkedHashMap 時(shí),必須在外部進(jìn)行必要的同步。由于 ServerBootstrap 和 Bootstrap 被調(diào)用方線程創(chuàng)建和使用,無法保證它的方法和成員變量不被并發(fā)訪問。因此,作為成員變量的 options 必須進(jìn)行正確的同步。由于考慮到鎖的范圍需要盡可能的小,所以對(duì)傳參的 option 和 value 的合法性判斷不需要加鎖,保證鎖的范圍盡可能的細(xì)粒度。
Netty 加鎖的地方非常多,大家在閱讀代碼的時(shí)候可以仔細(xì)體會(huì)下,為什么有的地方要加鎖,有的地方有不需要?如果不需要,為什么?當(dāng)你對(duì)鎖的原理理解以后,對(duì)于這些鎖的使用時(shí)機(jī)和技巧理解起來就相對(duì)容易了。
Netty 源碼學(xué)習(xí)
關(guān)鍵類庫學(xué)習(xí)
Netty 的核心類庫可以分為 5 大類,需要熟練掌握:
1、ByteBuf 和相關(guān)輔助類:ByteBuf 是個(gè) Byte 數(shù)組的緩沖區(qū),它的基本功能應(yīng)該與 JDK 的 ByteBuffer 一致,提供以下幾類基本功能:
- 7 種 Java 基礎(chǔ)類型、byte 數(shù)組、ByteBuffer(ByteBuf)等的讀寫。
- 緩沖區(qū)自身的 copy 和 slice 等。
- 設(shè)置網(wǎng)絡(luò)字節(jié)序。
- 構(gòu)造緩沖區(qū)實(shí)例。
- 操作位置指針等方法。
- 動(dòng)態(tài)的擴(kuò)展和收縮。
從內(nèi)存分配的角度看,ByteBuf 可以分為兩類:堆內(nèi)存(HeapByteBuf)字節(jié)緩沖區(qū):特點(diǎn)是內(nèi)存的分配和回收速度快,可以被 JVM 自動(dòng)回收;缺點(diǎn)就是如果進(jìn)行 Socket 的 I/O 讀寫,需要額外做一次內(nèi)存復(fù)制,將堆內(nèi)存對(duì)應(yīng)的緩沖區(qū)復(fù)制到內(nèi)核 Channel 中,性能會(huì)有一定程度的下降。直接內(nèi)存(DirectByteBuf)字節(jié)緩沖區(qū):非堆內(nèi)存,它在堆外進(jìn)行內(nèi)存分配,相比于堆內(nèi)存,它的分配和回收速度會(huì)慢一些,但是將它寫入或者從 Socket Channel 中讀取時(shí),由于少了一次內(nèi)存復(fù)制,速度比堆內(nèi)存快。
2、Channel 和 Unsafe:io.netty.channel.Channel 是 Netty 網(wǎng)絡(luò)操作抽象類,它聚合了一組功能,包括但不限于網(wǎng)路的讀、寫,客戶端發(fā)起連接、主動(dòng)關(guān)閉連接,鏈路關(guān)閉,獲取通信雙方的網(wǎng)絡(luò)地址等。它也包含了 Netty 框架相關(guān)的一些功能,包括獲取該 Chanel 的 EventLoop,獲取緩沖分配器 ByteBufAllocator 和 pipeline 等。Unsafe 是個(gè)內(nèi)部接口,聚合在 Channel 中協(xié)助進(jìn)行網(wǎng)絡(luò)讀寫相關(guān)的操作,它提供的主要功能如下表所示:
Unsafe API 功能列表3、ChannelPipeline 和 ChannelHandler: Netty 的 ChannelPipeline 和 ChannelHandler 機(jī)制類似于 Servlet 和 Filter 過濾器,這類攔截器實(shí)際上是職責(zé)鏈模式的一種變形,主要是為了方便事件的攔截和用戶業(yè)務(wù)邏輯的定制。Servlet Filter 是 JEE Web 應(yīng)用程序級(jí)的 Java 代碼組件,它能夠以聲明的方式插入到 HTTP 請(qǐng)求響應(yīng)的處理過程中,用于攔截請(qǐng)求和響應(yīng),以便能夠查看、提取或以某種方式操作正在客戶端和服務(wù)器之間交換的數(shù)據(jù)。攔截器封裝了業(yè)務(wù)定制邏輯,能夠?qū)崿F(xiàn)對(duì) Web 應(yīng)用程序的預(yù)處理和事后處理。過濾器提供了一種面向?qū)ο蟮哪K化機(jī)制,用來將公共任務(wù)封裝到可插入的組件中。這些組件通過 Web 部署配置文件(web.xml)進(jìn)行聲明,可以方便地添加和刪除過濾器,無須改動(dòng)任何應(yīng)用程序代碼或 JSP 頁面,由 Servlet 進(jìn)行動(dòng)態(tài)調(diào)用。通過在請(qǐng)求 / 響應(yīng)鏈中使用過濾器,可以對(duì)應(yīng)用程序(而不是以任何方式替代)的 Servlet 或 JSP 頁面提供的核心處理進(jìn)行補(bǔ)充,而不破壞 Servlet 或 JSP 頁面的功能。由于是純 Java 實(shí)現(xiàn),所以 Servlet 過濾器具有跨平臺(tái)的可重用性,使得它們很容易地被部署到任何符合 Servlet 規(guī)范的 JEE 環(huán)境中。
Netty 的 Channel 過濾器實(shí)現(xiàn)原理與 Servlet Filter 機(jī)制一致,它將 Channel 的數(shù)據(jù)管道抽象為 ChannelPipeline,消息在 ChannelPipeline 中流動(dòng)和傳遞。ChannelPipeline 持有 I/O 事件攔截器 ChannelHandler 的鏈表,由 ChannelHandler 對(duì) I/O 事件進(jìn)行攔截和處理,可以方便地通過新增和刪除 ChannelHandler 來實(shí)現(xiàn)不同的業(yè)務(wù)邏輯定制,不需要對(duì)已有的 ChannelHandler 進(jìn)行修改,能夠?qū)崿F(xiàn)對(duì)修改封閉和對(duì)擴(kuò)展的支持。ChannelPipeline 是 ChannelHandler 的容器,它負(fù)責(zé) ChannelHandler 的管理和事件攔截與調(diào)度:
圖 3 ChannelPipeline 對(duì)事件流的攔截和處理流Netty 中的事件分為 inbound 事件和 outbound 事件。inbound 事件通常由 I/O 線程觸發(fā),例如 TCP 鏈路建立事件、鏈路關(guān)閉事件、讀事件、異常通知事件等。
Outbound 事件通常是由用戶主動(dòng)發(fā)起的網(wǎng)絡(luò) I/O 操作,例如用戶發(fā)起的連接操作、綁定操作、消息發(fā)送等操作。ChannelHandler 類似于 Servlet 的 Filter 過濾器,負(fù)責(zé)對(duì) I/O 事件或者 I/O 操作進(jìn)行攔截和處理,它可以選擇性地?cái)r截和處理自己感興趣的事件,也可以透?jìng)骱徒K止事件的傳遞。基于 ChannelHandler 接口,用戶可以方便地進(jìn)行業(yè)務(wù)邏輯定制,例如打印日志、統(tǒng)一封裝異常信息、性能統(tǒng)計(jì)和消息編解碼等。
4、EventLoop:Netty 的 NioEventLoop 并不是一個(gè)純粹的 I/O 線程,它除了負(fù)責(zé) I/O 的讀寫之外,還兼顧處理以下兩類任務(wù):
普通 Task:通過調(diào)用 NioEventLoop 的 execute(Runnable task) 方法實(shí)現(xiàn),Netty 有很多系統(tǒng) Task,創(chuàng)建它們的主要原因是:當(dāng) I/O 線程和用戶線程同時(shí)操作網(wǎng)絡(luò)資源時(shí),為了防止并發(fā)操作導(dǎo)致的鎖競(jìng)爭(zhēng),將用戶線程的操作封裝成 Task 放入消息隊(duì)列中,由 I/O 線程負(fù)責(zé)執(zhí)行,這樣就實(shí)現(xiàn)了局部無鎖化。
定時(shí)任務(wù):通過調(diào)用 NioEventLoop 的 schedule(Runnable command, long delay, TimeUnit unit) 方法實(shí)現(xiàn)。
Netty 的線程模型并不是一成不變的,它實(shí)際取決于用戶的啟動(dòng)參數(shù)配置。通過設(shè)置不同的啟動(dòng)參數(shù),Netty 可以同時(shí)支持 Reactor 單線程模型、多線程模型和主從 Reactor 多線層模型。它的工作原理如下所示:
圖 4 Netty 的線程模型通過調(diào)整線程池的線程個(gè)數(shù)、是否共享線程池等方式,Netty 的 Reactor 線程模型可以在單線程、多線程和主從多線程間切換,這種靈活的配置方式可以最大程度地滿足不同用戶的個(gè)性化定制。
為了盡可能地提升性能,Netty 在很多地方進(jìn)行了無鎖化的設(shè)計(jì),例如在 I/O 線程內(nèi)部進(jìn)行串行操作,避免多線程競(jìng)爭(zhēng)導(dǎo)致的性能下降問題。表面上看,串行化設(shè)計(jì)似乎 CPU 利用率不高,并發(fā)程度不夠。但是,通過調(diào)整 NIO 線程池的線程參數(shù),可以同時(shí)啟動(dòng)多個(gè)串行化的線程并行運(yùn)行,這種局部無鎖化的串行線程設(shè)計(jì)相比一個(gè)隊(duì)列—多個(gè)工作線程的模型性能更優(yōu)。它的設(shè)計(jì)原理如下圖所示:
圖 5 NioEventLoop 串行執(zhí)行 ChannelHandler5、Future 和 Promise:在 Netty 中,所有的 I/O 操作都是異步的,這意味著任何 I/O 調(diào)用都會(huì)立即返回,而不是像傳統(tǒng) BIO 那樣同步等待操作完成。異步操作會(huì)帶來一個(gè)問題:調(diào)用者如何獲取異步操作的結(jié)果?ChannelFuture 就是為了解決這個(gè)問題而專門設(shè)計(jì)的。下面我們一起看它的原理。ChannelFuture 有兩種狀態(tài):uncompleted 和 completed。當(dāng)開始一個(gè) I/O 操作時(shí),一個(gè)新的 ChannelFuture 被創(chuàng)建,此時(shí)它處于 uncompleted 狀態(tài)——非失敗、非成功、非取消,因?yàn)?I/O 操作此時(shí)還沒有完成。一旦 I/O 操作完成,ChannelFuture 將會(huì)被設(shè)置成 completed,它的結(jié)果有如下三種可能:
- 操作成功。
- 操作失敗。
- 操作被取消。
ChannelFuture 的狀態(tài)遷移圖如下所示:
圖 6 ChannelFuture 狀態(tài)遷移圖Promise 是可寫的 Future,Future 自身并沒有寫操作相關(guān)的接口,Netty 通過 Promise 對(duì) Future 進(jìn)行擴(kuò)展,用于設(shè)置 I/O 操作的結(jié)果,它的接口定義如下:
圖 7 Netty 的 Promise 接口定義關(guān)鍵流程學(xué)習(xí)
需要重點(diǎn)掌握 Netty 服務(wù)端和客戶端的創(chuàng)建,以及創(chuàng)建過程中使用到的核心類庫和 API、以及消息的發(fā)送和接收、消息的編解碼。
Netty 服務(wù)端創(chuàng)建流程如下:
圖 8 Netty 服務(wù)端創(chuàng)建流程Netty 客戶端創(chuàng)建流程如下:
圖 9 Netty 客戶端創(chuàng)建流程Netty 項(xiàng)目實(shí)踐
實(shí)踐主要分為兩類,如果項(xiàng)目中需要用到 Netty,則直接在項(xiàng)目中應(yīng)用,通過實(shí)踐來不斷提升對(duì) Netty 的理解和掌握。如果暫時(shí)使用不到,則可以通過學(xué)習(xí)一些開源的 RPC 或者服務(wù)框架,看這些框架是怎么集成并使用 Netty 的。以 gRPC Java 版為例,我們一起看下 gRPC 是如何使用 Netty 的。
gRPC 服務(wù)端
gRPC 通過對(duì) Netty HTTP/2 的封裝,向用戶屏蔽底層 RPC 通信的協(xié)議細(xì)節(jié),Netty HTTP/2 服務(wù)端的創(chuàng)建流程如下:
圖 10 Netty HTTP/2 服務(wù)端創(chuàng)建流程服務(wù)端 HTTP/2 消息的讀寫主要通過 gRPC 的 NettyServerHandler 實(shí)現(xiàn),它的類繼承關(guān)系如下所示:
圖 11 gRPC NettyServerHandler 類繼承關(guān)系從類繼承關(guān)系可以看出,NettyServerHandler 主要負(fù)責(zé) HTTP/2 協(xié)議消息相關(guān)的處理,例如 HTTP/2 請(qǐng)求消息體和消息頭的讀取、Frame 消息的發(fā)送、Stream 狀態(tài)消息的處理等,相關(guān)接口定義如下:
圖 12 NettyServerHandler 處理 HTTP/2 協(xié)議消息相關(guān)接口gRPC 客戶端
gRPC 的客戶端調(diào)用主要包括基于 Netty 的 HTTP/2 客戶端創(chuàng)建、客戶端負(fù)載均衡、請(qǐng)求消息的發(fā)送和響應(yīng)接收處理四個(gè)流程,gRPC 的客戶端調(diào)用總體流程如下圖所示:
gRPC 的客戶端調(diào)用總體流程如下圖所示:
圖 13 gRPC 客戶端總體調(diào)用流程gRPC 的客戶端調(diào)用流程如下:
客戶端 Stub(GreeterBlockingStub) 調(diào)用 sayHello(request),發(fā)起 RPC 調(diào)用。
通過 DnsNameResolver 進(jìn)行域名解析,獲取服務(wù)端的地址信息(列表),隨后使用默認(rèn)的 LoadBalancer 策略,選擇一個(gè)具體的 gRPC 服務(wù)端實(shí)例。
如果與路由選中的服務(wù)端之間沒有可用的連接,則創(chuàng)建 NettyClientTransport 和 NettyClientHandler,發(fā)起 HTTP/2 連接。
對(duì)請(qǐng)求消息使用 PB(Protobuf)做序列化,通過 HTTP/2 Stream 發(fā)送給 gRPC 服務(wù)端。
接收到服務(wù)端響應(yīng)之后,使用 PB(Protobuf)做反序列化。
回調(diào) GrpcFuture 的 set(Response) 方法,喚醒阻塞的客戶端調(diào)用線程,獲取 RPC 響應(yīng)。
需要指出的是,客戶端同步阻塞 RPC 調(diào)用阻塞的是調(diào)用方線程(通常是業(yè)務(wù)線程),底層 Transport 的 I/O 線程(Netty 的 NioEventLoop)仍然是非阻塞的。
線程模型
gRPC 服務(wù)端線程模型整體上可以分為兩大類:
- 網(wǎng)絡(luò)通信相關(guān)的線程模型,基于 Netty4.1 的線程模型實(shí)現(xiàn)。
- 服務(wù)接口調(diào)用線程模型,基于 JDK 線程池實(shí)現(xiàn)。
gRPC 服務(wù)端線程模型和交互圖如下所示:
圖 14 gRPC 服務(wù)端線程模型其中,HTTP/2 服務(wù)端創(chuàng)建、HTTP/2 請(qǐng)求消息的接入和響應(yīng)發(fā)送都由 Netty 負(fù)責(zé),gRPC 消息的序列化和反序列化、以及應(yīng)用服務(wù)接口的調(diào)用由 gRPC 的 SerializingExecutor 線程池負(fù)責(zé)。
gRPC 客戶端的線程主要分為三類:
- 業(yè)務(wù)調(diào)用線程
- 客戶端連接和 I/O 讀寫線程
- 請(qǐng)求消息業(yè)務(wù)處理和響應(yīng)回調(diào)線程
gRPC 客戶端線程模型工作原理如下圖所示(同步阻塞調(diào)用為例):
圖 15 客戶端調(diào)用線程模型客戶端調(diào)用主要涉及的線程包括:
- 應(yīng)用線程,負(fù)責(zé)調(diào)用 gRPC 服務(wù)端并獲取響應(yīng),其中請(qǐng)求消息的序列化由該線程負(fù)責(zé)。
- 客戶端負(fù)載均衡以及 Netty Client 創(chuàng)建,由 grpc-default-executor 線程池負(fù)責(zé)。
- HTTP/2 客戶端鏈路創(chuàng)建、網(wǎng)絡(luò) I/O 數(shù)據(jù)的讀寫,由 Netty NioEventLoop 線程負(fù)責(zé)。
- 響應(yīng)消息的反序列化由 SerializingExecutor 負(fù)責(zé),與服務(wù)端不同的是,客戶端使用的是 ThreadlessExecutor,并非 JDK 線程池。
SerializingExecutor 通過調(diào)用 responseFuture 的 set(value),喚醒阻塞的應(yīng)用線程,完成一次 RPC 調(diào)用。
gRPC 采用的是網(wǎng)絡(luò) I/O 線程和業(yè)務(wù)調(diào)用線程分離的策略,大部分場(chǎng)景下該策略是最優(yōu)的。但是,對(duì)于那些接口邏輯非常簡(jiǎn)單,執(zhí)行時(shí)間很短,不需要與外部網(wǎng)元交互、訪問數(shù)據(jù)庫和磁盤,也不需要等待其它資源的,則建議接口調(diào)用直接在 Netty /O 線程中執(zhí)行,不需要再投遞到后端的服務(wù)線程池。避免線程上下文切換,同時(shí)也消除了線程并發(fā)問題。
例如提供配置項(xiàng)或者接口,系統(tǒng)默認(rèn)將消息投遞到后端服務(wù)調(diào)度線程,但是也支持短路策略,直接在 Netty 的 NioEventLoop 中執(zhí)行消息的序列化和反序列化、以及服務(wù)接口調(diào)用。
減少鎖競(jìng)爭(zhēng)優(yōu)化:當(dāng)前 gRPC 的線程切換策略如下:
圖 16 gRPC 線程鎖競(jìng)爭(zhēng)優(yōu)化之后的 gRPC 線程切換策略:
圖 17 gRPC 線程鎖競(jìng)爭(zhēng)優(yōu)化通過線程綁定技術(shù)(例如采用一致性 hash 做映射), 將 Netty 的 I/O 線程與后端的服務(wù)調(diào)度線程做綁定,1 個(gè) I/O 線程綁定一個(gè)或者多個(gè)服務(wù)調(diào)用線程,降低鎖競(jìng)爭(zhēng),提升性能。
Netty 故障定位技巧
盡管 Netty 應(yīng)用廣泛,非常成熟,但是由于對(duì) Netty 底層機(jī)制不太了解,用戶在實(shí)際使用中還是會(huì)經(jīng)常遇到各種問題,大部分問題都是業(yè)務(wù)使用不當(dāng)導(dǎo)致的。Netty 使用者需要學(xué)習(xí) Netty 的故障定位技巧,以便出了問題能夠獨(dú)立、快速的解決。‘’
接收不到消息
如果業(yè)務(wù)的 ChannelHandler 接收不到消息,可能的原因如下:
定位策略如下:
內(nèi)存泄漏
通過 jmap -dump:format=b,file=xx pid 命令 Dump 內(nèi)存堆棧,然后使用 MemoryAnalyzer 工具對(duì)內(nèi)存占用進(jìn)行分析,查找內(nèi)存泄漏點(diǎn),然后結(jié)合代碼進(jìn)行分析,定位內(nèi)存泄漏的具體原因,示例如下所示:
圖 18 通過 MemoryAnalyzer 工具分析內(nèi)存堆棧性能問題
如果出現(xiàn)性能問題,首先需要確認(rèn)是 Netty 問題還是業(yè)務(wù)問題,通過 jstack 命令或者 jvisualvm 工具打印線程堆棧,按照線程 CPU 使用率進(jìn)行排序(top -Hp 命令采集),看線程在忙什么。通常如果采集幾次都發(fā)現(xiàn) Netty 的 NIO 線程堆棧停留在 select 操作上,說明 I/O 比較空閑,性能瓶頸不在 Netty,需要繼續(xù)分析看是否是后端的業(yè)務(wù)處理線程存在性能瓶頸:
圖 19 Netty NIO 線程運(yùn)行堆棧如果發(fā)現(xiàn)性能瓶頸在網(wǎng)絡(luò) I/O 讀寫上,可以適當(dāng)調(diào)大 NioEventLoopGroup 中的 work I/O 線程數(shù),直到 I/O 處理性能能夠滿足業(yè)務(wù)需求。
作者簡(jiǎn)介
李林鋒,10 年 Java NIO、平臺(tái)中間件設(shè)計(jì)和開發(fā)經(jīng)驗(yàn),精通 Netty、Mina、分布式服務(wù)框架、API Gateway、PaaS 等,《Netty 進(jìn)階之路》、《分布式服務(wù)框架原理與實(shí)踐》作者。目前在華為終端應(yīng)用市場(chǎng)負(fù)責(zé)業(yè)務(wù)微服務(wù)化、云化、全球化等相關(guān)設(shè)計(jì)和開發(fā)工作。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/10191973.html
總結(jié)
以上是生活随笔為你收集整理的Netty 学习和进阶策略的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis 集群分布式锁与 API 网关
- 下一篇: 苏宁的企业架构演进