在哪里破坏knex连接

where to destroy knex connection

我正在使用 knex with pg

我有一个类似下面的项目。

dbClient.js

const dbClient = require('knex')({
  client: 'pg',
  connection: {
    host: '127.0.0.1',
    user: 'user',
    password: 'password',
    database: 'staging',
    port: '5431'
  }
})

module.exports = dbClient

libs.js

const knex = require('./dbClient.js')

async function doThis(email) {
  const last = await knex('users').where({email}).first('last_name').then(res => res.last_name)
  // knex.destroy()
  return last
}

async function doThat(email) {
  const first = await knex('users').where({email}).first('first_name').then(res => res.first_name)
  // knex.destroy()
  return first
}

module.exports = {
  doThat,
  doThis
}

test01.js

const {doThis, doThat} = require('./libs.js');

(async () => {
  try {
    const res1 = await doThis('user53@gmail.com')
    console.log(res1)
    const res2 = await doThat('user53@gmail.com')
    console.log(res2)
  } catch (err) {
    console.log(err)
  }
})()

knex.destroy()libs.js 中删除时,如上所示。 node test01 可以输出 res1res2。但问题是连接无限期挂起,CMD 永远不会 return.

但是如果我从 libs.js 中取消注释 knex.destroy(),那么 doThis 将执行,CMD 将在 doThat 处挂起,因为在 doThis.

我的问题是:

knex.destroy() 的最佳位置是哪里?或者还有其他方法吗?

感谢您的宝贵时间!

您可能通常不需要显式调用 knex.destroy() – 文档本身暗示了这一点(强调我的):

If you ever need to explicitly teardown the connection pool, you may use knex.destroy([callback]).

Knex destroy() 似乎是一次性手术。销毁连接后,可能需要一个全新的连接池才能进行下一次操作。

您导出的数据库客户端模块 is cached 到节点模块缓存中,并且不会在您每次需要时创建新的连接池。

这是预期用途,池应该在应用程序退出或所有测试完成时销毁。如果你有理由为每个操作 create/destroy 连接(比如在无服务器环境中),你不应该重用被破坏的客户端,而是每次都创建一个新实例。

否则,就达不到连接池的目的了。


关于 lambda/server-less 环境的更新

从技术上讲,函数及其资源将在 lambda 函数 运行 之后释放,这包括它可能已打开的任何连接。这对于真正的无状态函数是必需的。因此,建议在功能完成后关闭连接。但是,很多函数 opening/closing 很多连接最终可能会使数据库服务器 运行 失去连接(请参阅协商数据库服务器和 Lambda 函数之间的连接的 this discussion for example). One solution might be to use an intermediate pool like PgBouncer or PgPool

另一种方式是平台提供商 (AWS) 为 lambda 环境添加特殊的池化功能,让它们共享长期存在的资源。

在每次查询后销毁连接就像在每次弹奏音符时收拾吉他。演出一开始就拉出来,把所有的歌都放完,收起来。

同样,在为应用程序的其余部分完成连接后销毁连接,而不是在每次这​​样的查询之后。在 Web 服务器中,这可能永远不会发生,因为您将在某个不确定的点用信号终止它,并且在此之前应用程序可能需要活动连接。

对于测试,您可能希望使用 destroy 函数来避免挂起。同样,在您展示的(人为的?)应用程序中,如果遇到挂起并且应用程序卡住,请在完成连接后销毁一次连接。

这是 Mocha 的一个说明性示例,它在评论中被提及并且似乎是一个非常合理的假设,即它(或类似的东西)正在被结束在这个线程中的人们使用。在所有测试之前设置、在所有测试之后拆除以及进行 per-test 案例设置和拆除的模式是通用的。

与您的问题相关,after(() => knex.destroy()); 是所有测试结束时的拆卸调用。没有这个,摩卡挂起。请注意,我们还关闭了每个测试的 http 服务器,因此有多个候选挂起测试套件需要注意。

server.js:

const express = require("express");

const createServer = (knex, port=3000) => {
  const app = express();
  
  app.get("/users/:username", (request, response) => {
    knex
      .where("username", request.params.username)
      .select()
      .first()
      .table("users")
      .then(user => user ? response.json({data: user})
                         : response.sendStatus(404))
      .catch(err => response.sendStatus(500))
  });
  
  const server = app.listen(port, () =>
    console.log(`[server] listening on port ${port}`)
  );
  return {
    app,
    close: cb => server.close(() => {
      console.log("[server] closed");
      cb && cb();
    })
  };
};
module.exports = {createServer};

server.test.js:

const chai = require("chai");
const chaiHttp = require("chai-http");
const {createServer} = require("./server");
const {expect} = chai;
chai.use(chaiHttp);
chai.config.truncateThreshold = 0;

describe("server", function () {
  this.timeout(3000);
  let knex;
  let server;
  let app;
  
  before(() => {
    knex = require("knex")({
      client: "pg",
      connection: "postgresql://postgres@localhost",
    });
  });
  beforeEach(done => {
    server = createServer(knex);
    app = server.app;
    knex
      .schema
      .dropTableIfExists("users")
      .then(() => 
        knex.schema.createTable("users", table => {
          table.increments();
          table.string("username");
        })
      )
      .then(() => knex("users").insert({
        username: "foo"
      }))
      .then(() => done())
      .catch(err => done(err));
  });
  afterEach(done => server.close(done));
  after(() => knex.destroy());
  
  it("should get user 'foo'", done => {
    chai
      .request(app)
      .get("/users/foo")
      .then(response => {
        expect(response.status).to.equal(200);
        expect(response).to.be.json;
        expect(response.body).to.be.instanceOf(Object);
        expect(response.body.data).to.be.instanceOf(Object);
        expect(response.body.data.username).to.eq("foo");
        done();
      })
      .catch(err => done(err));
  });
});

套餐:

"knex": "0.21.6",
"express": "4.17.1",
"mocha": "8.0.1",
"pg": "8.3.0",    
"node": "12.19.0"