自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Serverless 工程實(shí)踐|Serverless 應(yīng)用開發(fā)觀念的轉(zhuǎn)變

開發(fā)
在 Serverless 架構(gòu)下,雖然更多精力是關(guān)注業(yè)務(wù)代碼,但是實(shí)際上對(duì)一些配置和成本也是需要關(guān)注的,并且必要的時(shí)候還需要根據(jù)配置與成本對(duì) Serverless 應(yīng)用進(jìn)行配置和代碼優(yōu)化。

前言:在 Serverless 架構(gòu)下,雖然更多精力是關(guān)注業(yè)務(wù)代碼,但是實(shí)際上對(duì)一些配置和成本也是需要關(guān)注的,并且必要的時(shí)候還需要根據(jù)配置與成本對(duì) Serverless 應(yīng)用進(jìn)行配置和代碼優(yōu)化。

Serverless 應(yīng)用開發(fā)觀念的轉(zhuǎn)變

Serverless 架構(gòu)帶來的除了一種新的架構(gòu)、一種新的編程范式,還包括思路上的轉(zhuǎn)變,尤其是開發(fā)過程中的一些思路轉(zhuǎn)變。有人說要把 Serverless 架構(gòu)看成一種天然的分布式架構(gòu),需要用分布式架構(gòu)的思路去開發(fā) Serverless 應(yīng)用。誠然,這種說法是正確的。但是在一些情況下,Serverless 還有一些特性,所以要轉(zhuǎn)變開發(fā)觀念。

1、文件上傳方法

在傳統(tǒng) Web 框架中,上傳文件是非常簡單和便捷的,例如 Python 的 Flask 框架:

f = request.files['file']f.save('my_file_path')
但是在 Serverless 架構(gòu)下,文件卻不能直接上傳,原因如下:

一般情況下,一些云平臺(tái)的API網(wǎng)關(guān)觸發(fā)器會(huì)將二進(jìn)制文件轉(zhuǎn)換成字符串,不便直接獲取和存儲(chǔ);
一般情況下,API 網(wǎng)關(guān)與 FaaS 平臺(tái)之間傳遞的數(shù)據(jù)包有大小限制,很多平臺(tái)限制數(shù)據(jù)包大小為 6MB 以內(nèi);
FaaS 平臺(tái)大多是無狀態(tài)的,即使存儲(chǔ)到當(dāng)前實(shí)例中,也會(huì)隨著實(shí)例釋放而使文件丟失。
所以,傳統(tǒng) Web 框架中常用的上傳文件方案不太適合在 Serverless 架構(gòu)中直接使用。在 Serverless 架構(gòu)中,上傳文件的方法通常有兩種:一種是轉(zhuǎn)換為 Base64 格式后上傳,將文件持久化到對(duì)象存儲(chǔ)或者 NAS 中,但 API 網(wǎng)關(guān)與 FaaS 平臺(tái)之間傳遞的數(shù)據(jù)包有大小限制,所以此方法通常適用于上傳頭像等小文件的業(yè)務(wù)場景。

另一種上傳方法是通過對(duì)象存儲(chǔ)等平臺(tái)來上傳,因?yàn)榭蛻舳酥苯油ㄟ^密鑰等來將文件直傳到對(duì)象存儲(chǔ)是有一定風(fēng)險(xiǎn)的,所以通常是客戶端發(fā)起上傳請(qǐng)求,函數(shù)計(jì)算根據(jù)請(qǐng)求內(nèi)容進(jìn)行預(yù)簽名操作,并將預(yù)簽名地址返給客戶端,客戶端再使用指定的方法上傳,上傳完成之后,通過對(duì)象存儲(chǔ)觸發(fā)器等來對(duì)上傳結(jié)果進(jìn)行更新等,如下圖所示。

在 Serverless 架構(gòu)下文件上傳文件示例

以阿里云函數(shù)計(jì)算為例,針對(duì)上述兩種常見的上傳方法通過 Bottle 來實(shí)現(xiàn)。在函數(shù)計(jì)算中,先初始化對(duì)象存儲(chǔ)相關(guān)的對(duì)象等:

初始化對(duì)象存儲(chǔ)相關(guān)的對(duì)象等:

  1. AccessKey = {   "id"'',   "secret"''}OSSConf = {    'endPoint''oss-cn-hangzhou.aliyuncs.com',    'bucketName''bucketName',    'objectSignUrlTimeOut'60}#獲取/上傳文件到OSS的臨時(shí)地址auth = oss2.Auth(AccessKey['id'], AccessKey['secret'])bucket = oss2.Bucket(auth, OSSConf['endPoint'], OSSConf['bucketName'])#對(duì)象存儲(chǔ)操作getUrl = lambda object, method: bucket.sign_url(method, object, OSSConf['object    SignUrlTimeOut'])getSignUrl = lambda object: getUrl(object, "GET")putSignUrl = lambda object: getUrl(object, "PUT")#獲取隨機(jī)字符串randomStr = lambda len: "".join(random.sample('abcdefghijklqrstuvwxyz123456789    ABCDEFGZSA' * 100, len)) 

第一種上傳方法,通過 Base64 上傳之后,將文件持久化到對(duì)象存儲(chǔ):

  1. #文件上傳# URI: /file/upload# Method: POST@bottle.route('/file/upload'"POST")def postFileUpload():    try:    pictureBase64 = bottle.request.GET.get('picture''').split("base64,")[1]    object = randomStr(100)    with open('/tmp/%s' % object, 'wb') as f:        f.write(base64.b64decode(pictureBase64))        bucket.put_object_from_file(object, '/tmp/%s' % object)        return response({        "status"'ok',        })    except Exception as e:    print("Error: ", e)    return response(ERROR['SystemError'], 'SystemError'

第二種上傳方法,獲取預(yù)簽名的對(duì)象存儲(chǔ)地址,再在客戶端發(fā)起上傳請(qǐng)求,直傳到對(duì)象存儲(chǔ):

  1. #獲取文件上傳地址# URI: /file/upload/url# Method: GET@bottle.route('/file/upload/url'"GET")def getFileUploadUrl():    try:        object = randomStr(100)        return response({                    "upload": putSignUrl(object),                "download"'https://download.xshu.cn/%s' % (object)             })         except Exception as e:           print("Error: ", e)              return response(ERROR['SystemError'], 'SystemError'

HTML 部分:

  1. <div style="width: 70%">      <div style="text-align: center">           <h3>Web端上傳文件</h3>      </div>      <hr>      <div>          <p>                  方案1:上傳到函數(shù)計(jì)算進(jìn)行處理再轉(zhuǎn)存到對(duì)象存儲(chǔ),這種方法比較直觀,問題是 FaaS 平臺(tái)與 API 網(wǎng)關(guān)處有數(shù)據(jù)包大小上限,而且對(duì)二進(jìn)制文件處理并不好。              </p>          <input type="file" name="file" id="fileFc"/>           <input type="button" onclick="UpladFileFC()" value="上傳"/>       </div>        <hr>       <div>         <p>                        方案2:直接上傳到對(duì)象存儲(chǔ)。流程是先從函數(shù)計(jì)算獲得臨時(shí)地址并進(jìn)行數(shù)據(jù)存儲(chǔ)(例如將文件信息存到 Redis 等),然后再從客戶端將文件上傳到對(duì)象存儲(chǔ),之后通過對(duì)象存儲(chǔ)觸發(fā)器觸發(fā)函數(shù),從存儲(chǔ)系統(tǒng)(例如已經(jīng)存儲(chǔ)到Redis)讀取到信息,再對(duì)圖像進(jìn)行處理。          </p>             <input type="file" name="file" id="fileOss"/>          <input type="button" onclick="UpladFileOSS()" value="上傳"/>      </div></div> 

通過 Base64 上傳的客戶端 JavaScript 實(shí)現(xiàn):

  1. function UpladFileFC() {      const oFReader = new FileReader();      oFReader.readAsDataURL(document.getElementById("fileFc").files[0]);       oFReader.onload = function (oFREvent) {          const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new                    ActiveXObject("Microsoft.XMLHTTP"))           xmlhttp.onreadystatechange = function () {                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {                            alert(xmlhttp.responseText)            }              }                const url = "https://domain.com/file/upload"           xmlhttp.open("POST", url, true);              xmlhttp.setRequestHeader("Content-type""application/json");               xmlhttp.send(JSON.stringify({                   picture: oFREvent.target.result             }));     }} 

 客戶端通過預(yù)簽名地址,直傳到對(duì)象存儲(chǔ)的客戶端 JavaScript 實(shí)現(xiàn):

  1. function doUpload(bodyUrl) {      const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new Active               XObject("Microsoft.XMLHTTP"));        xmlhttp.open("PUT", bodyUrl, true);      xmlhttp.onload = function () {           alert(xmlhttp.responseText)      };       xmlhttp.send(document.getElementById("fileOss").files[0]);    }        function UpladFileOSS() {    const xmlhttp = window.XMLHttpRequest ? (new XMLHttpRequest()) : (new Active        XObject("Microsoft.XMLHTTP"))    xmlhttp.onreadystatechange = function () {        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {                        const body = JSON.parse(xmlhttp.responseText)                     if (body['url']) {                  doUpload(body['url'])               }        }        }        const getUploadUrl = 'https://domain.com/file/upload/url'       xmlhttp.open("POST", getUploadUrl, true);     xmlhttp.setRequestHeader("Content-type""application/json");     xmlhttp.send();} 

整體效果如圖中所示。

Serverless 架構(gòu)下文件上傳實(shí)驗(yàn) Web 端效果

此時(shí),我們可以在當(dāng)前頁面進(jìn)行不同類型的文件上傳方案實(shí)驗(yàn)。

2、文件讀寫與持久化方法

應(yīng)用在執(zhí)行過程中,可能會(huì)涉及文件的讀寫操作,或者是一些文件的持久化操作。在傳統(tǒng)的云主機(jī)模式下,可以直接讀寫文件,或者將文件在某個(gè)目錄下持久化,但是在 Serverless 架構(gòu)下并不是這樣的。

由于 FaaS 平臺(tái)是無狀態(tài)的,并且用過之后會(huì)被銷毀,因此文件并不能直接持久化在實(shí)例中,但可以持久化到其他的服務(wù)中,例如對(duì)象存儲(chǔ)、NAS 等。

同時(shí),在不配置 NAS 的情況下,F(xiàn)aaS 平臺(tái)通常情況下只具備 /tmp 目錄可寫權(quán)限,所以部分臨時(shí)文件可以緩存在 /tmp 文件夾下。

3、慎用部分 Web 框架的特性

(1) 異步

函數(shù)計(jì)算是請(qǐng)求級(jí)別的隔離,所以可以認(rèn)為這個(gè)請(qǐng)求結(jié)束了,實(shí)例就有可能進(jìn)入一個(gè)靜默狀態(tài)。而在函數(shù)計(jì)算中,API 網(wǎng)關(guān)觸發(fā)器通常是同步調(diào)用(以阿里云函數(shù)計(jì)算為例,通常只在定時(shí)觸發(fā)器、OSS 事件觸發(fā)器、MNS 主題觸發(fā)器和 IoT 觸發(fā)器等幾種情況下是異步觸發(fā))。

這就意味著當(dāng) API 網(wǎng)關(guān)將結(jié)果返給客戶端的時(shí)候,整個(gè)函數(shù)就會(huì)進(jìn)入靜默狀態(tài),或者被銷毀,而不是繼續(xù)執(zhí)行完異步方法。所以通常情況下像 Tornado 等框架就很難在 Serverless 架構(gòu)下發(fā)揮其異步的作用。當(dāng)然,如果使用者需要異步能力,可以參考云廠商所提供的異步方法。

以阿里云函數(shù)計(jì)算為例,阿里云函數(shù)計(jì)算為用戶提供了一種異步調(diào)用能力。當(dāng)函數(shù)的異步調(diào)用被觸發(fā)后,函數(shù)計(jì)算會(huì)將觸發(fā)事件放入內(nèi)部隊(duì)列,并返回請(qǐng)求 ID,而不會(huì)返回具體的調(diào)用情況及函數(shù)執(zhí)行狀態(tài)。如果用戶希望獲得異步調(diào)用的結(jié)果,可以通過配置異步調(diào)用目標(biāo)來實(shí)現(xiàn),如圖所示。

函數(shù)異步功能原理簡圖

(2) 定時(shí)任務(wù)

在 Serverless 架構(gòu)下,應(yīng)用一旦完成當(dāng)前請(qǐng)求,就會(huì)進(jìn)入靜默狀態(tài),甚至實(shí)例會(huì)被銷毀,這就導(dǎo)致一些自帶定時(shí)任務(wù)的框架沒有辦法正常執(zhí)行定時(shí)任務(wù)。函數(shù)計(jì)算通常是由事件觸發(fā),不會(huì)自主定時(shí)啟動(dòng)。例如 Egg 項(xiàng)目中設(shè)定了一個(gè)定時(shí)任務(wù),但是在實(shí)際的函數(shù)計(jì)算中如果沒有通過觸發(fā)器觸發(fā)該函數(shù),該函數(shù)不會(huì)被觸發(fā),也不會(huì)從內(nèi)部自動(dòng)啟動(dòng)來執(zhí)行定時(shí)任務(wù),此時(shí)可以使用定時(shí)觸發(fā)器,通過定時(shí)觸發(fā)器觸發(fā)指定方法來替代定時(shí)任務(wù)。

4、要注意應(yīng)用組成結(jié)構(gòu)

(1) 靜態(tài)資源與業(yè)務(wù)邏輯

在 Serverless 架構(gòu)下,靜態(tài)資源更應(yīng)該在對(duì)象存儲(chǔ)與 CDN 的加持下對(duì)外提供服務(wù),否則所有的資源都在函數(shù)中。通過函數(shù)計(jì)算對(duì)外暴露,不僅會(huì)讓函數(shù)的業(yè)務(wù)邏輯并發(fā)度降低,也會(huì)造成更多的成本。尤其是將一些已有的程序遷移到 Serverless 架構(gòu)上,例如 Wordpress 等,更要注意將靜態(tài)資源與業(yè)務(wù)邏輯進(jìn)行拆分,否則在高并發(fā)情況下,性能與成本都將會(huì)受到比較嚴(yán)峻的考驗(yàn)。

(2) 業(yè)務(wù)邏輯的拆分

在眾多云廠商中,函數(shù)的收費(fèi)標(biāo)準(zhǔn)都是依靠運(yùn)行時(shí)間、配置的內(nèi)存以及產(chǎn)生的流量收費(fèi)的。如果一個(gè)函數(shù)的內(nèi)存設(shè)置不合理,會(huì)導(dǎo)致成本成倍增加。想要保證內(nèi)存設(shè)置合理,更要保證業(yè)務(wù)邏輯結(jié)構(gòu)的可靠性。

以阿里云函數(shù)計(jì)算為例,一個(gè)應(yīng)用有兩個(gè)對(duì)外接口,其中有一個(gè)接口的內(nèi)存消耗在 128MB 以下,另一個(gè)接口的內(nèi)存消耗穩(wěn)定在 3000MB 左右。這兩個(gè)接口平均每天會(huì)被觸發(fā) 10000 次,并且時(shí)間消耗均在 100 毫秒。如果兩個(gè)接口寫到一個(gè)函數(shù)中,那么這個(gè)函數(shù)可能需要將內(nèi)存設(shè)置在 3072MB,同時(shí)用戶請(qǐng)求內(nèi)存消耗較少的接口在冷啟動(dòng)情況下難以得到較好的性能;如果兩個(gè)接口分別寫到函數(shù)中,則兩個(gè)函數(shù)內(nèi)存分別設(shè)置成 128MB 以及 3072MB 即可,如表所示。

通過上表可以明確看出合理、適當(dāng)?shù)夭鸱謽I(yè)務(wù)會(huì)在一定程度上節(jié)約成本。上面例子的成本節(jié)約近 50%。

責(zé)任編輯:梁菲 來源: 阿里云云棲號(hào)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)