Knex: 如何将Knex与AWS Lambda结合使用?

创建于 2017-01-20  ·  34评论  ·  资料来源: knex/knex

在测试一些代码时,我遇到了连接池问题。 我期望我的lambda函数可能在几秒钟内被调用数千次,并且很难找出连接到数据库的最佳方法。 这是节点/ postgres的一个非常相似的问题:本质上,问题是如果一个连接可用,我需要能够从池中获得连接,但是由于AWS(不可靠)重用的方式,我不能依赖于现有的池lambda容器。

基本上,我正在寻找一种可靠地获取或创建与数据库的连接的方法。 我还没有找到任何示例(例如while(!availableConnections) { tryToGetConnection() } 。我需要与node-pool进行交互吗?如何使用Knex?

insightful question

最有用的评论

抱歉,我的句子很中间,我的孩子就像在地板上扔了3升水:1st_place_medal:我稍后会更新上面的评论...

所有34条评论

仅当连接来自同一节点进程时,Knex池才有效。

如果AWS Lambda实例未运行共享节点进程,则只需在每个Lambda实例中创建具有最小/最大连接数为1的新池,并希望您的数据库具有足够好的设置以允许数百个同时连接(在RDS中取决于实例大小) )。

阅读此内容后https://forums.aws.amazon.com/thread.jspa?threadID=216000

看起来lambda确实在共享一些进程,因此,如果您能确定哪个是创建的lambda容器的最大数量,则应该能够计算出最佳的池大小(每个容器都有各自的池,因此与DB的总连接为池。 max *最大容器数)。

无论如何,不​​需要从池中进行手动连接请求,knex自动等待连接,并且如果一切都在几秒钟内结束,则在该时间内不会触发任何超时。

如果您从数据库中收到错误消息,说已达到最大连接数,则需要减小最大池大小。

抱歉,我的句子很中间,我的孩子就像在地板上扔了3升水:1st_place_medal:我稍后会更新上面的评论...

谢谢回复。 听起来好像估计最大池大小将是我最好的选择,即使连接数随时间变化很大。

需要明确的是,没有办法在Knex中关闭连接,对吗? 只有破坏连接池的能力吗? Lambda有时会重复使用容器的事实使一切都丢了。

销毁连接池也会销毁所有连接(优雅地等待它们首先完成),我想当lambda销毁容器时,当进程终止时,其所有打开的TCP套接字都将隐式关闭。

我不明白为什么每个请求后都应该尝试显式关闭连接,因为这会破坏池化的好处。 通过创建大小为1的池并随后销毁它,您将获得相同的效果。

您还可以为池配置空闲超时,如果不使用连接,而只是在池中等待操作,它将自动关闭连接。

我可以使用Knex将COPY查询发送到RedShift集群,而不等待结果吗?

一旦到达Lambda函数的结尾,使用pg Pool进行此操作即可终止查询。

@BardiaAfshin如果在到达lambda函数结束时,lambda容器被销毁并释放了所有套接字,则在这种情况下,db查询也会死掉并且可能无法完成。

我不确定在读取结果值之前由于未完成隐式事务而将COPY查询回滚的情况下,PostgreSQL对客户端连接结束的反应如何...

无论如何,是否可以发送查询都不取决于knex,但这取决于aws lambda和postgresql的工作方式。

我的观察是该查询在RedShift上被杀死,并被回滚。

无论如何,不​​需要从池中进行手动连接请求,knex自动等待连接,并且如果一切都在几秒钟内结束,则在该时间内不会触发任何超时。

我正在运行一个数据库负载测试脚本,这似乎不是事实。 超过30个并发连接的任何内容都会立即超时,而不是等待打开的连接。

查询完成后,是否可以手动释放当前使用的连接?

有没有人可以进入AWS Lambda并与我们分享这些示例代码? 我希望有人可以共享knex / postgres / lambda的模式和/或反模式。

这是我现在正在使用的-我敢肯定它可以改进,但是希望能证明我是否在正确的道路上……

'use strict';
var pg = require('pg');

function initKnex(){
  return require('knex')({
      client: 'pg',
      connection: { ...details... }
  });
}

module.exports.hello = (event, context) =>
{
  var knex = initKnex();

  // Should I be returning knex here or in the final catch?
  knex
  .select('*')
  .from('my_table')
  .then(function (rows) {
    context.succeed('Succeeded: ' + JSON.stringify(rows || []));
  })
  .catch(function (error) {
    context.fail(error);
  })
  .then(function(){
    // is destroy overkill? - is there an option for knex.client.release, etc?
    knex.destroy();
  })
}

我在同一条船上-尽管经过大量的搜寻和测试,但仍未弄清楚最好的方法是什么。 我现在正在做的是:

const dbConfig = require('./db');
const knex = require('knex')(dbConfig);

exports.handler = function (event, context, callback) {
...
connection = {..., pool: { min: 1, max: 1 },

这样,连接(每个容器最多1个)将保持活动状态,因此可以轻松地重用该容器。 最后,我不会破坏我的联系。

http://blog.rowanudell.com/database-connections-in-lambda/

不知道这是否是最好的方法,但是到目前为止,它对我还是有用的。

/耸肩

@austingayler

我不确定在声明const knex时会发生什么-这是在初始连接处建立的吗? 有人可以澄清吗? (我假设您的dbConfig中有连接信息)

在您的代码中,难道每次调用处理程序时,连接本身都不会被覆盖并重新创建吗?

只是在同一个船上同一个人聊天,我没有太多运气试图弄清楚容器与池的大小(根据

const knex = require('knex');

const client = knex(dbConfig);

client(tableName).select('*')
  .then((result) => { 

    return Promise.all([
      result,
      client.destroy(),
    ])  
  })
  .then(([ result ]) => {

    return result;
  });

TL; DR:只需在调用回调之前设置context.callbackWaitsForEmptyEventLoop = false

AWS Lambda等待空事件循环(默认情况下)。 因此,即使执行回调,函数也可能引发Timeout错误。

请查看以下链接以获取详细信息:
https://github.com/apex/apex/commit/1fe6e91a46e76c2d5c77877be9ce0c206e9ef9fb

@elhigu @tgriesser :这不是一个麻烦的问题。 这绝对是Lambda环境的问题。 我认为将此问题标记为问题,应该很好解决:)

@mooyoul是的,绝对不是knex问题,但也许是文档问题...尽管我认为我不希望将任何AWS Lambda特定内容用于knex文档,所以请关闭。

请看这个链接
https://stackoverflow.com/questions/49347210/why-aws-lambda-keeps-timing-out-when-using-knex-js
您需要关闭数据库连接,否则Lambda会运行直到超时。

有没有人发现一种将Lambda与Knex结合使用的完美模式?

正确关闭连接后还有其他问题吗?

我要避免的事情是关闭连接,并使其在Lambda调用中可用。

您确定Lambda允许维持通话之间的状态吗?

查看https://scalegrid.io/blog/how-to-use-mongodb-connection-pooling-on-aws-lambda/的node.js部分,它提出了一种解决非空事件循环的方法。

我不喜欢这样破坏线程,但是由于我认为它在评论中迷失了方向,因此上述@mooyoul回答对我们很有用。

如果您不将该标志设置为false,则Lambda等待事件循环为空。 如果这样做,则函数在调用回调后立即完成执行。

对我来说,它可以在我的本地计算机上运行,​​但在部署后却无法运行。 我有点被误导了。

事实证明,我的Lambda函数未打开RDS入站源。 在Stack Overflow找到解决方案:将RDS入站源更改为0.0.0.0/0或使用VPC。

更新RDS入站源之后,我可以成功使用Lambda与Knex。

我正在使用的Lambda运行时是Node.js 8.10包含以下软件包:

knex: 0.17.0
pg: 7.11.0

下面使用异步的代码也可以正常工作

const Knex = require('knex');

const pg = Knex({ ... });

module.exports. submitForm = async (event) => {
  const {
    fields,
  } = event['body-json'] || {};

  return pg('surveys')
    .insert(fields)
    .then(() => {
      return {
        status: 200
      };
    })
    .catch(err => {
      return {
        status: 500
      };
    });
};

希望它将对将来可能遇到相同问题的人们有所帮助。

我想引起人们注意的是serverless-mysql程序包,该程序包包装了标准的mysql驱动程序,但处理了该线程中描述的围绕连接池管理的许多lambda特定的痛点。

但是我不认为Knex目前不能使用serverless-mysql (根据此问题),因为无法交换其他驱动程序。 由于serverless-mysql使用promise而不是回调,因此也可能存在不兼容性。

最好的方法可能是在Knex中添加新的客户端实现。 我很乐意尝试一下,但是想请一个更熟悉Knex的人告诉我他们是否认为这是合理/可行的?

另外,在思考的同时,我可以使用Knex _build_但不能执行MySQL查询。 因此,只需调用toSQL()并将输出传递给serverless-mysql即可执行。

我想知道的是,是否可以在没有任何数据库连接的情况下配置Knex? 打开从未使用过的连接是没有意义的。

以下工作有效吗?

connection = {..., pool: { min: 0, max: 0 ) },

@disbelief您可以在不连接的情况下初始化knex。 在本节末尾的文档https://knexjs.org/#Installation -client中有一个示例

const knex = require('knex')({client: 'mysql'});

const generatedQuery = knex('table').where('id',1).toSQL().toNative();

@elhigu啊,很酷,谢谢。 在过渡期间会给予帮助。

更新:以上方法似乎无效。 如果Knex无法建立db连接,即使没有任何调用执行查询,它也会引发错误。

@disbelief是否找到了解决方案?

@fdecampredon不。 目前,我只是使用squel构建查询,对它们调用toString() ,然后将其传递给serverless-mysql客户端。

@disbelief令我惊讶的是,没有数据库连接,构建查询失败。

您介意发布用于构建knex客户端的代码和配置吗? 还有knex版本。

以下对我来说很好用
节点v8.11.1
mysql :2.13.0
knex :0.18.3

const k = require('knex')

const client = k({ client: 'mysql' })

console.log('Knex version:', require('knex/package.json').version)
// => 0.18.3
console.log('sql:', client('table').where('id',1).toSQL().toNative())
// => { sql: 'select * from `table` where `id` = ?', bindings: [ 1 ] }

根据Lambda的资源回收工作方式,应始终将Knex实例保留在任何函数或类的外部。 另外,重要的是池中的连接数最多为1。

就像是:

const Knex = require('knex');
let instance = null;

module.exports = class DatabaseManager {
  constructor({ host, user, password, database, port = 3306, client = 'mysql', pool = { min: 1, max: 1 }}) {
    this._client = client;
    this._poolOptions = pool;
    this._connectionOptions = {
      host: DB_HOST || host,
      port: DB_PORT || port,
      user: DB_USER || user,
      password: DB_PASSWORD || password,
      database: DB_NAME || database,
    };
  }

  init() {
    if (instance !== null) {
      return;
    }

    instance = Knex({
      client: this._client,
      pool: this._poolOptions,
      connection: this._connectionOptions,
      debug: process.env.DEBUG_DB == true,
      asyncStackTraces: process.env.DEBUG_DB == true,
    });
  }

  get instance() {
    return instance;
  }
}

通过这种方式,您可以最大程度地利用Lambda的回收利用,即每个激活(冻结)的容器将仅保持相同的连接。

附带说明一下,请记住,如果您不限制并发容器的数量,则Lambda默认情况下会向外扩展。

我已经让Knex在Lambda中运行了将近一年,没有任何问题。 我在Lambda函数之外声明了我的Knex实例,并使用了其他帖子中提到的context.callbackWaitsForEmptyEventLoop = false

也就是说,在过去的一天中,Lambda方面似乎有所改变,因为我现在发现Postgres中的连接数量激增。 连接似乎没有关闭。

使用上述方法的其他人在过去的一天左右有看到任何机会吗?

@jamesdixon刚刚阅读了这篇文章,同时重构了我们在lambda上的一些knex实现。 有任何更新吗? context.callbackWaitsForEmptyEventLoop = false停止工作?

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

lanceschi picture lanceschi  ·  3评论

hyperh picture hyperh  ·  3评论

fsebbah picture fsebbah  ·  3评论

rarkins picture rarkins  ·  3评论

marianomerlo picture marianomerlo  ·  3评论