每個(gè)接觸過(guò) Serverless 的人應(yīng)該都聽(tīng)過(guò)這樣一句話(huà):“Serverless 是無(wú)狀態(tài)的?!鳖櫭剂x,無(wú)狀態(tài)就是沒(méi)有狀態(tài),我們無(wú)法使用它來(lái)保存狀態(tài),用完即銷(xiāo)毀。那么,在 Serverless 架構(gòu)下(這里特指 FaaS 平臺(tái)),函數(shù)的前一次運(yùn)行和這一次運(yùn)行,不會(huì)有聯(lián)系呢?前一次運(yùn)行的結(jié)果也不會(huì)影響這一次呢?
函數(shù)的無(wú)狀態(tài)探索
首先,需要明確的是 Serverless 的關(guān)鍵特征:運(yùn)行成本更低、自動(dòng)擴(kuò)縮容、事件驅(qū)動(dòng)、無(wú)狀態(tài)性。其中,無(wú)狀態(tài)性是說(shuō)開(kāi)發(fā)者可以直接將服務(wù)業(yè)務(wù)邏輯代碼部署,運(yùn)行在第三方提供的無(wú)狀態(tài)計(jì)算容器中。
那么,前一次運(yùn)行情況是否會(huì)影響這一次呢?準(zhǔn)確來(lái)說(shuō),只有在容器沒(méi)有被復(fù)用的情況下是這樣的。但是在實(shí)際的項(xiàng)目中,為了降低冷啟動(dòng)率,提高瞬時(shí)產(chǎn)生的高并發(fā)應(yīng)對(duì)能力,往往會(huì)采用容器復(fù)用,而這可能會(huì)讓“無(wú)狀態(tài)性“變得比較復(fù)雜。
我們以騰訊云的 SCF 為例,在控制臺(tái)創(chuàng)建一個(gè)函數(shù),使用以下的代碼測(cè)試一下具體情況:

我們可以看到,通過(guò)點(diǎn)擊測(cè)試按鈕,輸出了日志:Test,接下來(lái),多次點(diǎn)擊:


可以看到,隨著我們點(diǎn)擊測(cè)試按鈕,每次都在日志準(zhǔn)確輸出了Test。接下來(lái),我們變換一下代碼:
同樣的方法,連續(xù)點(diǎn)擊三次測(cè)試,并且記錄結(jié)果:



通過(guò)這一組測(cè)試,我們發(fā)現(xiàn),這三個(gè)結(jié)果有點(diǎn)不太一樣:只有第一次請(qǐng)求的時(shí)候,執(zhí)行了這條語(yǔ)句:
為什么后幾次都沒(méi)有執(zhí)行這條語(yǔ)句呢?是沒(méi)執(zhí)行到這里?還是因?yàn)槿萜鲝?fù)用的原因,在接下來(lái)的幾次跳過(guò)了這個(gè)步驟?為什么會(huì)跳過(guò)這個(gè)步驟?為了搞清楚具體情況,我們?cè)賮?lái)做個(gè)測(cè)試:



可以看到,在第一次測(cè)試的時(shí)候,這個(gè)程序先執(zhí)行了:
執(zhí)行完成之后,tempNumber這個(gè)變量就會(huì)存在,在接下來(lái)的幾次調(diào)用中,都直接取了這個(gè)值。

也就是說(shuō),函數(shù)在復(fù)用容器的情況下被執(zhí)行(或者說(shuō)是被觸發(fā)),實(shí)際上可以認(rèn)為是已經(jīng)有一個(gè)進(jìn)程被啟動(dòng),每次觸發(fā)是通過(guò)這個(gè)進(jìn)程來(lái)調(diào)用入口方法,所以在方法之外的各種操作,實(shí)際上是冷啟動(dòng)的時(shí)候,在啟動(dòng)進(jìn)程時(shí)會(huì)被執(zhí)行。
因此,函數(shù)的無(wú)狀態(tài)性并不是前一次操作對(duì)后一次被觸發(fā)沒(méi)有影響。那么,所謂的無(wú)狀態(tài)到底指的是什么呢?
在 CNCF 發(fā)布的 Serverlss 白皮書(shū)中,是這樣描述的:Serverless 架構(gòu)通常是無(wú)狀態(tài)、不可變和短暫的。每個(gè)函數(shù)都以指定的角色和明確定義有限的資源訪問(wèn)權(quán)限運(yùn)行。什么樣的程序或者服務(wù)適合 Serverless 架構(gòu)?白皮書(shū)中是這樣表述的:無(wú)狀態(tài),短暫的,對(duì)瞬間冷啟動(dòng)時(shí)間沒(méi)有過(guò)多需求的程序適合使用 Serverless 架構(gòu)。
所以,函數(shù)的無(wú)狀態(tài)實(shí)際上可以認(rèn)為是:函數(shù)是運(yùn)行在第三方提供的無(wú)狀態(tài)計(jì)算容器中的,并且在容器無(wú)復(fù)用、存在冷啟動(dòng)的情況下,函數(shù)可以認(rèn)為是無(wú)狀態(tài);由于各個(gè)廠商的容器降低冷啟動(dòng)方案是不同的,容器復(fù)用方案也都是未公開(kāi)的,所以什么時(shí)候可能會(huì)復(fù)用容器,怎么復(fù)用也是未知的,這就要求我們函數(shù)的功能本身要保證是無(wú)狀態(tài)的。例如,在函數(shù)中,保存某些數(shù)據(jù)到緩存中,下次觸發(fā)的時(shí)候從緩存中獲得對(duì)應(yīng)內(nèi)容就是容易產(chǎn)生異常的操作,因?yàn)樵茝S商無(wú)法保證這次請(qǐng)求是否復(fù)用了已有容器,以及復(fù)用的已有容器是否就是上次進(jìn)行緩存的容器。
拓展
根據(jù)上面討論的內(nèi)容,我們可以進(jìn)行一些實(shí)踐化的應(yīng)用:
1. 通過(guò)容器復(fù)用,完成初始化操作
剛剛說(shuō)過(guò)了,如果在容器復(fù)用的前提下,在函數(shù)外面執(zhí)行的內(nèi)容是可以直接使用的,所以我們實(shí)際上是可以在外層進(jìn)行一些初始化的,例如:

以上圖的代碼為例,通過(guò)這樣的初始化,就不用每次調(diào)用函數(shù)都進(jìn)行一次數(shù)據(jù)庫(kù)的初始化 / 鏈接等,而是可以復(fù)用已有的鏈接。如果是在 main_handler 中進(jìn)行數(shù)據(jù)庫(kù)的初始化 / 鏈接,會(huì)影響函數(shù)性能,在高并發(fā)的情況下更容易把數(shù)據(jù)庫(kù)的鏈接打滿(mǎn),造成惡劣影響。
2. 小心容器復(fù)用,不要掉進(jìn)坑里
我之前寫(xiě)過(guò)一個(gè) SCF 打包 Python 依賴(lài)的小工具,運(yùn)行在 SCF 中,測(cè)試的時(shí)候是好好的,但是項(xiàng)目上線(xiàn)之后,我發(fā)現(xiàn)了一個(gè)問(wèn)題:只有冷啟動(dòng)的情況下,依賴(lài)是可以被打包的,如果出現(xiàn)容器復(fù)用的情況,就會(huì)出現(xiàn)依賴(lài)打包失敗的問(wèn)題。
經(jīng)過(guò)仔細(xì)排查才發(fā)現(xiàn),原來(lái)是因?yàn)橐粋€(gè)對(duì)象在使用完成之后未被清理,由于容器是被復(fù)用,或者說(shuō)是“這個(gè)對(duì)象也被復(fù)用了”,在執(zhí)行指定方法的時(shí)候,看到對(duì)象已存在,就會(huì)直接用這個(gè)對(duì)象,導(dǎo)致本次函數(shù)的觸發(fā)使用了上次殘留的對(duì)象,發(fā)生異常。
所以說(shuō),當(dāng)程序在云函數(shù)中連續(xù)執(zhí)行多次的時(shí)候,開(kāi)始成功后來(lái)失敗,很可能就是由于某些資源復(fù)用,導(dǎo)致程序出錯(cuò)。
3. 我就想要一種狀態(tài)
有的人在使用云函數(shù)的時(shí)候,可能真的需要有一種狀態(tài)來(lái)記錄某些事情,例如博客系統(tǒng)判斷管理員用戶(hù)是否登錄,本來(lái)可以直接放到緩存中的操作,此時(shí)不能放進(jìn)去,那應(yīng)該怎么處理,如何記錄管理員是否已經(jīng)登陸了后臺(tái),或者說(shuō)如何確定這個(gè)用戶(hù)是否為管理員?
這種情況是很常見(jiàn)的,我們可以融合兩套方案:
方案 1: 采用 Token 機(jī)制
方案 2: 采用緩存機(jī)制
所謂的采用 Token 機(jī)制和緩存機(jī)制融合方案,就是在管理員用戶(hù)登陸之后,會(huì)生成一個(gè) Token,這個(gè) Token 就記錄到數(shù)據(jù)庫(kù)中,同時(shí)這個(gè) Token 也會(huì)被寫(xiě)到緩存中。當(dāng)用戶(hù)請(qǐng)求發(fā)起后,函數(shù)會(huì)先嘗試在緩存中獲取結(jié)果,如果沒(méi)獲取到,就連接數(shù)據(jù)庫(kù)進(jìn)行獲取。
總結(jié)
Serverless 架構(gòu)可以被看成是一個(gè)新的技術(shù),一種新的框架,很多時(shí)候,我們不能用已有的態(tài)度去衡量新鮮事物。同樣,一個(gè)特性也很難直接用好壞去形容,就無(wú)狀態(tài)性來(lái)說(shuō),真的是有幾種鐘愛(ài),就有幾種迷茫。
作者介紹:
劉宇,騰訊 Serverless 團(tuán)隊(duì)后臺(tái)研發(fā)工程師。畢業(yè)于浙江大學(xué),碩士研究生學(xué)歷,曾在滴滴出行、騰訊科技做產(chǎn)品經(jīng)理,本科開(kāi)始有自主創(chuàng)業(yè)經(jīng)歷,是 Anycodes 在線(xiàn)編程的負(fù)責(zé)人(該軟件累計(jì)下載量超 100 萬(wàn)次)。目前投身于 Serverless 架構(gòu)研發(fā),著書(shū)《Serverless 架構(gòu):從原理、設(shè)計(jì)到項(xiàng)目實(shí)戰(zhàn)》,參與開(kāi)發(fā)和維護(hù)多個(gè) Serverless 組件,是活躍的 Serverless Framework 的貢獻(xiàn)者,也曾多次公開(kāi)演講和分享 Serverless 相關(guān)技術(shù)與經(jīng)驗(yàn),致力于 Serverless 的落地與項(xiàng)目上云。