网络通信优化之通信协议:如何优化RPC网络通信?
RPC 通信是大型服務框架的核心
我們經常討論微服務,首要應該了解的就是微服務的核心到底是什么,這樣我們在做技術選型時,才能更準確地把握需求。
就我個人理解,我認為微服務的核心是遠程通信和服務治理。遠程通信提供了服務之間通信的橋梁,服務治理則提供了服務的后勤保障。所以,我們在做技術選型時,更多要考慮的是這兩個核心的需求。
服務的拆分增加了通信的成本,特別是在一些搶購或者促銷的業務場景中,如果服務之間存在方法調用,比如,搶購成功之后需要調用訂單系統、支付系統、券包系統等,這種遠程通信就很容易成為系統的瓶頸。所以,在滿足一定的服務治理需求的前提下,對遠程通信的性能需求就是技術選型的主要影響因素。
目前,很多微服務框架中的服務通信是基于 RPC 通信實現的,在沒有進行組件擴展的前提下,SpringCloud 是基于 Feign 組件實現的 RPC 通信(基于 Http+Json 序列化實現), Dubbo 是基于 SPI 擴展了很多 RPC 通信框架,包括 RMI、Dubbo、Hessian 等 RPC 通信框架(默認是 Dubbo+Hessian 序列化)。不同的業務場景下,RPC 通信的選擇和優化標準也不同。
我們部門在選擇微服務框架時,選擇了 Dubbo。當時的選擇標準就是RPC 通信可以支持搶購類的高并發,在這個業務場景中,請求的特點是瞬時高峰、請求量大和傳入、傳出參數數據包較小。而 Dubbo 中的 Dubbo 協議就很好地支持了這個請求。
無論從響應時間還是吞吐量上來看,單一 TCP 長連接+Protobuf 序列化實現的 RPC 通信框架都有著非常明顯的優勢。
在高并發場景下,我們選擇后端服務框架或者中間件部門自行設計服務框架時,RPC 通信是重點優化的對象。
什么是 RPC 通信
無論是微服務、SOA、還是 RPC 架構,它們都是分布式服務架構,都需要實現服務之間的互相通信,我們通常把這種通信統稱為 RPC 通信。
RPC(Remote Process Call),即遠程服務調用,是通過網絡請求遠程計算機程序服務的通信技術。RPC 框架封裝好了底層網絡通信、序列化等技術,我們只需要在項目中引入各個服務的接口包,就可以實現在代碼中調用 RPC 服務同調用本地方法一樣。正因為這種方便、透明的遠程調用,RPC 被廣泛應用于當下企業級以及互聯網項目中,是實現分布式系統的核心。
RMI(Remote Method Invocation)是 JDK 中最先實現了 RPC 通信的框架之一,RMI的實現對建立分布式 Java 應用程序至關重要,是 Java 體系非常重要的底層技術,很多開源的 RPC 通信框架也是基于 RMI 實現原理設計出來的,包括 Dubbo 框架中也接入了RMI 框架。接下來我們就一起了解下 RMI 的實現原理,看看它存在哪些性能瓶頸有待優化。
RMI:JDK 自帶的 RPC 通信框架
目前 RMI 已經很成熟地應用在了 EJB 以及 Spring 框架中,是純 Java 網絡分布式應用系統的核心解決方案。RMI 實現了一臺虛擬機應用對遠程方法的調用可以同對本地方法的調用一樣,RMI 幫我們封裝好了其中關于遠程通信的內容。
RMI 的實現原理
RMI 遠程代理對象是 RMI 中最核心的組件,除了對象本身所在的虛擬機,其它虛擬機也可以調用此對象的方法。而且這些虛擬機可以不在同一個主機上,通過遠程代理對象,遠程應用可以用網絡協議與服務進行通信。
RMI 在高并發場景下的性能瓶頸
Java 默認序列化
RMI 的序列化采用的是 Java 默認的序列化方式,性能并不是很好,而且其它語言框架也暫時不支持 Java 序列化。
TCP 短連接
由于 RMI 是基于 TCP 短連接實現,在高并發情況下,大量請求會帶來大量連接的創建和銷毀,這對于系統來說無疑是非常消耗性能的。
阻塞式網絡 I/O
網絡通信存在 I/O 瓶頸,如果在 Socket 編程中使用傳統的 I/O 模型,在高并發場景下基于短連接實現的網絡通信就很容易產生 I/O 阻塞,性能將會大打折扣。
一個高并發場景下的 RPC 通信優化路徑
SpringCloud 的 RPC 通信和 RMI 通信的性能瓶頸就非常相似。SpringCloud 是基于 Http通信協議(短連接)和 Json 序列化實現的,在高并發場景下并沒有優勢。
RPC 通信包括了建立通信、實現報文、傳輸協議以及傳輸數據編解碼等操作,接下來我們就從每一層的優化出發,逐步實現整體的性能優化。
要實現不同機器間的網絡通信,我們先要了解計算機系統網絡通信的基本原理。網絡通信是兩臺設備之間實現數據流交換的過程,是基于網絡傳輸協議和傳輸數據的編解碼來實現的。其中網絡傳輸協議有 TCP、UDP 協議,這兩個協議都是基于 Socket 編程接口之上,為某類應用場景而擴展出的傳輸協議。
基于 TCP 協議實現的 Socket 通信是有連接的,而傳輸數據是要通過三次握手來實現數據傳輸的可靠性,且傳輸數據是沒有邊界的,采用的是字節流模式。
基于 UDP 協議實現的 Socket 通信,客戶端不需要建立連接,只需要創建一個套接字發送數據報給服務端,這樣就不能保證數據報一定會達到服務端,所以在傳輸數據方面,基于UDP 協議實現的Socket 通信具有不可靠性。UDP 發送的數據采用的是數據報模式,每個UDP 的數據報都有一個長度,該長度將與數據一起發送到服務端。
通過對比,我們可以得出優化方法:為了保證數據傳輸的可靠性,通常情況下我們會采用TCP 協議。如果在局域網且對數據傳輸的可靠性沒有要求的情況下,我們也可以考慮使用UDP 協議,畢竟這種協議的效率要比 TCP 協議高。
如果是基于 TCP 協議實現 Socket 通信,我們還能做哪些優化呢?
服務之間的通信不同于客戶端與服務端之間的通信。客戶端與服務端由于客戶端數量多,基于短連接實現請求可以避免長時間地占用連接,導致系統資源浪費。
但服務之間的通信,連接的消費端不會像客戶端那么多,但消費端向服務端請求的數量卻一樣多,我們基于長連接實現,就可以省去大量的 TCP 建立和關閉連接的操作,從而減少系統的性能消耗,節省時間。
建立兩臺機器的網絡通信,我們一般使用 Java 的 Socket 編程實現一個 TCP 連接。傳統的 Socket 通信主要存在 I/O 阻塞、線程模型缺陷以及內存拷貝等問題。我們可以使用比較成 熟的通信框架,比如 Netty。Netty4 對 Socket 通信編程做了很多方面的優化,具體見下方。
實現非阻塞 I/O:多路復用器 Selector 實現了非阻塞 I/O 通信。
高效的 Reactor 線程模型:Netty 使用了主從 Reactor 多線程模型,服務端接收客戶端請求連接是用了一個主線程,這個主線程用于客戶端的連接請求操作,一旦連接建立成功,將會監聽 I/O 事件,監聽到事件后會創建一個鏈路請求。
鏈路請求將會注冊到負責 I/O 操作的 I/O 工作線程上,由 I/O 工作線程負責后續的 I/O 操作。利用這種線程模型,可以解決在高負載、高并發的情況下,由于單個 NIO 線程無法監聽海量客戶端和滿足大量 I/O 操作造成的問題。
串行設計:服務端在接收消息之后,存在著編碼、解碼、讀取和發送等鏈路操作。如果這些操作都是基于并行去實現,無疑會導致嚴重的鎖競爭,進而導致系統的性能下降。為了提升性能,Netty 采用了串行無鎖化完成鏈路操作,Netty 提供了 Pipeline 實現鏈路的各個操作在運行期間不進行線程切換。
零拷貝:一個數據從內存發送到網絡中,存在著兩次拷貝動作,先是從用戶空間拷貝到內核空間,再是從內核空間拷貝到網絡 I/O 中。而 NIO 提供的ByteBuffer 可以使用 Direct Buffers 模式,直接開辟一個非堆物理內存,不需要進行字節緩沖區的二次拷貝,可以直接將數據寫入到內核空間。
除了以上這些優化,我們還可以針對套接字編程提供的一些 TCP 參數配置項,提高網絡吞吐量,Netty 可以基于 ChannelOption 來設置這些參數。
TCP_NODELAY:TCP_NODELAY 選項是用來控制是否開啟 Nagle 算法。Nagle 算法通過緩存的方式將小的數據包組成一個大的數據包,從而避免大量的小數據包發送阻塞網絡,提高網絡傳輸的效率。我們可以關閉該算法,優化對于時延敏感的應用場景。
SO_RCVBUF 和 SO_SNDBUF:可以根據場景調整套接字發送緩沖區和接收緩沖區的大小。
SO_BACKLOG:backlog 參數指定了客戶端連接請求緩沖隊列的大小。服務端處理客戶端連接請求是按順序處理的,所以同一時間只能處理一個客戶端連接,當有多個客戶端進來的時候,服務端就會將不能處理的客戶端連接請求放在隊列中等待處理。
SO_KEEPALIVE:當設置該選項以后,連接會檢查長時間沒有發送數據的客戶端的連接狀態,檢測到客戶端斷開連接后,服務端將回收該連接。我們可以將該時間設置得短一些,來提高回收連接的效率。
量身定做報文格式
接下來就是實現報文,我們需要設計一套報文,用于描述具體的校驗、操作、傳輸數據等內容。為了提高傳輸的效率,我們可以根據自己的業務和架構來考慮設計,盡量實現報體小、滿足功能、易解析等特性。
編碼、解碼
序列化編碼和解碼的過程,對于實現一個好的網絡通信協議來說, 兼容優秀的序列化框架是非常重要的。如果只是單純的數據對象傳輸,我們可以選擇性能相對較好的 Protobuf 序列化,有利于提高網絡通信的性能。
調整 Linux 的 TCP 參數設置選項
如果 RPC 是基于 TCP 短連接實現的,我們可以通過修改 Linux TCP 配置項來優化網絡通信。
我們可以通過 sysctl -a | grep net.xxx 命令運行查看 Linux 系統默認的的 TCP 參數設置, 如果需要修改某項配置,可以通過編輯 vim/etc/sysctl.conf,加入需要修改的配置項, 并通過 sysctl -p 命令運行生效修改后的配置項設置。通常我們會通過修改以下幾個配置項來提高網絡吞吐量和降低延時。
總結
以上是生活随笔為你收集整理的网络通信优化之通信协议:如何优化RPC网络通信?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文献阅读(167)NoC神经网络加速器
- 下一篇: 计算机网络中常见的名词缩写