AWS 无服务器速成课程 - 使用 Lambda 和 Aurora 无服务器构建 API
启动 MySQL
更改用户授权
每个堆栈溢出需要编辑密码验证
确保更改 db.js DB_PASSWORD
自从 AWS正式发布 AWS RDS Aurora 的无服务器选项以来,我一直翘首以盼,热切地期待着各种可能性。这将意味着构建无服务器架构的突破。不再需要使用 SQL 管理连接池。不再需要担心可用连接数上限。这将使使用无服务器进行数据库密集型操作变得可行。
不久前,我写了一篇文章,解释如何使用 MongoDB 构建无服务器 API。但是,我发现了一种模式。系统中的吞吐量较高,即使数据库实例可以承担负载,也会导致连接断开,因为可用连接数有限。
TL;DR
我已经将 MongoDB 的示例重构为使用 SQL。无论您使用哪种数据库提供商,都无需担心,但本文展示的示例使用的是AWS Aurora Serverless。您可以在此处查看代码,或者,如果您想学习如何自行设置所有内容,请继续阅读。
我们会做什么?
本文将向您展示如何将 MySQL 数据库即服务连接到无服务器 API。我们将使用 AWS RDS 创建一个数据库集群,并配置 Aurora Serverless。这种设置的优点在于,您可以根据服务使用量付费,甚至包括数据库!这是理想的无服务器场景。
如果您是新手该怎么办?
让我们分解一下基础知识。我们将使用AWS Lambda作为计算服务。AWS Lambda 函数本质上是一个容器。一旦 Lambda 被调用,容器就会启动并运行代码。这时,我们需要初始化数据库连接,也就是函数第一次被调用时,也就是容器第一次初始化的时候。之后对 Lambda 函数的每个请求都将使用现有的数据库连接。分解一下,听起来很简单。让我们开始吧。
配置项目
我假设你已经对无服务器框架有了基本的了解。我还希望你已经设置了一个 AWS 账户。如果没有,请查看此处。
1.创建服务
与往常一样,我们需要一个新的服务来保存我们所有的代码。
$ sls create -t aws-nodejs -p web-api && cd web-api
运行此命令,您将获得一个不错的样板,可以开始开发您的函数。它将设置基本函数和事件触发器。您还会看到我们web-api
立即进入了目录。我们进入此目录是为了设置所需的依赖项。
2.安装模块
总共我们只需要三个依赖项。MySQL ORM 调用 Sequelize 来处理连接和映射关系,并serverless-offline
用于本地开发。
确保您位于web-api目录中。首先,安装serverless-offline
,然后mysql2
安装sequelize
。
$ npm init -y
$ npm i --save-dev serverless-offline
$ npm i --save mysql2 sequelize
就是这样,让我们暂时离开终端,跳转到 AWS 来创建数据库集群。
3. 在 AWS RDS 上创建数据库
我一直讨厌通过 Web 控制台设置资源,但这却是必要之恶。请耐心等待,跳转到您的 AWS 账户并选择RDS。您将进入类似这样的页面。
单击橙色的“创建数据库”按钮并继续操作。
首先,您需要选择要使用的引擎。选择 Amazon Aurora,然后单击“下一步”。
指定引擎后,将 Aurora 配置为无服务器,为集群添加名称并配置主用户名和密码。
只需再配置一组高级设置,就可以完成了。
您将在此处设置容量和网络。由于 Aurora Serverless 位于 VPC 内,因此您必须配置访问权限。此处您将选择默认 VPC。
通过附加配置,您可以设置您的备份。完成所有设置后,点击橙色的“创建数据库”按钮。
最后,它将开始配置集群。您将被重定向并看到类似这样的输出。
但是,我们感兴趣的是细节部分。
在这里,您可以访问安全组和子网。您需要将它们添加到AWS Lambda函数中,以便它们能够与数据库通信。下一节将详细介绍。
注意:如果您想访问 AWS Aurora Serverless 集群,请按照本教程了解如何使用 CLI 进行操作,或者按照 AWS 官方教程通过Cloud9进行连接。您需要连接到集群并手动创建数据库,因为 Sequelize 无法为您创建它们。
Statsbot的工程师们向我展示了AWS RDS在处理海量数据方面有多么出色。有传言说,他们正在开发一个名为Cube.js的完全无服务器开源分析框架。我迫不及待地想亲自使用它。希望 Aurora Serverless 能有所帮助。
编写代码
在 AWS 上配置数据库资源总是一件很麻烦的事,尤其是新推出的无服务器数据库产品。幸运的是,配置部分已经完成了。现在我们需要在serverless.yml文件中编写无服务器资源的配置,并将实际的 CRUD 方法添加到handler.js中。
4.配置 YAML
无服务器框架在设置初始项目结构方面非常有帮助。它可以搭建起您刚开始工作时所需的几乎所有基础框架。返回到您创建无服务器项目的目录,并使用您喜欢的代码编辑器打开它。
首先打开serverless.yml文件,稍作休息。在这里,您会看到一堆带有示例配置的注释。我喜欢简洁的代码,所以我们先删除它们,然后粘贴这段代码。
service: web-api
custom:
secrets: ${file(secrets.json)}
provider:
name: aws
runtime: nodejs8.10
timeout: 30
stage: ${self:custom.secrets.NODE_ENV}
environment:
NODE_ENV: ${self:custom.secrets.NODE_ENV}
DB_NAME: ${self:custom.secrets.DB_NAME}
DB_USER: ${self:custom.secrets.DB_USER}
DB_PASSWORD: ${self:custom.secrets.DB_PASSWORD}
DB_HOST: ${self:custom.secrets.DB_HOST}
DB_PORT: ${self:custom.secrets.DB_PORT}
vpc:
securityGroupIds:
- ${self:custom.secrets.SECURITY_GROUP_ID}
subnetIds:
- ${self:custom.secrets.SUBNET1_ID}
- ${self:custom.secrets.SUBNET2_ID}
- ${self:custom.secrets.SUBNET3_ID}
- ${self:custom.secrets.SUBNET4_ID}
functions:
healthCheck:
handler: handler.healthCheck
events:
- http:
path: /
method: get
cors: true
create:
handler: handler.create
events:
- http:
path: notes
method: post
cors: true
getOne:
handler: handler.getOne
events:
- http:
path: notes/{id}
method: get
cors: true
getAll:
handler: handler.getAll
events:
- http:
path: notes
method: get
cors: true
update:
handler: handler.update
events:
- http:
path: notes/{id}
method: put
cors: true
destroy:
handler: handler.destroy
events:
- http:
path: notes/{id}
method: delete
cors: true
plugins:
- serverless-offline
让我们逐一分析一下。此provider
部分包含有关云提供商的所有基本信息,以及运行时、函数超时、环境变量和 VPC 配置的数据。这里需要设置 VPC,因为 AWS Aurora Serverless 只能在 VPC 后运行,因此您需要将AWS Lambda 函数放在同一个 VPC 中才能访问数据库。
继续,custom
顶部有一个部分,我们在其中加载环境变量,然后将它们传递给该environment
部分。
最后,我们来谈谈函数部分。我们总共添加了 6 个函数:healthCheck、create、getOne、getAll、update和destroy。它们都指向handler.js文件中同名的导出函数。它们的事件触发器与由AWS API Gateway处理的 HTTP 事件挂钩。这意味着,对指定路径的 HTTP 请求将触发引用的函数。
差不多就是这样了,最后一件事是添加一个插件部分和serverless-offline。我们上面安装了这个模块,我们将在部署到 AWS 之前用它来测试服务。
5. 添加机密
您在serverless.yml中看到我们引用了一个 secrets 文件。在继续下一步之前,我们需要创建它并确保它能够正确加载我们的 secrets。
在项目的根目录中创建一个secrets.json
并粘贴此代码片段。
{
"DB_NAME": "test",
"DB_USER": "root",
"DB_PASSWORD": "root",
"DB_HOST": "127.0.0.1",
"DB_PORT": 3306,
"NODE_ENV": "dev",
"SECURITY_GROUP_ID": "sg-xx",
"SUBNET1_ID": "subnet-xx",
"SUBNET2_ID": "subnet-xx",
"SUBNET3_ID": "subnet-xx",
"SUBNET4_ID": "subnet-xx"
}
此设置在本地开发环境中运行良好。一旦您想要将其部署到 AWS,则必须添加您自己的密钥。AWS Aurora Serverless 将输出如上图所示的所有内容。这里重要的密钥是数据库连接参数、安全组 ID 和子网 ID。
关于 YAML 的所有内容就到这里。接下来我们来试试handler.js吧。终于,来点真正的代码吧!
6. 充实功能
首先,我们将定义 6 个函数。它们将描述我们想要的初始布局和行为。完成后,我们将使用Sequelize创建数据库连接和数据库交互逻辑。
切换到handler.js文件。你会看到默认的样板代码。猜怎么着?删除所有内容,然后添加下面的代码。
module.exports.healthCheck = async () => {
await connectToDatabase()
console.log('Connection successful.')
return {
statusCode: 200,
body: JSON.stringify({ message: 'Connection successful.' })
}
}
module.exports.create = async (event) => {
try {
const { Note } = await connectToDatabase()
const note = await Note.create(JSON.parse(event.body))
return {
statusCode: 200,
body: JSON.stringify(note)
}
} catch (err) {
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: 'Could not create the note.'
}
}
}
module.exports.getOne = async (event) => {
try {
const { Note } = await connectToDatabase()
const note = await Note.findById(event.pathParameters.id)
if (!note) throw new HTTPError(404, `Note with id: ${event.pathParameters.id} was not found`)
return {
statusCode: 200,
body: JSON.stringify(note)
}
} catch (err) {
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: err.message || 'Could not fetch the Note.'
}
}
}
module.exports.getAll = async () => {
try {
const { Note } = await connectToDatabase()
const notes = await Note.findAll()
return {
statusCode: 200,
body: JSON.stringify(notes)
}
} catch (err) {
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: 'Could not fetch the notes.'
}
}
}
module.exports.update = async (event) => {
try {
const input = JSON.parse(event.body)
const { Note } = await connectToDatabase()
const note = await Note.findById(event.pathParameters.id)
if (!note) throw new HTTPError(404, `Note with id: ${event.pathParameters.id} was not found`)
if (input.title) note.title = input.title
if (input.description) note.description = input.description
await note.save()
return {
statusCode: 200,
body: JSON.stringify(note)
}
} catch (err) {
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: err.message || 'Could not update the Note.'
}
}
}
module.exports.destroy = async (event) => {
try {
const { Note } = await connectToDatabase()
const note = await Note.findById(event.pathParameters.id)
if (!note) throw new HTTPError(404, `Note with id: ${event.pathParameters.id} was not found`)
await note.destroy()
return {
statusCode: 200,
body: JSON.stringify(note)
}
} catch (err) {
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: err.message || 'Could destroy fetch the Note.'
}
}
}
我理解你觉得这段代码有点长。别担心,这只是 6 个基本async
函数。该connectToDatabase()
函数解析为一个包含所有数据库模型的对象。本教程中我们只实现该Note
模型。该函数背后的逻辑是连接到 SQL 数据库,缓存连接,并确保在现有连接处于活动状态时永不重试连接。
但是等等,我们还没有定义或创建任何这些。好吧,我是故意这么做的,首先我想让你明白这并不复杂,与使用 Node.js 和 Express 创建 API 也没什么不同。
7.添加数据库连接
下一步是添加数据库连接。当 AWS Lambda 函数首次调用时(称为冷启动),AWS 会启动一个容器来运行代码。这时我们就可以连接到数据库了。所有后续请求都将使用现有的数据库连接。从概念上讲,这很容易理解,但当我们需要在代码中理解它时,就会变得非常棘手。就这样吧!
在服务的根目录中,与handler.js相邻的位置创建一个新文件。给它一个合乎逻辑的名称db.js,并添加以下代码。
const Sequelize = require('sequelize')
const NoteModel = require('./models/Note')
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
dialect: 'mysql',
host: process.env.DB_HOST,
port: process.env.DB_PORT
}
)
const Note = NoteModel(sequelize, Sequelize)
const Models = { Note }
const connection = {}
module.exports = async () => {
if (connection.isConnected) {
console.log('=> Using existing connection.')
return Models
}
await sequelize.sync()
await sequelize.authenticate()
connection.isConnected = true
console.log('=> Created a new connection.')
return Models
}
在顶部,我们需要Sequelize
和NoteModel
。
注意:我们尚未创建模型,但请稍等,我们将在此部分之后立即完成该操作。
然后我们初始化sequelize
,传入连接变量并建立连接。调用NoteModel
将初始化模型,然后将其传递给Models
常量。任意connections
对象仅用作连接的缓存。确保在非必要情况下不要同步数据库。await
使用.sync()
和.authenticate()
方法的第二个原因是确保在初始函数调用期间(处理任何业务逻辑之前)建立数据库连接。
创建db.js文件后,我们在handler.js中引入它。只需将此代码片段添加到处理程序的顶部即可。
const connectToDatabase = require('./db') // initialize connection
// simple Error constructor for handling HTTP error codes
function HTTPError (statusCode, message) {
const error = new Error(message)
error.statusCode = statusCode
return error
}
8. 添加 Note 模型
跳回到handler.js。你可以看到我们正在调用const { Note } = await connectToDatabase()
函数来检索数据,但是没有定义 Note 模型。好了,现在是时候了。
在服务根目录中创建一个新文件夹,并将其命名为models。在其中创建另一个文件并将其命名为Note.js。这只是一个简单的模型定义。
module.exports = (sequelize, type) => {
return sequelize.define('note', {
id: {
type: type.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: type.STRING,
description: type.STRING
})
}
就这样。是时候尝试一下了。
注意:请确保将您的添加secrets.json
到您的.gitignore
文件中。
进行一些测试怎么样?
我们已经准备好测试 API 了。为了保险起见,我们先在本地运行一下。需要注意的一点是,运行时需要添加一个标志serverless-offline
。
$ sls offline start --skipCacheInvalidation
注意: Serverless Offline 默认在每次运行时都会使 Node 所需的缓存失效,我们添加了此标志来禁用它。在 Node.js 中,当您使用require()
一个模块时,它会存储该模块的缓存版本,这样所有后续调用require()
都无需从文件系统重新加载该模块。
在终端中运行该命令后,您应该会看到类似这样的内容。
所有路线均已启动并运行。我使用Insomniahttp://localhost:3000/notes
创建了一个JSON 格式的POST 请求。
检查终端,你会看到=> using new database connection
get logs 消息,这意味着初始数据库连接已建立。再次发送 POST 请求,你会看到=> using existing database connection
get logs 消息。
太棒了,添加新笔记成功了。让我们使用getAll方法检索刚刚添加的所有笔记。
亲自尝试其他端点。玩完后再回来这里。
部署到 AWS
现在到了棘手的部分。请务必记下您将在 AWS Aurora Serverless 控制台中获取的所有数据库参数,当然还有安全组 ID 和子网 ID。通常,您会有一个安全组和三个子网。但是,实际数量可能会有所不同,所以不用担心。获取这些值后,将它们添加到您的secrets.json
。就这样!您已准备好部署。
无服务器框架让部署变得快速而轻松。您只需运行一个命令即可。
$ sls deploy
它将自动在 AWS 上配置资源,打包并将所有代码推送到 S3,然后从 S3 发送到 Lambda 函数。终端应该会显示类似如下的输出。
注意:您可以使用提供的端点重复上述测试过程。但是,请耐心等待冷启动时间。AWS Aurora Serverless 启动可能需要 10 秒左右。VPC 中的 AWS Lambda 也会出现类似的问题。
这就是部署过程的全部内容。很简单吧?这就是我如此热爱无服务器框架的原因。
总结
这是一个不断发展的架构。随着无服务器关系数据库的兴起,创建完全无服务器基础设施的可能性无穷无尽。我已尽力解释创建合适 API 的过程。希望您能从中获益良多。欢迎深入探索无服务器架构及其带来的一切可能性!
当然,这里再次分享一下我的代码库,如果你想让更多人在 GitHub 上看到它,就点个 Star 吧。如果你想阅读我之前关于无服务器的一些思考,请访问我的个人资料或订阅我的无服务器新闻通讯!
我彻底体验了 AWS Aurora Serverless。希望大家喜欢阅读这篇文章,就像我喜欢写这篇文章一样。如果喜欢,就拍拍那只小独角兽,这样 dev.to 上会有更多人看到这篇文章。如果你需要一个无服务器 SQL 分析框架,别忘了给 Cube.js 的朋友们点个赞!下次再见,保持好奇心,玩得开心。
免责声明:Tracetest赞助了这篇博文。使用可观察性可以将测试创建和故障排除的时间和精力减少 80%。
文章来源:https://dev.to/adnanrahic/a-crash-course-on-serverless-with-aws---building-apis-with-lambda-and-aurora-serverless-3fn3