深入淺出RPC框架|青訓營筆記
這是我參與「第三屆青訓營 -後端場」筆記創作活動的的第6篇筆記
目錄
- 01基本概念
- 02分層設計
- 03關鍵指標
01基本概念
1.1 本地函式呼叫
呼叫過程: 1. 將a和b的值壓棧 2. 通過函式指標找到calculate函式,進入函式取出棧中的值2和3,將其賦予x和y。 3. 計算x*y,並將結果存在Z。 4. 將Z的值壓棧,然後從calculate返回。 5. 從棧中取出Z返回值,並賦值給result。
1.2 遠端函式呼叫(RPC-Remote Procedure Calls)
需要解決的問題: 1. 函式對映 需要告訴服務要呼叫哪個函式,因此函式有自己的ID,在做RPC的時候附上這個ID,並且還要有一個ID和函式的對照關係表。
-
資料轉換成位元組流 客戶端把引數先轉成一個位元組流,傳給服務端後,再把位元組流轉成自己能讀取的格式。
-
網路傳輸 需要保證網路高效穩定地傳輸資料。
1.3 RPC概念模型
①發起本地呼叫
②資料打包
③資料傳送給對端
④資料接收
⑤解壓資料
⑥資料處理
⑦結果資料打包
⑧傳送返回資料
⑨接收資料
⑩解壓資料
⑪資料結果返回
1984年Nelson發表了論文《Implementing Remote Procedure Calls》,其中提出了RPC的過程由5個模型組成: User、User Stub、RPC Runtime、Server- Stub、Server。
1.4一次RPC的完整過程
- IDL (Interface description language)檔案 IDL通過一種中立的方式來描述介面,使得在不同平臺上執行的物件和用不同語言編寫的程式可以相互通訊。(約定呼叫規範)
- 生成程式碼 通過編譯器工具把IDL檔案轉換成語言對應的靜態庫。(具體呼叫的時候使用者程式碼需要依賴生成程式碼,所以可以把使用者程式碼和生成程式碼看做一個整體)
- 編解碼 從記憶體中表示到位元組序列的轉換稱為編碼,反之為解碼,也常叫做序列化和反序列化。
- 通訊協議 規範了資料在網路中的傳輸內容和格式。除必須的請求/響應資料外,通常還會包含額外的元資料。
- 網路傳輸 通常基於成熟的網路庫走TCP/UDP傳輸。
1.5 RPC的好處
1.單一職責,有利於分工協作和運維開發(開發可以採用不同語言;部署及運維都是獨立的) 2.可擴充套件性強,資源使用率更優(壓力過大時可以獨立擴充資源,底層基礎服務可以複用,達到節省資源的目的) 3.故障隔離(一個模組發生故障,不會影響整體,服務的整體可靠性更高)
1.6 RPC帶來的問題 -- RPC框架解決
1.服務宕機,對方應該如何處理? 2.在呼叫過程中發生網路異常,如何保證訊息的可達性? 3.請求量突增導致服務無法及時處理,有哪些應對措施?
01.小結
- 本地函式呼叫和RPC呼叫的區別:函式對映、資料轉成位元組流、網路傳輸
- RPC的概念模型: User、 User-Stub、 RPC- Runtime、Server Stub、Server
- 一次PRC的完整過程,並講解了RPC的基本概念定義
- RPC帶來好處的同時也帶來了不少新的問題,將由RPC框架來解決
02分層設計
2.1分層設計-以Apache Thrift為例
2.2編解碼層
- 生成程式碼 依賴同一份IDL檔案(同一份約束)生成不同語言的生成程式碼。
- 資料格式 1.語言特定的格式
許多程式語言都內建了將記憶體物件編碼為位元組序列的支援,例如Java有java.io.Serializable (優點:方便,可以用很少的額外程式碼實現記憶體物件的儲存與恢復。 缺點:這類編碼通常與特定的程式語言深度繫結,其他語言很難讀取這種資料。有安全和相容性問題。)
2.文字格式
JSON、XML、CSV等文字格式,具有人類可讀性 (缺點:XML和CSV不能區分數字和字串,JSON不區分整數和浮點數,不能指定精度,資料量大時問題更嚴重;沒有強制模型約束,實際操作中往往只能採用文件方式來進行約定,這可能會給除錯帶來些不便;JSON在一些語言中的序列化和反序列化需要採用反射機制,編碼解碼費時間,所以還存在效能較差的問題)
3.二進位制編碼
具備跨語言和高效能等優點,常見有Thrift 的BinaryProtocol, Protobuf 等(實現有多種,例TLV編碼和Varint編碼)
- 二進位制編碼 TLV編碼 Tag:標籤,可以理解為型別 Lenght:長度 Value:值,Value也可以是個TLV結構
struct Person {
1: required string userName,
2: optional 164 favor iteNumber,
3: optional list<string> interests
}
- 選型 相容性
支援自動增加新的欄位,而不影響老的服務,這將提高系統的靈活度。
通用性
支援跨平臺、跨語言(①技術層面,序列化協議要支援跨平臺、跨語言。②流行程度,可以判斷此協議是否成熟,同時序列化和反序列化需要多方參與,很少人使用的協議往往意味著昂貴的學習成本。)
效能
從空間和時間兩個維度來考慮,也就是編碼後資料大小和編碼耗費時長。
2.5 協議層
- 概念 特殊結束符
一個特殊字元作為每個協議單元結束的標示。(缺點:過於簡單,對於一個協議單元必須要全部讀入才能夠進行處理,除此之外必須要防止使用者傳輸的資料不能同結束符相同,否則就會出現紊亂。 HTTP協議頭就是以回車(CR)加換行(LF)符號序列結尾。)
變長協議
以定長加不定長的部分組成,其中定長的部分需要描述不定長的內容長度。(一般都是自定義協議,有header和payload組成,使用比較廣泛)
-
協議構造:
- LENGTH(32bits):資料包大小,不包含自身長度
- HEADER MAGIC(16bits):標識版本資訊,協議解析時
- 候快速校驗
- SEQUENCE NUMBER(32bits):表示資料包的seqlD,
- 可用於多路複用,單連線內遞增
- HEADER SIZE(16bits):頭部長度,從第14個位元組開始計算一直到PAYLOAD前
- PROTOCOL ID(unit8編碼):編解碼方式,取值有ProtocollDBinary = 0;ProtocollDCompact = 2兩種。
- NUM TRANSFORMS(uint8編碼):表示TRANSFORM個數
- TRANSFORM ID(unit8編碼):壓縮方式,如zlib和snappy
- INFO ID(unit8編碼):傳遞一些定製的meta資訊
- PAYLOAD:訊息體
- 協議解析
2.3 網路通訊層:
- Sockets API
介於通訊層和應用層之間
套接字程式設計中的客戶端必須知道兩個資訊:伺服器的IP地址、埠號。
socket函式建立一個套接字, bind將一個套接字繫結到一個地址上。listen 監聽進來的連線,backlog指定掛起的連線佇列的長度,當客戶端連線的時候,伺服器可能正在處理其他邏輯而未呼叫accept接受連線,此時會導致這個連線被掛起,核心維護掛起的連線佇列,backlog則指定這個佇列的長度,accept函式從佇列中取出連線請求並接收它,然後這個連線就從掛起佇列移除。
如果佇列未滿,客戶端呼叫connect馬上成功,如果滿了可能會阻塞等待佇列未滿(實際上在Linux中測試並不是這樣的結果,還需專門研究)。Linux的backlog預設是128, 通常情況下,我們也指定為128即可。
connect客戶端向伺服器發起連線,accept 接收一個連線請求,如果沒有連線則會直阻塞直到有連線進來。 得到客戶端的fd之後,就可以呼叫read, write函式和客戶端通訊,讀寫方式和其他I/O類似。
read從fd讀資料,socket預設是阻塞模式的,如果對方沒有寫資料,read會一直阻塞著。
write寫fd寫資料,socket預設是阻塞模式的,如果對方沒有寫資料,write會一直阻塞著。
socket關閉套接字,當另-端socket關閉後, 這一端讀寫的情況:
- 嘗試去讀會得到一個EOF, 並返回0。
- 嘗試去寫會觸發一個SIGPIPE訊號, 並返回-1和ermno=EPIPE, SIGPIPE的預設行為是終止程式,所以通常我們應該忽略這個訊號,避免程式終止。
- 如果這一端不去讀寫,我們可能沒有辦法知道對端的socket關閉了。
-
網路庫 提供易用API
- 封裝底層Socket API
-
連線管理和事件分發 功能
-
協議支援: tcp、 udp 和uds等
- 優雅退出、異常處理等
效能
- 應用層buffer減少copy
- 高效能定時器、物件池等
02.小結
- RPC框架主要核心有三層:編解碼層、協議層和網路通訊層
- 二進位制編解碼的實現原理和選型要點
- 協議的一般構造, 以及框架協議解析的基本流程
- Socket API的呼叫流程,以及選型網路庫時要考察的核心指標
03關鍵指標
3.1 穩定性-保障策略
- 熔斷:保護呼叫方,防止被呼叫的服務出現問題而影響到整個鏈路
- 限流:保護被呼叫方,防止大流量把服務壓垮(降級處理/返回限流異常)
- 超時控制:避免浪費資源在不可用節點上(超時主動停掉不太重要的業務) (以上三種都是快速返回,避免資源浪費在不可呼叫的請求上,也是服務降級的手段)
3.2穩定性-請求成功率
- 負載均街
- 重試(會加大直接下游的負載,有放大故障的風險) 防止重試風暴,限制單點重試和限制鏈路重試。
3.3穩定性-長尾請求
長尾請求一般是指明顯高於均值的那部分佔比較小的請求。業界關於延遲有一個常用的P99標準, P99 單個請求響應耗時從小到大排列,順序處於99%位置的值即為P99值,那後面這1%就可以認為是長尾請求。在較複雜的系統中,長尾延時總是會存在。造成這個的原因非常多,常見的有網路抖動,GC,系統排程。
我們預先設定一個閾值 t3 (比超時時間小,通常建議是RPC請求延時的pct99),當Req1發出去後超過t3時間都沒有返回,那我直接發起重試請求Req2,這樣相當於同時有兩個請求執行。然後等待請求返回,只要Resp1或者Resp2任意一個返回成功的結果, 就可以立即結束這次請求,這樣整體的耗時就是t4,它表示從第一個請求發出到第一個成功結果返回之 間的時間,相比於等待超時後再發出請求,這種機制能大大減少整體延時。
3.4穩定性-註冊中介軟體
Kitex Client和Server的建立介面]均採用Option模式,提供了極大的靈活性,很方便就能主入這些穩定性策略
3.5易用性
開箱即用: 合理的預設引數選項、豐富的文件
周邊工具: 生成程式碼工具、腳手架工具
Kitex使用Suite來打包自定義的功能,提供「一鍵配置基礎依賴」的體驗
- 生成服務程式碼腳手架
- 支援protobuf 和thrift
- 內建功能豐富的選項
- 支援自定義的生成程式碼外掛
3.6擴充套件性
- Middleware
- Option
- 編解碼層
- 協議層
- 網路傳輸層
- 程式碼生成工具外掛擴充套件
一次請求發起首先會經過治理層面, 治理相關的邏輯被封裝在middleware中,這些middleware會被構造成一個有序呼叫鏈逐個執行,比如服務發現、路由、負載均衡、超時控制等,mw執行後就會進入到remote模組,完成與遠端的通訊。
3.7觀測性
Log、Metric、Tracing
內建觀測性服務
除了傳統的Log、Metric、Tracing 三件套之外,對於框架來說可能還不夠,還有些框架自身狀態需要暴露出來,例如當前的環境變數、配置、Client/Server初始化引數、快取資訊等。
3.8 高效能
這裡分兩個維度,高效能意味著高吞吐和低延遲,兩者都很重要,甚至大部分場景下低延遲更重要。
多路複用可以大大減少了連線帶來的資源消耗,並且提升了服務端效能,我們的測試中服務端吞吐可提升30%。
呼叫端向服務端的一個節點發送請求,併發場景下,如果是非連線多路複用,每個請求都會持有一個連線, 直到請求結束連線才會被關閉或者放入連線池複用,併發量與連線數是對等的關係。
而使用連線多路複用,所有請求都可以在一個連線上完成,連線資源利用上的差異明顯。
03.小結
- 框架通過中介軟體來注入各種服務治理策略,保障服務的穩定性。
- 通過提供合理的預設配置和方便的命令列I具可以提升框架的易用性。
- 框架應當提供豐富的擴充套件點,例如核心的傳輸層和協議層。
- 觀測性除了傳統的Log、Metric 和Tracing之外,內建狀態暴露服務也很有必要。
- 效能可以從多個層面去優化,例如選擇高效能的編解碼協議和網路庫。
課程總結
- 從本地函式呼叫引出RPC的基本概念。
- 重點講解了RPC框架的核心的三層,編解碼層、協議層和網路傳輸層。
- 圍繞RPC框架的核心指標,例如穩定性、可擴充套件性和高效能等,展開講解相關的知識。
————THE END————