【工程師必學的災難應變術】遭遇全球性 GCP 故障,17 Media 用 4 個措施應對高流量壓力

【為什麼我們要挑選這篇文章】每晚 11 點是 17 Media 的流量尖峰,此時的流量是離峰的 6 倍,QPS(queries per second)超過 10,000,是 17 Media 的「小小雙十一挑戰」。

然而在今年 11/1 中午,GCP(Google 雲端平台)發生三年來最大規模的故障,卻遲遲無法修復;面對即將到來的夜間流量尖峰,17 Media 的工程團隊如何應對?(責任編輯:郭家宏)

Google Cloud Platform(GCP)在萬聖節隔天發生了三年來最大規模的故障,在故障的 12 小時內,因為網路問題造成無法新增機器。這次故障也是個好機會來檢視 17 Media 的 Site Reliability Engineering(SRE)團隊平日的防護及練習的成果。筆者有幸參與故障排除全程,本文將以一位 17 Media SRE 觀點來介紹當天發生經過,探討為何這次 17 Media 受影響特別大,以及日後的改進事項,與大家一同交流。

晚上 11 點是 17 Media 的流量高峰

目前主要負擔線上流量的有十組伺服器,全都以容器透過 Google Kubernetes Engine(GKE)運行,利用 CPU 用量自動擴容。每日尖峰及離峰流量差距約為六倍,尖峰約在晚上 11 點鐘左右,QPS 超過 10,000。這對開發團隊是相當刺激的一件事,因為每天晚上都是個小小的雙十一挑戰,如果不小心寫出了 bug,當天晚上就會收到很真實的用戶反饋。

17 Media 線上系統 QPS 流量圖,每日峰值約在 11:00 pm,凹下去那塊就是故障當晚的流量。

17 Media 的主要用戶分布於亞洲及美國,主力開發由台灣團隊負責,整個後端程式由 Golang 所撰寫。為了要讓開發團隊更為敏捷,SRE 每個上班日都會佈署新版本:透過 Jenkins 在凌晨四點自動將前一日 master branch 的代碼佈署到 staging 環境,SRE 則是在上班後將 staging 環境測試穩定後的代碼佈署到生產(production)環境。

GCP 故障,17 Media 的工程師如何應對即將到來的高流量?

事故發生於 11/1 中午,當時一位 SRE 發現 staging 環境的伺服器無法正常部署新版本,一段時間後在 GCP 狀態頁面確定是 GCP 的故障,造成無法開啟新機器。這對我們意味著幾件事:

1. 無法佈署新版本。
2. 當 GKE 認為某個容器健康狀態異常而決定關閉時,將會無法開啟新機器替換。
3. 當系統有擴容需求時,將會無法開啟新機器。

其中 3. 的影響最大。在故障剛發生時(12:00)系統處於離峰狀態,依照過往的流量波形,預估稍晚流量上升後系統會無法支撐。但由於以往 GCP 故障多在一小時內修復,當下我們不太擔心。

SRE team lead 在 Slack 的公告。
為了要讓各團隊即時同步資訊,該公告也翻譯成英文。
以及日文版本的公告。

數個小時過後,從 GCP 狀態頁面仍看不出修復跡象,這時團隊開始緊張了,因為晚上就是高峰期,現有的系統資源肯定支撐不住流量。SRE 團隊很快討論後決定了以下應變措施:

1. 用戶限流
2. 關閉次要功能
3. 卸載運算需求低的容器,改運行關鍵容器
4. 跨雲佈署服務

由於無法佈署新版本,需要改動後端代碼的 hotfix 就不在考慮範圍內,所以這次的應變措施大多以運維的角度出發,跟以往與後端團隊合作的模式不同。以下分別介紹各項應變措施:

1. 用戶限流

當新上線用戶因為限流而被無法使用時,app 會顯示爆氣的 17 寶寶。

這是一項限制用戶數的功能。系統會持續追蹤目前線上人數,當發現無法支撐過多的用戶時,會將超出上限的新上線用戶擋在系統外。新上線的用戶會看到超載訊息,而已經在線上的用戶可以繼續保有良好的直播體驗。

我們預估了當前系統可以支撐的用戶數並開啟限制功能。從事後看來這個措施效果最好,因為這是一個開發成熟的功能,當下只要定義人數上限就會產生效果。

2. 關閉次要功能

目前 17 Media 線上服務大多為 CPU bounded。當 CPU 成為限制資源時,我們只好透過重新分配 CPU 的運用來讓重要的工作可以順利完成。對於直播平台來說,最重要的體驗就是直播本身,任何佔用 CPU 的非直播功能都可以關閉。但由於這些改動不能牽涉代碼改變(因為無法佈署新版本)所以可以關閉的功能有限。最後僅關閉了一些可以動態開關的功能,例如排行榜以及發送紅包。

3. 卸載運算需求低的容器,改運行關鍵容器

當可以使用的運算資源無法擴充時,除了關閉次要功能外,另一個方式是重新分配各個服務所運行的容器數量。對於某些延遲要求比較低的服務(例如 job queue worker),可以進一步降低機器數,把運算資源分配給延遲要求高的服務(例如 API service)。

這個方式說起來容易做起來難。由於更換容器不屬於 SRE 團隊日常工作之一,實際執行上只能土法煉鋼進入一台一台機器手動更換,而且更換到第三台後就因為不明原因失敗了。相信大家都有聽過寵物跟牲畜的故事,一時要將牲畜當作一隻隻的寵物看待並不是件容易的事。

4. 手動跨雲佈署

17 Media 的伺服器位於美西,剛好是各大雲服務供應商都有機房的位置。如果當下沒辦法在 GCP 開機器,那就在 AWS 開機器吧!

因為總故障時間長達 12 小時,前面幾項工作也很快的嘗試完了,我們大部分的時間花在跨雲佈署的準備。團隊已經好一陣子沒接觸 AWS 的容器服務,我們不確定哪個解決方案能在短時間解決問題,所以決定分頭嘗試不同的服務,包括 EKS / ECS / EC2 以及 Elastic Beanstalk,能夠嘗試的我們都試了。後來每個人分別遭遇了不同的問題,由於團隊對於 GCP 黏著度較深,導致這項方案完全失敗。現在反思當時應該要所有人專注在同一項 AWS 服務,或許能在時限內找出一項解決方案。

GCP 全球性故障,為何 17 Media 受到的影響特別大?

這是全球性故障,我們原先預期全球各大 app 都會發生問題。但在網路上搜尋一輪的結果超出意料:各大 app 都運作正常。相較於其他公司,17 Media 受到的影響相當大。事後研究發現可能原因有二:

1. 故障發生於台灣時間中午 12:00,也就是美西時間凌晨 2:00,對於大部分美國的 app 來說已經過了尖峰時段,不會有擴容需求,受到的影響也低。相對 17 Media app 從下午過後流量就一路往上到半夜的高峰,無法擴容的影響相當大。

2. 17 Media 的主要功能集中在少數服務上,當服務所承載的一部分功能耗盡服務資源時(例如 CPU),該服務上承載的其他功能也跟著異常,造成全面性災難。其他公司的 app 當下僅有部分功能異常。這也是大家所熟知的單體服務(monolith)與微服務(micro services)在災備隔離(fault isolation)上的差異,但實際發生時感受還是特別深刻。

可藉由多雲佈署與微服務,降低系統故障的衝擊

SRE 團隊的風格是快速嘗試且不責怪(blamelessness),在故障後的隔天上班日,團隊就開會檢討了當天的應變以及日後的改進。從事後反思當下的措施,真正奏效的反而是平日已經準備好的災難預備方案。這也讓我們認真檢討改進方案,若日後發生一樣的故障時服務能繼續運行。以下兩項是團隊討論之後得出的改進方案:多雲佈署及微服務,分別屬於 SRE 及 backend 的工作範疇:

多雲佈署服務

多雲佈署是個解決雲服務故障的好方法,畢竟各大雲服務商從來沒有同時故障過;當其中一個雲服務故障時,可以將流量導至另一個雲來排除問題。但由於其運維複雜度以及可能帶來的延遲上升,之前並不是 SRE 團隊的工作重心。這次故障讓我們認真思考多雲佈署的可能性。我們計畫從無狀態(stateless)服務開始,原因如下:

1. 無狀態服務特性:因為無狀態特性,這類服務很適合隨著流量動態增減機器。也因為這個特性,在這次故障受到影響的幾乎是無狀態的服務。

2. 影響範圍廣:17 Media 的服務大部分為無狀態,如果解決了這類服務的單點故障(single point of failure)問題等於解決了大部分的問題。

一般來說無狀態服務前面有一層 load balancer 分散流量,服務之下會有另一層服務或資料庫(database)來儲存或提供狀態。同時因為資料庫的存取延遲較長,前面多半有一層快取(cache)來降低延遲,同時降低資料庫負擔。也有人稱這為 3-tier architecture。

一般常見的 3-tier architecture 結構

這一類運維層面的改動有個重點,必須對開發人員無感,也就是不能造成開發人員額外負擔。

1. 對於下層服務的讀寫比例約為 9:1。
2. 資料庫讀寫:較慢,但因為前面放了快取所以對用戶影響較低。
3. 快取讀取:必須要快,因為大量的資料來自於快取。
4. 快取寫入:也需要快,但因為寫入比例低,對用戶影響較小。
5. 快取跟資料庫之間可以是最終一致(eventual consistency),但是誤差時間不能太長(例如不能超過一分鐘)。

基於以上假設,我們計畫做以下架構改動:在 AWS 建立另一組無狀態服務,前面由 weighted DNS 分散流量;在兩個雲服務商各建立一組快取以加速服務讀取速度,但寫入一律透過 GCP;資料庫讀寫依舊透過 GCP 。下圖描述了計畫中的改動。

計畫中的多雲佈署服務

在開發人員眼中,這個策略是非常合適的,因為開發人員對於資料庫及快取的假設不因這個策略而改變,也就是說工程師可以在不知道底層複雜度的前提下進行開發。原因如下:

1. GCP 這側的讀寫快取是位於同個雲內,所以快且穩定。
2. AWS 側的快取寫入 GCP,延遲較高。但因為寫入量少所以影響不大。
3. AWS 側的讀取來自同個雲內的快取,延遲低。但會有短暫的資料不一致:資料跨雲寫進 GCP 的快取後,需要一陣子才會同步回 AWS 的快取,這會造成讀取到錯誤的舊資料。但拿常用的快取服務 Redis 來說,如果兩個資料中心距離很近,兩座 Redis 之間資料差距遠小於一秒,對 17 Media 的應用場景來說可忽略不計。

在 SRE 眼中,這個策略略有風險:17 Media 的 DNS 服務直接面向用戶,切換 DNS weighting 的時候會有 DNS cache poisoning 的問題;但這可以透過後續架構微調解決(例如前面再搭一層 load balancer),所以目前較不擔心。

微服務

在之前提到,這次 17 Media 會成為重災戶,其中一個原因是主要功能集中在少數服務,解決方法就是將單體服務(monolith)分拆成微服務(micro services)。但要怎麼拆就是個大學問,拆分方式需要視團隊組成而決定,後端團隊正在討論不同拆分的可行性。

系統跨足多地區,更新訊息包含中英日等多國語言

在這次故障的處理過程中,讓我感到產品團隊的責任越來越重大。目前團隊支撐的已經是個多元的直播產品,平台上包括早期用戶大多來自台灣,系統故障的內部 Slack 訊息只需準備中文;但現在已經跨足許多地區,這次故障時內部更新訊息包含中英日等語言。

以上改進事項正在進行中,有興趣歡迎一起討論。也歡迎你參與進化的過程,17 Media 工程團隊持續徵才中,包括 backend 以及 SRE。在 17 你可以遇到頂尖好手,這是一個多元化 app 的壯大旅程,歡迎加入我們!(職缺列表

(本文經 Roy Chung-Cheng Lou 授權轉載,並同意 TechOrange 編寫導讀與修訂標題,原文標題為 〈從 GCP 故障看 17 Media 工程團隊的災難應變 〉。首圖來源:Flickr CC Licensed)

更多關於工程師的技術

在家從零自學沒問題!22 歲數據工程師大推 3 本必看的機器學習入門書
會寫程式只是基本,「懂得刪程式碼」的工程師才是企業要的人才!
超省時 GitHub 新功能!點選函數就能看定義,記憶差的工程師不用再苦苦尋找了

點關鍵字看更多相關文章: