Mongoose: 回调停止发生

创建于 2016-09-13  ·  53评论  ·  资料来源: Automattic/mongoose

我已经使用mongoose几年了,但最近我遇到了很多问题,连接似乎消失了。

连接设置

// Connecting to MongoDB 3.2.1
var connString = "mongodb://xxxxx:xxxxx<strong i="8">@xxxxx</strong>:xxxx,xxxx:xxxx/xxx?ssl=true";
var connOptions = {
  "mongos": { "ssl": true, "sslValidate": false }
};
mongoose.connect(connString, connOptions, function(err, db) {
  if(err) { console.log("Error connecting to db: "+err); }
});

问题

猫鼬只是停止回电。 它不会发出任何错误,我的服务器和数据库上的内存/cpu 很好,我可以从任何其他服务器连接。 重新启动总是修复它:

db.Something.count({}, callback);

版本

mongoose 4.4.11 ,当回调停止时mongoose.connection.readyState === UNAUTHORIZED

所以我升级mongoose 4.6.0但在 4 小时内回调再次停止,现在是mongoose.connection.readyState === CONNECTED

有任何想法吗

我有什么想法可以让这种情况停止发生吗?

我很高兴收集更多调试信息,只要让我知道要获取什么信息即可。

underlying library issue

最有用的评论

不幸的是,由于 mongodb 驱动程序无法发布,因此不得不再次将其推回。 很抱歉,感谢您的耐心等待 :+1: 🌴

所有53条评论

您是连接到独立的、副本集还是分片集群? 另外,您能否确认您正在使用 SSL?

这几天对我来说是个大问题。 每个正在运行的应用程序都以越来越慢的事件循环结束,从而导致整个系统的延迟增加。 每次重新启动 Node 应用程序都解决了该问题,并且在运行几分钟或几小时后它又会再次出现。

恢复到 mongoose 4.5.10 解决了这个问题。

  • 连接到副本集
  • 使用 SSL

与 4.6.0 类似的问题,我不确定我是否连接到副本集,但肯定使用 SSL。

FWIW,4.6.0 不断断开连接。 4.5.10 不错。

@djanowski您能否提供更多信息,例如您的连接代码是什么样的以及您拥有什么版本的 mongodb?

@vkarpov15我使用的是 MongoDB 3.2.6。

连接选项:

auth: {
  authMechanism: 'SCRAM-SHA-1',
},
mongos: {
  ssl: true,
  sslValidate: true,
  sslCA: [cert],
},

单个 mongos 还是多个 mongos?

多个mongos。 使用 Compose Mongo 部署。

我还能做些什么来帮助解决此问题? 这阻碍了我们转向更新的版本......

在没有看到您的代码的情况下,我很难做出任何真正的假设。 如果您使用mongoose.connect连接,我会在此问题开始发生时检查mongoose.connection.readyState的值。 另外,我会启用 mongoose 调试模式mongoose.set('debug', true) ,它将为每个 mongoose 操作将调试消息打印到控制台。 这些消息是否正在打印肯定有助于调试。

@vkarpov15这是我用来连接的代码: https : //gist.github.com/sommestad/c0c6a7fa4feaadf84ecbb59cc3432c90
它是私有模块的一部分,因此是 Gist。

_(连接包装模块的原因是该连接偶尔会在其他私有 NPM 模块中重用,这在历史上与 mongoose 没有很好的配合。)_

当这些问题发生时,我启用了连接状态的日志记录,但连接本身似乎没有改变(至少没有发出任何事件)。 因此,客户端似乎没有断开连接。

它没有与 URI 中的replicaSet连接(目标数据库是Compose Deployment )。 如前所述,使用了 SSL。 此外,流量通过 AWS NAT 网关进行路由。 AWS NAT 有 5 分钟的空闲超时,但即使在高流量期间也会出现这些问题。

对我来说有些问题,你解决了吗?

@youth7没有想法。 您是否还连接到组合部署? 请提供更多信息

我还能做些什么来尝试找出造成这种情况的原因? 有什么理论可以研究吗?

@sommestad没什么具体的。 你有任何来自 compose 的日志吗?

我尝试了mongoose 4.7.2但在 48 小时后遇到了同样的问题。 在这一点上,我对4.4.11感到束手无策,它有自己的问题。

我在我们的演示服务器上安装了4.7.2希望收集日志,但它已经运行了两周没有问题。 也许数据/连接/等的数量会引发问题?

一旦它开始发生,重现它的代码非常简单。 基本上任何查询的回调都会消失:

db.Something.find({}, callback); //this callback is never fired

正如我提到的,当这种情况发生时, mongoose.connection.readyState === CONNECTED

奇怪的。 我的意思是 mongodb 日志 - 您可以从发生该错误时转储 mongodb 服务器日志吗?

发生此故障时,我扫描了数据库日志,但看不到任何异常情况。 没有与连接数、断开连接、连接问题或类似问题相关的问题。 我还联系了 Compose,他也看不到任何异常。

@bendytree你能在那个演示服务器上扔一些卷吗,也许使用 HTTP 负载测试工具?

@bendytree另外,它是否发生在 Node(4.x、6.x 和 7.x)的所有发行版上?

@vkarpov15我们能否推出 4.7.7,以便我可以使用最新的 MongoDB 驱动程序进行测试? 4.7.6 被固定到一个似乎已被弃用的版本。

@vkarpov15 66d559b19a86c70e30a8f083d03eb22566571b7e 谢谢! 我试试看。

同样在这里,这里有一个 SSL 副本,几分钟/几小时后,请求就停止到 callback() (我添加了一些更详细的日志记录)并且没有错误。 我昨晚已升级到 4.7.7,但仍有问题。

编辑:我试图降级到 4.7.0,但它是最糟糕的(在不到 5 分钟的时间内出现问题)。 我正在尝试 4.5.10。

@gierschv节点 6.9.x?

@djanowski不,仍在 4.6/4.7.x LTS 上。

@djanowski我只在 Node 4.4.2 上试过

保留此问题的可能重复列表:

  • #4638
  • #4690
  • #4901

仅供参考,4.5.10 在这里似乎运行良好,过去 5 小时没有问题。 我在所有机器上的 CPU 使用率也很高,如 #4690,这似乎也被降级修复了。

这里没有新进展? 我们还能做些什么来帮助找到错误?

@sommestad你对最新的猫鼬还有这个问题吗?

我还不敢升级,因为上次它基本上破坏了我们的整个系统(更改出现在“一段时间”之后,因此如果不长时间运行就很难验证)。 如果你有理由相信它是固定的,我们可以尝试系统中更孤立的部分吗?

编辑:ping @varunjayaraman

就我而言,几周前我们切换到 Node 6.x LTS 并升级到最后一个 mongoose 版本,此后没有问题。

大约两个月前,我们升级到 Node 6.10.2、Mongoose 4.9.9。 仍然看到同样的问题。

我们的连接字符串的格式mongodb://username:[email protected]:1234,db2.com:2345/db-name?ssl=true

console.log("fetching user...");
userCollection.findOne({_id: userId}, function(err, user){
  console.log("done"); // this never happens
  ...
});

一旦回调停止,它们就再也不会开始。 如果我们重新启动该过程,那么它连接正常。

如果我们可以收集任何数据来帮助排除故障,那么我们很乐意提供帮助。

我看到了同样的问题,可以用下面的代码重现它(抱歉,快速模型/代码风格)。

为了模拟连接超时,我将 poolSize 降低到 1 并触发对 2 个端点(正常/慢速)的请求,这些端点按以下顺序调用:

  1. 正常端点(立即返回结果)
  2. 慢端点(将启动慢查询)
  3. 普通端点(它将等待可用连接,因为它已被 2 使用)
  4. 正常端点(将在一分钟后触发,当 2 和 3 导致 MongoError / 连接超时时)

使用:

// Require modules
const mongoose = require('mongoose');
const express = require('express');
const request = require('request');
const parseJson = require('body-parser').json();

// Use native Promises in Mongoose
mongoose.Promise = Promise;

// Create the test schema
const testSchema = new mongoose.Schema({
  name: {
    type: String,
    trim: true,
    default: 'test'
  }
});

// Create the mongoose model
mongoose.model('Test', testSchema, 'test');

// Create some test data
mongoose.model('Test').create([
  {name: 'test1'},
  {name: 'test2'},
  {name: 'test3'},
  {name: 'test4'},
  {name: 'test5'}
]);

// Create the Express based app
const app = express();

// Create the express router
const router = express.Router();

// Create a normal route
router.route('/normal').get(parseJson, function (req, res, next) {
  mongoose.model('Test').find({}).then(
    function (data) {
      res.status(200).json(data);
    }
  ).catch(
    function (err) {
      res.status(400).json(err);
    }
  );
});

// Create up a slow route
router.route('/slow').get(parseJson, function (req, res, next) {
  mongoose.model('Test').find({$where: 'sleep(10000) || true'}).then(
    function (data) {
      res.status(200).json(data);
    }
  ).catch(
    function (err) {
      res.status(400).json(err);
    }
  );
});

// Add middleware to console log every request
var requestNumber = 1;
app.use(function (req, res, next) {
  console.log('Request ' + requestNumber, req.method, req.url);
  requestNumber++;
  next();
});

// Use the router
app.use('/', router);

// Listen for requests
app.listen(4000, function () {
  console.log('Server listening on port 4000');
});

// Catch any uncaught exceptions
process.on('uncaughtException', function (err) {
  console.log('Uncaught exception', err);
});

// Catch any unhandled rejections
process.on('unhandledRejection', function (reason) {
  console.error('Unhandled Rejection', reason);
});

// Database connection options
const connectionOpts = {
  // Use the new connection logic
  useMongoClient: true,

  // Do not auto reconnect (the Node code will auto reconnect)
  autoReconnect: false,

  // Use a poolsize of 1 to simulate a timeout using multiple request to slow and normal endpoints
  poolSize: 1
};

// Connection method with retry
const connectWithRetry = function () {
  // Check if we still need to connect
  if (mongoose.connection.readyState !== mongoose.Connection.STATES.connected) {
    // Connect
    return mongoose.connect('mongodb://localhost:27017/test', connectionOpts).catch(
      function (err) {
        console.log('Can not connect to mongo', err);
      }
    );
  }
};

// Helper function to execute a request to the normal endpoint
const doRequestNormal = function () {
  request({
    url: 'http://localhost:4000/normal',
    json: true,
    timeout: 60000
  }, function (error, response, body) {
    if (error) {
      console.log('Error on normal request', error, response);
    }
    console.log('Performed normal request, body:', body);
  });
};

// Helper function to execute a request to the normal endpoint
const doRequestSlow = function () {
  request({
    url: 'http://localhost:4000/slow',
    json: true,
    timeout: 60000
  }, function (error, response, body) {
    if (error) {
      console.log('Error on slow request', error, response);
    }
    console.log('Performed slow request', body);
  });
};

// Helper function to simulate requests
const doRequests = function () {
  doRequestNormal();
  doRequestSlow();
  doRequestNormal();

  setTimeout(function () {
    console.log('Do normal request after 1 minute');
    doRequestNormal();
  }, 60000);
};

// Connecting event
mongoose.connection.on('connecting', function () {
  console.log('Connecting to database...');
});

// Connected event
mongoose.connection.on('connected', function () {
  console.log('Database is connected, start requesting...');
  doRequests();
});

// Timeout event
mongoose.connection.on('timeout', function (err) {
  console.log('Database timeout error', err);
});

// Error event
mongoose.connection.on('error', function (err) {
  console.log('Database connection error', err);
});

// Disconnected event
mongoose.connection.on('disconnected', function () {
  console.error('Database got disconnected, reconnecting in 5 sec...');

  // Reconnect in 5 seconds
  setTimeout(connectWithRetry, 5000);
});

// Connect to the database with retry
connectWithRetry();

// Log the connection state every 10 seconds
setInterval(function () {
  console.log('Mongoose connection readystate is', mongoose.connection.readyState);
}, 10000);

控制台输出如下:

Connecting to database...
Server listening on port 4000
Database is connected, start requesting...
Request 1 GET /normal
Request 2 GET /slow
Request 3 GET /normal
Performed normal request, body: [ { _id: '597f8c6f41bf2e119594ba1a', __v: 0, name: 'test1' },
  { _id: '597f8c6f41bf2e119594ba1b', __v: 0, name: 'test2' },
  { _id: '597f8c6f41bf2e119594ba1c', __v: 0, name: 'test3' },
  { _id: '597f8c6f41bf2e119594ba1d', __v: 0, name: 'test4' },
  { _id: '597f8c6f41bf2e119594ba1e', __v: 0, name: 'test5' } ]
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Performed slow request { name: 'MongoError',
  message: 'connection 0 to localhost:27017 timed out' }
Performed normal request, body: { name: 'MongoError',
  message: 'connection 0 to localhost:27017 timed out' }
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Do normal request after 1 minute
Request 4 GET /normal
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Error on normal request { Error: ESOCKETTIMEDOUT
    at ClientRequest.<anonymous> (/Users/adriaanmeuris/Documents/Projecten/Mongoose timeout/node_modules/request/request.js:819:19)
    at ClientRequest.g (events.js:291:16)
    at emitNone (events.js:86:13)
    at ClientRequest.emit (events.js:185:7)
    at Socket.emitTimeout (_http_client.js:620:10)
    at Socket.g (events.js:291:16)
    at emitNone (events.js:86:13)
    at Socket.emit (events.js:185:7)
    at Socket._onTimeout (net.js:339:8)
    at ontimeout (timers.js:365:14)
    at tryOnTimeout (timers.js:237:5)
    at Timer.listOnTimeout (timers.js:207:5) code: 'ESOCKETTIMEDOUT', connect: false } undefined
Performed normal request, body: undefined
Mongoose connection readystate is 1
Mongoose connection readystate is 1

我每 10 秒监视一次数据库的 readyState,这表明连接仍然处于活动状态。 没有Mongoose错误/超时事件触发,并且从不调用请求4的回调(使用请求包60s后超时)。 您还可以通过访问http://localhost :4000/slow 和下一个http://localhost :4000/normal 进行测试。

我正在使用基于https://team.goodeggs.com/reconnecting-to-mongodb-when-mongoose-connect-fails-at-startup-83ca8496ca02 的自动重新连接逻辑,但这永远不会触发,因为 mongo 不会断开连接。

在某些情况下,当我评论创建一些测试数据的代码时,我无法重现(所以基本上是查询一个空集合)

可能的相关问题:

  • #4789
  • #4660

任何帮助都会让我不胜感激。

所以我做了更多的挖掘并重新编写了上面的脚本,以便在不使用 mongoose 的情况下使用 node-mongodb-native。 现在的结果大不相同:

连接设置和请求是一样的:

  1. 正常端点(立即返回结果)
  2. 慢端点(将启动慢查询)
  3. 普通端点(它将等待可用连接,因为它已被 2 使用)
  4. 正常端点(将在一分钟后触发)

代码:

// Require modules
const MongoClient = require('mongodb').MongoClient;
const Server = require('mongodb').Server;
const express = require('express');
const request = require('request');
const parseJson = require('body-parser').json();

// The db reference
var db;

// Function to insert test data
function insertTestData() {
  return new Promise(function (resolve, reject) {
    // Get the test collection
    var test = db.collection('test');

    // Insert some documents
    test.insertMany([
      {name: 'test1'},
      {name: 'test2'},
      {name: 'test3'},
      {name: 'test4'},
      {name: 'test5'}
    ], function (err, result) {
      if (err) {
        reject(err);
      } else {
        resolve();
      }
    });
  });
}

// Create the Express based app
const app = express();

// Create the express router
const router = express.Router();

// Create a normal route
router.route('/normal').get(parseJson, function (req, res, next) {
  // Get the documents collection
  var collection = db.collection('test');

  // Find some documents
  collection.find({}).toArray(function (err, data) {
    if (err) {
      res.status(400).json(err);
    } else {
      res.status(200).json(data);
    }
  });
});

// Create up a slow route
router.route('/slow').get(parseJson, function (req, res, next) {
  // Get the documents collection
  var collection = db.collection('test');

  // Find some documents
  collection.find({$where: 'sleep(10000) || true'}).toArray(function (err, data) {
    if (err) {
      res.status(400).json(err);
    } else {
      res.status(200).json(data);
    }
  });
});

// Add middleware to console log every request
var requestNumber = 1;
app.use(function (req, res, next) {
  console.log('Request ' + requestNumber, req.method, req.url);
  requestNumber++;
  next();
});

// Use the router
app.use('/', router);

// Listen for requests
app.listen(4000, function () {
  console.log('Server listening on port 4000');
});

// Catch any uncaught exceptions
process.on('uncaughtException', function (err) {
  console.log('Uncaught exception', err);
});

// Catch any unhandled rejections
process.on('unhandledRejection', function (reason) {
  console.error('Unhandled Rejection', reason);
});

// Database connection options
const connectionOpts = {
  // Do not auto reconnect (the Node code will auto reconnect)
  autoReconnect: false,

  // Use a poolsize of 1 to simulate a timeout using multiple request to slow and normal endpoints
  poolSize: 1
};

// Connection method with retry
const connectWithRetry = function () {

  return MongoClient.connect('mongodb://localhost:27017/test', connectionOpts).then(
    function (database) {
      db = database;
      setupEvents();
      console.log('Connected to mongo');
    }
  ).catch(
    function (err) {
      console.log('Can not connect to mongo', err);
      return err;
    }
  );
};

// Helper function to execute a request to the normal endpoint
const doRequestNormal = function () {
  request({
    url: 'http://localhost:4000/normal',
    json: true,
    timeout: 60000
  }, function (error, response, body) {
    if (error) {
      console.log('Error on normal request', error, response);
    }
    console.log('Performed normal request, body:', body);
  });
};

// Helper function to execute a request to the normal endpoint
const doRequestSlow = function () {
  request({
    url: 'http://localhost:4000/slow',
    json: true,
    timeout: 60000
  }, function (error, response, body) {
    if (error) {
      console.log('Error on slow request', error, response);
    }
    console.log('Performed slow request', body);
  });
};

// Helper function to simulate requests
const doRequests = function () {
  doRequestNormal();
  doRequestSlow();
  doRequestNormal();

  setTimeout(function () {
    console.log('Do normal request after 1 minute');
    doRequestNormal();
  }, 60000);
};

// Helper function to setup mongo events
function setupEvents() {
// Connecting event
  db.on('connecting', function () {
    console.log('Connecting to database...');
  });

// Connected event
  db.on('connected', function () {
    console.log('Database is connected, start requesting...');
  });

// Timeout event
  db.on('timeout', function (err) {
    console.log('Database timeout error', err);
  });

// Error event
  db.on('error', function (err) {
    console.log('Database connection error', err);
  });

// Disconnected event
  db.on('close', function () {
    console.error('Database got disconnected, reconnecting in 5 sec...');

    // Reconnect in 5 seconds
    setTimeout(connectWithRetry, 5000);
  });
}

// Connect to the database with retry
connectWithRetry().then(
  function () {
    return insertTestData();
  }
).then(
  function () {
    doRequests();
  }
);

控制台输出如下:

Server listening on port 4000
Connected to mongo
Request 1 GET /normal
Request 2 GET /slow
Request 3 GET /normal
Performed normal request, body: [ { _id: '598207c16caf9224cf3b8897', name: 'test1' },
  { _id: '598207c16caf9224cf3b8898', name: 'test2' },
  { _id: '598207c16caf9224cf3b8899', name: 'test3' },
  { _id: '598207c16caf9224cf3b889a', name: 'test4' },
  { _id: '598207c16caf9224cf3b889b', name: 'test5' } ]
Performed slow request [ { _id: '598207c16caf9224cf3b8897', name: 'test1' },
  { _id: '598207c16caf9224cf3b8898', name: 'test2' },
  { _id: '598207c16caf9224cf3b8899', name: 'test3' },
  { _id: '598207c16caf9224cf3b889a', name: 'test4' },
  { _id: '598207c16caf9224cf3b889b', name: 'test5' } ]
Performed normal request, body: [ { _id: '598207c16caf9224cf3b8897', name: 'test1' },
  { _id: '598207c16caf9224cf3b8898', name: 'test2' },
  { _id: '598207c16caf9224cf3b8899', name: 'test3' },
  { _id: '598207c16caf9224cf3b889a', name: 'test4' },
  { _id: '598207c16caf9224cf3b889b', name: 'test5' } ]
Do normal request after 1 minute
Request 4 GET /normal
Performed normal request, body: [ { _id: '598207c16caf9224cf3b8897', name: 'test1' },
  { _id: '598207c16caf9224cf3b8898', name: 'test2' },
  { _id: '598207c16caf9224cf3b8899', name: 'test3' },
  { _id: '598207c16caf9224cf3b889a', name: 'test4' },
  { _id: '598207c16caf9224cf3b889b', name: 'test5' } ]

没有像前面的脚本那样触发 Mongo 错误/超时事件,但请求 2 和 3 正确解析,并且请求 4 的回调也被触发 - 与使用 Mongoose 的脚本相反。

我试图排除这是否是 Mongoose 与 Mongo 的问题 - 所以任何关于比较这些脚本结果的帮助将不胜感激。 也许@vkarpov15可以加入这个? 谢谢!

@adriaanmeuris这很奇怪,这是我运行你的第一个脚本时得到的输出:

$ node gh-4513.js 
Connecting to database...
Server listening on port 4000
Database is connected, start requesting...
Request 1 GET /normal
Request 2 GET /slow
Request 3 GET /normal
Performed normal request, body: [ { _id: '5998e4a79bb81d37ac4b1a26', __v: 0, name: 'test1' },
  { _id: '5998e4a79bb81d37ac4b1a27', __v: 0, name: 'test2' },
  { _id: '5998e4a79bb81d37ac4b1a28', __v: 0, name: 'test3' },
  { _id: '5998e4a79bb81d37ac4b1a29', __v: 0, name: 'test4' },
  { _id: '5998e4a79bb81d37ac4b1a2a', __v: 0, name: 'test5' } ]
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Mongoose connection readystate is 1
Performed slow request [ { _id: '5998e4a79bb81d37ac4b1a26', __v: 0, name: 'test1' },
  { _id: '5998e4a79bb81d37ac4b1a27', __v: 0, name: 'test2' },
  { _id: '5998e4a79bb81d37ac4b1a28', __v: 0, name: 'test3' },
  { _id: '5998e4a79bb81d37ac4b1a29', __v: 0, name: 'test4' },
  { _id: '5998e4a79bb81d37ac4b1a2a', __v: 0, name: 'test5' } ]
Performed normal request, body: [ { _id: '5998e4a79bb81d37ac4b1a26', __v: 0, name: 'test1' },
  { _id: '5998e4a79bb81d37ac4b1a27', __v: 0, name: 'test2' },
  { _id: '5998e4a79bb81d37ac4b1a28', __v: 0, name: 'test3' },
  { _id: '5998e4a79bb81d37ac4b1a29', __v: 0, name: 'test4' },
  { _id: '5998e4a79bb81d37ac4b1a2a', __v: 0, name: 'test5' } ]
Mongoose connection readystate is 1
Do normal request after 1 minute
Request 4 GET /normal
Performed normal request, body: [ { _id: '5998e4a79bb81d37ac4b1a26', __v: 0, name: 'test1' },
  { _id: '5998e4a79bb81d37ac4b1a27', __v: 0, name: 'test2' },
  { _id: '5998e4a79bb81d37ac4b1a28', __v: 0, name: 'test3' },
  { _id: '5998e4a79bb81d37ac4b1a29', __v: 0, name: 'test4' },
  { _id: '5998e4a79bb81d37ac4b1a2a', __v: 0, name: 'test5' } ]
Mongoose connection readystate is 1
Mongoose connection readystate is 1
^C
$ 

猫鼬保持联系。 你能澄清一下你的 mongodb、node 和 mongoose 版本吗?

感谢测试! 事实上猫鼬总是保持连接,但回调停止发生(你可以看到请求进入 Express,但它们会超时)

我正在测试:

  • 节点 6.8.1
  • MongoDB 2.2.30
  • 猫鼬 4.11.5

我已经更新为:

  • 节点 6.8.1
  • MongoDB 2.2.31
  • 猫鼬 4.11.7

我仍然可以使用我发布的第一个脚本在本地重现该问题。 它仅在 Mongo 连接超时时发生,因此您可能需要更改模拟此的查询:

mongoose.model('Test').find({$where: 'sleep(10000) || true'})

(我不确定这是否会在每台机器上触发超时,因此可能需要对此查询进行更改才能重现)

更新到 mongoose 4.10.4 后,我们不再看到这些问题。
似乎有点奇怪,因为其他人似乎遇到了问题,但许多服务运行了几周,没有任何问题再次出现的迹象。 🤔

但是,我们确实以与之前@adriaanmeuris相同的方式看到了问题,因此可能是我们没有遇到任何超时。

运行节点 8。

我们仍然定期看到这个问题。

我无法使用@adriaanmeuris脚本重现它,但这里有一个修改后的版本,每次为我重现它:

Bendytree/猫鼬问题-4513

谢谢@bendytree ,用你的脚本我也可以重现:

7) Database connecting...
49) Database connected...
49) Dropping...
57) Creating...
156) FastQuery-A: starting...
166) FastQuery-A: success: 1
1158) SlowQuery: starting...
2158) FastQuery-B: starting...
362165) SlowQuery: failed: MongoError: connection 0 to localhost:27017 timed out
362167) FastQuery-B: failed: MongoError: connection 0 to localhost:27017 timed out
605159) FastQuery-C: starting...
1210149) Giving Up...

它本质上与我的脚本相同,但超时持续时间更长。 我想在本地模拟超时需要更多/更少的时间,具体取决于您的设置,因为不是每个人都可以使用我的脚本(使用 10 秒睡眠)重现。

@vkarpov15你能试试这个更新的脚本吗? 我已经用猫鼬 4.8.11 进行了测试和复制。

我设法用@bendytree重现,这是socketTimeoutMS太低的情况。 这

362591) SlowQuery: failed: MongoError: connection 0 to localhost:27017 timed out
362591) FastQuery-B: failed: MongoError: connection 0 to localhost:27017 timed out

消息指示 mongodb 驱动程序的套接字超时。 mongodb 驱动程序默认在 30 秒后超时,但您的操作系统也有一个您需要注意的套接字超时,因此通常最好避免超过 15 秒的查询。 但是,您可以使用socketTimeoutMS选项配置 mongodb 的套接字超时:

mongoose.connect(DB_URL, {
  useMongoClient: true,
  autoReconnect: false,
  poolSize: 1,
  socketTimeoutMS: 0
}).catch((err)=>{ log('Connect failed', err); });

将该选项设置为 0,您的脚本几乎可以在我的机器上成功,10 分钟的查询仍然会终止,但回调仍在继续

$ node gh-4513.js 
9) Database connecting...
43) Database connected...
43) Dropping...
53) Creating...
177) FastQuery-A: starting...
189) FastQuery-A: success: 1
1174) SlowQuery: starting...
2174) FastQuery-B: starting...
601181) SlowQuery: failed: MongoError: Interrupted by the host
601182) FastQuery-B: success: 1
605173) FastQuery-C: starting...
605174) FastQuery-C: success: 1

但后续查询成功。

无论哪种方式,猫鼬在这种情况下都应该发出超时事件,因此我们将解决该问题。

我认为您将 connectTimeoutMS(默认值为 30000 毫秒)与 socketTimeoutMS(默认值为 360000 毫秒)混淆了。

建议将 socketTimeoutMS 设置为通过驱动程序运行的最慢操作长度的两到三倍,将 socketTimeoutMS 设置为 0 实际上意味着应用操作系统默认套接字超时值(更多信息请访问 http://mongodb.github.io/ node-mongodb-native/2.2/reference/faq/)。

所以这个问题仍然存在:当达到这个限制(固定或默认操作系统超时)时,为什么这些回调停止发生?

@ vkarpov15[email protected] ,我现在看到的超时事件,感谢。 但是,您仍然会看到此回调问题(使用@bendytree的原始脚本,为了更快地测试,您可以将 LONG_QUERY_DURATION_IN_MS 设置为 2000,将 socketTimeoutMS 设置为 1000)

我做了一些更多的测试,发现这也可以在没有猫鼬的情况下重现。 在阅读http://mongodb.github.io/node-mongodb-native/2.2/reference/faq/ 时,我发现: Closing the socket forces a reconnect of the driver’s connection pool and introduces latency to any other operations which are queued up

尽管mongoose.connection.readyState始终为 1,但我尝试将autoReconnecttrue ,重新运行脚本:

8) Database connecting...
47) Database connected...
48) Dropping...
73) Creating...
123) FastQuery-A: starting...
129) FastQuery-A: success: 1
1122) SlowQuery: starting...
2126) FastQuery-B: starting...
3130) Database timeout...
3133) SlowQuery: failed: MongoError: connection 0 to localhost:27017 timed out
3133) FastQuery-B: failed: MongoError: connection 0 to localhost:27017 timed out
6063) Database timeout...
7122) FastQuery-C: starting...
7125) FastQuery-C: success: 1
8129) Database timeout...
11070) Database timeout...
13073) Database timeout...

所以看起来回调正在使用 autoReconnect 设置为 true 👍

进一步阅读这篇文章后: http :

所以我尝试将autoReconnectfalse + 将bufferMaxEntries0 ,这是输出:

8) Database connecting...
51) Database connected...
51) Dropping...
58) Creating...
112) FastQuery-A: starting...
116) FastQuery-A: success: 1
1115) SlowQuery: starting...
2115) FastQuery-B: starting...
3123) Database timeout...
3123) SlowQuery: failed: MongoError: connection 0 to localhost:27017 timed out
3123) FastQuery-B: failed: MongoError: connection 0 to localhost:27017 timed out
7115) FastQuery-C: starting...
7117) FastQuery-C: failed: MongoError: no connection available for operation and number of stored operation > 0

这解释了为什么回调没有在@bendytree脚本的第一个版本中触发。

@vkarpov15现在留给我们两个问题:

  • 为什么没有发出连接事件(我希望看到断开连接的事件)
  • 超时事件事件似乎不断发射

这是更新后的脚本(基于@bendytree的脚本,用 [email protected][email protected] 测试过):

const LONG_QUERY_DURATION_IN_MS = 2000;
const DB_URL = 'mongodb://localhost:27017/test_mongoose_callbacks';

const mongoose = require('mongoose');
mongoose.Promise = Promise;

var startTime = new Date().getTime();
var log = (msg) => {
  var seconds = Math.round(new Date().getTime() - startTime);
  console.log(seconds+") "+msg);
};

var Model = mongoose.model('Test', new mongoose.Schema({
  name: { type: String, trim: true, default: 'test' }
}), 'test');

mongoose.connection.on('connecting',   () => { log('Database connecting...'); });
mongoose.connection.on('timeout',      () => { log('Database timeout...'); });
mongoose.connection.on('error',        () => { log('Database error...'); });
mongoose.connection.on('disconnected', () => { log('Database disconnected...'); });

const doRequest = function (title, slow) {
  var logRequest = (msg) => { log(title+": "+msg); };

  logRequest("starting...");
  var filter = slow ? {$where: 'sleep('+LONG_QUERY_DURATION_IN_MS+') || true'} : {};
  Model.count(filter).exec((err, count) => {
    logRequest(err ? "failed: "+err : "success: "+count);
  });
};

mongoose.connection.on('connected', () => {
  log("Database connected...");
 log("Dropping...");
 mongoose.connection.db.dropDatabase(function(){
   log("Creating...");
   Model.create([ {name: 'test1'} ], function(){
      setTimeout(() => { doRequest("FastQuery-A", false); }, 0);
      setTimeout(() => { doRequest("SlowQuery", true);  }, 1000);
      setTimeout(() => { doRequest("FastQuery-B", false); }, 2000);
      setTimeout(() => { doRequest("FastQuery-C", false); }, LONG_QUERY_DURATION_IN_MS+5000);
      setTimeout(() => { log("Giving Up..."); }, LONG_QUERY_DURATION_IN_MS+30000);
    });
  });
});

mongoose.connect(DB_URL, {
  useMongoClient: true,
  autoReconnect: true,
  //bufferMaxEntries: 0, // Uncomment to disable buffering operations 
  poolSize: 1,
  socketTimeoutMS: 1000
}).catch((err)=>{ log('Connect failed', err); });

setInterval(()=>{
  if (mongoose.connection.readyState === 1) return;
  log('Mongoose.readystate = ', mongoose.connection.readyState);
}, 1000);

是的,你说得对,我们需要在这方面做得更好。 timeout事件是必要的,但不足以处理这种情况。

未发出 'disconnected' 的原因是 'timeout' 意味着一个套接字超时,而不是整个池。 Mongoose 需要知道在超时事件发生时池中有多少套接字仍然处于活动状态,我需要进行更多调查以弄清楚我们如何做到这一点。

将进一步调查超时事件不断重新发射的原因。 感谢您的耐心等待:+1:

我将其追溯到 mongodb 驱动程序中的几个错误: https :

@vkarpov15在我自己的测试中,我遇到mongoose.connection.readyState永远不会改变。 这是同样的问题吗?

@r3wt该问题应该在 4.11.13 中修复,您使用的是哪个版本?

@vkarpov15我在发帖时使用的是 4.11.11。 令人沮丧的是,这种明显的不良行为可以潜入如此著名的图书馆的生产中。 也许需要一些更好的测试程序。 (我不是想不尊重,我知道维护开源项目是多么困难。有时它很糟糕)

@vkarpov15我正在开发一个猫鼬插件。 插件必须阻塞直到连接准备好,否则它会死锁等待连接(出于某些原因)。 我用来等待连接准备就绪的代码如下:

function ready(){
    console.log('ready()');
    return new Promise((resolve,reject)=>{
        var _int = setInterval(()=>{
            if(mongoose.connection.readyState == 1 || forceReady){
                clearInterval(_int);
                console.log('mongoose connected ready().resolve()')
                resolve();
            }
        },200);
    })
}

我也尝试通过mongoose.connnection.on('connected')收听,但可惜也没有用。 在空白安装上它工作正常。 但是结合其他猫鼬插件和监听事件的东西,这就是问题发生的地方。 connection.readyState 永远保持为 0,并且永远不会发出 connected 事件。

在我看来,似乎require('mongoose') !== require('mongoose') 。 我希望这是有道理的。

IE:

  1. 用户脚本require('mongoose')
  2. 插件require('mongoose')
  3. 插件 b require('mongoose')

用户脚本和插件接收有关连接的信息。 但插件 b 从来没有。 是否有意义?

@r3wt确实,这绝对令人沮丧。 我们已经添加了实际停止/启动 mongodb 服务器的测试,并断言 mongoose 做了正确的事情以防止将来发生这种情况。 我们很抱歉给您带来麻烦。

至于阻塞直到连接准备好, mongoose.connection.on('connected')应该可以工作。 你在用什么其他插件? 尝试运行npm list | grep "mongoose" ,您断言require('mongoose') !== require('mongoose')使我认为您在某处有多个版本的猫鼬。

不幸的是,由于 mongodb 驱动程序无法发布,因此不得不再次将其推回。 很抱歉,感谢您的耐心等待 :+1: 🌴

@vkarpov15只想对您在此修复程序上所做的工作表示感谢。 我们已经在生产中使用它几个月了,问题是 0。

@bendytree感谢您的客气话和您的耐心让这个问题重现。 mongodb 驱动程序团队一直忙于为 mongodb 3.6 版本及时发布 3.0,所以他们还没有能够发布带有此修复程序的 2.x 版本,我将再次 ping 他们。 节日快乐!

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

相关问题

CodeurSauvage picture CodeurSauvage  ·  3评论

Igorpollo picture Igorpollo  ·  3评论

adamreisnz picture adamreisnz  ·  3评论

efkan picture efkan  ·  3评论

lukasz-zak picture lukasz-zak  ·  3评论