【问题标题】:Having "Timeout acquiring a connection" after upgrading Knex升级 Knex 后出现“获取连接超时”
【发布时间】:2019-12-07 08:25:15
【问题描述】:

在我的公司,我们的应用程序通过多个 EC2 实例和一个 RDS 数据库在 NodeJS 上运行。

我们的应用程序需要一些升级,因为一些依赖项已经很旧了,我们所做的其中一项引起我们注意的升级是更新我们的数据库库:mysql(从 2.16.0 到 2.17.0)、knex(从 0.12. 2 到 0.19.1)和书架(0.10.2 到 0.15.1)。

检查变更日志后,不需要更改任何代码,因此我们很快设法将其上传到我们的暂存服务器。

我们的应用程序突然变得太慢了。所有数据都需要几秒钟才能加载,而我们主要用户的仪表板在同一台服务器上加载只需几毫秒,大约需要 30 秒。几分钟后,整个应用程序完全没有响应。

为了检查问题是否仅与依赖项升级有关,我们已设法将其降级到工作版本,并且应用程序恢复正常速度。又升级了,又慢了。

我们已经开始通过 New Relic 分析 RDS 方面是否存在问题。什么都没有。没有峰值,没有高 CPU 使用率,没有缓慢的查询或其他任何事情。然后我们来检查连接池,发现适合我们的 knex 版本使用“generic-pool”,而新版本使用“tarn”。

所以我们开始调试池,发现它被指定的查询填满,完全冻结了一段时间,然后开始抛出“TimeoutError:Knex:超时获取连接。池可能已满”错误。

但是,填充所有池并冻结的查询最有趣的是,它根本不应该生成(并且在使用不存在此问题的过时版本时不会生成)。

在我们的应用程序中,我们只在两种情况下对联系人表执行 SELECT 请求:

首先,显然是当用户想要列出他们的联系人时:

let contacts = await Contacts.forge({ 'list_owner': udata.id }).fetchAll()

其次,在检查联系人匹配以判断某些信息是否应该对指定用户可见时,具体取决于信息所有者的隐私设置:

let checkContact = await Contacts.where({
        list_owner: target_user,
        contact: udata.id
}).fetch()

经过几次 grepping,我可以保证我们的代码库中没有其他地方可以从联系人表中选择。在我们的调试中,我们没有发现未定义的值,并且我们的调查显示查询在之前的代码运行时运行。但正如您在屏幕截图中看到的那样,查询 knex 运行没有条件:

select `contacts`.* from `contacts`

我们认为这就是它填满池的原因(因为请求每个用户的联系人是一项艰巨的工作),但同时,我们看不出 knex 为何运行这样的查询,因为:

  • knex 升级后未进行任何代码更改
  • 使用旧 knex 版本时不存在问题(我们的生产服务器已启动并使用过时的 knex 版本运行)
  • 我们使用 Redis 进行了大量缓存(但无论如何,数据库没有过载,旧的 Knex 版本可以正常工作)
  • 如果问题确实是缺少条件,我们可以在之前发现它,因为每个用户都会看到相同的联系人列表。

什么可能导致这样的问题?

【问题讨论】:

  • 您是否在 Knex/MySQL 中创建了问题?似乎它可能是一个错误。
  • 书架也可能发生了某种变化。您应该尝试找出导致此问题的确切依赖关系,否则很难猜测。
  • 是的,我会将所有模块回滚并一次独立升级一个,以确定是哪个模块导致了问题。
  • 谢谢各位。我们将按模块进行,并获取可能的错误报告所需的信息。会及时通知大家。
  • 您需要隔离导致该行为的原因,并从代码的这些部分创建单个文件测试用例。这样一来,代码的问题就很明显了。如果没有隔离,我确信这至少不是由 knex 中的错误引起的。

标签: mysql node.js amazon-rds knex.js bookshelf.js


【解决方案1】:

对于一些可能落在这里的人!

如果这没有意义,并且您最近将 nodejs 升级到 v14!这可能是原因!

我有问题,上次拉了我的头发安静了一次! 在尝试跟踪我所做的不同之后,我以某种方式使用了 nvm!因为它以前工作过!我想到了它并使用 v13 对其进行了测试!它又成功了!

所以请注意!可能就是这样,可以为您节省大量时间和压力!

问题

Nodejs v14 做出了重大改变!并且pg 模块受到影响! pg 开始在connect() call 处退出进程!

修复节点 v14+

如果您使用的是postgres!使用 nodejs v14 及更高版本!确保使用版本>=8.0.3的驱动模块pg!并更好地升级到最新版本

npm install pg@latest --save

如果你没有使用postgres!尝试更新您的数据库驱动程序!可能是一样的!也可以尝试使用 nodejs V13。确认是同一个问题!

v14 发生了什么

如果你像我一样想知道细节和发生了什么!?

使用节点 V14! api上发生了一些重大变化!也改变了很多东西!包括Openssl版本!

对于postgres!还有pg 模块!这个问题在这个comment 中描述过这个thread

初始的 readyState(一个私有/未记录的 API,

pg uses) 的 net.Socket 似乎已从“关闭”变为“打开” 在节点 14 中。

完美的向后兼容性很难解决,但我想我 有一个足够接近的补丁。

按照这个PR

你可以在this diffing看到变化

总之如前所述! onReady 的 api 更改为 net.Socket ! 而实施的解决方案是根本不使用 onReady!

按照这个

Connection 现在总是在其流上调用 connect 时调用它。

在旧版本中,仅当套接字处于closed 状态时才调用连接! readyState 的使用被淘汰了!

查看this line

你可以理解!

取决于实施!许多事情可能会或不会受到这些核心变化的影响!

Nodejs v14 相关改动

因为我想看看变化发生在哪里!给你

https://github.com/nodejs/node/pull/32272

也可以查看更改日志:

https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V14.md

详细原因 + 退出且没有记录错误

还要提一下重大变化!使pg 使进程退出connect() call。这就是让它退出的原因!并且要看到日志记录! 对此更详细!这是怎么回事! Sequelize 有 postgres 方言实现!哪个用pg!还有pg客户端!创建连接!该连接有一个connect 事件!当它连接时它会发出它!并且因为 node v14 将流的行为更改为以 open!流连接被跳过!因为readyState 检查(预计关闭,但改为打开!)!并且流被视为已连接(其他块)!哪里不是!并且直接发出connect 事件!什么时候发生!客户端将调用连接对象的requestSsl()startup() 方法!两者都会打电话给this._stream.write。因为流没有连接!发生错误!此错误未捕获!然后是sequelize driver中的promise!将一直悬而未决!然后事件循环变空! Nodejs 默认行为只是退出!

您可以通过代码行看到该步骤:

为什么 nodejs 退出(未解决的承诺)

https://github.com/nodejs/node/issues/22088

Node exits without error and doesn't await promise (Event callback)

what happens when a Promise never resolves?

【讨论】:

    【解决方案2】:

    您必须在查询执行后销毁连接。

     var knex = new Knex(config)
      knex(table)
        .where({ id: 1 })
        .then((result) => {
          callback(output)
        })
        .catch((err) => {
          err.error = true
          callback(err)
        })
        .finally(() => {
          knex.destroy()
        })
    })
    

    【讨论】:

    • 不,你根本没有
    猜你喜欢
    • 2020-11-04
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 2021-09-21
    • 1970-01-01
    • 1970-01-01
    • 2016-02-14
    相关资源
    最近更新 更多