莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

小莉慢品發表於2021-10-08
莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

最近這些年,以容器和函式計算為代表的雲原生技術,正用一種全新的方式改變著我們構建和部署應用。AFK(劍與遠征)在上線之前也完成了戰鬥校驗場景容器化的落地,容器為我們帶了簡易運維,彈性伸縮等好處。但在上線後,我們逐步發現容器在彈性伸縮時延上面的不足,轉而將目光聚焦到了另外一種雲原生技術:函式計算。本文結合了AFK的實踐,來分享一下兩種雲原生技術在彈性計算的使用、原理和思考。

——拉德,AFK(劍與遠征)主程

一、相關概念

什麼是彈性伸縮

系統能夠隨請求的增減,實現系統擴容,實現高可用,在業務下降的時候,能實現系統縮容,減少成本。簡而言之,是一種低成本的可擴充套件計算。

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

什麼是冷啟動

冷啟動是指一個應用從無到可以對外服務的過程。在遊戲互動領域,一般5秒以上的等待時間,玩家是無法接受的。

什麼是容器技術

Docker幾乎是容器的代名詞,Docker提供了將應用程式的程式碼、執行時、系統工具、系統庫和配置打包到一個例項中的標準方法。Kubernetes (以下簡稱K8S)是容器叢集的管理系統,可以實現容器叢集的自動化部署、自動修復,自動擴縮容、無縫應用升級等功能。

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

什麼是函式計算

函式計算又稱Faas,全稱Function as a service,是 Serverless(雲端計算的方向之一) 的子集,也是整個 Serverless 的核心。Serverless ,又稱 "無伺服器",是開發者將伺服器邏輯執行在無狀態的計算容器中,完全由第三方管理的。Faas具備細粒度呼叫,實時伸縮,無需關心底層基礎設施等特性。

什麼是戰鬥校驗

戰鬥校驗是在伺服器跑的一段戰鬥邏輯程式碼,用來驗證玩家客戶端上傳的戰鬥是否有作弊的情況。

戰鬥校驗的彈性需求

戰鬥校驗一般需要逐幀計算,CPU消耗會非常高,通常1隊v1隊的戰鬥需要n毫秒,而5隊v5隊的戰鬥則需要相應5n毫秒。以AFK國服為例,每天跨天重置任務,同時也是整個戰鬥校驗叢集的業務高峰,而每天夜裡,隨著線上人數的減少,整個戰鬥校驗叢集進入業務低谷期。兩者CPU消耗差異有10倍之多。

二、容器的彈性伸縮

容器是通過輪訓監控的指標資料,根據演算法執行排程實現自動伸縮,主要由應用層維度(Pod)和資源層維度(Node)兩部分配合排程完成。

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

應用層維度

應用層維度從應用場景來看,分為水平擴容和垂直擴容,篇幅有限,本篇介紹主流的水平擴容:

1. HPA

Horizontal Pod Autoscaling,是Kubernetes中實現POD水平自動伸縮的功能。HPA元件會每隔30s從Metrics Server等監控元件獲取CPU等監控指標,根據演算法計算出期望副本數,通知Depolyment等元件執行擴縮容。

擴縮容演算法:Desired Pods = ceil(sum(MetricValue) / Target)

舉個例子:當前HPA設定CPU閥值:70%。當前有3個Pod,CPU都是90%。那期望的Pod數量=ceil(90%*3/70%) = 5,此時HPA會進行新建2個Pod,進行水平擴容。

HPA CPU

按CPU伸縮配置比較容易,但是有個缺陷,如果毛刺帶來的負載大於100%,HPA的認知就會被限制,無法通過一次計算就得出還需要多少個POD,就需要多次調整。每次調整中間還需要間隔一個擴張冷卻週期,預設3分鐘。如下圖,Pod想從1個變成8個,需要經過3次計算週期。

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

HPA QPS

所以HPA就有了Custom metrics擴充套件,常用的是QPS。通過配置單個Pod的QPS>10,當流量操作QPS閥值時候,叢集就會自動擴容。QPS是一個潛在的約定,就是叢集的每秒的請求消耗CPU比較平均,當特定時間點某種CPU消耗較多的請求大幅增加時候,QPS無法正確衡量。導致叢集擴容的數量不能滿足業務需求。

2. CronHPA

結合上文,我們會發現HPA的擴容排程有一定時延,業務遇到毛刺時候,HPA無法及時調整Pod到業務期望數量,造成部分應用不可用。這種業務抖動會給玩家帶來非常差的體驗。為了解決這個問題,我們引入了CronHPA,有點像Linux的Crontab,根據業務之前週期性的規律,在業務高峰前10分鐘提前準備好資源,滿足高負載的需求。CronHPA跟HPA不同,HPA是官方版本提供了實現方式,CronHPA基本要依賴於開源社群或者雲廠商的實現方式。AFK所用的是阿里雲的CronHPA,這裡講一下CronHPA的演進:

現在大部分廠商的CronHPA

目前大部分雲廠商還是這種實現方式,包括之前的阿里雲,CronHPA和HPA都是直接控制Deployment,來排程應用擴縮容的,因為二者無法彼此感知,所以CronHPA擴張上來的Pod很快會被HPA回收,兩者並不相容。如下圖所示:

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

當時為了同時獲得HPA和CronHPA的特性,AFK一直保持著雙SVC的配置,分別支援HPA和CronHPA。每次和阿里雲容器同學交流,我們總是敦促對方改進。

阿里雲的新版CronHPA

終於阿里雲的CronHPA終於在2020年3月開始相容HPA了,演算法也比較有意思,這裡列一下實現方式:

① CronHPA控制的TargetRef從Deployment變成了HPA

② 擴容時候調整HPA的Min值= max(CronHPA Scale Up Replicas, Current replicas)

③ 縮容時候調整HPA的Min值= min(CronHPA Scale Down Replicas, Current Replicas)

我們可以看到,這個實現方式一直在調整的是HPA的Min值,而非當前的Replicas數量。這樣一個設計有個很巧妙的地方,比如我們的HPA是min/max:10/99999,n點-n+4點(n點為最高峰)是業務高峰。我們可以配置成n-1點50分擴容到100, n點10分(不需要等到12點)縮容到10。我們發現儘管n點10分 HPA的Min已經調整成10了,但是當前的Pod數量還是按CPU計算的,並沒有直接減少,而是隨著業務的減少慢慢向HPA的Min靠攏。這樣不需要保持整個n點-n+4點都維持在100的規模,極大減少了資源浪費。

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

在實際使用的時候,CronHPA非常適合有周期規律的應用場景,但是為了應對預估不足的流量,或者突發的流量,還是需要配置HPA作為最後的保障。

資源層維度

在資源層維度,目前主流方案是通過Cluster Autoscaler來進行節點的水平伸縮。當Pod不足時,Cluster Autoscaler安裝叢集配置的擴容例項規格,到雲廠商的公共資源池去購買例項,初始化之後,註冊到叢集中,Kube Scheduler會排程新的Pod到這個節點上。鏈路越長,越容易出錯,結果往往就是叢集彈不起來.

這裡列出一些限制:

  • 動態購買ECS:涉及的限制有庫存、按量Ecs Quota、批量建立機器雲盤限流等
  • 機器初始,安裝K8S:涉及的限制有Yum源、Metadata Server、Ram Role、內網Open Api等
  • 選擇terway網路(一種和vpc打通的網路方式):涉及的限制有Eni數量Quota、Eni併發建立限流、Ecs規格所支援的Eni網路卡數、Ram Role等。

AFK被坑過兩次:

  • 一次我們所需的擴容的六代機型,嚴重低於阿里雲公共資源池的水位線,導致Cluster Autoscaler無法購買到機器,叢集擴容失敗。因為叢集擴容是按配置的機型順序來擴容的,建議配置一些舊機型,保證水位線的充足。
  • CentOS社群Yum源許可權變動,流入到下游的阿里雲,導致彈性購買的機器無法初始化(403錯誤),也就無法加入到K8S叢集,叢集擴容失敗。阿里就此發了覆盤報告,從阿里側杜絕這個問題。

容器的冷啟動鏈路

除了鏈路太長給彈性帶來不穩定性外,整個資源層擴容時間,也是在2-3分鐘左右。為了解決伸縮時延過長的問題,社群釋出了一個新的元件,Virtual Kubelet,通過它,K8S的節點可以用其他服務來偽裝。比如阿里雲的ECI,底層使用基於Kata的安全沙箱容器,對容器執行環境進行深度優化,提供比虛擬機器更快的啟動速度,整個資源層擴容時間可以縮減到30秒以內。

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

三、函式計算的彈性

上文提到Faas是serverless的核心,我們首先糾正兩個經常誤解的名詞:

  • Serverless並不是說不需要伺服器了,可能叫server-free相對好理解一點,只是說程式設計師可以不用關心server,關注業務實現就可以了。就好比用Erlang的同學,可以不用像C++那樣手工分配和釋放記憶體了,而是交給了GC,但是記憶體還是在那。
  • 函式計算實際上跑的是個程式碼組合單元,而不是程式碼裡面的單個函式。這裡的function可以理解為功能,或者main這樣的功能函式入口可能更為妥當。


函式計算的排程

相較於K8S複雜的邏輯,函式計算對開發者來說是非常友好的,整個呼叫流程如下:

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

如圖所示,排程系統在感知有請求過來之後:

  • 如果有閒置的例項,則直接返回空閒例項,交給API Server處理請求。路徑:1->2->3->4.a->5->6->7->8->9
  • 如果沒有閒置例項,排程系統就會到FAAS維護的ECS池中去取ECS,這個時間很短,可以忽略。經過例項初始化之後,交給API Server處理請求。路徑:1->2->3→4.b1->4.b2->5->6->7->8->9。同時FAAS的ECS池會非同步地去雲廠商的公共資源池補貨。

函式計算將複雜性下沉到了基礎設施,通過監控更加細粒度的Function執行情況。客戶端請求服務,如果有例項空閒,就直接返回例項,用來計算。如果所有例項忙碌,就會非常快速的建立新的例項來承壓(沒有演算法,就是這麼暴力)。

函式計算的冷啟動鏈路

函式計算的例項的冷啟動鏈路也非常簡單,如下:

莉莉絲遊戲「劍與遠征」專案組主程:探討容器和函式計算的“彈性”

鏈路介紹:

  • 獲取ECS:函式計算自己維護了一個ECS池,所以獲取ECS時間,基本可以忽略。同時ECS池會到公共資源池非同步補貨,以此保證水位線。
  • 下載程式碼:從OSS下載使用者程式碼並解壓,一般程式碼包相對映象小太多,時間在毫秒級別。
  • 啟動容器:Docker的容器映象已經打到ECS的映象裡面了,所以這部分只需要將使用者程式碼放到容器固定目錄,建立和開始容器, 在百毫秒級別。
  • 啟動服務:執行環境的初始化,比如nodejsserver的啟動,大概百毫秒級別。
  • 服務載入依賴:主要是一些指令碼語言把庫載入記憶體的實際,如果庫比較小,這個時間也在毫秒級別。
  • 執行處理邏輯:這個就是執行時間,時間按照處理邏輯來,AFK在百毫秒到秒級別。

忽略業務邏輯執行邏輯的情況下,FAAS從排程,到獲取ECS,到服務啟動,基本在1秒+左右。

四、實際業務使用

談到彈性,我們離不開負載均衡,服務負載不均的時候,則會導致業務出現部分不可用。接下來我們以AFK為例,來看下容器和函式計算在業務使用上的差異:

負載配置

容器的SVC一般需要掛載SLB對外提供服務,負載均衡依賴於SLB的輪詢等機制。輪詢機制無法感知Pod的實際負載,可能會引起負載不均。下面列舉一下AFK在實際使用中,遇到過一些負載不均的情況:

問題:一段戰鬥校驗程式碼,在極小概率下,算出了非常大的座標,引起了邏輯死迴圈,導致卡死的程式無法正常響應SLB過來的請求,SLB無法感知Pod,依然會持續傳送新的請求,新的請求也會持續失敗。

解決辦法:引入了一個超時報警機制,在業務層做監控,記錄超時的資料,本地復現修復。

問題:之前AFK是一個Node上起的多個Pod,當時用的是Node作為SLB的伺服器單元,因為每個Node上起的Pod數量不一致,會導致每個Pod處理的請求數量不一致,負載不均。

解決辦法:將Pod直接作為SLB的伺服器單元,掛載到SLB後面。

問題:也是一個Node起多個Pod引起的問題,有些Node上起了2個Pod,導致Node的CPU超過50%,Linux的系統排程會增加額外開銷,導致Pod效能下降,處理相同請求的負載變高。

解決辦法:儘量讓Pod均勻分佈在Node上面,已保證Node的CPU基本一致。或者一個Node上部署一個Pod。

問題:機型不一致,導致的負載不均。五代機和六代機混用,六代機的效能比五代機提升15%左右,混用會導致分配至五代機上的Pod負載偏高。

解決辦法:在叢集擴容中,將六代機型放在首位,儘量保證機型不混用。

函式計算的話,開發人員就無需關注負載,排程系統會合理安排每個請求,保證及時有例項來處理請求。對於上面提到的死迴圈問題, 函式計算也貼心的提供了超時殺程式機制,避免死迴圈引起的費用問題。

伸縮配置

容器需要配置HPA和CronHPA,如下:

  • HPA配置 :CPU>70%時候,進行擴容
  • CronHPA :  n-1點50分 從10擴容到100,n點10分 從100縮容到10,滿足每天n點的業務高峰(結合上文,此處調整的是HPA的Min值,而非當前實際Pod數量)

函式計算不需要進行伸縮相關配置,依然是由排程系統根據請求,實時取出空閒例項或者建立新的例項,來處理請求。

對比可以發現,函式計算無需關心負載和伸縮相關配置,極大的減輕了開發人員的負擔,使得開發人員更加關注業務本身。

五、容器和函式計算,該怎麼選擇?

可以互相彌補

函式計算因為其極致彈性,往往可以很好的契合我們的彈性業務需求。但是,函式計算目前不是一款社群產品,本身由雲廠商實現,是個黑盒,我們也不能登入到例項上去看現場,要嚴重依賴打點和tracing來定位問題。容器呢,儘管彈性不如函式計算,但是對於我們來說卻比較自由。二者在使用上,都無需對程式碼做修改,所以AFK最終的方案是容器為主,函式計算的結合方式,在容器請求異常之後,到函式計算這邊重試(當然也建議部分請求互為主備,主動切一些流量到函式計算,只有到達一定量級,雲廠商才會有熱情的support)。這樣保證大部分請求都落在容器上,方便我們定位問題。在一些突發流量或者流量估計不足的情況下,系統又能急速擴張,滿足業務需求。兩種雲產品相結合,也同時增加了容錯性,畢竟這兩款產品的彈性都依賴一定複雜的鏈路。

甚至互相結合

目前一款叫Knative的社群產品正在公測,Knative的定位為基於 K8S的函式計算解決方案,結合了容器和函式計算。通過引入Queue-Proxy的模式已經實現了縮容到0的需求,引入KPA演算法,可以把應用層擴容帶入到了10秒以內。Knative團隊一直在努力,在解決探活,臃腫等問題後,應用層的擴容有望達到秒級,達到使用者無感。


原文:https://mp.weixin.qq.com/s/Yb3PJABUgZFQWkquYT5CBQ

相關文章