MongoDB 个来自 AWS Lambda 的连接

MongoDB connections from AWS Lambda

我正在寻找使用连接到 MongoDB 数据库的 AWS Lambda/API 网关创建 RESTful API。我读到 MongoDB 的连接相对昂贵,因此最好的做法是在建立连接后保留连接以供重用,而不是为每个新查询建立新连接。

这对于普通应用程序来说非常简单,因为您可以在启动期间建立连接并在应用程序生命周期内重用它。但是,由于 Lambda 被设计为无状态的,因此保留此连接似乎不太直接。

因此,我想知道解决此数据库连接问题的最佳方法是什么?每次调用 Lambda 函数时,我是否都被迫建立新连接,或者是否有办法 pool/cache 这些连接以实现更高效的查询?

谢谢。

不幸的是,您可能必须创建自己的 RESTful API 来回答 MongoDB 请求,直到 AWS 提出一个请求。到目前为止,他们只有您需要的 Dynamo DB。

Restheart 是与 MongoDB 一起运行的基于 REST 的服务器。它将 Mongo 中的大多数 CRUD 操作映射到 GET、POST 等,当您需要编写自定义处理程序(例如,专门的 geoNear、geoSearch 查询)时具有可扩展支持的请求

我以前遇到过几次同样的问题,但我已经通过将我的 mongo 放在 EC2 的同一个帐户上解决了。 我在我的 lambda 函数所在的同一 AWS EC2 帐户上创建了一个 mongo 数据库。

现在我可以使用私有 IP 从 lambda 函数访问我的 mongo。

AWS Lambda 函数应定义为无状态函数,因此它们不能像连接池那样保持状态。

AWS forum post中也提出了这个问题。 2015 年 10 月 5 日,AWS 工程师 Sean 发帖称,您应该 在每个请求上打开和关闭连接,方法是在处理程序块之外的代码初始化时创建一个池。但两天后,同一位工程师发帖称你不应该这样做

问题是您无法控制 Lambda 的运行时环境。我们确实知道这些环境(或容器)被重用,如 blog post by Tim Wagner 所述。但是缺乏控制会驱使您耗尽所有资源,例如达到数据库中的连接限制。但这取决于你。

您可以使用 RESTHeart 通过 HTTP 访问数据库,而不是从您的 lambda 函数连接到 MongoDB。 MongoDB 的连接池由 RESTHeart 维护。请记住,就性能而言,您将在每个请求上打开一个到 RESTHeart 的新 HTTP 连接,而不是像在传统应用程序中那样使用 HTTP 连接池。

除了保存连接以供重用外,增加lambda函数的内存分配。 AWS 分配 CPU 与内存分配成比例,当从 128MB 更改为 1.5Gb 时,连接到 mongodb atlas 时连接时间从 4s 下降到 0.5s。

在此处阅读更多内容:https://aws.amazon.com/lambda/faqs/

我 运行 一些测试执行 Java Lambda 函数连接到 MongoDB Atlas。

正如其他发布者已经指出的那样,亚马逊确实会重复使用这些实例,但是这些实例可能会被回收并且无法确定确切的行为。因此,最终可能会出现陈旧的连接。我每 5 分钟收集一次数据,每 5 分钟将其推送到 Lambda 函数。

Lambda 基本上是这样的:

  • 建立或重用连接
  • 查询一条记录
  • 写入或更新一条记录
  • 关闭连接或保持打开状态

实际数据量很少。根据一天中的不同时间,它在 1 - 5 kB 之间变化。我只用了128MB。

N.Virgina 中的 Lambdas 运行 因为这是 free tier 绑定的位置。

每次打开和关闭连接时,大多数调用需要 4500 - 9000 毫秒。重用连接时,大多数调用都在 300 - 900 毫秒之间。检查 Atlas 控制台,连接数保持稳定。对于这种情况,重用连接是值得的。使用 Java 驱动程序建立连接甚至断开与副本集的连接都相当昂贵。

对于大规模部署,应该运行 进行更全面的测试。

您应该假设 lambda 是无状态的,但事实是大多数时候 vm 只是被冻结并且 确实 维护 一些状态。亚马逊为每个请求启动一个新流程是愚蠢的,因此他们经常重复使用相同的流程,您可以利用这一点来避免抖动连接。

避免为每个请求连接(在重新使用 lambda 进程的情况下):

  1. 编写处理程序假设进程被重新使用,以便您连接到数据库并让 lamba 重新使用连接池(从 [=12 返回的 db 承诺=]).

  2. 为了让 lambda 不挂起等待您关闭数据库连接,db.close(),在服务请求后告诉它不要等待空事件循环。

示例:

var db = MongoClient.connect(MongoURI);

module.exports.targetingSpec = (event, context, callback) => {
  context.callbackWaitsForEmptyEventLoop = false;
  db.then((db) => {
    // use db
  });
};

来自关于 context.callbackWaitsForEmptyEventLoop 的文档:

callbackWaitsForEmptyEventLoop The default value is true. This property is useful only to modify the default behavior of the callback. By default, the callback will wait until the Node.js runtime event loop is empty before freezing the process and returning the results to the caller. You can set this property to false to request AWS Lambda to freeze the process soon after the callback is called, even if there are events in the event loop. AWS Lambda will freeze the process, any state data and the events in the Node.js event loop (any remaining events in the event loop processed when the Lambda function is called next and if AWS Lambda chooses to use the frozen process). For more information about callback, see Using the Callback Parameter.

简短的回答是肯定的,您需要创建一个新连接并在 lambda 完成之前关闭它。

长答案实际上是在我的测试期间,你可以像这样在你的处理程序中传递你的数据库连接(mysql 示例,因为这就是我必须提供的),你不能依赖这个有一个连接所以检查我下面的例子,它可能是一旦你的 Lambda 没有被执行很长时间它确实失去了处理程序的状态(冷启动),我需要做更多的测试来找出答案,但我已经注意到如果 Lambda 使用以下示例获得大量流量,它不会创建新连接。

// MySQL.database.js
    import * as mysql from 'mysql'

    export default mysql.createConnection({
        host: 'mysql db instance address',
        user: 'MYSQL_USER',
        password: 'PASSWORD',
        database: 'SOMEDB',
    })

然后在您的处理程序中导入它并将其传递给正在执行的 lambda。

// handler.js
import MySQL from './MySQL.database.js'

const funcHandler = (func) => {
    return (event, context, callback) => {
        func(event, context, callback, MySQL)
    }
}

const handler = {
    someHandler: funcHandler(someHandler),
}

export default handler

现在在您的 Lambda 中,您可以...

export default (event, context, callback, MySQL) => {
  context.callbackWaitsForEmptyEventLoop = false
  // Check if their is a MySQL connection if not, then open one.

 // Do ya thing, query away etc etc

  callback(null, responder.success()) 


}

他能找到响应者示例 抱歉,它是 ES5,因为那是提出问题的地方。

希望对您有所帮助!

是的,有一种方法可以将 cache/retain 连接到 MongoDB,它的名称是连接池。您也可以像这样将它与 lambda 函数一起使用:
有关更多信息,您可以点击以下链接:
Using Mongoose With AWS Lambda
Optimizing AWS Lambda(a bit out date)

const mongoose = require('mongoose');

let conn = null;

const uri = 'YOUR CONNECTION STRING HERE';

exports.handler = async function(event, context) {
  // Make sure to add this so you can re-use `conn` between function calls.
  context.callbackWaitsForEmptyEventLoop = false;

  const models = [{name: 'User', schema: new mongoose.Schema({ name: String })}]
  conn = await createConnection(conn, models)
  //e.g.
  const doc = await conn.model('User').findOne({})
  console.log('doc: ', doc);
};

const createConnection = async (conn,models) => {
  // Because `conn` is in the global scope, Lambda may retain it between
  // function calls thanks to `callbackWaitsForEmptyEventLoop`.
  // This means your Lambda function doesn't have to go through the
  // potentially expensive process of connecting to MongoDB every time.

    if (conn == null || (conn && [0, 3].some(conn.readyState))) {
        conn = await mongoose.createConnection(uri, {
        // Buffering means mongoose will queue up operations if it gets
        // disconnected from MongoDB and send them when it reconnects.
        // With serverless, better to fail fast if not connected.
          bufferCommands: false, // Disable mongoose buffering
          bufferMaxEntries: 0, // and MongoDB driver buffering
          useNewUrlParser: true,
          useUnifiedTopology: true,
          useCreateIndex: true
        })
        for (const model of models) {
          const { name, schema } = model
          conn.model(name, schema)
        }
      }
  
      return conn
  }

从 AWS Lambda 连接的官方最佳实践

You should define the client to the MongoDB server outside the AWS Lambda handler function. Don't define a new MongoClient object each time you invoke your function. Doing so causes the driver to create a new database connection with each function call. This can be expensive and can result in your application exceeding database connection limits.

As an alternative, do the following:

  1. Create the MongoClient object once.
  2. Store the object so your function can reuse the MongoClient across function invocations.

第 1 步

将对 MongoClient.connect() 函数的调用隔离到它自己的模块中,以便可以跨函数重用连接。让我们为此创建一个文件 mongo-client.js

mongo-client.js:

const { MongoClient } = require('mongodb');

// Export a module-scoped MongoClient promise. By doing this in a separate
// module, the client can be shared across functions.
const client = new MongoClient(process.env.MONGODB_URI);

module.exports = client.connect();

第 2 步

导入新模块并在函数处理程序中使用它来连接数据库。

一些-file.js:

const clientPromise = require('./mongodb-client');

// Handler
module.exports.handler = async function(event, context) {
  // Get the MongoClient by calling await on the connection promise. Because
  // this is a promise, it will only resolve once.
  const client = await clientPromise;
  
  // Use the connection to return the name of the connected database for example.
  return client.db().databaseName;
}

资源

有关详细信息,请查看 this Docs