RPC實戰(zhàn)與核心原理

RPC全稱是Remote Procedure Call,即遠程過程調(diào)用。
RPC的作用:

  • 屏蔽遠程調(diào)用跟本地調(diào)用的區(qū)別,讓我們感受就是調(diào)用項目內(nèi)的方法;
  • 隱藏底層網(wǎng)絡通信的復雜性,專注于業(yè)務邏輯。

網(wǎng)絡傳輸?shù)臄?shù)據(jù)必須是二進制數(shù)據(jù)。

整體協(xié)議就變成三部分內(nèi)容:固定部分、協(xié)議頭部分、協(xié)議體內(nèi)容。在協(xié)議頭部中有一個固定的部分,指定頭部長度,然后在指定協(xié)議體的部分。

序列化的就是將對象轉換成二進制數(shù)據(jù)的過程,反序列化就是反過來二進制轉換為對象的過程。

常用的序列化

JDK原生序列化

序列化具體的實現(xiàn)是有ObjectOutputStream完成的,反序列化的具體實現(xiàn)是有ObjectInputStream完成的。

JSON

JSON序列化需要注意的事項:

  • JSON序列化時額外開銷比較大,對于大數(shù)據(jù)量服務這意味著需要巨大的內(nèi)容和磁盤開銷;
  • JSON沒有類型,性能不會太好。

Hessian

Hessian是動態(tài)類型、二進制、緊湊的,并且可跨語言一直的一種序列化框架。
Hessian要比JDK、JSON更加緊湊,性能上要好,生成的字節(jié)數(shù)也要小。
Hessian本身也有問題,Java常見對象的類型不支持,比如:

  • Linked系列,LinkedHashMap、LinkedHashSet等,可以通過擴展CollectionDeserializer類修復。
  • Local類,可以通過ContextSerializerFactory類修復;
  • Byte/Short反序列化的時候變成Integer。

ProtoBuf

ProtoBuf是一種輕便、搞笑的結構化數(shù)據(jù)存儲格式,可以用于結構化數(shù)據(jù)序列化,支持Java、Python、C++、Go等語言。
ProtoBuf使用的時候需要定義IDL, 然后使用不同語言的IDL編譯器,生成序列化工具類,優(yōu)點是:

  • 序列化后體積相比JSON、Hessian小很多;
  • IDL能清晰地描述語義,足以幫助并保證應用程序之間的類型不會丟失,無需類似XML解析器;
  • 序列化反序列化速度很快,不需要通過反射獲取類型;
  • 消息格式升級和兼容性不錯,可以做到向后兼容。

ProtoBuf不支持的類型:

  • 不支持null
  • 不支持單純的Map、List集合對象,需要包在對象里面。

RPC框架中網(wǎng)絡IO模型

阻塞IO

應用進程發(fā)起IO系統(tǒng)調(diào)用后,應用進程被阻塞,賺到內(nèi)核空間處理。內(nèi)核開始等待數(shù)據(jù),等待到數(shù)據(jù)之后,在將內(nèi)核中的數(shù)據(jù)拷貝到用戶內(nèi)存中,
整個IO處理完畢后返回進程。最后應用的進程接觸阻塞狀態(tài),運行業(yè)務邏輯。等待數(shù)據(jù)和拷貝數(shù)據(jù)操作時線程會一直處于阻塞狀態(tài)。

IO多路服用

多路就是指多個通道,也就是多個網(wǎng)絡連接的IO,復用是指多個通道復用在一個復用器上。

IO多路復用更適合高并發(fā)的場景。

零拷貝

所謂零拷貝,就是取消用戶空間與內(nèi)核空間之間的數(shù)據(jù)拷貝操作,應用進程每一次的讀寫操作,都可以通過一種方式,讓應用進程向用戶空間寫入或者讀取數(shù)據(jù),
就如同直接向內(nèi)核空間寫入或者讀取數(shù)據(jù)一樣,在通過dma將內(nèi)核中的數(shù)據(jù)拷貝到網(wǎng)卡,或者將網(wǎng)卡中的數(shù)據(jù)copy到內(nèi)核。
零拷貝的兩種實現(xiàn)方式:mmap+write方式,sendFile方式。

mmap+write

mmap是一種內(nèi)存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現(xiàn)文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系。
mmap 通過內(nèi)存映射,將文件映射到內(nèi)核緩沖區(qū),同時,用戶空間可以共享內(nèi)核空間的數(shù)據(jù)。這樣,在進行網(wǎng)絡傳輸時,就可以減少內(nèi)核空間到用戶空間的拷貝次數(shù)。
這樣就可以省掉原來內(nèi)核read緩沖區(qū)copy數(shù)據(jù)到用戶緩沖區(qū),但是還是需要內(nèi)核read緩沖區(qū)將數(shù)據(jù)copy到內(nèi)核socket緩沖區(qū)。

sendFile

數(shù)據(jù)被 DMA 引擎從文件復制到內(nèi)核緩沖區(qū),然后調(diào)用 write 方法時,從內(nèi)核緩沖區(qū)進入到 Socket,這時,是沒有上下文切換的,因為都在內(nèi)核空間。

mmap 和 sendFile 的區(qū)別

  • mmap 適合小數(shù)據(jù)量讀寫,sendFile 適合大文件傳輸。
  • mmap 需要 4 次上下文切換,3 次數(shù)據(jù)拷貝;sendFile 需要 3 次上下文切換,最少 2 次數(shù)據(jù)拷貝。
  • sendFile 可以利用 DMA 方式,減少 CPU 拷貝,mmap 則不能(必須從內(nèi)核拷貝到 Socket 緩沖區(qū))。
    在這個選擇上:rocketMQ 在消費消息時,使用了 mmap。kafka 使用了 sendFile。

Netty的零拷貝是完全站在用戶空間上,也就是JVM上,它的零拷貝主要是偏向于數(shù)據(jù)操作的優(yōu)化上。

Netty對數(shù)據(jù)操作進行的優(yōu)化

  • Netty 提供了CompositeByteBuf類,可以將多個ByteBuf合并為一個邏輯上的ByteBuf,避免了各個ByteBuf之間的拷貝。
  • ByteBuffer支持slice操作,因此可以將ByteBuf分解為多個共享同一個存儲區(qū)域的ByteBuf,避免了內(nèi)存的拷貝。
  • 通過wrap操作,可以將byte[] 數(shù)組、ByteBuf、ByteBuffer等包裝盛一個Netty ByteBuf對象,進而避免拷貝操作。
  • Netty 的ByteBuf可以采用Direct Buffers,使用堆外直接內(nèi)存進行socket的讀寫操作。
  • Netty還提供了FileRegion中保證NIO的FileChannel.transferTo方法實現(xiàn)了零拷貝。

面向接口編程,屏蔽RPC處理流程

使用反射來進行處理。

可擴展的框架:

會使用spi方式,在springboot中使用condition接口也可以實現(xiàn)。

服務發(fā)現(xiàn):到底是要CP還是AP

選擇AP。
在大規(guī)模的服務集群下,注冊中心所面臨的挑戰(zhàn)就是超大批量服務節(jié)點同時上下線,注冊中心集群接受到大量服務變更請求,集群件各節(jié)點間需要同步大量服務節(jié)點數(shù)據(jù),
到最如下問題:

  • 注冊中心負載過高;
  • 各節(jié)點數(shù)據(jù)不一致;
  • 服務下發(fā)不及時或者下發(fā)錯誤的服務節(jié)點列表。

RPC框架依賴的注冊中心的服務數(shù)據(jù)的一致性其實并不滿足CP,只要滿足AP即可。可以使用消息總線的通知機制,來保證注冊中心數(shù)據(jù)的最終一致性,來解決這些問題。

當節(jié)點達到一定數(shù)量時,集中進行注冊操作,會導致cpu持續(xù)升高,最終宕機。

健康監(jiān)測:這個節(jié)點都掛了,為啥還要瘋狂發(fā)請求

要保證業(yè)務正常:不同區(qū)域、不同機架去檢測,保證業(yè)務正常。

負載均衡:節(jié)點收到的流量不一樣

dubbo的負載均衡方法:

  • 基于權重隨機算法:將請求按照權重進行分配,權重越大,分配的越多。
  • 基于最小活躍調(diào)用數(shù)算法:活躍少越少,表明該服務提供者效率越高,單位時間內(nèi)可處理更多的請求。
  • 基于hash一致性:適用于服務有狀態(tài)的場景。
  • 基于加權輪詢算法:權重越大,節(jié)點選中的更多。

異常重試:在約定時間內(nèi)安全重試

異常重試需要關注的點:

  • 保證被重試的業(yè)務服務是具有冪等性的;
  • 超時重試前重置計時;
  • 針對業(yè)務返回的異常,設置重試是異常名單;
  • 重試時負載均衡選取節(jié)點時要剔除前一次訪問的節(jié)點

優(yōu)雅關閉:如何避免服務停機帶來的業(yè)務損失

服務對象在關閉過程中,會拒絕的新的請求,同時根據(jù)引用計數(shù)器等待正在處理的請求全部結束之后才會真正關閉。
Runtime.getRuntime().addShutdownHook(this);,注冊一個JVM關閉的鉤子,這個鉤子可以在以下幾種場景被調(diào)用:

  • 程序正常退出
  • 使用System.exit()
  • 終端使用Ctrl+C觸發(fā)的中斷
  • 系統(tǒng)關閉
  • 使用Kill pid命令干掉進程(不要帶-9)

優(yōu)雅啟動

在服務啟動之后,要對系統(tǒng)進行預熱,可以執(zhí)行一些測試數(shù)據(jù),在整個系統(tǒng)啟動并且預熱之后,然后在進行服務注冊。

熔斷限流

可以使用Guava RateLimiter、Lua+Redis、Sentinel進行限流,hystrix、resilience4j進行熔斷。

異步RPC

使用CompletableFuture、Future、ThreadPoolExecutor來進行請求。

參考文獻

Spring Boot 2.0 之優(yōu)雅停機
Spring boot 2.0 之優(yōu)雅停機

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容