天天看點

SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發

2018年上半年,螞蟻金服決定基于 Istio 訂制自己的 ServiceMesh 解決方案,并在6月底正式對外公布了 SOFAMesh,詳情可直接點選之前的文章檢視:大規模微服務架構下的Service Mesh探索之路 。

在 SOFAMesh 的開發過程中,針對遇到的實際問題,我們給出了一套名為 x-protocol 的解決方案,本文将會對這個解決方案進行詳細的講解,後面會有更多内容,歡迎持續關注本系列文章。

上一篇:SOFAMesh中的多協定通用解決方案x-protocol介紹系列(1) : DNS通用尋址方案

SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發

前言

在 Istio 和 Envoy 中,對通訊協定的支援,主要展現在 HTTP/1.1和 HTTP/2上,而我們 SOFAMesh,則需要支援以下幾個 RPC 協定:

SOFARPC:這是螞蟻金服大量使用的RPC協定(已開源)

HSF RPC:這是阿裡集團内部大量使用的RPC協定(未開源)

Dubbo RPC: 這是社群廣泛使用的RPC協定(已開源)

更适合的平衡點:性能和功能

對于服務間通訊解決方案,性能永遠是一個值得關注的點。而 SOFAMesh 在項目啟動時就明确要求在性能上要有更高的追求,為此,我們不得不在 Istio 标準實作之外尋求可以擷取更高性能的方式,比如支援各種 RPC 協定。

期間有兩個發現:

1.Istio 在處理所有的請求轉發如 REST/gRPC 時,會解碼整個請求的 header 資訊,拿到各種資料,提取為 Attribute,然後以此為基礎,提供各種豐富的功能,典型如 Content Based Routing。

2.而在測試中,我們發現:解碼請求協定的 header 部分,對 CPU 消耗較大,直接影響性能。

是以,我們有了一個很簡單的想法:是不是可以在轉發時,不開啟部分功能,以此換取轉發過程中的更少更快的解碼消耗?畢竟,不是每個服務都需要用到 Content Based Routing 這樣的進階特性,大部分服務隻使用 Version Based Routing,尤其是使用 RPC 通訊協定的服務,沒有HTTP那麼表現力豐富的 header,對 Content Based Routing 的需求要低很多。

此外,對于部分對性能有極高追求的服務,不開啟進階特性而換取更高的性能,也是一種滿足性能要求的折中方案。考慮到系統中總存在個别服務對性能非常敏感,我們覺得 Service Mesh 提供一種性能可以接近直連的方案會是一個有益的補充。為了滿足這些特例而不至于是以整體否決 Service Mesh 方案,我們需要在 Service Mesh 的大架構下提供一個折中方案。

請求轉發

在我們進一步深入前,我們先來探讨一下實作請求轉發的技術細節。

有一個關鍵問題:當 Envoy/SOFA MOSN 這樣的代理程式,接收到來自用戶端的TCP 請求時,需要獲得哪些資訊,才可以正确的轉發請求到上遊的伺服器端?

最關鍵的資訊:destination 首先,毫無疑問的,必須拿到 destination 目的地,也就是用戶端請求必須通過某種方式明确的告之代理該請求的 destination,這樣代理程式才能根據這個 destionation去找到正确的目标伺服器,然後才有後續的連接配接目标伺服器和轉發請求等操作。
SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發
Destination 資訊的表述形式可能有: 1.IP位址

可能是伺服器端執行個體實際工作的 IP 位址和端口,也可能是某種轉發機制,如Nginx/HAProxy 等反向代理的位址或者 Kubernetes 中的 ClusterIP。

舉例:“192.168.1.1:8080”是實際IP位址和端口,“10.2.0.100:80”是 ngxin 反向代理位址,“172.168.1.105:80”是Kubernetes的ClusterIP。

2.目标服務的辨別符

可用于名字查找,如服務名,可能帶有各種字首字尾。然後通過名字查找/服務發現等方式,得到位址清單(通常是IP位址+端口形式)。

舉例:“userservice”是标準服務名, “com.alipay/userservice”是加了域名字首的服務名, “service.default.svc.cluster.local”是k8s下完整的全限定名。

Destination資訊在請求封包中的攜帶方式有:

1.通過通訊協定傳遞

這是最常見的形式,标準做法是通過header頭,典型如HTTP/1.1下一般使用 host header,舉例如“Host: userservice”。HTTP/2下,類似的使用“:authority” header。

對于非HTTP協定,通常也會有類似的設計,通過協定中某些字段來承載目标位址資訊,隻是不同協定中這個字段的名字各有不同。如SOFARPC,HSF等。

有些通訊協定,可能會将這個資訊存放在payload中,比如後面我們會介紹到的dubbo協定,導緻需要反序列化payload之後才能拿到這個重要資訊。

2.通過TCP協定傳遞 這是一種非常特殊的方式,通過在TCP option傳遞,上一節中我們介紹Istio DNS尋址時已經詳細介紹過了。 TCP拆包 如何從請求的通訊協定中擷取destination?這涉及到具體通訊協定的解碼,其中第一個要解決的問題就是如何在連續的TCP封包中将每個請求内容拆分開,這裡就涉及到經典的TCP沾包、拆包問題。
SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發

轉發請求時,由于涉及到負載均衡,我們需要将請求發送給多個伺服器端執行個體。是以,有一個非常明确的要求:就是必須以單個請求為機關進行轉發。即單個請求必須完整的轉發給某台伺服器端執行個體,負載均衡需要以請求為機關,不能将一個請求的多個封包包分别轉發到不同的伺服器端執行個體。是以,拆包是請求轉發的必備基礎。

由于篇幅和主題限制,我們不在這裡展開TCP沾包、拆包的原理。後面針對每個具體的通訊協定進行分析時再具體看各個協定的解決方案。

多路複用的關鍵參數:RequestId

RequestId用來關聯request和對應的response,請求封包中攜帶一個唯一的id值,應答封包中原值傳回,以便在處理response時可以找到對應的request。當然在不同協定中,這個參數的名字可能不同(如streamid等)。

嚴格說,RequestId對于請求轉發是可選的,也有很多通訊協定不提供支援,比如經典的HTTP1.1就沒有支援。但是如果有這個參數,則可以實作多路複用,進而可以大幅度提高TCP連接配接的使用效率,避免出現大量連接配接。稍微新一點的通訊協定,基本都會原生支援這個特性,比如SOFARPC、Dubbo、HSF,還有HTTP/2就直接內建了多路複用的支援。

HTTP/1.1不支援多路複用(http1.1有提過支援幂等方法的pipeline機制但是未能普及),用的是經典的ping-pong模式:在請求發送之後,必須獨占目前連接配接,等待伺服器端給出這個請求的應答,然後才能釋放連接配接。是以HTTP/1.1下,并發多個請求就必須采用多連接配接,為了提升性能通常會使用長連接配接+連接配接池的設計。而如果有了requestid和多路複用的支援,用戶端和Mesh之間理論上就可以隻用一條連接配接(實踐中可能會選擇建立多條)來支援并發請求:

SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發
而Mesh與伺服器(也可能是對端的Mesh)之間,也同樣可以受益于多路複用技術,來自不同用戶端而去往同一個目的地的請求可以混雜在同一條連接配接上發送。通過RequestId的關聯,Mesh可以正确将reponse發送到請求來自的用戶端。
SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發
由于篇幅和主題限制,我們不在這裡展開多路複用的原理。後面針對每個具體的通訊協定進行分析時再具體看各個協定的支援情況。 請求轉發參數總結

上面的分析中,我們可以總結到,對于Sidecar,要正确轉發請求:

必須擷取到destination資訊,得到轉發的目的地,才能進行服務發現類的尋址

必須要能夠正确的拆包,然後以請求為機關進行轉發,這是負載均衡的基礎

可選的RequestId,這是開啟多路複用的基礎

是以,這裡我們的第一個優化思路就出來了:盡量隻解碼擷取這三個資訊,滿足轉發的基本要求。其他資訊如果有性能開銷則跳過解碼,所謂“快速解碼轉發”。基本原理就是犧牲資訊完整性追求性能最大化。

而結合上一節中我們引入的DNS通用尋址方案,我們是可以從請求的TCP options中得到ClusterIP,進而實作尋址。這個方式可以實作不解碼請求封包,尤其是header部分解碼destination資訊開銷大時。這是我們的第二個優化思路:跳過解碼destination資訊,直接通過ClusterIP進行尋址。

具體的實作則需要結合特定通訊協定的實際情況進行。

主流通訊協定

現在我們開始,以Proxy、Sidecar、Service Mesh的角度來看看目前主流的通訊協定和我們前面列舉的需要在SOFAMesh中支援的幾個協定。

SOFARPC/bolt協定

SOFARPC 是一款基于 Java 實作的 RPC 服務架構,詳細資料可以查閱 官方文檔。SOFARPC 支援 bolt,rest,dubbo 協定進行通信。REST、dubbo後面單獨展開,這裡我們關注bolt協定。

bolt 是螞蟻金服集團開放的基于 Netty 開發的網絡通信架構,其協定格式是變長,即協定頭+payload。具體格式定義如下,以request為例(response類似):

SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發
我們隻關注和請求轉發直接相關的字段:

bolt協定是定長+變長的複合結構,前面22個位元組長度固定,每個位元組和協定字段的對應如圖所示。其中classLen、headerLen和contentLen三個字段指出後面三個變長字段className、header、content的實際長度。和通常的變長方案相比隻是變長字段有三個。拆包時思路簡單明了:

先讀取前22個位元組,解出各個協定字段的實際值,包括classLen,headerLen和contentLen

按照classLen、headerLen和contentLen的大小,繼續讀取className、header、content

Destination Bolt協定中的header字段是一個map,其中有一個key為“service”的字段,傳遞的是接口名/服務名。讀取稍微麻煩一點點,需要先解碼整個header字段,這裡對性能有影響。 RequestId

Blot協定固定字段中的requestID字段,可以直接讀取。

SOFARPC中的bolt協定,設計的比較符合請求轉發的需要,TCP拆包,讀取RequestID,都沒有性能問題。隻是Destination的擷取需要解碼整個header,性能開銷稍大。

總結:适合配合DNS通用解碼方案,跳過對整個header部分的解碼,進而提升性能。當然由于這個header本身也不算大,優化的空間有限,具體提升需要等對比測試的結果出來。

HSF協定

HSF協定是經過精心設計工作在4層的私有協定,由于該協定沒有開源,是以不便直接暴露具體格式和字段詳細定義。

不過基本的設計和bolt非常類似:

  • 采用變長格式,即協定頭+payload
  • 在協定頭中可以直接拿到服務接口名和服務方法名作為Destination
  • 有RequestID字段

基本和bolt一緻,考慮到Destination可以直接讀取,比bolt還要友善一些,HSF協定可以說是對請求轉發最完美的協定。

總結:目前的實作方案也隻解碼了這三個關鍵字段,速度足夠快,不需要繼續優化。

Dubbo協定

Dubbo協定也是類似的協定頭+payload的變長結構,其協定格式如下:

SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發

其中long類型的id字段用來把請求request和傳回的response對應上,即我們所說的RequestId。

這樣TCP拆包和多路複用都輕松實作,稍微麻煩一點的是:Destination在哪裡?Dubbo在這裡的設計有點不夠理想,在協定頭中沒有字段可以直接讀取到Destination,需要去讀取data字段,也就是payload,裡面的path字段通常用來儲存服務名或者接口名。method字段用來表示方法名。

從設計上看,path字段和method字段被存放在payload中有些美中不足。慶幸的是,讀取這兩個字段的時候不需要完整的解開整個payload,好險,不然,那性能會沒法接受的。

以hession2為例,data字段的組合是:dubbo version + path + interface version + method + ParameterTypes + Arguments + Attachments。每個字段都是一個byte的長度+字段值的UTF bytes。是以讀取時并不複雜,速度也足夠快。

基本和HSF一緻,就是Destination的讀取稍微麻煩一點,放在payload中的設計讓人吓了一跳,好在有驚無險。整體說還是很适合轉發的。

總結:同HSF,不需要繼續優化。

HTTP/1.1

HTTP/1.1的格式應該大家都熟悉,而在這裡,不得不指出,HTTP/1.1協定對請求轉發是非常不友好的(甚至可以說是惡劣!):

  1. HTTP請求在拆包時,需要先按照HTTP header的格式,一行一行讀取,直到出現空行表示header結束
  2. 然後必須将整個header的内容全部解析出來,才能取出Content-Length header
  3. 通過Content-Length 值,才能完成對body内容的讀取,實作正确拆包
  4. 如果是chunked方式,則更複雜一些
  5. Destination通常從Host header中擷取
  6. 沒有RequestId,完全無法實作多路複用

這意味着,為了完成最基本的TCP拆包,必須完整的解析全部的HTTP header資訊,沒有任何可以優化的空間。對比上面幾個RPC協定,輕松自如的快速擷取幾個關鍵資訊,HTTP無疑要重很多。這也造成了在ServiceMesh下,HTTP/1.1和REST協定的性能總是和其他RPC方案存在巨大差異。

對于注定要解碼整個header部分,完全沒有優化空間可言的HTTP/1.1協定來說,Content Based Routing 的解碼開銷是必須付出的,無論是否使用 Content Based Routing 。是以,快速解碼的構想,對HTTP/1.1無效。

總結:受HTTP/1.1協定格式限制,上述兩個優化思路都無法操作。

HTTP/2和gRPC

作為HTTP/1.1的接班人,HTTP/2則表現的要好很多。

備注:當然HTTP/2的協定格式複雜多了,由于篇幅和主題的限制,這裡不詳細介紹HTTP/2的格式。

首先HTTP/2是以幀的方式組織封包的,所有的幀都是變長,固定的9個位元組+可變的payload,Length字段指定payload的大小:

SOFAMesh中的多協定通用解決方案x-protocol介紹系列(2):快速解碼轉發

HTTP2的請求和應答,也被稱為Message,是由多個幀構成,在去除控制幀之外,Message通常由Header幀開始,後面接CONTINUATION幀和Data幀(也可能沒有,如GET請求)。每個幀都可以通過頭部的Flags字段來設定END_STREAM标志,表示請求或者應答的結束。即TCP拆包的問題在HTTP/2下是有非常标準而統一的方式完成,完全和HTTP/2上承載的協定無關。

HTTP/2通過Stream內建多路複用,這裡的Stream Identifier 扮演了類似前面的RequestId的角色。

而Destination資訊則通過Header幀中的僞header :authority 來傳遞,類似HTTP/1.1中的Host header。不過HTTP/2下header會進行壓縮,讀取時稍微複雜一點,也存在需要解壓縮整個header幀的性能開銷。考慮到拆包和擷取RequestId都不需要解包(隻需讀取協定頭,即HTTP/2幀的固定字段),速度足夠快,是以存在很大的優化空間:不解碼header幀,直接通過DNS通用尋址方案,這樣性能開銷大為減少,有望獲得極高的轉發速度。

總結:HTTP/2的幀設計,在請求轉發時表現的非常友好。唯獨Destination資訊放在header中,會造成必須解碼header幀。好在DNS通用尋址方案可以彌補,實作快速解碼和轉發。

Service Mesh時代的RPC理想方案

在文章的最後,我們總結并探讨一下,對于Service Mesh而言,什麼樣的RPC方案是最理想的?

  1. 必須可以友善做TCP拆包,最好在協定頭中就簡單搞定,标準方式如固定協定頭+length字段+可變payload。HSF協定、 bolt協定和dubbo協定表現完美,HTTP/2采用幀的方式,配合END_STREAM标志,方式獨特但有效。HTTP/1.1則是反面典型。
  2. 必須可以友善的擷取destination字段,同樣最好在協定頭中就簡單搞定。HSF協定表現完美,dubbo協定藏在payload中但終究還是可以快速解碼有驚無險的過關,bolt協定和HTTP/2協定就很遺憾必須解碼header才能拿到,好在DNS通用尋址方案可以彌補,但終究丢失了服務名和方法名資訊。HTTP/1.1依然是反面典型。
  3. 最好有RequestId字段,同樣最好在協定頭中就簡單搞定。這方面HSF協定、dubbo協定、bolt協定表現完美,HTTP/2協定更是直接內建支援。HTTP/1.1繼續反面典型

是以,僅以友善用最佳性能進行轉發,對 Service Mesh、sidecar 友好而言,最理想的 RPC 方案是:

傳統的變長協定

固定協定頭+length 字段+可變 payload,然後在固定協定頭中直接提供 RequestId 和destination。

基于幀的協定

以 HTTP/2 為基礎,除了請求結束的标志位和 RequestId 外,還需要通過幀的固定字段來提供 destination 資訊。

或許,在未來,在 Service Mesh 普及之後,對 Service Mesh 友好成為 RPC 協定的特别優化方向,我們會看到表現完美更适合Service Mesh時代的新型 RPC 方案。

相關連結:

SOFA 文檔:

http://www.sofastack.tech/

SOFA:

https://github.com/alipay

SOFAMosn:

https://github.com/alipay/sofa-mosn

SOFAMesh:

https://github.com/alipay/sofa-mesh

公衆号:金融級分布式架構(Antfin_SOFA)

繼續閱讀