Netty 和 RPC 框架线程模型分析
https://www.infoq.cn/article/9Ib3hbKSgQaALj02-90y
1. 背景
1.1 線程模型的重要性
對于 RPC 框架而言,影響其性能指標(biāo)的主要有三個要素:
I/O 模型:采用的是同步 BIO、還是非阻塞的 NIO、以及全異步的事件驅(qū)動 I/O(AIO)。
協(xié)議和序列化方式:它主要影響消息的序列化、反序列化性能,以及消息的通信效率。
線程模型:主要影響消息的讀取和發(fā)送效率、以及調(diào)度的性能。
除了對性能有影響,在一些場景下,線程模型的變化也會影響到功能的正確性,例如 Netty 從 3.X 版本升級到 4.X 版本之后,重構(gòu)和優(yōu)化了線程模型。當(dāng)業(yè)務(wù)沒有意識到線程模型發(fā)生變化時,就會踩到一些性能和功能方面的坑。
1.2 Netty 和 RPC 框架的線程模型關(guān)系
作為一個高性能的 NIO 通信框架,Netty 主要關(guān)注的是 I/O 通信相關(guān)的線程工作策略,以及提供的用戶擴(kuò)展點 ChannelHandler 的執(zhí)行策略,示例如下:
圖 1 Netty 多線程模型該線程模型的工作特點如下:
有專門一個(一組)NIO 線程 -Acceptor 線程用于監(jiān)聽服務(wù)端,接收客戶端的 TCP 連接請求。
網(wǎng)絡(luò) I/O 操作 - 讀、寫等由一個 NIO 線程池負(fù)責(zé),線程池可以采用標(biāo)準(zhǔn)的 JDK 線程池實現(xiàn),它包含一個任務(wù)隊列和 N 個可用的線程,由這些 NIO 線程負(fù)責(zé)消息的讀取、解碼、編碼和發(fā)送。
1 個 NIO 線程可以同時處理 N 條鏈路,但是 1 個鏈路只對應(yīng) 1 個 NIO 線程,防止發(fā)生并發(fā)操作問題。
對于 RPC 框架,它的線程模型會更復(fù)雜一些,除了通信相關(guān)的 I/O 線程模型,還包括服務(wù)接口調(diào)用、服務(wù)訂閱 / 發(fā)布等相關(guān)的業(yè)務(wù)側(cè)線程模型。對于基于 Netty 構(gòu)建的 RPC 框架,例如 gRPC、Apache ServiceComb 等,它在重用 Netty 線程模型的基礎(chǔ)之上,也擴(kuò)展實現(xiàn)了自己的線程模型。
2. Netty 線程模型
2.1 線程模型的變更
2.1.1?Netty 3.X?版本線程模型
Netty 3.X 的 I/O 操作線程模型比較復(fù)雜,它的處理模型包括兩部分:
Inbound:主要包括鏈路建立事件、鏈路激活事件、讀事件、I/O 異常事件、鏈路關(guān)閉事件等。
Outbound:主要包括寫事件、連接事件、監(jiān)聽綁定事件、刷新事件等。
我們首先分析下 Inbound 操作的線程模型:
圖 2 Netty 3 Inbound 操作線程模型從上圖可以看出,Inbound 操作的主要處理流程如下:
I/O 線程(Work 線程)將消息從 TCP 緩沖區(qū)讀取到 SocketChannel 的接收緩沖區(qū)中。
由 I/O 線程負(fù)責(zé)生成相應(yīng)的事件,觸發(fā)事件向上執(zhí)行,調(diào)度到 ChannelPipeline 中。
I/O 線程調(diào)度執(zhí)行 ChannelPipeline 中 Handler 鏈的對應(yīng)方法,直到業(yè)務(wù)實現(xiàn)的 Last Handler。
Last Handler 將消息封裝成 Runnable,放入到業(yè)務(wù)線程池中執(zhí)行,I/O 線程返回,繼續(xù)讀 / 寫等 I/O 操作。
業(yè)務(wù)線程池從任務(wù)隊列中彈出消息,并發(fā)執(zhí)行業(yè)務(wù)邏輯。
通過對 Netty 3 的 Inbound 操作進(jìn)行分析我們可以看出,Inbound 的 Handler 都是由 Netty 的 I/O Work 線程負(fù)責(zé)執(zhí)行。
下面我們繼續(xù)分析 Outbound 操作的線程模型:
圖 3 Netty 3 Outbound 操作線程模型從上圖可以看出,Outbound 操作的主要處理流程如下:
業(yè)務(wù)線程發(fā)起 Channel Write 操作,發(fā)送消息。
Netty 將寫操作封裝成寫事件,觸發(fā)事件向下傳播。
寫事件被調(diào)度到 ChannelPipeline 中,由業(yè)務(wù)線程按照 Handler Chain 串行調(diào)用支持 Downstream 事件的 Channel Handler。
執(zhí)行到系統(tǒng)最后一個 ChannelHandler,將編碼后的消息 Push 到發(fā)送隊列中,業(yè)務(wù)線程返回。
Netty 的 I/O 線程從發(fā)送消息隊列中取出消息,調(diào)用 SocketChannel 的 write 方法進(jìn)行消息發(fā)送。
2.1.2?Netty 4.X?版本線程模型
相比于 Netty 3.X 系列版本,Netty 4.X 的 I/O 操作線程模型比較簡答,它的原理圖如下所示:
圖 4 Netty 4 Inbound 和 Outbound 操作線程模型從上圖可以看出,Outbound 操作的主要處理流程如下:
I/O 線程 NioEventLoop 從 SocketChannel 中讀取數(shù)據(jù)報,將 ByteBuf 投遞到 ChannelPipeline,觸發(fā) ChannelRead 事件。
I/O 線程 NioEventLoop 調(diào)用 ChannelHandler 鏈,直到將消息投遞到業(yè)務(wù)線程,然后 I/O 線程返回,繼續(xù)后續(xù)的讀寫操作。
業(yè)務(wù)線程調(diào)用 ChannelHandlerContext.write(Object msg) 方法進(jìn)行消息發(fā)送。
如果是由業(yè)務(wù)線程發(fā)起的寫操作,ChannelHandlerInvoker 將發(fā)送消息封裝成 Task,放入到 I/O 線程 NioEventLoop 的任務(wù)隊列中,由 NioEventLoop 在循環(huán)中統(tǒng)一調(diào)度和執(zhí)行。放入任務(wù)隊列之后,業(yè)務(wù)線程返回。
I/O 線程 NioEventLoop 調(diào)用 ChannelHandler 鏈,進(jìn)行消息發(fā)送,處理 Outbound 事件,直到將消息放入發(fā)送隊列,然后喚醒 Selector,進(jìn)而執(zhí)行寫操作。
通過流程分析,我們發(fā)現(xiàn) Netty 4 修改了線程模型,無論是 Inbound 還是 Outbound 操作,統(tǒng)一由 I/O 線程 NioEventLoop 調(diào)度執(zhí)行。
2.1.3 新老線程模型對比
在進(jìn)行新老版本線程模型對比之前,首先還是要熟悉下串行化設(shè)計的理念:
我們知道當(dāng)系統(tǒng)在運行過程中,如果頻繁的進(jìn)行線程上下文切換,會帶來額外的性能損耗。多線程并發(fā)執(zhí)行某個業(yè)務(wù)流程,業(yè)務(wù)開發(fā)者還需要時刻對線程安全保持警惕,哪些數(shù)據(jù)可能會被并發(fā)修改,如何保護(hù)?這不僅降低了開發(fā)效率,也會帶來額外的性能損耗。
為了解決上述問題,Netty 4 采用了串行化設(shè)計理念,從消息的讀取、編碼以及后續(xù) Handler 的執(zhí)行,始終都由 I/O 線程 NioEventLoop 負(fù)責(zé),這就意外著整個流程不會進(jìn)行線程上下文的切換,數(shù)據(jù)也不會面臨被并發(fā)修改的風(fēng)險,對于用戶而言,甚至不需要了解 Netty 的線程細(xì)節(jié),這確實是個非常好的設(shè)計理念,它的工作原理圖如下:
圖 5 Netty 4 的串行化設(shè)計理念一個 NioEventLoop 聚合了一個多路復(fù)用器 Selector,因此可以處理成百上千的客戶端連接,Netty 的處理策略是每當(dāng)有一個新的客戶端接入,則從 NioEventLoop 線程組中順序獲取一個可用的 NioEventLoop,當(dāng)?shù)竭_(dá)數(shù)組上限之后,重新返回到 0,通過這種方式,可以基本保證各個 NioEventLoop 的負(fù)載均衡。一個客戶端連接只注冊到一個 NioEventLoop 上,這樣就避免了多個 I/O 線程去并發(fā)操作它。
Netty 通過串行化設(shè)計理念降低了用戶的開發(fā)難度,提升了處理性能。利用線程組實現(xiàn)了多個串行化線程水平并行執(zhí)行,線程之間并沒有交集,這樣既可以充分利用多核提升并行處理能力,同時避免了線程上下文的切換和并發(fā)保護(hù)帶來的額外性能損耗。
了解完了 Netty 4 的串行化設(shè)計理念之后,我們繼續(xù)看 Netty 3 線程模型存在的問題,總結(jié)起來,它的主要問題如下:
Inbound 和 Outbound 實質(zhì)都是 I/O 相關(guān)的操作,它們的線程模型竟然不統(tǒng)一,這給用戶帶來了更多的學(xué)習(xí)和使用成本。
Outbound 操作由業(yè)務(wù)線程執(zhí)行,通常業(yè)務(wù)會使用線程池并行處理業(yè)務(wù)消息,這就意味著在某一個時刻會有多個業(yè)務(wù)線程同時操作 ChannelHandler,我們需要對 ChannelHandler 進(jìn)行并發(fā)保護(hù),通常需要加鎖。如果同步塊的范圍不當(dāng),可能會導(dǎo)致嚴(yán)重的性能瓶頸,這對開發(fā)者的技能要求非常高,降低了開發(fā)效率。
Outbound 操作過程中,例如消息編碼異常,會產(chǎn)生 Exception,它會被轉(zhuǎn)換成 Inbound 的 Exception 并通知到 ChannelPipeline,這就意味著業(yè)務(wù)線程發(fā)起了 Inbound 操作!它打破了 Inbound 操作由 I/O 線程操作的模型,如果開發(fā)者按照 Inbound 操作只會由一個 I/O 線程執(zhí)行的約束進(jìn)行設(shè)計,則會發(fā)生線程并發(fā)訪問安全問題。由于該場景只在特定異常時發(fā)生,因此錯誤非常隱蔽!一旦在生產(chǎn)環(huán)境中發(fā)生此類線程并發(fā)問題,定位難度和成本都非常大。
講了這么多,似乎 Netty 4 完勝 Netty 3 的線程模型,其實并不盡然。在特定的場景下,Netty 3 的性能可能更高,如果編碼和其它 Outbound 操作非常耗時,由多個業(yè)務(wù)線程并發(fā)執(zhí)行,性能肯定高于單個 NioEventLoop 線程。
但是,這種性能優(yōu)勢不是不可逆轉(zhuǎn)的,如果我們修改業(yè)務(wù)代碼,將耗時的 Handler 操作前置,Outbound 操作不做復(fù)雜業(yè)務(wù)邏輯處理,性能同樣不輸于 Netty 3, 但是考慮內(nèi)存池優(yōu)化、不會反復(fù)創(chuàng)建 Event、不需要對 Handler 加鎖等 Netty 4 的優(yōu)化,整體性能 Netty 4 版本肯定會更高。
2.2 Netty 4.X 版本線程模型實踐經(jīng)驗
2.2.1 時間可控的簡單業(yè)務(wù)直接在 I/O 線程上處理
如果業(yè)務(wù)非常簡單,執(zhí)行時間非常短,不需要與外部網(wǎng)元交互、訪問數(shù)據(jù)庫和磁盤,不需要等待其它資源,則建議直接在業(yè)務(wù) ChannelHandler 中執(zhí)行,不需要再啟業(yè)務(wù)的線程或者線程池。避免線程上下文切換,也不存在線程并發(fā)問題。
2.2.2 復(fù)雜和時間不可控業(yè)務(wù)建議投遞到后端業(yè)務(wù)線程池統(tǒng)一處理
對于此類業(yè)務(wù),不建議直接在業(yè)務(wù) ChannelHandler 中啟動線程或者線程池處理,建議將不同的業(yè)務(wù)統(tǒng)一封裝成 Task,統(tǒng)一投遞到后端的業(yè)務(wù)線程池中進(jìn)行處理。
過多的業(yè)務(wù) ChannelHandler 會帶來開發(fā)效率和可維護(hù)性問題,不要把 Netty 當(dāng)作業(yè)務(wù)容器,對于大多數(shù)復(fù)雜的業(yè)務(wù)產(chǎn)品,仍然需要集成或者開發(fā)自己的業(yè)務(wù)容器,做好和 Netty 的架構(gòu)分層。
2.2.3 業(yè)務(wù)線程避免直接操作 ChannelHandler
對于 ChannelHandler,I/O 線程和業(yè)務(wù)線程都可能會操作,因為業(yè)務(wù)通常是多線程模型,這樣就會存在多線程操作 ChannelHandler。為了盡量避免多線程并發(fā)問題,建議按照 Netty 自身的做法,通過將操作封裝成獨立的 Task 由 NioEventLoop 統(tǒng)一執(zhí)行,而不是業(yè)務(wù)線程直接操作。
3. gRPC 線程模型
gRPC 的線程模型主要包括服務(wù)端線程模型和客戶端線程模型,其中服務(wù)端線程模型主要包括:
-
服務(wù)端監(jiān)聽和客戶端接入線程(HTTP /2 Acceptor)。
-
網(wǎng)絡(luò) I/O 讀寫線程。
-
服務(wù)接口調(diào)用線程。
客戶端線程模型主要包括:
-
客戶端連接線程(HTTP/2 Connector)。
-
網(wǎng)絡(luò) I/O 讀寫線程。
-
接口調(diào)用線程。
-
響應(yīng)回調(diào)通知線程。
3.1 服務(wù)端線程模型
gRPC 服務(wù)端線程模型整體上可以分為兩大類:
-
網(wǎng)絡(luò)通信相關(guān)的線程模型,基于 Netty4.1 的線程模型實現(xiàn)。
-
服務(wù)接口調(diào)用線程模型,基于 JDK 線程池實現(xiàn)。
3.1.1 服務(wù)端線程模型概述
gRPC 服務(wù)端線程模型和交互圖如下所示:
圖 6 gRPC 服務(wù)端線程模型其中,HTTP/2 服務(wù)端創(chuàng)建、HTTP/2 請求消息的接入和響應(yīng)發(fā)送都由 Netty 負(fù)責(zé),gRPC 消息的序列化和反序列化、以及應(yīng)用服務(wù)接口的調(diào)用由 gRPC 的 SerializingExecutor 線程池負(fù)責(zé)。
3.1.2 服務(wù)調(diào)度線程模型
gRPC 服務(wù)調(diào)度線程主要職責(zé)如下:
-
請求消息的反序列化,主要包括:HTTP/2 Header 的反序列化,以及將 PB(Body) 反序列化為請求對象。
-
服務(wù)接口的調(diào)用,method.invoke(非反射機(jī)制)。
-
將響應(yīng)消息封裝成 WriteQueue.QueuedCommand,寫入到 Netty Channel 中,同時,對響應(yīng) Header 和 Body 對象做序列化。
服務(wù)端調(diào)度的核心是 SerializingExecutor,它同時實現(xiàn)了 JDK 的 Executor 和 Runnable 接口,既是一個線程池,同時也是一個 Task。
SerializingExecutor 聚合了 JDK 的 Executor,由 Executor 負(fù)責(zé) Runnable 的執(zhí)行,代碼示例如下:
其中,Executor 默認(rèn)使用的是 JDK 的 CachedThreadPool,在構(gòu)建 ServerImpl 的時候進(jìn)行初始化,代碼如下:
當(dāng)服務(wù)端接收到客戶端 HTTP/2 請求消息時,由 Netty 的 NioEventLoop 線程切換到 gRPC 的 SerializingExecutor,進(jìn)行消息的反序列化、以及服務(wù)接口的調(diào)用,代碼示例如下:
相關(guān)的調(diào)用堆棧,示例如下:
響應(yīng)消息的發(fā)送,由 SerializingExecutor 發(fā)起,將響應(yīng)消息頭和消息體序列化,然后分別封裝成 SendResponseHeadersCommand 和 SendGrpcFrameCommand,調(diào)用 Netty NioSocketChannle 的 write 方法,發(fā)送到 Netty 的 ChannelPipeline 中,由 gRPC 的 NettyServerHandler 攔截之后,真正寫入到 SocketChannel 中,代碼如下所示:
響應(yīng)消息體的發(fā)送堆棧如下所示:
Netty I/O 線程和服務(wù)調(diào)度線程的運行分工界面以及切換點如下所示:
圖 7 網(wǎng)絡(luò) I/O 線程和服務(wù)調(diào)度線程交互圖事實上,在實際服務(wù)接口調(diào)用過程中,NIO 線程和服務(wù)調(diào)用線程切換次數(shù)遠(yuǎn)遠(yuǎn)超過 4 次,頻繁的線程切換對 gRPC 的性能帶來了一定的損耗。
3.2 客戶端線程模型
gRPC 客戶端的線程主要分為三類:
業(yè)務(wù)調(diào)用線程。
客戶端連接和 I/O 讀寫線程。
請求消息業(yè)務(wù)處理和響應(yīng)回調(diào)線程。
3.2.1 客戶端線程模型概述
gRPC 客戶端線程模型工作原理如下圖所示(同步阻塞調(diào)用為例):
圖 8 客戶端調(diào)用線程模型客戶端調(diào)用主要涉及的線程包括:
-
應(yīng)用線程,負(fù)責(zé)調(diào)用 gRPC 服務(wù)端并獲取響應(yī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)用。
3.2.2 客戶端調(diào)用線程模型
客戶端調(diào)用線程交互流程如下所示:
圖 9 客戶端線程交互原理圖請求消息的發(fā)送由用戶線程發(fā)起,相關(guān)代碼示例如下:
HTTP/2 Header 的創(chuàng)建、以及請求參數(shù)反序列化為 Protobuf,均由用戶線程負(fù)責(zé)完成,相關(guān)代碼示例如下:
用戶線程將請求消息封裝成 CreateStreamCommand 和 SendGrpcFrameCommand,發(fā)送到 Netty 的 ChannelPipeline 中,然后返回,完成線程切換。后續(xù)操作由 Netty NIO 線程負(fù)責(zé),相關(guān)代碼示例如下:
客戶端響應(yīng)消息的接收,由 gRPC 的 NettyClientHandler 負(fù)責(zé),相關(guān)代碼如下所示:
接收到 HTTP/2 響應(yīng)之后,Netty 將消息投遞到 SerializingExecutor,由 SerializingExecutor 的 ThreadlessExecutor 負(fù)責(zé)響應(yīng)的反序列化,以及 responseFuture 的設(shè)值,相關(guān)代碼示例如下:
3.3 線程模型總結(jié)
消息的序列化和反序列化均由 gRPC 線程負(fù)責(zé),而沒有在 Netty 的 Handler 中做 CodeC,原因如下:Netty4 優(yōu)化了線程模型,所有業(yè)務(wù) Handler 都由 Netty 的 I/O 線程負(fù)責(zé),通過串行化的方式消除鎖競爭,原理如下所示:
圖 10 Netty4 串行執(zhí)行 Handler如果大量的 Handler 都在 Netty I/O 線程中執(zhí)行,一旦某些 Handler 執(zhí)行比較耗時,則可能會反向影響 I/O 操作的執(zhí)行,像序列化和反序列化操作,都是 CPU 密集型操作,更適合在業(yè)務(wù)應(yīng)用線程池中執(zhí)行,提升并發(fā)處理能力。因此,gRPC 并沒有在 I/O 線程中做消息的序列化和反序列化。
4. Apache ServiceComb 微服務(wù)框架線程模型
Apache ServiceComb 底層通信框架基于 Vert.X(Netty) 構(gòu)建,它重用了 Netty 的 EventLoop 線程模型,考慮到目前同步 RPC 調(diào)用仍然是主流模式,因此,針對同步 RPC 調(diào)用,在 Vert.X 線程模型基礎(chǔ)之上,提供了額外的線程模型封裝。
下面我們分別對同步和異步模式的線程模型進(jìn)行分析。
4.1 同步模式
核心設(shè)計理念是 I/O 線程(協(xié)議棧)和微服務(wù)調(diào)用線程分離,線程調(diào)度模型如下所示:
圖 11 ServiceComb 內(nèi)置線程池同步模式下 ServiceComb 的線程模型特點如下:
線程池用于執(zhí)行同步模式的業(yè)務(wù)邏輯。
網(wǎng)絡(luò)收發(fā)及 reactive 模式的業(yè)務(wù)邏輯在 Eventloop 中執(zhí)行,與線程池?zé)o關(guān)。
默認(rèn)所有同步方法都在一個全局內(nèi)置線程池中執(zhí)行。
如果業(yè)務(wù)有特殊的需求,可以指定使用自定義的全局線程池,并且可以根據(jù) schemaId 或 operationId 指定各自使用獨立的線程池,實現(xiàn)隔離倉的效果。
基于 ServiceComb 定制線程池策略實現(xiàn)的微服務(wù)隔離倉效果如下所示:
圖 12 基于 ServiceComb 的微服務(wù)故障隔離倉4.2 異步模式
ServiceComb 的異步模式即純 Reactive 機(jī)制,它的代碼示例如下:
復(fù)制代碼| ? | public?interface?Intf{ |
| ? | CompletableFuture<String> hello(String?name); |
| ? | } |
| ? | |
| ? | CompletableFuture<String> future =?new?CompletableFuture<>(); |
| ? | intf.hello(name).whenComplete((result, exception) -> { |
| ? | if?(exception ==?null) { |
| ? | future.complete("from remote: "?+ result); |
| ? | return; |
| ? | } |
| ? | ? |
| ? | future.completeExceptionally(exception); |
| ? | }); |
| ? | return?future; |
與之對應(yīng)的線程調(diào)度流程如下所示:
圖 13 基于 ServiceComb 的 Reactive 線程模型它的特點總結(jié)如下:
所有功能都在 eventloop 中執(zhí)行,并不會進(jìn)行線程切換。
橙色箭頭走完后,對本線程的占用即完成了,不會阻塞等待應(yīng)答,該線程可以處理其他任務(wù)。
當(dāng)收到遠(yuǎn)端應(yīng)答后,由網(wǎng)絡(luò)數(shù)據(jù)驅(qū)動開始走紅色箭頭的應(yīng)答流程。
只要有任務(wù),線程就不會停止,會一直執(zhí)行任務(wù),可以充分利用 cpu 資源,也不會產(chǎn)生多余的線程切換,去無謂地消耗 cpu。
4.3. 線程模型總結(jié)
ServiceComb 的同步和異步 RPC 調(diào)用對應(yīng)的線程模型存在差異,對于純 Reactive 的異步,I/O 讀寫與微服務(wù)業(yè)務(wù)邏輯執(zhí)行共用同一個 EventLoop 線程,在一次服務(wù)端 RPC 調(diào)用時不存在線程切換,性能最優(yōu)。但是,這種模式也存在一些約束,例如要求微服務(wù)業(yè)務(wù)邏輯執(zhí)行過程中不能有任何可能會導(dǎo)致同步阻塞的操作,包括但不限于數(shù)據(jù)庫操作、緩存讀寫、第三方 HTTP 服務(wù)調(diào)用、本地 I/O 讀寫等(本質(zhì)就是要求全棧異步)。
對于無法做到全棧異步的業(yè)務(wù),可以使用 ServiceComb 同步編程模型,同時根據(jù)不同微服務(wù)接口的重要性和優(yōu)先級,利用定制線程池策略,實現(xiàn)接口級的線程隔離。
需要指出的是,ServiceComb 根據(jù)接口定義來決定采用哪種線程模型,如果返回值是 CompletableFuture,業(yè)務(wù)又沒有對接口指定額外的線程池,則默認(rèn)采用 Reactive 模式,即業(yè)務(wù)微服務(wù)接口由 Vert.X 的 EventLoop 線程執(zhí)行。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/articles/10483988.html
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Netty 和 RPC 框架线程模型分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lucene实战--打分算法没有那么难!
- 下一篇: lucene-solr本地调试方法