微服務框架的實現:舍與不捨

網路通訊頻道發表於2022-10-24

生活中充滿了各種trade-off(權衡),程式設計開發中也是如此。本文將透過實戰的角度,分享在開發微服務框架的過程中,針對不同的元件做的一些抉擇,包括,協議支援的多少?資料傳輸採用TCP還是UDP?網路處理是普通處理模型還是定製的epoll?序列化框架那麼多,該用哪種?註冊中心選哪家?路由方式哪種好,如何限流等等。

微服務框架的實現:舍與不捨

▲資深工程師晁嶽攀(鳥窩)

嘉賓介紹: Go微服務框架rpcx的作者,擁有20餘年的軟體開發經驗,先後在清華同方、Motorola、微博等公司工作,出版了國內第一本原創Scala圖書《Scala集合技術手冊》,並在臺灣發行了繁體版。Go語言的佈道者,在GopherChina meetup/GopherChina大會上分享過《Go微服務框架實踐》、《Go併發程式設計》等主題。

以下是晁嶽攀老師在SACC2022大會的演講實錄:

▲rpcx微服務框架

一、協議的實現

事實上,每個擁有一定規模的網際網路大廠都有自己的微服務框架。比如,阿里巴巴、嗶哩嗶哩、百度、今日頭條、學而思、好未來等。因為業務繁多需要微服務來劃分,中間的呼叫關係必須透過框架來實現。

▲框架獨有介面(rpcx)

如上圖所示,採用壓縮的方式來實現。第一個是Magic number(魔法數字),使用特殊的位元組(0x08)來標明Request開頭。第二個是Version(版本號),在做微服務設計的時候,要儘量做到協議的相容性。

後面幾個位元組,比如Message Type、Heartbeat、Oneway、Compress Type、Message Status Type、Serialize Type、Reserred,使用bit做設定,將其壓縮在一起,儘量減少記憶體的佔用。

接下來,Message ID使用了8個位元組的資料。最後是size of rest data,size of serrice Path,size of serrice Method,size of meta,size of payload等資料的處理。

▲框架獨有介面(dubbo)

dubbo的處理方式與rpcx是類似的,它使用兩個位元組做魔法數,分別是高位和低位。後面是標記它的請求響應、需要往返、事件、序列化ID、狀態、RPC請求ID等資訊,接下來是訊息體資料長度。

▲框架獨有介面(motan)

motan的協議設計與rpcx是類似的。grpc是架構在HTTP2.0基礎之上的協議,它的請求是Request-Headers,加一系列的Message。對於請求,EOS(流結束)是透過在最後接收到的資料幀上出現END_STREAM標誌來表示的。

Response-Headers和Trailers-Only都在單個HTTP2報頭幀塊中傳遞。對於響應,流的結束是透過在最後一個接收到的帶有Trailers的報頭幀上的END_STREAM標誌來指示的。

thrift框架中的message表示一次介面呼叫、介面呼叫結果或者異常。這個函式中主要是呼叫相應的write函式來序列和寫入thrift的版本、message的name以及seqid等基本資訊,name欄位是函式的名字。

如果使用通用的請求,常用的協議有HTTP access、Restful API、JSON RPC2.0、WebSocket等。JSON-RPC是一個無狀態且輕量級的遠端過程呼叫(RPC)協議。從效能的角度來考慮,實現特定的協議是比較常用的一種手段。

二、資料傳輸

在協議實現之後,客戶端透過哪種傳輸方式把訊息傳輸給服務端,服務端又透過什麼方式將訊息返還給客戶端。常見的資料傳輸方式有幾種,比如HTTP、TCP、UDP、Unix domain socket等。如果想要實現更高效能網路,我們可以在特定網路協議基礎之上做資料傳輸。

▲HTTP 1.1 vs HTTP pipelining

HTTP1.0的每一次請求都伴隨著一次三次握手的過程,並且是序列的請求,增加了不必要的效能開銷。HTTP1.1新增了長連結的通訊方式,減少了效能損耗。HTTP Pipelining是把多個HTTP請求放到一個TCP連線中一一傳送,而在傳送過程中不需要等待伺服器對前一個請求的響應。

▲HTTP 1.1 vs HTTP 2.0 vs HTTP 3.0

HTTP1.1安全性不足和效能不高;HTTP2.0完全相容HTTP1.0,是“更安全的HTTP,更快的HTTPS”,頭部壓縮,多路複用等技術充分利用了頻寬,降低了延遲。HTTP3.0的底層支撐協議QUIC基於UDP實現,又含TCP的特點,實現了又快又可靠的協議。

▲raw TCP

TCP提供了一個邏輯上的連線,在進行資料傳輸之前必須建立連線,在資料傳輸之後必須終止連線。TCP為了保證資料的可靠性,要求有應答機制,應答機制實際上是透過一個基本的序列號和相對應的回應號來進行完成的。TCP本質上是一種面向連線的、非常可靠的資料傳輸方式,是基於IP協議來做的。

KCP是一個基於UDP實現快速、可靠、向前糾錯的的協議,能以比TCP浪費10%-20%的頻寬的代價,換取平均延遲降低30%-40%,且最大延遲降低三倍的傳輸效果。純演算法實現,並不負責底層協議(如UDP)的收發。

kcp-go是用go實現了KCP協議的一個庫,其實KCP類似TCP,協議的實現也很多參考TCP協議的實現,滑動視窗,快速重傳,選擇性重傳,慢啟動等。KCP和TCP一樣,也分客戶端和監聽端。

Unix domain socket又叫IPC(inter-process communication程式間通訊)socket,用於實現同一主機上的程式間通訊。它有SOKCET_DGRAM(資料包套接字)和SOCKET_STREAM(流套接字)兩種模式,類似於UDP和TCP,但是面向訊息的UNIX socket也是可靠的,訊息既不會丟失也不會順序錯亂。

三、網路處理庫

Go基於I/O multiplexing和goroutine構建了一個簡潔而高效能的原生網路模型(基於Go的I/O多路複用netpoll),提供了goroutine-per-connection這樣簡單的網路程式設計模式。

▲connection per goroutine

雖然goroutine是輕量級的,但也並非建立的越多越好。其中有幾個原因,goroutine只負責訊息讀取解析,當它的數量較大時,會佔用很大的記憶體,消耗大量的CPU資源。

worker pool是一個Erlang程式池,其中的工作程式是Erlang的gen server模式程式。工作中常用worker pool模式,可以控制goroutine的數量, 防止goroutine洩露和暴漲。

Netpoll是一款Go語言高效能、I/O非阻塞(NIO)網路庫,專注於RPC場景。由於EpollWait回撥之後,SubReactor內是序列處理I/O事件的,導致排在最後的事件可能會有長尾問題。

四、序列化方式

編解碼主要有“通用跨平臺”和“專有高效能”兩種方式,通用跨語言庫有Protobuf、MessagePack、JSON(標準庫JSON、json-iterator/go、easyjson等)、XML、Thrift。特定語言的編解碼方式有Hessian 2、andyleap/gencode、colfer、zebrapack。

▲序列化方式

上圖參考,我們可以看出,各個序列化方式Marshal與Unmarshal的資料。我們不要把自己的平臺限制在某一種序列化方式,而是應該支援定製化,將決定權交給使用者。

五、註冊中心

在微服務框架之下,我們要引入註冊中心,它是微服務框架依賴的一個基礎服務。常見的註冊中心有分散式一致性的平臺,比如zookeeper(CP)、etcd(CP)、consul(CP)、Eureka(AP)。

▲分散式一致性

分散式註冊中心遵循CAP原理,指的是在一個分散式系統中,Consistency(一致性)、 Availability(可用性)、Partition Tolerance(分割槽容錯性),三者無法同時滿足。

當滿足CA時,要保持資料一致性,就必須進行節點資料的同步;同時要滿足可用性,則響應時間必須較短,就要去資料同步時間很短,這樣就不能部署太多的節點,也就無法滿則高可用性。

當CP滿足時,要進行資料同步,且機器數量較多,這樣資料的同步時間就會比較長,無法保證較快的響應。當滿足AP時,既要有一定機器數量,又要保證較快的響應時間,就無法進行節點資料的同步。

國內網際網路大廠一般自研註冊中心,實現AP系統,來保證可用性。比如,阿里nacos、微博vintage、騰訊Polaris Mesh。那麼,中小企業如何選擇註冊中心?可以選擇etc, consul,做好zk +本地快取的功能;使用dns、redis、mysql等雲服務;採用大廠的AP系統。

六、路由選擇

最簡單的方式是利用隨機函式選擇節點,有無法區分權重;無法根據效能實時調整;無法進行復雜情況下的選擇;隨機不隨機,比如可能出現111112222233333的情況等問題。

利用輪詢的方式選擇節點,每個節點可以均勻,壓力也平均,但面臨無法區分權重;無法根據效能實時調整;無法進行復雜情況下的選擇等問題。

▲基於權重的隨機演算法

基於權重,可以避免隨機演算法可能的壓力集中。Nginx的演算法可以平均生成每個節點: smooth weighted round-robin balancing algorithm。

基於請求的服務和方法,以及請求引數,利用一致性雜湊演算法,總是選擇固定的節點,動態調整節點。

網路質量優先,client定期測試各節點的網路質量,根據網路質量分配權重。地理位置優先,同機房優先,同區域優先,國內優先。

基於特殊的需求,允許使用者定製,比如正常呼叫同機房優先;如果失敗,第二次從備份機房呼叫。

七、限流與降級

降級是臨時禁用非核心功能,比如明星出軌、結婚離婚等重大公共事件,秒殺、搶紅包等流量激增的時候,功能遮蔽但是不下線。

限流是客戶端和服務端限流,從“根”上限制,避免無意義的頻寬傳輸;無法避免業務偷偷放量;基於令牌桶和漏桶,需要處理burst場景。

八、測試

rpc測試的困難,需要客戶端和服務端才能真正模擬,持續整合框架機器可能不允許網路連線,配置服務端和客戶端略微複雜。網路傳輸單獨測試,客戶端和服務端業務測試時使用mock。虛假連線,把客戶端請求直接給服務端處理,把客戶端請求直接給服務端處理github.com/akutz/memconn。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545813/viewspace-2919913/,如需轉載,請註明出處,否則將追究法律責任。

相關文章