优化托管于阿里云函数计算的Node.js应用 - 以Parse为例

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,内容安全 1000次 1年
对象存储 OSS,恶意文件检测 1000次 1年
简介: 介绍部署于函数计算的应用常见的问题的处理和优化

上文介绍了如何快速迁移Parse到阿里云函数计算,但是这只是一个跑起来的例子,还有一些问题需要我们优化。本文会介绍常见的优化点和方法,从方法来看适用于所有Serverless平台的应用。

Serverless的缺陷

没有任何技术形态是完美的,Serverless提供了良好的可伸缩性和并发性,提供了细粒度的资源分配,优化了成本,相对的也有难以调试等缺点。

这些问题是Serverless这种技术形态自身造成的,并不是阿里云函数计算独有的。不同的云厂商可以通过周边建设来弥补一些问题,比如阿里云函数计算的日志和监控相对比较完善,Serverless Devs工具解决了一部分调试问题。

用更传统的观点来理解Serverless的本质,可以看作扩容缩容策略极端激进的集群,而每个函数都是部署在这一个一个机器上而已。云厂商的机器特别迷你,计价单位颗粒小。而缩容策略可以将为0,扩容策略可以近乎无限大,缩容策略是固定,不可以自定义。

那么对于一个随时可能创建随时可能被销毁的机器,部署于其中的服务要面临两个方面的问题

  • 服务销毁
  • 服务启动

服务销毁时内存、文件系统的数据都丢失了。服务启动的时候需要一些必要的初始化,需要启动程序。

我们先看下销毁引起的持久化问题。

持久化改进

Parse是支持文件上传的,存储文件的FileAdapter是可以自定义的。

一般来说对于文件需求,可以直接使用阿里云对象存储OSS,一般选择标准型就可以了。


aliyun-oss-price.png

Parse官方不支持阿里云OSS,理论上可以使用parse-server-s3-adapter,但是我之前没有配置过,可以完全可以自定义,直接使用OSS官方的SDK就行了。


'use strict';
varOSS=require('ali-oss').Wrapper;
constDEFAULT_OSS_REGION="oss-cn-hangzhou";
functionrequiredOrFromEnvironment(options, key, env) {
options[key] =options[key] ||process.env[env];
if (!options[key]) {
throw`OSSAdapter requires option '${key}' or env. variable ${env}`;
    }
returnoptions;
}
functionfromEnvironmentOrDefault(options, key, env, defaultValue) {
options[key] =options[key] ||process.env[env] ||defaultValue;
returnoptions;
}
functionoptionsFromArguments(args) {
letoptions= {};
letaccessKeyOrOptions=args[0];
if (typeofaccessKeyOrOptions=='string') {
options.accessKey=accessKeyOrOptions;
options.secretKey=args[1];
options.bucket=args[2];
letotherOptions=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);
returnoptions;
}
functionOSSAdapter() {
varoptions=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;
letossOptions= {
accessKeyId: options.accessKey, accessKeySecret: options.secretKey, bucket: this._bucket, region: this._region    };
this._ossClient=newOSS(ossOptions);
this._ossClient.listBuckets().then((val) => {
varbucket=val.buckets.filter((bucket) => {
returnbucket.name===this._bucket        }).pop();
this._hasBucket=!!bucket;
    });
}
OSSAdapter.prototype.createBucket=function () {
if (this._hasBucket) {
returnPromise.resolve();
    } else {
returnthis._ossClient.putBucket(this._bucket, this._region).then(() => {
this._hasBucket=true;
if (this._directAccess) {
returnthis._ossClient.putBucketACL(this._bucket, this._region, 'public-read');
            }
returnPromise.resolve();
        }).then(() => {
returnthis._ossClient.useBucket(this._bucket, this._region);
        });
    }
};
OSSAdapter.prototype.createFile=function (filename, data, contentType) {
letoptions= {};
if (contentType) {
options.headers= {'Content-Type': contentType}
    }
returnthis.createBucket().then(() => {
returnthis._ossClient.put(this._bucketPrefix+filename, newBuffer(data), options);
    });
};
OSSAdapter.prototype.deleteFile=function (filename) {
returnthis.createBucket().then(() => {
returnthis._ossClient.delete(this._bucketPrefix+filename);
    });
};
OSSAdapter.prototype.getFileData=function (filename) {
returnthis.createBucket().then(() => {
returnthis._ossClient.get(this._bucketPrefix+filename).then((val) => {
returnPromise.resolve(val.content);
        }).catch((err) => {
returnPromise.reject(err);
        });
    });
};
OSSAdapter.prototype.getFileLocation=function (config, filename) {
varurl=this._ossClient.signatureUrl(this._bucketPrefix+filename);
url=url.replace(/^http:/, "https:");
returnurl;
};
module.exports=OSSAdapter;
module.exports.default=OSSAdapter;

这个是我正在用的adapter,可以参考使用。特别是getFileLocation,要根据自己情况使用。

Parse还有一个缓存,一般默认使用本地环境,但是考虑到Serverless的特性,这一部分还是要持久化用于加速。官方提供的RedisCacheAdapter可以直接使用。Redis集群要求不是很高,最好复用已有的,单独使用成本有点高。


启动改进


Serverless函数的生命周期问题一直是迁移的阻碍,比较明显的是异步请求丢失、优雅下线困难。阿里云函数计算对于模型有一定扩展,额外提供了一些Hook。


aliyun-function-model.png


初始化只会进行一次,preFreeze和preStop就是退出前的Hook,这三处也是同样的计费。


由于Parse也涉及到数据库连接,所以可以将数据库连接部分移动到initialize中。

除了生命周期上一般来说还有一些选择


提升内存分配:函数计算可以自行配置内存,对于部分应用(特别是有初始化扫描等)加大内存可以改进启动速度


调整框架或者平台:对于NodeJs而言,新版本普遍都有性能上的优化,选用尽可能新的NodeJs版本也可以加速启动。如果实在对时间很敏感,可能要考虑Rust等启动速度更友好的语言。


在启动函数中初始化更多的共享资源:这个其实不能解决第一次冷启动的时间,但是可以让每次call的耗时更少。


缩减包大小:对于不必要的三方库优先移除,也可以使用更精简的版本进行替换。


定时激活:这个最早在AWS Lambda上广泛使用,其实本质上是保留一个常驻实例,但是依赖的云厂商的机制。比如AWS Lambda大约30-40分钟回收之前的活跃实例。这样只需要一个定时触发器就可以进行激活操作。这个方法在所有Serverless平台都可以使用。但是需要正确处理来自HTTP触发器和Event触发器的逻辑。


参考

https://github.com/ali-sdk/ali-oss

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
4天前
|
人工智能 运维 物联网
云大使 X 函数计算 FC 专属活动上线!享返佣,一键打造 AI 应用
如今,AI 技术已经成为推动业务创新和增长的重要力量。但对于许多企业和开发者来说,如何高效、便捷地部署和管理 AI 应用仍然是一个挑战。阿里云函数计算 FC 以其免运维的特点,大大降低了 AI 应用部署的复杂性。用户无需担心底层资源的管理和运维问题,可以专注于应用的创新和开发,并且用户可以通过一键部署功能,迅速将 AI 大模型部署到云端,实现快速上线和迭代。函数计算目前推出了多种规格的云资源优惠套餐,用户可以根据实际需求灵活选择。
|
16天前
|
敏捷开发 人工智能 JavaScript
Figma-Low-Code:快速将Figma设计转换为Vue.js应用,支持低代码渲染、数据绑定
Figma-Low-Code 是一个开源项目,能够直接将 Figma 设计转换为 Vue.js 应用程序,减少设计师与开发者之间的交接时间,支持低代码渲染和数据绑定。
54 3
Figma-Low-Code:快速将Figma设计转换为Vue.js应用,支持低代码渲染、数据绑定
|
10天前
|
JavaScript 前端开发
【Vue.js】监听器功能(EventListener)的实际应用【合集】
而此次问题的核心就在于,Vue实例化的时机过早,在其所依赖的DOM结构尚未完整构建完成时就已启动挂载流程,从而导致无法找到对应的DOM元素,最终致使计算器功能出现异常,输出框错误地显示“{{current}}”,并且按钮的交互功能也完全丧失响应。为了让代码结构更为清晰,便于后续的维护与管理工作,我打算把HTML文件中标签内的JavaScript代码迁移到外部的JS文件里,随后在HTML文件中对其进行引用。
33 8
|
13天前
|
存储 人工智能 Serverless
7分钟玩转 AI 应用,函数计算一键部署 AI 生图大模型
人工智能生成图像(AI 生图)的领域中,Stable Diffusion WebUI 以其强大的算法和稳定的输出质量而闻名。它能够快速地从文本描述中生成高质量的图像,为用户提供了一个直观且高效的创作平台。而 ComfyUI 则以其用户友好的界面和高度定制化的选项所受到欢迎。ComfyUI 的灵活性和直观性使得即使是没有技术背景的用户也能轻松上手。本次技术解决方案通过函数计算一键部署热门 AI 生图大模型,凭借其按量付费、卓越弹性、快速交付能力的特点,完美实现低成本,免运维。
|
30天前
|
人工智能 Serverless API
尽享红利,Serverless构建企业AI应用方案与实践
本次课程由阿里云云原生架构师计缘分享,主题为“尽享红利,Serverless构建企业AI应用方案与实践”。课程分为四个部分:1) Serverless技术价值,介绍其发展趋势及优势;2) Serverless函数计算与AI的结合,探讨两者融合的应用场景;3) Serverless函数计算AIGC应用方案,展示具体的技术实现和客户案例;4) 业务初期如何降低使用门槛,提供新用户权益和免费资源。通过这些内容,帮助企业和开发者快速构建高效、低成本的AI应用。
74 12
|
2月前
|
存储 缓存 监控
如何使用内存监控工具来优化 Node.js 应用的性能
需要注意的是,不同的内存监控工具可能具有不同的功能和特点,在使用时需要根据具体工具的要求和操作指南进行正确使用和分析。
82 31
|
2月前
|
JavaScript 前端开发 API
深入理解Node.js事件循环及其在后端开发中的应用
本文旨在揭示Node.js的核心特性之一——事件循环,并探讨其对后端开发实践的深远影响。通过剖析事件循环的工作原理和关键组件,我们不仅能够更好地理解Node.js的非阻塞I/O模型,还能学会如何优化我们的后端应用以提高性能和响应能力。文章将结合实例分析事件循环在处理大量并发请求时的优势,以及如何避免常见的编程陷阱,从而为读者提供从理论到实践的全面指导。
|
2月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
57 3
|
4月前
|
人工智能 自然语言处理 Serverless
阿里云函数计算 x NVIDIA 加速企业 AI 应用落地
阿里云函数计算与 NVIDIA TensorRT/TensorRT-LLM 展开合作,通过结合阿里云的无缝计算体验和 NVIDIA 的高性能推理库,开发者能够以更低的成本、更高的效率完成复杂的 AI 任务,加速技术落地和应用创新。
204 13
|
5月前
|
Serverless API 异构计算
函数计算产品使用问题之修改SD模版应用的运行环境
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。