利用 Serverless 和 MongoDB 解决隐形扩展问题
自软件工程成为一门专业以来,我们一直致力于服务全球用户。随之而来的是扩展性以及如何解决这一问题。很多时候,将软件扩展到难以想象的规模的想法是为时过早且没有必要的。
随着无服务器架构和后端即服务提供商的兴起,情况已经完全不同了。现在我们面临的问题不再是如何向上和向外扩展,而是如何在不造成过重负载的情况下扩展数据库连接。
由于我们对底层基础设施的了解有限,除了编写健壮、高效的代码并使用适当的工具来缓解这个问题之外,我们别无他法。
真的是这样吗?🤔
数据库如何在无服务器架构中工作?
使用传统服务器时,您的应用程序会在启动时连接数据库。这很合乎逻辑,对吧?它首先会通过连接字符串连接到数据库,只有在连接完成后,应用程序的其余部分才会初始化。
无服务器架构的处理方式略有不同。代码实际上只有在触发函数后才会首次运行。这意味着你必须在同一个函数调用期间同时完成数据库连接的初始化和数据库交互。
每次函数运行时都执行这个过程会非常低效且耗时。因此,无服务器开发人员使用一种称为连接池的技术,仅在第一次函数调用时创建数据库连接,并在后续每次调用中重复使用该连接。现在你可能想知道这怎么可能实现?
简而言之,lambda 函数本质上就是一个小型容器。它会被创建并保持运行状态一段时间,即使它并非一直都在运行。只有当它超过 15 分钟处于非活动状态时,才会被终止。
这样一来,我们的数据库连接就有了 15 到 20 分钟的活跃时间,可以随时使用,而不会出现任何性能损失。
将 Lambda 与 MongoDB Atlas 结合使用
这里有一段简单的代码片段供您参考。
// db.js
const mongoose = require('mongoose')
const connection = {}
module.exports = async () => {
if (connection.isConnected) {
console.log('=> using existing database connection')
return
}
console.log('=> using new database connection')
const db = await mongoose.connect(process.env.DB)
connection.isConnected = db.connections[0].readyState
}
仔细查看上面的代码后,你会发现它的逻辑很清晰。代码顶部,我们引入mongoose并初始化了一个名为 `connection` 的对象connection。仅此而已。我们将使用这个连接对象作为缓存,用来存储数据库连接是否存在。
首次db.js需要并调用该文件时,它会将 Mongoose 连接到数据库连接字符串。之后每次调用都会重用已建立的连接。
以下是它在我们的 lambda 函数中的显示效果handler。
const connectToDatabase = require('./db')
const Model = require('./model')
module.exports.create = async (event) => {
try {
const db = await connectToDatabase()
const object = Model.create(JSON.parse(event.body))
return {
statusCode: 200,
body: JSON.stringify(object)
}
} catch (err) {
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: 'Could not create the object.'
}
}
}
这种简单的模式可以让你的 Lambda 函数缓存数据库连接,从而显著提升速度。是不是很棒?😊
这一切都很棒,但如果我们的数据库连接数达到上限怎么办?问得好!这里有一个可行的解决方案。
连接数限制如何规定?
如果您担心连接数限制,可以考虑使用后端即服务 (BaaS) 来解决这个问题。理想情况下,BaaS 会创建一个连接池,供您的函数使用,而无需担心达到连接上限。具体来说,服务提供商会提供一个 REST API 来处理实际的数据库交互,而您只需使用这些 API 即可。
资深读者可能会考虑自己创建 API 来托管连接池,或者使用 GraphQL 之类的工具。这两种方案都非常适合,具体取决于你的使用场景。不过,我将重点介绍如何使用现成的工具快速上手。
将 Lambda 与 MongoDB Stitch 结合使用
如果你像我一样是 MongoDB 的忠实用户,不妨了解一下他们名为Stitch的后端即服务解决方案。它提供了一个简单的 API 来与 MongoDB 驱动程序交互。你只需要创建一个 Stitch 应用,将其连接到你已运行的 Atlas 集群即可。在 Stitch 应用中,你需要启用匿名登录,并创建数据库名称和集合。
安装 Stitch npm 模块,并在代码中引用您的 Stitch 应用 ID,然后开始调用 API。
const { StitchClientFactory, BSON } = require('mongodb-stitch')
const { ObjectId } = BSON
const appId = 'notes-stitch-xwvtw'
const database = 'stitch-db'
const connection = {}
module.exports = async () => {
if (connection.isConnected) {
console.log('[MongoDB Stitch] Using existing connection to Stitch')
return connection
}
try {
const client = await StitchClientFactory.create(appId)
const db = client.service('mongodb', 'mongodb-atlas').db(database)
await client.login()
const ownerId = client.authedId()
console.log('[MongoDB Stitch] Created connection to Stitch')
connection.isConnected = true
connection.db = db
connection.ownerId = ownerId
connection.ObjectId = ObjectId
return connection
} catch (err) {
console.error(err)
}
}
如您所见,模式非常相似。我们创建一个 Stitch 客户端连接,并在后续的每个请求中重复使用它。
lambda 函数本身看起来几乎与上面的示例相同。
const connectToDatabase = require('./db')
module.exports.create = async (event) => {
try {
const { db } = await connectToDatabase()
const { insertedId } = await db.collection('notes')
.insertOne(JSON.parse(event.body))
const addedObject = await db.collection('notes')
.findOne({ _id: insertedId })
return {
statusCode: 200,
body: JSON.stringify(addedObject)
}
} catch (err) {
return {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: 'Could not create the object.'
}
}
}
看起来很相似,我应该能适应。不过,Stitch 有一些很棒的开箱即用功能,比如客户端连接的身份验证和授权。这使得保护路由变得非常容易。
总结
在理想的无服务器架构中,我们无需担心数据库连接数限制。然而,要达到这种扩展性问题,所需的 API 用户访问量非常庞大。上面的例子展示了如何通过使用后端即服务 (BaaS) 提供商来缓解这个问题。尽管Stitch目前还不成熟,但它基于MongoDB构建,而 MongoDB 本身就是一个非常优秀的数据库。而且,将其与AWS Lambda结合使用,速度更是惊人。
要查看一些使用上述两种连接模式的项目,请点击此处:
如果你想阅读我之前关于无服务器架构的一些思考,请访问我的个人资料或订阅我的新闻通讯!
或者,您也可以看看我写的其他几篇关于无服务器架构的文章:
- 如何使用 Serverless 将 Node.js 应用程序部署到 AWS Lambda
- AWS Lambda 和 Node.js 入门
- 使用 JSON Web Tokens 保护 Serverless API 的速成课程
- 将你的 Node.js REST API 迁移到 Serverless
- 使用 Node.js 和 MongoDB 构建无服务器 REST API
- 使用 Node.js 进行 Serverless 速成课程
希望你们喜欢这篇文章,就像我喜欢写它一样。下次再见,保持好奇心,玩得开心!
免责声明:Tracetest赞助了这篇博文。利用可观测性,可将测试创建和故障排除所需的时间和精力减少80%。
文章来源:https://dev.to/adnanrahic/solving-invisible-scaling-issues-with-serverless-and-mongodb-4m55