Serverless 掀起新的前端技术变革

最近关于 Serverless 的讨论越来越多。看似与前端关系不大的 Serverless,其实早已和前端有了颇深渊源,并且将掀起新的前端技术变革。本文主要就根据个人理解和总结,从前端开发模式的演进、基于 Serverless 的前端开发案例以及 Serverless 开发最佳实践等方面,与大家探讨 Serverless 中的前端开发模式。 ##前端开发模式的演进 首先回顾一下前端开发模式的演进,我觉得主要有四个阶段。 * 基于模板渲染的动态页面 * 基于 AJAX 的前后端分离 * 基于 Node.js 的前端工程化 * 基于 Node.js 的全栈开发 ##基于模板渲染的动态页面 在早起的互联网时代,我们的网页很简单,就是一些静态或动态的页面,主要目的是用来做信息的展示和传播。这个时候开发一个网页也很容易,主要就是通过 JSP、PHP 等技术写一些动态模板,然后通过 Web Server 将模板解析成一个个 HTML 文件,浏览器只负责渲染这些 HTML 文件。这个阶段还没有前后端的分工,通常是后端工程师顺便写了前端页面。 ##基于 AJAX 的前后端分离 2005 年 AJAX 技术的正式提出,翻开了 Web 开发的新篇章。基于 AJAX,我们可以把 Web 分为前端和后端,前端负责界面和交互,后端负责业务逻辑的处理。前后端通过接口进行数据交互。我们也不再需要在各个后端语言里面写着难以维护的 HTML。网页的复杂度也由后端的 Web Server 转向了浏览器端的 JavaScript。也正因如此,开始有了前端工程师这个职位。 ##基于 Node.js 的前端工程化 2009年 Node.js 的出现,对于前端工程师来说,也是一个历史性的时刻。随着 Node.js 一同出现的还有 CommonJS 规范和 npm 包管理机制。随后也出现了 Grunt、Gulp、Webpack 等一系列基于 Node.js 的前端开发构建工具。 在 2013 年前后,前端三大框架 React.js/Angular/Vue.js 相继发布第一个版本。我们可以从以往基于一个个页面的开发,变为基于一个个组件进行开发。开发完成后使用 webpack 等工具进行打包构建,并通过基于 Node.js 实现的命令行工具将构建结果发布上线。前端开发开始变得规范化、标准化、工程化。 ##基于 Node.js 的全栈开发 Node.js 对前端的重要意义还有,以往只能运行在浏览器中的 JavaScript 也可以运行在服务器上,前端工程师可以用自己最熟悉的语言来写服务端的代码。于是前端工程师开始使用 Node.js 做全栈开发,开始由前端工程师向全栈工程师的方向转变。这是前端主动突破自己的边界。 另一方面,前端在发展,后端也在发展。也差不多在 Node.js 诞生那个时代,后端普遍开始由巨石应用模式由微服务架构转变。这也就导致以往的前后端分工出现了分歧。随着微服务架构的兴起,后端的接口渐渐变得原子性,微服务的接口也不再直接面向页面,前端的调用变得复杂了。于是 BFF(Backend For Frontend)架构应运而生,在微服务和前端中间,加了一个 BFF 层,由 BFF 对接口进行聚合、裁剪后,再输出给前端。而 BFF 这层不是后端本质工作,且距离前端最近和前端关系最大,所以前端工程师自然而然选择了 Node.js 来实现。这也是当前 Node.js 在服务端较为广泛的应用。 ##下一代前端开发模式 可以看到,每一次前端开发模式的变化,都因某个变革性的技术而起。先是 AJAX,而后是 Node.js。那么下一个变革性的技术是什么?不言而喻,就是 Serverless。如果你想和更多 Serverless 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态#Serverless 服务中的前端解决方案 ##Serverless 简介 根据 CNCF 的定义,Serverless 是指构建和运行不需要服务器管理的应用程序的概念。(serverless-overview)

Serverless computing refers to the concept of building and running applications that do not require server management. --- CNCF

其实 Serverless 早已和前端产生了联系,只是我们可能没有感知。比如 CDN,我们把静态资源发布到 CDN 之后,就不需要关心 CDN 有多少个节点、节点如何分布,也不需要关心它如何做负载均衡、如何实现网络加速,所以 CDN 对前端来说是 Serverless。再比如对象存储,和 CDN 一样,我们只需要将文件上传到对象存储,就可以直接使用了,不需要关心它如何存取文件、如何进行权限控制,所以对象存储对前端工程师来说是 Serverless。甚至一些第三方的 API 服务,也是 Serverless,因为我们使用的时候,不需要去关心服务器。 当然,有了体感还不够,我们还是需要一个更精确的定义。从技术角度来说,Serverless 就是 FaaS 和 BaaS 的结合。 Serverless = FaaS + BaaS。
1.jpg
简单来讲,FaaS(Function as a Service) 就是一些运行函数的平台,比如阿里云的函数计算、AWS 的 Lambda 等。 BaaS(Backend as a Service)则是一些后端云服务,比如云数据库、对象存储、消息队列等。利用 BaaS,可以极大简化我们的应用开发难度。 Serverless 则可以理解为运行在 FaaS 中的,使用了 BaaS 的函数。 Serverless 的主要特点有: * 事件驱动 * 函数在 FaaS 平台中,需要通过一系列的事件来驱动函数执行。 * 无状态 * 因为每次函数执行,可能使用的都是不同的容器,无法进行内存或数据共享。如果要共享数据,则只能通过第三方服务,比如 Redis 等。 * 无运维 * 使用 Serverless 我们不需要关心服务器,不需要关心运维。这也是 Serverless 思想的核心。 * 低成本 * 使用 Serverless 成本很低,因为我们只需要为每次函数的运行付费。函数不运行,则不花钱,也不会浪费服务器资源 ##Serverless 服务中的前端解决方案架构图
2.jpg
上图是当前主要的一些 Serverless 服务,以及对应的前端解决方案。 从下往上,分别是基础设施和开发工具。 基础设施主要是一些云计算厂商提供,包括云计算平台和各种 BaaS 服务,以及运行函数的 FaaS 平台。 前端主要是 Serverless 的使用者,所以对前端来说,最重要的开发工具这一层,我们需要依赖开发工具进行 Serverless 开发、调试和部署。 ##框架(Framework) 如今还没有一个统一的 Serverless 标准,不同云计算平台提供的 Serverless 服务很可能是不一样的,这就导致我们的代码,无法平滑迁移。Serverless 框架一个主要功能是简化 Serverless 开发、部署流程,另一主要功能则是屏蔽不同 Serverless 服务中的差异,让我们的函数能够在不改动或者只改动很小一部分的情况下,在其他 Serverless 服务中也能运行。 常见的 Serverless 框架有 Serverless Framework、ZEIT Now、Apex 等。不过这些基本都是国外公司做的,国内还没有这样的平台。 ##Web IDE 和 Serverless 紧密相关的 Web IDE 主要也是各个云计算平台的 Web IDE。利用 Web IDE,我们可以很方便地在云端开发、调试函数,并且可以直接部署到对应的 FaaS 平台。这样的好处是避免了在本地安装各种开发工具、配置各种环境。常见的 Web IDE 有 AWS 的 Cloud9、阿里云的函数计算 Web IDE、腾讯云的 Cloud Studio。从体验上来说,AWS Cloud9 最好。 ##命令行工具 当然,目前最主要的开发方式还是在本地进行开发。所以在本地开发 Serverless 的命令行工具也必不可少。 命令行工具主要有两类,一类是云计算平台提供的,如 AWS 的 aws、 Azure 的 az、阿里云的 fun;还有一类是 Serverless 框架提供的,如 serverless、now。 大部分工具如 serverless、fun 等,都是用 Node.js 实现的。 下面是几个命令行工具的例子。 ##创建
# serverless
$ serverless create --template aws-nodejs --path myService
 # fun
$ fun init -n qcondemo helloworld-nodejs8
##部署
# serverless
$ serverless deploy
 # fun
$ fun deploy
##调试
# serverless
$ serverless invoke [local] --function functionName
 # fun
$ fun local invoke functionName
##应用场景 在开发工具上面一层,则是 Serverless 的一些垂直应用场景。除了使用传统的服务端开发,目前使用 Serverless 技术的还有小程序开发,未来可能还会设计物联网领域(IoT)。 ##不同 Serverless 服务的对比
3.jpg
上图从支持语言、触发器、价格等多个方面对不同 Serverless 服务进行了对比,可以发现有差异,也有共性。 比如几乎所有 Serverless 服务都支持 Node.js/Python/Java 等语言。 从支持的触发器来看,几乎所有服务也都支持 HTTP、对象存储、定时任务、消息队列等触发器。当然,这些触发器也与平台自己的后端服务相关,比如阿里云的对象存储触发器,是基于阿里云的 OSS 产品的存取等事件触发的;而 AWS 的对象存储触发器,则是基于 AWS 的 S3 的事件触发的,两个平台并不通用。这也是当前 Serverless 面临的一个问题,就是标准不统一。 从计费的角度来看,各个平台的费用基本一致。在前面也提到,Serverless 的计费是按调用次数计费。对于各个 Serverless,每个月都有 100 万次的免费调用次数,之后差不多 ¥1.3/百万次;以及 400,000 GB-s 的免费执行时间,之后 ¥0.0001108/GB-s。所以在应用体量较小的时候,使用 Serverless 是非常划算的。 #基于 Serverless 的前端开发模式 在本章节,主要以几个案例来说明基于 Serverless 的前端开发模式,以及它和以往的前端开发有什么不一样。 在开始具体的案例之前,先看一下传统开发流程。
4.png
在传统开发流程中,我们需要前端工程师写页面,后端工程师写接口。后端写完接口之后,把接口部署了,再进行前后端联调。联调完毕后再测试、上线。上线之后,还需要运维工程师对系统进行维护。整个过程涉及多个不同角色,链路较长,沟通协调也是一个问题。 而基于 Serverless,后端变得非常简单了,以往的后端应用被拆分为一个个函数,只需要写完函数并部署到 Serverless 服务即可,后续也不用关心任何服务器的运维操作。后端开发的门槛大幅度降低了。因此,只需要一个前端工程师就可以完成所有的开发工作。
5.png
当然,前端工程师基于 Serverless 去写后端,最好也需要具备一定的后端知识。涉及复杂的后端系统或者 Serverless 不适用的场景,还是需要后端开发,后端变得更靠后了。 ##基于 Serverless 的 BFF 一方面,对不同的设备需要使用不同的 API,另一方面,由于微服务导致前端接口调用的复杂,所以前端工程师开始使用 BFF 的方式,对接口进行聚合裁剪,以得到适用于前端的接口。 下面是一个通用的 BFF 架构。
6.jpg
最底层的就是各种后端微服务,最上层就是各种前端应用。在微服务和应用之前,就是通常由前端工程师开发的 BFF。 这样的架构解决了接口协调的问题,但也带来了一些新的问题。 比如针对每个设备开发一个 BFF 应用,也会面临一些重复开发的问题。而且以往前端只需要开发页面,关注于浏览器端的渲染即可,现在却需要维护各种 BFF 应用。以往前端也不需要关心并发,现在并发压力却集中到了 BFF 上。总的来说运维成本非常高,通常前端并不擅长运维。 Serverless 则可以帮我们很好的解决这些问题。基于 Serverless,我们可以使用一个个函数来实各个接口的聚合裁剪。前端向 BFF 发起的请求,就相当于是 FaaS 的一个 HTTP 触发器,触发一个函数的执行,这个函数中来实现针对该请求的业务逻辑,比如调用多个微服务获取数据,然后再将处理结果返回给前端。这样运维的压力,就由以往的 BFF Server 转向了 FaaS 服务,前端再也不用关心服务器了。
7.jpg
上图则是基于 Serverless 的 BFF 架构。为了更好的管理各种 API,我们还可以添加网关层,通过网关来管理所有 API(比如阿里云的网关),比如对 API 进行分组、分环境。基于 API 网关,前端就不直接通过 HTTP 触发器来执行函数,而是将请求发送至网关,再由网关去触发具体的函数来执行。 ##基于 Serverless 的服务端渲染 基于当下最流行的三大前端框架(React.js/Anguler/Vue.js),现在的渲染方式大部分都是客户端渲染。页面初始化的时候,只加载一个简单 HTML 以及对应的 JS 文件,再由 JS 来渲染出一个个页面。这种方式最主要的问题就是白屏时间和 SEO。 为了解决这个问题,前端又开始尝试服务端渲染。本质思想其实和最早的模板渲染是一样的。都是前端发起一个请求,后端 Server 解析出一个 HTML 文档,然后再返回给浏览器。只不过以往是 JSP、PHP 等服务端语言的模板,现在是基于 React、Vue 等实现的同构应用,这也是如今的服务端渲染方案的优势。 但服务端渲染又为前端带来了一些额外的问题:运维成本,前端需要维护用于渲染的服务器。 Serverless 最大的优点就是可以帮我们减少运维,那 Serverless 能不能用于服务端渲染呢?当然也是可以的。 传统的服务端渲染,每个请求的 path 都对应着服务端的每个路由,由该路由实现对应 path 的 HTML 文档渲染。用于渲染的服务端程序,就是这些集成了这些路由的应用。 使用 Serverless 来做服务端渲染,就是将以往的每个路由,都拆分为一个个函数,再在 FaaS 上部署对应的函数。这样用户请求的 path,对应的就是每个单独的函数。通过这种方式,就将运维操作转移到了 FaaS 平台,前端做服务端渲染,就不用再关心服务端程序的运维部署了。
8.jpg
ZEIT 的 Next.js 就对基于 Serverless 的服务端渲染做了很好的实现。下面就是一个简单的例子。 代码结构如下:
.
├── next.config.js
├── now.json
├── package.json
└── pages
    ├── about.js
    └── index.js
// next.config.js
module.exports = {
  target: 'serverless'
} 
其中 pages/about.js 和 pages/index.js 就是两个页面,在 next.config.js 配置了使用 Zeit 提供的 Serverless 服务。 然后使用 now 这个命令,就可以将代码以 Serverless 的方式部署。部署过程中,pages/about.js 和 pages/index.js 就分别转换为两个函数,负责渲染对应的页面。
9.jpg
##基于 Serverless 的小程序开发 目前国内使用 Serverless 较多的场景可能就是小程开发了。具体的实现就是小程序云开发,支付宝小程序和微信小程序都提供了云开发功能。 在传统的小程序开发中,我们需要前端工程师进行小程序端的开发;后端工程师进行服务端的开发。小程序的后端开发和其他的后端应用开发,本质是是一样的,需要关心应用的负载均衡、备份冗灾、监控报警等一些列部署运维操作。如果开发团队人很少,可能还需要前端工程师去实现服务端。 但基于云开发,就只需要让开发者关注于业务的实现,由一个前端工程师就能够完成整个应用的前后端开发。因为云开发将后端封装为了 BaaS 服务,并提供了对应的 SDK 给开发者,开发者可以像调用函数一样使用各种后端服务。应用的运维也转移到了提供云开发的服务商。
10.jpg
下面分别是使用支付宝云开发(Basement)的一些例子,函数就是定义在 FaaS 服务中的函数。 ##操作数据库
// `basement` 是一个全局变量
// 操作数据库
basement.db.collection('users')
    .insertOne({
        name: 'node',
        age: 18,
    })
    .then(() => {
        resolve({ success: true });
    })
    .catch(err => {
        reject({ success: false });
    });
##上传图片
// 上传图片
basement.file
    .uploadFile(options)
    .then((image) => {
        this.setData({
            iconUrl: image.fileUrl,
        });
    })
    .catch(console.error);
##调用函数
// 调用函数
basement.function
    .invoke('getUserInfo')
    .then((res) => { 
        this.setData({ 
            user: res.result
        });
    })
    .catch(console.error} 
##通用 Serverless 架构 基于上述几个 Serverless 开发的例子,就可以总结出一个通用的 Serverless 架构。
11.jpg
其中最底层就是实现复杂业务的后端微服务(Backend)。然后 FaaS 层通过一系列函数实现业务逻辑,并为前端直接提供服务。对于前端开发者来说,前端可以通过编写函数的方式来实现服务端的逻辑。对于后端开发者来说,后端变得更靠后了。如果业务比较较淡,FaaS 层能够实现,甚至也不需要微服务这一层了。 同时不管是在后端、FaaS 还是前端,我们都可以去调用云计算平台提供的 BaaS 服务,大大降低开发难度、减少开发成本。小程序云开发,就是直接在前端调用 BaaS 服务的例子。 #Serverless 开发最佳实践 基于 Serverless 开发模式和传统开发模式最大的不同,就是传统开发中,我们是基于应用的开发。开发完成后,我们需要对应用进行单元测试和集成测试。而基于 Serverless,开发的是一个个函数,那么我们应该如何对 Serverless 函数进行测试?Serverless 函数的测试和普通的单元测试又有什么区别? 还有一个很重要的点是,基于 Serverless 开发的应用性能如何?应该怎么去提高 Serverless 应用的性能? 本章主要就介绍一下,基于 Serverless 的函数的测试和函数的性能两个方面的最佳实践。 ##函数的测试 虽然使用 Serverless 我们可以简单地进行业务的开发,但它的特性也给我们的测试带来了一些挑战。主要有以下几个方面。 Serverless 函数是分布式的,我们不知道也无需知道函数是部署或运行在哪台机器上,所以我们需要对每个函数进行单元测试。Serverless 应用是由一组函数组成的,函数内部可能依赖了一些别的后端服务(BaaS),所以我们也需要对 Serverless 应用进行集成测试。 运行函数的 FaaS 和 BaaS 在本地也难以模拟。除此之外,不同平台提供的 FaaS 环境可能不一致,不平台提供的 BaaS 服务的 SDK 或接口也可能不一致,这不仅给我们的测试带来了一些问题,也增加了应用迁移成本。 函数的执行是由事件驱动的,驱动函数执行的事件,在本地也难以模拟。 那么如何解决这些问题呢? 根据 Mike Cohn 提出的测试金字塔,单元测试的成本最低,效率最高;UI 测试(集成)测试的成本最高,效率最低,所以我们要尽可能多的进行单元测试,从而减少集成测试。这对 Serverless 的函数测试同样适用。
12.jpg
为了能更简单对函数进行单元测试,我们需要做的就是将业务逻辑和函数依赖的 FaaS(如函数计算) 和 BaaS (如云数据库)分离。当 FaaS 和 BaaS 分离出去之后,我们就可以像编写传统的单元测试一样,对函数的业务逻辑进行测试。然后再编写集成测试,验证函数和其他服务的集成是否正常工作。 ##一个糟糕的例子 下面是一个使用 Node.js 实现的函数的例子。该函数做的事情就是,首先将用户信息存储到数据库中,然后给用户发送邮件。
const db = require('db').connect();
const mailer = require('mailer');

module.exports.saveUser = (event, context, callback) => {
  const user = {
    email: event.email,
    created_at: Date.now()
  }

  db.saveUser(user, function (err) {
    if (err) {
      callback(err);
    } else {
      mailer.sendWelcomeEmail(event.email);
      callback();
    }
  });
};
这个例子主要存在两个问题:
  1. 业务逻辑和 FaaS 耦合在一起。主要就是业务逻辑都在 saveUser 这个函数里,而 saveUser 参数的 event 和 conent 对象,是 FaaS 平台提供的。
  2. 业务逻辑和 BaaS 耦合在一起。具体来说,就是函数内使用了 db 和 mailer 这两个后端服务,测试函数必须依赖于 db 和 mailer。
##编写可测试的函数 基于将业务逻辑和函数依赖的 FaaS 和 BaaS 分离的原则,对上面的代码进行重构。
class Users {
  constructor(db, mailer) {
    this.db = db;
    this.mailer = mailer;
  }

  save(email, callback) {
    const user = {
      email: email,
      created_at: Date.now()
    }

    this.db.saveUser(user, function (err) {
      if (err) {
        callback(err);
      } else {
        this.mailer.sendWelcomeEmail(email);
        callback();
      }
  });
  }
}

module.exports = Users;
const db = require('db').connect();
const mailer = require('mailer');
const Users = require('users');

let users = new Users(db, mailer);

module.exports.saveUser = (event, context, callback) => {
  users.save(event.email, callback);
};
在重构后的代码中,我们将业务逻辑全都放在了 Users 这个类里面,Users 不依赖任何外部服务。测试的时候,我们也可以不传入真实的 db 或 mailer,而是传入模拟的服务。 下面是一个模拟 mailer 的例子。
// 模拟 mailer
const mailer = {
  sendWelcomeEmail: (email) => {
    console.log(`Send email to ${email} success!`);
  },
};
这样只要对 Users 进行充分的单元测试,就能确保业务代码如期运行。 然后再传入真实的 db 和 mailer,进行简单的集成测试,就能知道整个函数是否能够正常工作。 重构后的代码还有一个好处是方便函数的迁移。当我们想要把函数从一个平台迁移到另一个平台的时候,只需要根据不同平台提供的参数,修改一下 Users 的调用方式就可以了,而不用再去修改业务逻辑。 ##小结 综上所述,对函数进行测试,就需要牢记金字塔原则,并遵循以下原则:
  1. 将业务逻辑和函数依赖的 FaaS 和 BaaS 分离
  2. 对业务逻辑进行充分的单元测试
  3. 将函数进行集成测试验证代码是否正常工作
##函数的性能 使用 Serverless 进行开发,还有一个大家都关心的问题就是函数的性能怎么样。 对于传统的应用,我们的程序启动起来之后,就常驻在内存中;而 Serverless 函数则不是这样。 当驱动函数执行的事件到来的时候,首先需要下载代码,然后启动一个容器,在容器里面再启动一个运行环境,最后才是执行代码。前几步统称为冷启动(Cold Start)。传统的应用没有冷启动的过程。 下面是函数生命周期的示意图:
13.jpg
冷启动时间的长短,就是函数性能的关键因素。优化函数的性能,也就需要从函数生命周期的各个阶段去优化。 ##不同编程语言对冷启动时间的影响 在此之前,已经有很多人测试过不同编程语言对冷启动时间的影响,比如: * Compare coldstart time with different languages, memory and code sizes -by Yan Cui * Cold start / Warm start with AWS Lambda - by Erwan Alliaume * Serverless: Cold Start War - by Mikhail Shilkov
14.jpg
从这些测试中能够得到一些统一的结论: * 增加函数的内存可以减少冷启动时间 * C#、Java 等编程语言的能启动时间大约是 Node.js、Python 的 100 倍 基于上述结论,如果想要 Java 的冷启动时间达到 Node.js 那么小,可以为 Java 分配更大的内存。但更大的内存意味着更多的成本。 ##函数冷启动的时机 刚开始接触 Serverless 的开发者可能有一个误区,就是每次函数执行,都需要冷启动。其实并不是这样。 当第一次请求(驱动函数执行的事件)来临,成功启动运行环境并执行函数之后,运行环境会保留一段时间,以便用于下一次函数执行。这样就能减少冷启动的次数,从而缩短函数运行时间。当请求达到一个运行环境的限制时,FaaS 平台会自动扩展下一个运行环境。
15.jpg
以 AWS Lambda 为例,在执行函数之后,Lambda 会保持执行上下文一段时间,预期用于另一次 Lambda 函数调用。其效果是,服务在 Lambda 函数完成后冻结执行上下文,如果再次调用 Lambda 函数时 AWS Lambda 选择重用上下文,则解冻上下文供重用。 下面以两个小测试来说明上述内容。 我使用阿里云的函数计算实现了一个 Serverless 函数,并通过 HTTP 事件来驱动。然后使用不同并发数向函数发起 100 个请求。 首先是一个并发的情况:
16.jpg
可以看到第一个请求时间为 302ms,其他请求时间基本都在 50ms 左右。基本就能确定,第一个请求对应的函数是冷启动,剩余 99 个请求,都是热启动,直接重复利用了第一个请求的运行环境。 接下来是并发数为 10 的情况:
17.jpg
可以发现,前 10 个请求,耗时基本在 200ms-300ms,其余请求耗时在 50ms 左右。于是可以得出结论,前 10 个并发请求都是冷启动,同时启动了 10 个运行环境;后面 90 个请求都是热启动。 这也就印证了之前的结论,函数不是每次都冷启动,而是会在一定时间内复用之前的运行环境。 ##执行上下文重用 上面的结论对我们提高函数性能有什么帮助呢?当然是有的。既然运行环境能够保留,那就意味着我们能对运行环境中的执行上下文进行重复利用。 来看一个例子:
const mysql = require('mysql');

module.exports.saveUser = (event, context, callback) => {

    // 初始化数据库连接
    const connection = mysql.createConnection({ /[i] ... [/i]/ });
    connection.connect();

    connection.query('...');

};
上面例子实现的功能就是在 saveUser 函数中初始化一个数据库连接。这样的问题就是,每次函数执行的时候,都会重新初始化数据库连接,而连接数据库又是一个比较耗时的操作。显然这样对函数的性能是没有好处的。 既然在短时间内,函数的执行上下文可以重复利用,那么我们就可以将数据库连接放在函数之外:
const mysql = require('mysql');

// 初始化数据库连接
const connection = mysql.createConnection({ /[i] ... [/i]/ });
connection.connect();


module.exports.saveUser = (event, context, callback) => {

    connection.query('...');

};
这样就只有第一次运行环境启动的时候,才会初始化数据库连接。后续请求来临、执行函数的时候,就可以直接利用执行上下文中的 connection,从而提后续高函数的性能。 大部分情况下,通过牺牲一个请求的性能,换取大部分请求的性能,是完全可以够接受的。 ##给函数预热 既然函数的运行环境会保留一段时间,那么我们也可以通过主动调用函数的方式,隔一段时间就冷启动一个运行环境,这样就能使得其他正常的请求都是热启动,从而避免冷启动时间对函数性能的影响。 这是目前比较有效的方式,但也需要有一些注意的地方:
  1. 不要过于频繁调用函数,至少频率要大于 5 分钟
  2. 直接调用函数,而不是通过网关等间接调用
  3. 创建专门处理这种预热调用的函数,而不是正常业务函数
这种方案只是目前行之有效且比较黑科技的方案,可以使用,但如果你的业务允许“牺牲第一个请求的性能换取大部分性能”,那也完全不必使用该方案。 ##小结 总体而言,优化函数的性能就是优化冷启动时间。上述方案都是开发者方面的优化,当然还一方面主要是 FaaS 平台的性能优化。 总结一下上述方案,主要是以下几点:
  1. 选用 Node.js / Python 等冷启动时间短的编程语言
  2. 为函数分配合适的运行内存
  3. 执行上下文重用
  4. 为函数预热
#总结 作为前端工程师,我们一直在探讨前端的边界是什么。现在的前端开发早已不是以往的前端开发,前端不仅可以做网页,还可以做小程序,做 APP,做桌面程序,甚至做服务端。而前端之所以在不断拓展自己的边界、不断探索更多的领域,则是希望自己能够产生更大的价值。最好是用我们熟悉的工具、熟悉的方式来创造价值。 而 Serverless 架构的诞生,则可以最大程度帮助前端工程师实现自己的理想。使用 Serverless,我们不需要再过多关注服务端的运维,不需要关心我们不熟悉的领域,我们只需要专注于业务的开发、专注于产品的实现。我们需要关心的事情变少了,但我们能做的事情更多了。 Serverless 也必将对前端的开发模式产生巨大的变革,前端工程师的职能也将再度回归到应用工程师的职能。 如果要用一句话来总结 Serverless,那就是 Less is More。 原文链接:https://zhuanlan.zhihu.com/p/65914436

0 个评论

要回复文章请先登录注册