抖音春晚活動背後的 Service Mesh 流量治理技術

本文整理自火山引擎開發者社區 Meetup 的同名演講,主要介紹了抖音春晚紅包大規模流量場景下的 Service Mesh 流量治理技術。編程

背景與挑戰

2021 年的央視春晚紅包項目留給業務研發同窗的時間很是少,他們須要在有限的時間內完成相關代碼的開發測試以及上線。後端

整個項目涉及到不一樣的技術團隊,天然也會涉及衆多的微服務。這些微服務有各自的語言技術棧,包括 Go,C++,Java,Python,Node 等,同時又運行在很是複雜的環境中,好比容器、虛擬機、物理機等。這些微服務在整個抖音春晚活動的不一樣階段,可能又須要使用不一樣的流量治理策略來保證穩定性。緩存

所以基礎架構就須要爲這些來自不一樣團隊、用不一樣語言編寫的微服務提供統一的流量治理能力。安全

傳統微服務架構的應對

說到微服務,咱們先來看一下傳統的微服務架構是怎麼解決這些問題的。隨着企業組織的不斷髮展,產品的業務邏輯日漸複雜,爲了提高產品的迭代效率,互聯網軟件的後端架構逐漸從單體的大服務演化成了分佈式微服務。分佈式架構相對於單體架構,其穩定性和可觀測性要差一些。markdown

爲了提高這些點,咱們就須要在微服務框架上實現不少功能。例如:網絡

  • 微服務須要經過相互調用來完成原先單體大服務所實現的功能,這其中就涉及到相關的網絡通訊,以及網絡通訊帶來的請求的序列化、響應的反序列化
  • 服務間的相互調用涉及服務發現
  • 分佈式的架構可能須要不一樣的流量治理策略來保證服務之間相互調用的穩定性。
  • 微服務架構下還須要提高可觀測性能力,包括日誌、監控、Tracing 等。

經過實現以上這些功能,微服務架構也能解決前面提到的一些問題。可是微服務自己又存在一些問題:架構

  • 在多語言的微服務框架上實現多種功能,涉及的開發和運維成本很是高
  • 微服務框架上一些新 Feature 的交付或者版本召回,須要業務研發同窗配合進行相關的改動和發佈上線,會形成微服務框架的版本長期割裂不受控的現象。

那咱們怎麼去解決這些問題呢?在軟件工程的領域有這樣一句話:任何問題均可以經過增長一箇中間層去解決。而針對咱們前面的問題,業界已經給出了答案,這個中間層就是 Service Mesh(服務網格)。併發

自研 Service Mesh 實現

下面就給你們介紹一下火山引擎自研 Service Mesh 的實現。先看下面這張架構圖。負載均衡

圖中藍色矩形的 Proxy 節點是 Service Mesh 的數據面,它是一個單獨的進程,和運行着業務邏輯的 Service 進程部署在一樣的運行環境(同一個容器或同一臺機器)中。由這個 Proxy 進程來代理流經 Service 進程的全部流量,前面提到的須要在微服務框架上實現的服務發現、流量治理策略等功能就均可以由這個數據面進程完成。框架

圖中的綠色矩形是 Service Mesh 的控制面。咱們須要執行的路由流量、治理策略是由這個控制面決定的。它是一個部署在遠端的服務,由它和數據面進程下發一些流量治理的規則,而後由數據面進程去執行。

同時咱們也能夠看到數據面和控制面是與業務無關的,其發佈升級相對獨立,不須要通知業務研發同窗。

基於這樣的架構就能夠解決前文提到的一些問題:

  • 咱們不須要把微服務框架衆多的功能在每種語言上都實現一遍,只須要在 Service Mesh 的數據面進程中實現便可;
  • 同時由數據面進程屏蔽各類複雜的運行環境,Service 進程只須要和數據面進程通信便可;
  • 各類靈活多變的流量治理策略也均可以由 Service Mesh 的進程控制面服務進行定製。

Service Mesh 流量治理技術

接下來給你們介紹咱們的 Service Mesh 實現具體提供了哪些流量治理技術來保障微服務在面對抖音春晚活動的流量洪峯時可以有一個比較穩定的表現。

首先介紹一下流量治理的核心:

  • 路由:流量從一個微服務實體出發,可能須要進行一些服務發現或者經過一些規則流到下一個微服務。這個過程能夠衍生出不少流量治理能力。
  • 安全:流量在不一樣的微服務之間流轉時,須要經過身份認證、受權、加密等方式來保障流量內容是安全、真實、可信的。
  • 控制:在面對不一樣的場景時,用動態調整治理策略來保障微服務的穩定性。
  • 可觀測性:這是比較重要的一點,咱們須要對流量的狀態加以記錄、追蹤,並配合預警系統及時發現並解決問題。

以上的四個核心方面配合具體的流量治理策略,能夠提高微服務的穩定性,保障流量內容的安全,提高業務同窗的研發效率,同時在面對黑天鵝事件的時候也能夠提高總體的容災能力。

下面咱們繼續來看一下 Service Mesh 技術具體都提供了哪些流量治理策略來保障微服務的穩定性。

穩定性策略——熔斷

首先是熔斷。在微服務架構中,單點故障是一種常態。當出現單點故障的時候,如何保障總體的成功率是熔斷須要解決的問題。

熔斷能夠從客戶端的視角出發,記錄從服務發出的流量請求到達下游中每個節點的成功率。當請求達到下游的成功率低於某一閾值,咱們就會對這個節點進行熔斷處理,使得流量請求再也不打到故障節點上。

當故障節點恢復的時候,咱們也須要必定的策略去進行熔斷後的恢復。好比能夠嘗試在一個時間週期內發送一些流量打到這個故障節點,若是該節點仍然不能提供服務,就繼續熔斷;若是可以提供服務了,就逐漸加大流量,直到恢復正常水平。經過熔斷策略,能夠容忍微服務架構中個別節點的不可用,並防止進一步惡化帶來的雪崩效應。

穩定性策略——限流

另一個治理策略是限流。限流是基於這樣的一個事實:Server 在過載狀態下,其請求處理的成功率會下降。好比一個 Server 節點正常狀況下可以處理 2000 QPS,在過載狀況下(假設達到 3000 QPS),這個 Server 就只能處理 1000 QPS 甚至更低。限流能夠主動 drop 一些流量,使得 Server 自己不會過載,防止雪崩效應。

穩定性策略——降級

當 Server 節點進一步過載,就須要使用降級策略。降級通常有兩種場景:

  • 一種是按照比例丟棄流量。好比從 A 服務發出到 B 服務的流量,能夠按照必定的比例(20% 甚至更高)丟棄。
  • 另一種是旁路依賴的降級。假設 A 服務須要依賴 B、C、D 3 個服務,D 是旁路,能夠把旁路依賴 D 的流量掐掉,使得釋放的資源能夠用於核心路徑的計算,防止進一步過載。

穩定性策略——動態過載保護

熔斷、限流、降級都是針對錯誤發生時的治理策略,其實最好的策略是防患於未然,也就是接下來要介紹的動態過載保護。

前面提到了限流策略很難肯定閾值,通常是經過壓測去觀測一個節點可以承載的 QPS,可是這個上限量級可能會因爲運行環境的不一樣,在不一樣節點上的表現也不一樣。動態過載保護就是基於這樣一個事實:資源規格相同的服務節點,處理能力不必定相同。

如何實現動態過載保護?它分爲三個部分:過載檢測,過載處理,過載恢復。其中最關鍵的是如何判斷一個 Server 節點是否過載。

上圖中的 Ingress Proxy 是 Service Mesh 的數據面進程,它會代理流量併發往 Server 進程。圖中的 T3 能夠理解爲從 Proxy 進程收到請求到 Server 處理完請求後返回的時間。這個時間是否能夠用來判斷過載?答案是不能,由於 Server 有可能依賴於其餘節點。有多是其餘節點的處理時間變長了,致使 Server 的處理時間變長,這時 T3 並不能反映 Server 是處於過載的狀態。

圖中 T2 表明的是數據面進程把請求轉發到 Server 後,Server 真正處理到它的時間間隔。T2 可否反映過載的狀態?答案是能夠的。爲何能夠?舉一個例子,假設 Server 的運行環境是一個 4 核 8g 的實例,這就決定了該 Server 最多隻能同時處理 4 個請求。若是把 100 個請求打到該 Server,剩餘的 96 個請求就會處於 pending 的狀態。當 pending 的時間過長,咱們就能夠認爲是過載了。

檢測到 Server 過載以後應當如何進行處理?針對過載處理也有不少策略,咱們採用的策略是根據請求的優先級主動 drop 低優的請求,以此來緩解 Server 過載的狀況。當 drop 了一些流量後 Server 恢復了正常水平,咱們就須要進行相應的過載恢復,使得 QPS 可以達到正常狀態。

這個過程是如何體現動態性的?過載檢測是一個實時的過程,它有必定的時間週期。在每個週期內,當檢測到 Server 是過載的狀態,就能夠慢慢根據必定比例 drop 一些低優請求。在下一個時間週期,若是檢測到 Server 已經恢復了,又會慢慢調小 drop 的比例,使 Server 逐漸恢復。

動態過載保護的效果是很是明顯的:它能夠保證服務在大流量高壓的狀況下不會崩潰,該策略也普遍地應用於抖音春晚紅包項目中的一些大服務。

穩定性策略——負載均衡

接下來咱們看一下負載均衡策略。假設有一個服務 A 發出的流量要達到下游服務 B,A 和 B 都有一萬個節點,咱們如何保障從 A 出發的流量達到 B 中都是均衡的?作法其實有不少,比較經常使用的是隨機輪詢、加權虛機、加權輪詢,這些策略其實看名字就能知道是什麼意思了。

另外一種比較常見的策略是一致性哈希。哈希是指根據請求的一些特徵使得請求必定會路由到下游中的相同節點,將請求和節點創建起映射關係。一致性哈希策略主要應用於緩存敏感型服務,能夠大大提高緩存的命中率,同時提高 Server 性能,下降超時的錯誤率。當服務中有一些新加入的節點,或者有一些節點不可用了,哈希的一致性能夠儘量少地影響已經創建起的映射關係。

還有不少其餘的負載均衡策略,在生產場景中的應用範圍並非很普遍,這裏再也不贅述。

穩定性策略——節點分片

面對抖音春晚紅包這種超大流量規模的場景,還有一個比較有用的策略是節點分片。節點分片基於這樣一個事實:節點多的微服務,其長鏈接的複用率是很是低的。由於微服務通常是經過 TCP 協議進行通訊,須要先創建起 TCP 鏈接,流量流轉在 TCP 鏈接上。咱們會盡量地複用一個鏈接去發請求搜響應,以免因頻繁地進行鏈接、關閉鏈接形成的額外開銷。

當節點規模很是大的時候,好比說 Service A 和 Service B 都有 1 萬個節點,它們就須要維持很是多的長鏈接。爲避免維持這麼多長鏈接,一般會設置一個 idle timeout 的時間,當一個鏈接在必定的間隔內沒有流量通過的時候,這個鏈接就會被關掉。在服務節點規模很是大的場景下,長鏈接退化成的短鏈接,會使得每個請求都須要創建鏈接才能進行通信。它帶來的影響是:

  • 鏈接超時帶來的錯誤。
  • 性能會有所下降。

解決這個問題可使用節點分片的策略。實際上咱們在抖音春晚紅包的場景中也是很是普遍地使用了這個策略。這個策略對節點數較多的服務進行節點分片,而後創建起一種映射關係,使得以下圖中所示的 A 服務的分片 0 發出的流量必定能到達 service B 的分片 0。

這樣就能夠大大提高長鏈接的複用率。對於原先 1000010000 的對應關係,如今就變成了一個常態的關係,好比 100100。咱們經過節點分片的策略大大提高了長鏈接的複用率,下降了鏈接超時帶來的錯誤,而且提高了微服務的性能。

效率策略

前面提到的限流、熔斷、降級、動態過載保護、節點分片都是提高微服務穩定性相關的策略,還會有一些與效率相關的策略。

咱們先介紹一下泳道和染色分流的概念。

上圖中所示的某個功能可能涉及到 a、b、c、d、e、f 六個微服務。泳道能夠對這些流量進行隔離,每個泳道內完整地擁有這六個微服務,它們能夠完整的完成一個功能。

染色分流是指根據某些規則使得流量打到不一樣的泳道,而後藉此來完成一些功能,這些功能主要包括:

  • Feature 調試:在線上的開發測試過程當中,能夠把我的發出的一些請求打到本身設置的泳道並進行 Feature 調試。
  • 故障演練:在抖音春晚活動的一些服務開發完成以後,須要進行演練以對應對不一樣的故障。這時咱們就能夠把壓測流量經過一些規則引流到故障演練的泳道上。
  • 流量錄製回放:把某種規則下的流量錄製下來,而後進行相關回放,主要用於 bug 調試或在某些黑產場景下發現問題。

安全策略

安全策略也是流量治理的重要環節。咱們主要提供三種安全策略:

  • 受權:受權是指限定某一個服務可以被哪些服務調用。
  • 鑑權:當一個服務接收到流量時,須要鑑定流量來源的真實性。
  • 雙向加密(mTLS) :爲了防止流量內容被窺探、篡改或被攻擊,須要使用雙向加密。

經過以上的這些策略,咱們提供了可靠的身份認證,安全地傳輸加密,還能夠防止傳輸的流量內容被篡改或攻擊。

春晚紅包場景落地

經過前面提到的各類策略,咱們能夠大大提高微服務的穩定性以及業務研發的效率。可是當咱們落地這一套架構的時候也會遇到一些挑戰,最主要的挑戰是性能問題。咱們知道,經過增長一箇中間層,雖然提高了擴展性和靈活性,但同時也必然有一些額外的開銷,這個開銷就是性能。在沒有 Service Mesh 時,微服務框架的主要開銷來自於序列化與反序列化、網絡通信、服務發現以及流量治理策略。使用了 Service Mesh 以後,會多出兩種開銷:

協議解析

對於數據面進程代理的流量,須要對流量的協議進行必定的解析才能知道它從哪來到哪去。可是協議解析自己的開銷很是高,因此咱們經過增長一個 header (key 和 value 的集合) 能夠把流量的來源等服務元信息放到這個 header 裏,這樣只須要解析一兩百字節的內容就能夠完成相關的路由。

進程間通信

數據面進程會代理業務進程的流量,一般是經過 iptables 的方式進行。這種方案的 overhead 很是高,因此咱們採用了進程間通信的方式,經過和微服務框架約定一個 unix domain socket 地址或者一個本地的端口,而後進行相關的流量劫持。雖然這種方式相對於 iptables 會有一些性能提高,它自己也存在的額外的一些開銷。

咱們是如何下降進程間通信開銷的呢?在傳統的進程間通信裏,好比像 unix domain socket 或者本地的端口,會涉及到傳輸的內容在用戶態到內核態的拷貝。好比請求轉發給數據面進程會涉及到請求在用戶態和內核態之間拷貝,數據面進程讀出來的時候又會涉及內核態到用戶態的拷貝,那麼一來一回就會涉及到多達 4 次的內存拷貝。

咱們的解決方案是經過共享內存來完成的。共享內存是 Linux 下最高性能的一種進程間通信方式,可是它沒有相關的通知機制。當咱們把請求放到共享內存以後,另一個進程並不知道有請求放了進來。因此咱們須要引入一些事件通知的機制,讓數據面進程知道。咱們經過 unix domain socket 完成了這樣一個過程,它的效果是能夠減小內存的拷貝開銷。同時咱們在共享內存中引用了一個隊列,這個隊列能夠批量收割 IO,從而減小了系統的調用。它起到的效果也是很是明顯的,在抖音春晚活動的一些風控場景下,性能能夠提升 24%。

完成這些優化以後,要去落地的阻力就沒那麼大了。

總結

本次分享主要爲你們介紹了 Service Mesh 技術可以提供哪些流量治理能力來保證微服務的穩定和安全。主要包括三個核心點:

  • 穩定:面對瞬時億級 QPS 的流量洪峯, 經過 Service Mesh 提供的流量治理技術,保證微服務的穩定性。
  • 安全:經過 Service Mesh 提供的安全策略,保證服務之間的流量是安全可信的。
  • 高效:春晚活動涉及衆多不一樣編程語言編寫的微服務,Service Mesh 自然爲這些微服務提供了統一的流量治理能力,提高了開發人員的研發效率。

Q&A

Q:共享內存中的 IPC 通訊爲何可以減小系統調用?

A:當客戶端進程把一個請求放到共享內存中以後,咱們須要通知 Server 進程進行處理,會有一個喚醒的操做,每次喚醒意味着一個系統調用。當 Server 尚未被喚醒的時候,或者它正在處理請求時,下一個請求到來了,就不須要再執行相同的喚醒操做,這樣就使得在請求密集型的場景下咱們不須要去頻繁的喚醒,從而起到下降系統調用的效果。

Q:自研 Service Mesh 實現是純自研仍是基於 Istio 等社區產品?若是是自研使用的是 Go 仍是 Java 語言?數據面用的是 Envoy 麼?流量劫持用的 iptables 麼?

A

  1. 數據面是基於 Envoy 進行二次開發的,語言使用 C++。
  2. 流量劫持用與微服務框架約定好的的 uds 或者本地端口,不用 iptables。
  3. Ingess Proxy 和業務進程部署在一樣的運行環境裏,發佈升級不須要重啓容器。
相關文章
相關標籤/搜索