美團(tuán)Serverless產(chǎn)品落地與演進(jìn)
今天繼續(xù)大廠中間件系列,本期介紹的是美團(tuán)的Serverless產(chǎn)品。
美團(tuán)的Serverless產(chǎn)品叫做Nest,誕生于2019年。其產(chǎn)品發(fā)展經(jīng)歷了三個(gè)階段:
第一階段:快速驗(yàn)證Serverless產(chǎn)品基本能力,涵蓋構(gòu)建、發(fā)布、彈性伸縮、對(duì)接觸發(fā)源、函數(shù)執(zhí)行等,通過一些業(yè)務(wù)試點(diǎn)接入驗(yàn)證。
第二階段:保障產(chǎn)品穩(wěn)定性問題,包括優(yōu)化彈性伸縮穩(wěn)定性、冷啟動(dòng)速度、系統(tǒng)與業(yè)務(wù)可用性、容器穩(wěn)定性等工作。
第三階段:完善生態(tài),如研發(fā)工具、上下游產(chǎn)品打通、平臺(tái)開放能力補(bǔ)齊等,促進(jìn)產(chǎn)品業(yè)務(wù)收益。
Serverle服務(wù)主要包含兩部分:FaaS、BaaS。
FaaS:運(yùn)行在一個(gè)無狀態(tài)的計(jì)算容器中d 函數(shù)服務(wù),通常由事件觸發(fā)、生命周期很短,由三方托管。BaaS:建立在云服務(wù)生態(tài)之上的后端服務(wù),美團(tuán)內(nèi)部的中間件屬于這一范疇。由于彈性伸縮是Serverless平臺(tái)的必備能力,因此很多Serverless產(chǎn)品都是建立在K8S之上實(shí)現(xiàn)的,所以Nest也是將K8S作為基礎(chǔ)設(shè)施。
在云原生領(lǐng)域Go是開發(fā)主流,美團(tuán)主流語言是Java,所以Nest還是基于Java的。
針對(duì)于函數(shù)之間的隔離性,NodeJs語言函數(shù)使用的是不同進(jìn)程的隔離方式,Java采用的是類加載隔離,主要是Java進(jìn)程占用內(nèi)存空間較NodeJs進(jìn)程會(huì)大很多。
Nest上函數(shù)執(zhí)行的過程:流量由EventTrigger觸發(fā)到Nest平臺(tái)(FaaS平臺(tái)),Nest平臺(tái)根據(jù)流量特征路由到具體函數(shù)實(shí)例,觸發(fā)函數(shù)執(zhí)行,函數(shù)內(nèi)部執(zhí)行可以調(diào)用公司各個(gè)BaaS服務(wù),最終函數(shù)執(zhí)行完成,返回結(jié)果。
EventTrigger(事件觸發(fā)源)包括:Nginx、應(yīng)用網(wǎng)關(guān)、定時(shí)任務(wù)、消息隊(duì)列、RPC調(diào)用等。
Nest架構(gòu)包含以下幾個(gè)部分:
事件網(wǎng)關(guān):負(fù)責(zé)對(duì)接外部事件源流量,路由到函數(shù)實(shí)例上,同時(shí)負(fù)責(zé)統(tǒng)計(jì)各個(gè)函數(shù)進(jìn)出流量信息,為彈性伸縮提供決策數(shù)據(jù)支撐。
彈性伸縮:負(fù)責(zé)函數(shù)實(shí)例的彈性伸縮,根據(jù)運(yùn)行的流量數(shù)據(jù)及實(shí)例閾值配置計(jì)算所需函數(shù)實(shí)例數(shù),借助K8S資源控制能力,調(diào)整實(shí)例數(shù)。
控制器:負(fù)責(zé)K8S CRD控制邏輯實(shí)現(xiàn)。
函數(shù)實(shí)例:函數(shù)運(yùn)行實(shí)例,執(zhí)行函數(shù)代碼邏輯。
治理平臺(tái):面向用戶使用的平臺(tái),負(fù)責(zé)函數(shù)構(gòu)建、版本、發(fā)布、函數(shù)元數(shù)據(jù)管理等。

函數(shù)在Nest平臺(tái)執(zhí)行的流程包括:構(gòu)建、版本、部署、伸縮。
構(gòu)建:開發(fā)人員編寫完代碼和配置依賴后,生成鏡像或可執(zhí)行文件。
版本:構(gòu)建生成的鏡像或可執(zhí)行文件發(fā)布成一個(gè)不可變的版本。
部署:將版本發(fā)布,完成部署。
伸縮:根據(jù)函數(shù)實(shí)例流量及負(fù)載信息,進(jìn)行實(shí)例彈性伸縮。
區(qū)別于傳統(tǒng)部署過程,Serverless屏蔽了機(jī)器的概念,抽象了機(jī)器分組的概念。
一個(gè)機(jī)器分組由:SET(單元化架構(gòu)標(biāo)識(shí),機(jī)器帶有)+ 泳道(測(cè)試環(huán)境隔離標(biāo)識(shí),機(jī)器帶有)+ 區(qū)域(上海、北京等)組成。
用戶部署時(shí),只需在相應(yīng)分組上操作,不涉及對(duì)具體機(jī)器的操作。

函數(shù)是由事件觸發(fā)的,函數(shù)觸發(fā)流程如下:
流量進(jìn)入:先向事件源注冊(cè)事件網(wǎng)關(guān)信息,將流量引入到網(wǎng)關(guān),比如MQ事件源,通過注冊(cè)MQ消費(fèi)組,引入MQ的流量到事件網(wǎng)關(guān)。
流量適配:事件網(wǎng)關(guān)對(duì)事件源進(jìn)入的流量適配對(duì)接。
函數(shù)發(fā)現(xiàn):依據(jù)函數(shù)元數(shù)據(jù)(函數(shù)實(shí)例信息、配置信息等)獲取,類似于服務(wù)發(fā)現(xiàn)過程,事件網(wǎng)關(guān)將接收到的流量發(fā)送到具體函數(shù)實(shí)例,本質(zhì)是獲取K8S中內(nèi)置資源或CRD資源存儲(chǔ)的信息。
函數(shù)路由:事件流量路由的過程,將流量路由到特定的函數(shù)實(shí)例上。為支持傳統(tǒng)路由(SET、泳道、區(qū)域路由)和版本路由能力,采用多層路由,第一層路由到分組(SET、泳道、區(qū)域路由),第二層路由到具體版本。基于版本路由可以輕易支持金絲雀、藍(lán)綠發(fā)布。

FaaS服務(wù)執(zhí)行的是代碼片段,自身不能單獨(dú)執(zhí)行。函數(shù)的執(zhí)行首先需要的是函數(shù)環(huán)境。
Nest基于K8S實(shí)現(xiàn),函數(shù)需要運(yùn)行在K8S的Pod內(nèi),Pod內(nèi)是容器,容器里是運(yùn)行時(shí),運(yùn)行時(shí)是函數(shù)流量接收的入口。

不同流量下需要不同的實(shí)例數(shù),解決彈性伸縮需要考慮:什么時(shí)候伸縮、伸縮多少、伸縮速度要快。
伸縮時(shí)機(jī):根據(jù)流量Metrics實(shí)時(shí)計(jì)算函數(shù)期望實(shí)例數(shù),進(jìn)行擴(kuò)縮。流量的Metrics數(shù)據(jù)來源于事件網(wǎng)關(guān),主要統(tǒng)計(jì)函數(shù)并發(fā)度指標(biāo),彈性伸縮組件每秒鐘主動(dòng)從事件網(wǎng)關(guān)獲取一次Metrics數(shù)據(jù)。
伸縮算法:并發(fā)度 / 單機(jī)實(shí)例閾值 = 期望實(shí)例數(shù),收集Metrics數(shù)據(jù)及業(yè)務(wù)配置的閾值,通過算法計(jì)算出期望的實(shí)例數(shù),然后通過K8S接口設(shè)置具體實(shí)例數(shù)。
伸縮速度:主要取決于冷啟動(dòng)時(shí)間。
函數(shù)實(shí)例還支持0實(shí)例,當(dāng)函數(shù)無實(shí)例時(shí),事件網(wǎng)關(guān)會(huì)將函數(shù)請(qǐng)求流量緩存下來,同時(shí)立即通過流量Metrics驅(qū)動(dòng)彈性伸縮組件進(jìn)行擴(kuò)容,等實(shí)例啟動(dòng)完成后,將緩存的請(qǐng)求重試到擴(kuò)容的實(shí)例上觸發(fā)函數(shù)執(zhí)行。

當(dāng)前技術(shù)做不到毫秒級(jí)別的伸縮,因此在線上實(shí)際場(chǎng)景中,彈性伸縮不及預(yù)期時(shí),會(huì)導(dǎo)致服務(wù)穩(wěn)定性問題。
實(shí)例伸縮頻繁問題:在伸縮組件中增加了統(tǒng)計(jì)數(shù)據(jù)滑動(dòng)窗口,通過計(jì)算均值來平滑指標(biāo),同時(shí)延時(shí)縮容,同時(shí)增加了QPS指標(biāo)伸縮策略,QPS指標(biāo)和并發(fā)度更穩(wěn)定一些。
擴(kuò)容來不及問題:系統(tǒng)支持了提前擴(kuò)容方式,當(dāng)達(dá)到實(shí)例閾值的70%就擴(kuò)容,可以比較好的緩解這個(gè)問題,同時(shí)結(jié)合多個(gè)指標(biāo)(并發(fā)度、QPS、CPU、內(nèi)存)伸縮。
函數(shù)實(shí)例擴(kuò)容之后有一段冷啟動(dòng)時(shí)間,包括了資源調(diào)度、鏡像/代碼下載、啟動(dòng)容器、運(yùn)行時(shí)初始化、代碼初始化等工作,這些工作完成之后才可以執(zhí)行函數(shù),冷啟動(dòng)時(shí)間決定了彈性伸縮的速度。

平臺(tái)還需要具備高可用能力,包括平臺(tái)本身高可用及宿主在平臺(tái)之上的函數(shù)的高可用。
平臺(tái)高可用從架構(gòu)層、服務(wù)層、監(jiān)控運(yùn)營層、業(yè)務(wù)視角層保障。
架構(gòu)層對(duì)于有狀態(tài)的服務(wù)(彈性伸縮模塊),采用了主從架構(gòu),當(dāng)主節(jié)點(diǎn)異常時(shí),從節(jié)點(diǎn)立即替換。
同時(shí)架構(gòu)上橫向地域隔離,K8S兩地兩集群強(qiáng)隔離,服務(wù)(事件網(wǎng)關(guān)、彈性伸縮)集群內(nèi)兩地弱隔離(上海彈性伸縮只負(fù)責(zé)上海K8S集群內(nèi)的業(yè)務(wù)彈性伸縮,事件網(wǎng)關(guān)調(diào)度兩地K8S集群)。
同時(shí)縱向上采用業(yè)務(wù)線強(qiáng)隔離,不同業(yè)務(wù)線使用不同集群,K8S層資源采用namespace實(shí)現(xiàn)業(yè)務(wù)線弱隔離。

服務(wù)層的網(wǎng)關(guān)服務(wù),由于所有函數(shù)流量都經(jīng)過事件網(wǎng)關(guān),因此事件網(wǎng)關(guān)可用性非常重要,這層支持了異步和限流,保障服務(wù)的穩(wěn)定性。
運(yùn)營監(jiān)控層是完善監(jiān)控告警、梳理核心鏈路推動(dòng)相關(guān)依賴方進(jìn)行治理,定期梳理SOP通過故障演練發(fā)現(xiàn)系統(tǒng)隱患。
業(yè)務(wù)視角層需要在線不間斷巡檢,模擬用戶函數(shù)請(qǐng)求流量,實(shí)時(shí)監(jiān)測(cè)系統(tǒng)核心鏈路是否正常。
函數(shù)高可用包括:業(yè)務(wù)降級(jí)、限流能力,當(dāng)后端函數(shù)故障時(shí),可通過配置降級(jí),返回降級(jí)結(jié)果。針對(duì)異常的函數(shù)流量,平臺(tái)限制其流量,防止后端函數(shù)實(shí)例被異常流量打掛。
當(dāng)實(shí)例異常時(shí),平臺(tái)自動(dòng)隔離該實(shí)例并擴(kuò)容新實(shí)例,平臺(tái)支持業(yè)務(wù)多區(qū)部署,將函數(shù)實(shí)例打散到不同機(jī)房。當(dāng)宿主機(jī)、機(jī)房、地區(qū)故障時(shí),會(huì)立即在可用宿主機(jī)、可用機(jī)房、可用區(qū)重建實(shí)例。平臺(tái)同時(shí)提供函數(shù)運(yùn)行時(shí)的時(shí)延、成功率、實(shí)例伸縮、請(qǐng)求數(shù)等多種指標(biāo)監(jiān)控,當(dāng)指標(biāo)不符合預(yù)期時(shí),自動(dòng)告警給負(fù)責(zé)人。

為提高新擴(kuò)容實(shí)例的穩(wěn)定性,將運(yùn)維相關(guān)的Agent放到Sidecar容器中,業(yè)務(wù)進(jìn)程單獨(dú)在容器中,通過這種容器隔離機(jī)制,解決了異常CPU、資源搶占問題,保障了業(yè)務(wù)穩(wěn)定性。

Serverless產(chǎn)品少不了開發(fā)工具,一般是WebIDE,支持在線一站式代碼修改、構(gòu)建、發(fā)布、測(cè)試。
同時(shí)平臺(tái)還完善了函數(shù)的編排,通過OpenAPI開放了資源池,讓業(yè)務(wù)自定義資源池。
Serverless收益還是主要圍繞于彈性伸縮的降低成本,提高資源利用率。比如高頻業(yè)務(wù)可以收益40%~50%,低頻業(yè)務(wù)通過合并部署,極大降低成本。
提升研發(fā)效率上,讓研發(fā)同學(xué)聚焦于業(yè)務(wù)本身,快速研發(fā)、測(cè)試,依托于完備的在線日志與監(jiān)控能力,提高了需求上線速度。
未來工作圍繞于將傳統(tǒng)的Java微服務(wù)Serverless化,考慮結(jié)合服務(wù)治理體系,如ServiceMesh和Serverless相結(jié)合,Mesh為Serverless提供伸縮指標(biāo),在伸縮過程中基于Mesh實(shí)現(xiàn)精準(zhǔn)的流量調(diào)配。
