来自AWS Lambda的MongoDB连接

时间:2015-07-30 15:47:24

标签: node.js mongodb amazon-web-services aws-lambda

我希望使用连接到MongoDB数据库的AWS Lambda / API Gateway创建RESTful API。我已经读过与MongoDB的连接相对昂贵,因此最好的做法是在建立连接后保留连接以便重用,而不是为每个新查询建立新的连接。

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

因此,我想知道解决此数据库连接问题的最佳方法是什么?我是否每次调用Lambda函数时都强制建立新连接,或者是否有办法池/缓存这些连接以获得更有效的查询?

感谢。

9 个答案:

答案 0 :(得分:13)

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连接池。

答案 1 :(得分:5)

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

答案 2 :(得分:3)

你应该假设lambdas是无状态的,但实际情况是,大多数时候vm被冻结,而确实维持某些状态。亚马逊为每个请求启动一个新流程是愚蠢的,所以他们经常重复使用相同的流程,你可以利用这个来避免颠簸连接。

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

  1. 编写处理程序,假设重复使用该进程,以便连接到数据库并让lamba重用连接池(从db返回的MongoClient.connect承诺)。

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

  3. 示例:

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

    有关context.callbackWaitsForEmptyEventLoop的文档:

      

    <强> callbackWaitsForEmptyEventLoop   默认值是true。此属性仅用于修改回调的默认行为。默认情况下,回调将等到Node.js运行时事件循环为空,然后冻结进程并将结果返回给调用者。您可以将此属性设置为false,以便在调用回调后立即请求AWS Lambda冻结进程,即使事件循环中存在事件也是如此。 AWS Lambda将冻结进程,任何状态数据和Node.js事件循环中的事件(当下一次调用Lambda函数并且AWS Lambda选择使用冻结进程时,处理事件循环中的任何剩余事件)。有关回调的更多信息,请参阅使用回调参数。

答案 3 :(得分:2)

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

正如其他海报所述,亚马逊确实重用了实例,但这些实例可能会被回收,并且无法确定具体行为。所以最终可能会出现陈旧的联系。我每5分钟收集一次数据,然后每隔5分钟将数据推送到Lambda函数。

Lambda基本上做了:

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

实际的数据量非常低。根据一天的时间,它从1到5 kB不等。我只用了128 MB。

Lambdas在N.Virgina跑了,因为这是free tier所在的位置。

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

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

简短的回答是肯定的,你需要创建一个新的连接并在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()) 


}

响应者示例可以找到here.抱歉,这是ES5,因为问题就在那里。

希望这有帮助!

答案 6 :(得分:0)

是的,有一种方法可以缓存/保留与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
  }

答案 7 :(得分:-3)

除了保存连接以便重用之外,还要增加lambda函数的内存分配。 AWS按比例分配CPU到内存分配,当从128MB变为1.5Gb时,连接到mongodb atlas时,连接时间从4s下降到0.5s。

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

答案 8 :(得分:-8)

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

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