
一、相關概念
什麼是彈性伸縮
冷啟動是指一個應用從無到可以對外服務的過程。在遊戲交互領域,一般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團隊一直在努力,在解決探活,臃腫等問題後,應用層的擴容有望達到秒級,達到用戶無感。
—— 點擊下方公眾號名片,即刻關注我們 ——
——————— End———————
行業交流 /行業爆料/商務合作:
請加微信 cxx2744 或yukochan97
加入「手遊那點事」微信交流群:
請加群主微信curab_b 或yukochan97
內容投稿:
請發郵箱 helinyu@sykong.com