Service Mesh架構反思:數據平面和控制平面的界線該如何劃定?
導讀:
苦等一年,始終不見Istio的性能有實質性提升,失望之餘,開始反思Istio而至Service Mesh的架構。焦點所在:proxy到底該做哪些事情?架構的優美和性能的實際表現該如何平衡?
1
問題所在
Istio的性能,從去年5月問世以來,就是一塊心病。
前段時間Istio 0.7.1版本發布,不久我們得到了這個一個」喜訊」:Istio的性能在0.7.1版本中得到了大幅提升!
最大QPS從0.5.1的700,到0.6.0的1000,再到0.7.1的1700,縱向比較Istio最近的性能提升的確不錯,都2.5倍性能了。
但是,注意,QPS 700/1000/1700。是的,沒錯,你沒有看錯,沒有漏掉一個零或者兩個零。
稍微回顧一下近年中有自己有實際感受的例子:
幾個月之前寫了一個基於netty4.1的HTTP轉發的demo,沒做任何優化和調教,輕鬆10萬+的QPS
兩年前寫的dolphin框架,gRPC直連,15萬QPS輕鬆做到
然後,白衣和隔壁老王兩位同學輕描淡寫的好像說過,OSP極致性能過了20萬QPS了(背景補充:OSP是唯品會的服務化框架,sidecar模式,它的Proxy是被大家詬病以慢著稱的Java)。
當然Istio測試場景稍微複雜一些,畢竟不是空請求,但是如論如何,從QPS十幾二十萬這個級別,直落到QPS一兩千,中間有幾乎100倍的差距。這個差距未免有些太大,從實用性上說,2000級別的QPS,低了點。
反思的第一個懷疑對象是proxy轉發性能,但是隨即否決:多了proxy肯定會有影響,但是遠不至於此。而且,唯品會的OSP,還有華為的Service Mesher的性能表現也說明了問題不是出在proxy轉發上。
註:關於proxy可能帶來的性能開銷,在之前的文章 DreamMesh拋磚引玉(6)-性能開銷 一文中曾經有過詳細的分析,可以參考。
2
背景回顧
Istio的性能問題其實由來已久,很早就暴露,官方也為此做了改進,我們現在看到的 700/1000/1700 已經是改進之後的結果。
今天來好好聊下這個問題,這涉及到Istio的架構設計,甚至涉及到Service Mesh的演進過程。
註:關於Service Mesh的演進過程,在之前的演講DreamMesh拋磚引玉(6)-性能開銷。
Service Mesh剛出現時,以Linkerd/Envoy為代表:
這時的service mesh,主要是以proxy/sidecar的形式出現,功能基本都在sidecar中(linkerd有個named的模塊分出了部分功能)。請求通過sidecar轉發,期間發生的各種檢查/判斷/操作,都是在sidecar中進行。
因此,從性能上說,與客戶端/伺服器端直接連接相比,做的事情基本一致,只是多了proxy轉發的開銷。而這個開銷,與伺服器端業務處理的開銷相比,非常小,大多數情況下是可以忽略的。
然後Service Mesh演進到了第二代,Istio出現,和後面緊跟的Conduit。這個時候,在第一代的基礎上,Service Mesh開始在對系統的掌控上發力,控制平面由此誕生:
控制平面的加入,是一個非常令人興奮的事情,因為帶來了太多的實用型的功能,istio因此備受關注和推崇,隨後緊跟Istio腳步的Conduit也沿用了這套架構體系:
在這個架構當中,控制平面的三大模塊,其中的Pilot和Auth都不直接參与到traffic的處理流程,因此他們不會對運行時性能產生直接影響。
需要審視的是,需要參與到traffic流程的Mixer模塊,請注意上圖中,從envoy到mixer的兩個箭頭,這代表著兩次調用,而且是兩次遠程調用。下面這個圖,可以看的更清楚一些:
Mixer的職責
我們直接搬出mixer官方文檔對mixer的介紹:
https://istio.io/docs/concepts/policy-and-control/mixer.html
Mixer 提供三個核心功能:
前提條件檢查。在響應來自服務調用者的傳入請求之前,使得調用者能夠驗證許多前提條件。允許服務在響應來自服務消費者的傳入請求之前驗證一些前提條件。前提條件可以包括服務使用者是否被正確認證,是否在服務的白名單上,是否通過ACL檢查等等。
配額管理。使服務能夠在多個維度上分配和釋放配額,當服務消費者對有限的資源發生搶佔時,配額就成了一種有效的資源管理工具,它為服務之間的資源搶佔提供相對的公平。頻率控制就是配額的一個實例。
遙測報告。使服務能夠上報日誌和監控。在未來,它還將啟用針對服務運營商以及服務消費者的跟蹤和計費流。
然後,最關鍵的的是:
這些機制的應用是基於一組屬性的,每個請求都會將這些屬性呈現給Mixer。在Istio中,這些屬性是由對Sidecar代理(一般是Envoy)的每一次請求產生的。
也就是說,為了讓mixer能夠工作,就需要envoy從每次請求中獲取信息,然後發起兩次對mixer的請求:
在轉發請求之前:這時需要做前提條件檢查和配額管理,只有滿足條件的請求才會做轉發
在轉發請求之後:這時要上報日誌等,術語上稱為遙感信息,Telemetry,或者Reporting。
3
設計初衷
繼續搬運官方文檔:
後端基礎服務為構建的服務提供各種支持功能。這些功能包括訪問控制系統、計量收集捕獲系統、配額執行系統、計費系統等。傳統服務會直接和這些後端系統打交道,與後端緊密耦合,並集成其中的個性化語義和用法。
Mixer在應用程序代碼和基礎架構後端之間提供通用中介層。它的設計將策略決策移出應用層,用運維人員能夠控制的配置取而代之。應用程序代碼不再將應用程序代碼與特定後端集成在一起,而是與Mixer進行相當簡單的集成,然後Mixer負責與後端系統連接。
Mixer的設計目的是改變層次之間的邊界,以此來降低總體的複雜性。從服務代碼中剔除策略邏輯,改由運維人員進行控制。
非常優秀的設計,從系統架構上沒得說,解開應用和後端基礎設施的耦合,好處多多,這也是我們愛Istio的重要理由。
但是,注意,這段文字解釋的是:為什麼要將這些功能從應用裡面搬出來放進去service mesh和mixer。這點我舉雙手贊成,而我的疑問在於:這些邏輯從應用中移動到service mesh之後,為什麼要放mixer中,而不是proxy?
這就涉及到第二代service mesh的核心設計了:原有的sidecar被歸結為data plane,然後在data plane上增加control plane。那麼,最最重要的設計問題就來了,在這個架構中,從職責分工和部署上:
data plane和control plane的界限在哪裡?
4
Istio的選擇
當前,istio在這個問題上,選擇的是非常理想化的劃分方式:
data plane 只負責轉發,其中部分數據來自Pilot和Auth。
和後端基礎設施相關的功能放置在mixer中,通過各種adapter實現
這個方式架構清晰,職責分明,在不考慮性能時,無可挑剔。
再考慮到Istio的背景,Istio項目啟動時:
envoy已經基本成熟。這樣istio的data plane直接有現成的一個基礎,在這個基礎上新加control plane,合乎情理。
新寫的control plane用golang編寫,也是符合情理,畢竟中間件用go是個潮流,加上是google自己的產品。
同時envoy是基於c++的,寫各種很具體很繁瑣的外圍功能未免有些難受,比如mixer中和各種後端打交道的adapter。
所有,在我看來,istio當時選擇現在這樣一個架構,合乎情理,也比較自然。
只是,選擇這個方式之後,有些問題就無可迴避:
為了在請求處理過程中調用到mixer,就只得在轉發前後發起兩次對mixer的調用
由於envoy是c++,新的mixer是go,獨立部署
導致,這兩次調用, 都是遠程調用,而且也不是localhost的本地調用
這樣一來,性能開銷就無可避免。Istio去年5月出來之後,就被發現性能非常糟糕,比現在的QPS 700/1000/1700 還要糟糕很多。
##Istio的改進
之後,Istio給出了一個改良的方案:在envoy中增加mixer filter。這個Filter和控制面的Mixer組件進行通訊,完成策略控制和遙測數據收集功能。
關鍵之處,在於增加了緩存:
Mixer Filter中保存有策略判斷所需的數據緩存,因此大部分策略判斷在Envoy中就處理了,不需要發送請求到Mixer。
另外Envoy收集到的遙測數據會先保存在Envoy的緩存中,每隔一段時間再通過批量的方式上報到Mixer。
從官方博客Mixer and the SPOF Myth給出的信息看,這裡的緩存實現還做的很周到,甚至實現了兩級緩存。
緩存的工作方式如下:
Sidecar 中包含本地緩存,一大部分的前置檢查可以通過緩存來進行。
另外,sidercar 會把待發送的指標數據進行緩衝,這樣可能在幾千次請求之後才調用一次 Mixer。
前置檢查和請求處理是同步的
指標數據上送是使用 fire-and-forget 模式非同步完成的。
從描述上看非常令人滿意,如果這個方式實現了,那麼Mixer帶來的性能瓶頸應該可以解決。但是,對照一下時間,這個博客發表於2017年12月,在這之後的小半年,istio的性能還是徘徊在 QPS 700/1000/1700 的水準。
這裡,個人存在幾個質疑:
「一大部分的前置檢查可以通過緩存來進行」,大部分?可是只要有一個前置檢查不能緩存,就必須去mixer裡面請求
沒有看到任何關於quota的說明。到底quato怎麼辦?配額按說是沒法緩存的,否則配額就不準確了。而一旦需要執行配額管理,還是必須發起請求到mixer
envoy中要執行前置檢查,不僅僅需要緩存前置檢查的各種配置信息,也必須要求envoy有執行前置檢查的能力,即原來在mixer中實現前置檢查的所有邏輯代碼必須在envoy裡面再實現一次。
其中的第三條,會帶來一個悖論:
如果envoy沒有把所有的前置檢查的代碼邏輯都實現一遍,那麼走緩存就會造成執行結果和走mixer不一致,這不是一個嚴謹的做法;
如果envoy做的足夠嚴謹,保證了envoy的前置檢查可以100%覆蓋mixer裡面的實現,那何苦再保留mixer的前置檢查邏輯,去掉就好了,否則維持同一個代碼邏輯的兩套實現版本
TBD: 這幾個疑問貌似誤解,只能去翻代碼了。稍後更新這塊內容。
5
另一個選擇
Istio的選擇出發點,我理解是讓data plane保持簡單,儘可能將其它功能移出來。然而遇到性能問題,終究還是要做妥協,比如cache的出現。
如果我們放棄proxy最簡化的想法,不再將系統架構優美放在第一位,而是將性能放在首位,那麼,我們就可以有另外一種選擇。
我們再次審視mixer的三大功能:
前置條件檢查。這個是必須在請求的traffi處理流程中完成的,因為traffic必須等待檢查結果來決定下一步的行為,是拒絕請求還是繼續轉發,必須有明確答案。
配額管理。 同上,必須同步等待結果才能繼續。
遙測報告。這個可以優化,兩個方式,可以非同步提交,可以批量提交,當時實際代碼實現時會選擇非同步+批量。
因此,優化的方向很明顯,前置條件檢查和配額管理都是同步操作,應該移回proxy。遙測可以通過非同步+批量的方式減少對mixer請求的數量級來進行優化。
但是,對於遙測報告,還有一個重要的考量點:遙測報告涉及到日誌等數據提交,如果都集中式的先行提交給mixer,然後mixer再連接後端基礎設施,則數據量(尤其批量之後)會集中在mixer這個單點。因此,從性能考慮,遙測報告最好也是proxy直接提交合適。
按照這個思路,mixer幾乎被全部合併回proxy。Istio的架構需要做巨大的調整:control plane只留下Pilot和Auth,在envoy中重新實現一份mixer。
我不知道Istio會不會有這麼大的決心。
然而,快一年了,istio的性能始終徘徊在一個低的離譜的地步,總該找到某種方式來解決問題,對吧?
6
Conduit的選擇
作為linkerd慘遭istio阻擊之後的絕地反擊,conduit從出生開始就帶有悲劇色彩,強大的對手,小小的團隊,不翻盤就等死的處境。
這種情況下的conduit,在架構上選擇了直接吸收istio的優點,因此,istio提出的控制面板conduit幾乎照樣來了一份。也因此,istio mixer遇到的問題,conduit也沒躲過。從這個角度說,istio把自己和conduit都帶進坑了。
本周,帶著前面的疑問和擔憂,跑去conduit的slack上,簡單做了一下交流。得到的答覆如下:
kl [1:49 AM] @Sky Ao/敖小劍 thanks for the suggestions! totally agree that the policy stuff should be pushed out asynchronously. and in 0.4.0 we moved telemetry to a setup where prometheus scrapes stat data directly from the proxies themselves, separate from the request path, and no longer reliant on the controller
william [4:03 AM] @Sky Ao/敖小劍 we』re looking at ways to push policy into the proxies, rather than polling the controller per requet (and caching). but it』s still early. like Kevin says, telemetry is no longer pushed from the proxy in 0.4.0 already
也就是:
在新發布的0.4.0版本中,遙測報告已經從控制平面移出,現在是從proxy直接走
未來將會繼續將policy的部分移到proxy中(只是現在還沒有動)
可以說,conduit已經意識到坑的存在了,現在一條腿已經拔出來,另外一條腿要拔出來的話還需要一點時間。
頗感欣慰。
7
其它人的選擇
很早之前,和白衣同學深入討論這個話題,當時白衣就對mixer的做法有異議,因為性能問題是明擺著的。唯品會的OSP框架,選擇的是將功能都在proxy中完成,性能還是有足夠的保障。
近日在群內討論時,了解到華為的兄弟們從一開始就繞開了這個坑,直接在proxy中實現功能。
更新:聯繫到新浪微博的同學,確認motan mesh的做法也是直接做進去proxy的。
8
總結
好多年前,某位同學(依稀是白衣)曾經打過一個比喻,非常生動,大意如下:
架構師就是藝術家,精心雕刻了一個精美絕倫的花瓶,然後,為了某些考慮,又不得不在花瓶的底下墊了一塊板磚。
Service Mesh中數據平面和控制平面的關係,到了該重新認真審視和反思的時候了:花瓶雖美,但是那塊板磚,是不是該考慮墊上去了
未來的一段時間,我們不妨關注istio和conduit對此的選擇。
腦洞時間:
最後,腦洞一下:如果用go重寫proxy,替代envoy,那麼mixer裡面的功能要搬過來就是輕鬆自如。
做這個事情的前提: 是能提供一個go版本的proxy,做到足夠好,以便替代envoy的位置。
感嘆一下:如果當年envoy不是用c++,而是用go,事情就完全不一樣了。然後,conduit的proxy是rust寫的,rust用來實現proxy倒是神器,但是用來寫各種adapter,想必也為難。也許,未來我們會見到一個純go版本的service mesh?
※微軟Service Fabric正式開源,入局第三代微服務框架爭霸!
TAG:ServiceMesh中文網 |