優(yōu)化托管于阿里云函數(shù)計算的Node.js應(yīng)用 - 以Parse為例
上文介紹了怎么快速搬遷Parse到阿里云函數(shù)核算,可是這只是一個跑起來的比如,還有一些問題需求咱們優(yōu)化。本文會介紹常見的優(yōu)化點和辦法,從辦法來看適用于一切Serverless渠道的應(yīng)用。
Serverless的缺陷
沒有任何技能形狀是完美的,Serverless供給了杰出的可伸縮性和并發(fā)性,供給了細粒度的資源分配,優(yōu)化了成本,相對的也有難以調(diào)試等缺點。
這些問題是Serverless這種技能形狀本身形成的,并不是阿里云函數(shù)核算獨有的。不同的云廠商能夠經(jīng)過周邊建設(shè)來補償一些問題,比如阿里云函數(shù)核算的日志和監(jiān)控相對比較完善,Serverless Devs工具處理了一部分調(diào)試問題。
用更傳統(tǒng)的觀點來了解Serverless的本質(zhì),能夠看作擴容縮容戰(zhàn)略極端激進的集群,而每個函數(shù)都是布置在這一個一個機器上罷了。云廠商的機器特別迷你,計價單位顆粒小。而縮容戰(zhàn)略能夠?qū)?,擴容戰(zhàn)略能夠近乎無限大,縮容戰(zhàn)略是固定,不能夠自定義。
那么對于一個隨時可能創(chuàng)建隨時可能被毀掉的機器,布置于其間的服務(wù)要面臨兩個方面的問題
- 服務(wù)毀掉
- 服務(wù)發(fā)動
服務(wù)毀掉時內(nèi)存、文件體系的數(shù)據(jù)都丟失了。服務(wù)發(fā)動的時分需求一些必要的初始化,需求發(fā)動程序。
咱們先看下毀掉引起的耐久化問題。
耐久化改善
Parse是支撐文件上傳的,存儲文件的FileAdapter是能夠自定義的。
一般來說對于文件需求,能夠直接運用阿里云目標存儲OSS,一般挑選標準型就能夠了。
Parse官方不支撐阿里云OSS,理論上能夠運用parse-server-s3-adapter,可是我之前沒有配置過,能夠完全能夠自定義,直接運用OSS官方的SDK就行了。
'use strict'; var OSS = require('ali-oss').Wrapper; const DEFAULT_OSS_REGION = "oss-cn-hangzhou"; function requiredOrFromEnvironment(options, key, env) { options[key] = options[key] || process.env[env]; if (!options[key]) { throw `OSSAdapter requires option '${key}' or env. variable ${env}`; } return options; } function fromEnvironmentOrDefault(options, key, env, defaultValue) { options[key] = options[key] || process.env[env] || defaultValue; return options; } function optionsFromArguments(args) { let options = {}; let accessKeyOrOptions = args[0]; if (typeof accessKeyOrOptions == 'string') { options.accessKey = accessKeyOrOptions; options.secretKey = args[1]; options.bucket = args[2]; let otherOptions = args[3]; if (otherOptions) { options.bucketPrefix = otherOptions.bucketPrefix; options.region = otherOptions.region; options.directAccess = otherOptions.directAccess; options.baseUrl = otherOptions.baseUrl; options.baseUrlDirect = otherOptions.baseUrlDirect; } } else { options = accessKeyOrOptions || {}; } options = requiredOrFromEnvironment(options, 'accessKey', 'OSS_ACCESS_KEY'); options = requiredOrFromEnvironment(options, 'secretKey', 'OSS_SECRET_KEY'); options = requiredOrFromEnvironment(options, 'bucket', 'OSS_BUCKET'); options = fromEnvironmentOrDefault(options, 'bucketPrefix', 'OSS_BUCKET_PREFIX', ''); options = fromEnvironmentOrDefault(options, 'region', 'OSS_REGION', DEFAULT_OSS_REGION); options = fromEnvironmentOrDefault(options, 'directAccess', 'OSS_DIRECT_ACCESS', false); options = fromEnvironmentOrDefault(options, 'baseUrl', 'OSS_BASE_URL', null); options = fromEnvironmentOrDefault(options, 'baseUrlDirect', 'OSS_BASE_URL_DIRECT', false); return options; } function OSSAdapter() { var options = optionsFromArguments(arguments); this._region = options.region; this._bucket = options.bucket; this._bucketPrefix = options.bucketPrefix; this._directAccess = options.directAccess; this._baseUrl = options.baseUrl; this._baseUrlDirect = options.baseUrlDirect; let ossOptions = { accessKeyId: options.accessKey, accessKeySecret: options.secretKey, bucket: this._bucket, region: this._region }; this._ossClient = new OSS(ossOptions); this._ossClient.listBuckets().then((val) => { var bucket = val.buckets.filter((bucket) => { return bucket.name === this._bucket }).pop(); this._hasBucket = !!bucket; }); } OSSAdapter.prototype.createBucket = function () { if (this._hasBucket) { return Promise.resolve(); } else { return this._ossClient.putBucket(this._bucket, this._region).then(() => { this._hasBucket = true; if (this._directAccess) { return this._ossClient.putBucketACL(this._bucket, this._region, 'public-read'); } return Promise.resolve(); }).then(() => { return this._ossClient.useBucket(this._bucket, this._region); }); } }; OSSAdapter.prototype.createFile = function (filename, data, contentType) { let options = {}; if (contentType) { options.headers = {'Content-Type': contentType} } return this.createBucket().then(() => { return this._ossClient.put(this._bucketPrefix + filename, new Buffer(data), options); }); }; OSSAdapter.prototype.deleteFile = function (filename) { return this.createBucket().then(() => { return this._ossClient.delete(this._bucketPrefix + filename); }); }; OSSAdapter.prototype.getFileData = function (filename) { return this.createBucket().then(() => { return this._ossClient.get(this._bucketPrefix + filename).then((val) => { return Promise.resolve(val.content); }).catch((err) => { return Promise.reject(err); }); }); }; OSSAdapter.prototype.getFileLocation = function (config, filename) { var url = this._ossClient.signatureUrl(this._bucketPrefix + filename); url = url.replace(/^http:/, "https:"); return url; }; module.exports = OSSAdapter; module.exports.default = OSSAdapter;
這個是我正在用的adapter,能夠參閱運用。特別是getFileLocation,要根據(jù)自己情況運用。
Parse還有一個緩存,一般默許運用本地環(huán)境,可是考慮到Serverless的特性,這一部分還是要耐久化用于加快。官方供給的RedisCacheAdapter能夠直接運用。Redis集群要求不是很高,最好復用已有的,單獨運用成本有點高。
發(fā)動改善
Serverless函數(shù)的生命周期問題一直是搬遷的阻止,比較明顯的是異步懇求丟失、高雅下線困難。阿里云函數(shù)核算對于模型有必定擴展,額定供給了一些Hook。
初始化只會進行一次,preFreeze和preStop便是退出前的Hook,這三處也是同樣的計費。
因為Parse也涉及到數(shù)據(jù)庫銜接,所以能夠?qū)?shù)據(jù)庫銜接部分移動到initialize中。
除了生命周期上一般來說還有一些挑選
提升內(nèi)存分配:函數(shù)核算能夠自行配置內(nèi)存,對于部分應(yīng)用(特別是有初始化掃描等)加大內(nèi)存能夠改善發(fā)動速度
調(diào)整結(jié)構(gòu)或者渠道:對于NodeJs而言,新版別普遍都有性能上的優(yōu)化,選用盡可能新的NodeJs版別也能夠加快發(fā)動。假如實在對時刻很靈敏,可能要考慮Rust等發(fā)動速度更友好的語言。
在發(fā)動函數(shù)中初始化更多的共享資源:這個其實不能處理第一次冷發(fā)動的時刻,可是能夠讓每次call的耗時更少。
減縮包大小:對于不必要的三方庫優(yōu)先移除,也能夠運用更精簡的版別進行替換。
定時激活:這個最早在AWS Lambda上廣泛運用,其實本質(zhì)上是保存一個常駐實例,可是依靠的云廠商的機制。比如AWS Lambda大約30-40分鐘收回之前的活躍實例。這樣只需求一個定時觸發(fā)器就能夠進行激活操作。這個辦法在一切Serverless渠道都能夠運用??墒切枨笳_處理來自HTTP觸發(fā)器和Event觸發(fā)器的邏輯。