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

本文涉及的产品
对象存储 OSS,OSS 加速器 50 GB 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

相关实践学习
函数计算部署PuLID for FLUX人像写真实现智能换颜效果
只需一张图片,生成程序员专属写真!本次实验在函数计算中内置PuLID for FLUX,您可以通过函数计算+Serverless应用中心一键部署Flux模型,快速体验超写实图像生成的魅力。
从 0 入门函数计算
在函数计算的架构中,开发者只需要编写业务代码,并监控业务运行情况就可以了。这将开发者从繁重的运维工作中解放出来,将精力投入到更有意义的开发任务上。
相关文章
|
6月前
|
人工智能 运维 Serverless
函数计算 × MSE Nacos : 轻松托管你的 MCP Server
本文将通过一个具体案例,演示如何基于 MCP Python SDK 开发一个标准的 MCP Server,并将其部署至函数计算。在不修改任何业务代码的前提下,通过控制台简单配置,即可实现该服务自动注册至 MSE Nacos 企业版,并支持后续的动态更新与统一管理。
798 76
|
6月前
|
机器学习/深度学习 人工智能 Serverless
吉利汽车携手阿里云函数计算,打造新一代 AI 座舱推理引擎
当前吉利汽车研究院人工智能团队承担了吉利汽车座舱 AI 智能化的方案建设,在和阿里云的合作中,基于星睿智算中心 2.0 的 23.5EFLOPS 强大算力,构建 AI 混合云架构,面向百万级用户的实时推理计算引入阿里云函数计算的 Serverless GPU 算力集群,共同为智能座舱的交互和娱乐功能提供大模型推理业务服务,涵盖的场景如针对模糊指令的复杂意图解析、文生图、情感 TTS 等。
|
7月前
|
消息中间件 运维 监控
爆款游戏背后:尚娱如何借助阿里云 Kafka Serverless 轻松驾驭“潮汐流量”?
阿里云 Kafka 不仅为尚娱提供了高可靠、低延迟的消息通道,更通过 Serverless 弹性架构实现了资源利用率和成本效益的双重优化,助力尚娱在快速迭代的游戏市场中实现敏捷运营、稳定交付与可持续增长。
305 66
|
7月前
|
消息中间件 存储 运维
嘉银科技基于阿里云 Kafka Serverless 提升业务弹性能力,节省成本超过 20%
云消息队列 Kafka 版 Serverless 系列凭借其秒级弹性扩展、按需付费、轻运维的优势,助力嘉银科技业务系统实现灵活扩缩容,在业务效率和成本优化上持续取得突破,保证服务的敏捷性和稳定性,并节省超过 20% 的成本。
549 57
|
7月前
|
人工智能 机器人 Serverless
安诺机器人 X 阿里云函数计算 AI 咖啡印花解决方案
当云计算遇见具身智能,AI咖啡开启零售新体验。用户通过手机生成个性化图像,云端AI快速渲染,机器人精准复刻于咖啡奶泡之上,90秒内完成一杯可饮用的艺术品。该方案融合阿里云FunctionAI生图能力与安诺机器人高精度执行系统,实现AIGC创意到实体呈现的闭环,为线下零售提供低成本、高互动、易部署的智能化升级路径,已在商场、机场、展馆等场景落地应用。
安诺机器人 X 阿里云函数计算 AI 咖啡印花解决方案
|
人工智能 运维 安全
阿里云函数计算 AgentRun 全新发布,构筑智能体时代的基础设施
阿里云推出以函数计算为核心的AgentRun平台,通过创新体系解决开发、部署、运维难题,提供全面支持,已在多个真实业务场景验证,是AI原生时代重要基础设施。
|
7月前
|
运维 监控 JavaScript
基于 Node.js 图结构的局域网设备拓扑分析算法在局域网内监控软件中的应用研究
本文探讨图结构在局域网监控系统中的应用,通过Node.js实现设备拓扑建模、路径分析与故障定位,提升网络可视化、可追溯性与运维效率,结合模拟实验验证其高效性与准确性。
391 3
|
7月前
|
人工智能 运维 安全
阿里云函数计算 AgentRun 全新发布,构筑智能体时代的基础设施
云原生应用平台 Serverless 计算负责人杨皓然在云栖大会发表主题演讲“Serverless Agent 基础设施:助力大规模 Agent 部署与运维”。本议题深入介绍了阿里云以函数计算为核心打造的 Agent 基础设施——AgentRun,阐述其如何通过创新的运行时、模型服务、网关及可观测体系,为企业构筑坚实、高效、安全的 Agent 时代基石。
|
8月前
|
运维 NoSQL Serverless
《第四纪元》玩得轻松,构建也轻松 | 阿里云云原生 API 网关、函数计算助力 IGame 快速构建轻休闲游戏
在轻休闲游戏流量波动大、生命周期短的背景下,传统架构难以应对成本与扩展挑战。本文介绍了基于阿里云函数计算 FC 和 Redis 构建的新一代服务器架构,实现弹性伸缩、成本优化与高效运维,助力轻休闲游戏快速迭代与稳定运营,提升开发效率并降低运维复杂度。
《第四纪元》玩得轻松,构建也轻松 | 阿里云云原生 API 网关、函数计算助力 IGame 快速构建轻休闲游戏
|
DataWorks 数据挖掘 Serverless
阿里云EMR Serverless StarRocks 内容合集
阿里云 EMR StarRocks 提供存算分离架构,支持实时湖仓分析,适用于多种 OLAP 场景。结合 Paimon 与 Flink,助力企业高效处理海量数据,广泛应用于游戏、教育、生活服务等领域,显著提升数据分析效率与业务响应速度。
514 0