【问题标题】:MongoDB: is it possible to capture TTL events with Change Stream to emulate a scheduler (cronjob)?MongoDB:是否可以使用 Change Stream 捕获 TTL 事件以模拟调度程序(cronjob)?
【发布时间】:2019-01-17 00:12:58
【问题描述】:

我是 MongoDB 新手,我正在寻找一种方法来执行以下操作:

我收集了许多可用的“东西”来使用。 用户可以“保存”一个“事物”并减少可用事物的数量。 但他有时间在它到期之前使用它。 如果它过期了,这个东西必须回到集合中,再次增加它。

如果有一种方法可以在 Mongo 中监控“到期日期”,那将是理想的选择。但在我的搜索中,我只找到了一个自动删除整个文档的 TTL(生存时间)。

但是,我需要的是过期的“事件”......比我想知道是否可以使用 Change Streams 捕获此事件。然后我可以使用该事件再次增加“事物”。

有没有可能?还是有更好的方法来做我想做的事?

【问题讨论】:

标签: mongodb


【解决方案1】:

我能够使用 Change Streams 和 TTL 来模拟 cronjob。我已经发表了一篇文章,详细解释了我所做的事情,并在以下位置给出了学分: https://www.patreon.com/posts/17697287

但是,基本上,每当我需要为文档安排“事件”时,当我创建文档时,我也会同时创建一个事件文档。该事件文档的 _id 将与第一个文档的 id 相同。

另外,对于这个事件文档,我将设置一个 TTL。

当 TTL 到期时,我将使用更改流捕获其“删除”更改。然后我将使用更改的 documentKey(因为它与我要触发的文档的 id 相同)在第一个集合中找到目标文档,并对文档做任何我想做的事情。

我正在使用带有 Express 和 Mongoose 的 Node.js 来访问 MongoDB。 这是要在 App.js 中添加的相关部分:

const { ReplSet } = require('mongodb-topology-manager');

run().catch(error => console.error(error));

async function run() {
    console.log(new Date(), 'start');
    const bind_ip = 'localhost';
    // Starts a 3-node replica set on ports 31000, 31001, 31002, replica set
    // name is "rs0".
    const replSet = new ReplSet('mongod', [
        { options: { port: 31000, dbpath: `${__dirname}/data/db/31000`, bind_ip } },
        { options: { port: 31001, dbpath: `${__dirname}/data/db/31001`, bind_ip } },
        { options: { port: 31002, dbpath: `${__dirname}/data/db/31002`, bind_ip } }
    ], { replSet: 'rs0' });

    // Initialize the replica set
    await replSet.purge();
    await replSet.start();
    console.log(new Date(), 'Replica set started...');

    // Connect to the replica set
    const uri = 'mongodb://localhost:31000,localhost:31001,localhost:31002/' + 'test?replicaSet=rs0';
    await mongoose.connect(uri);
    var db = mongoose.connection;
    db.on('error', console.error.bind(console, 'connection error:'));
    db.once('open', function () {
        console.log("Connected correctly to server");
    });

    // To work around "MongoError: cannot open $changeStream for non-existent database: test" for this example
    await mongoose.connection.createCollection('test');

    // *** we will add our scheduler here *** //

    var Item = require('./models/item');
    var ItemExpiredEvent = require('./models/scheduledWithin');

    let deleteOps = {
      $match: {
          operationType: "delete" 
      }
    };

    ItemExpiredEvent.watch([deleteOps]).
        on('change', data => {
            // *** treat the event here *** //
            console.log(new Date(), data.documentKey);
            Item.findById(data.documentKey, function(err, item) {
                console.log(item);
            });
        });

    // The TTL set in ItemExpiredEvent will trigger the change stream handler above
    console.log(new Date(), 'Inserting item');
    Item.create({foo:"foo", bar: "bar"}, function(err, cupom) {
        ItemExpiredEvent.create({_id : item._id}, function(err, event) {
            if (err) console.log("error: " + err);
            console.log('event inserted');
        });
    });

}

这是模型/ScheduledWithin 的代码:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var ScheduledWithin = new Schema({
    _id: mongoose.Schema.Types.ObjectId,
}, {timestamps: true}); 
// timestamps: true will automatically create a "createdAt" Date field

ScheduledWithin.index({createdAt: 1}, {expireAfterSeconds: 90});

module.exports = mongoose.model('ScheduledWithin', ScheduledWithin);

【讨论】:

    【解决方案2】:

    感谢详细的代码。

    我有两个部分替代方案,只是为了提供一些想法。

    1。 鉴于我们至少可以取回 _id,如果您只需要已删除文档中的特定密钥,则可以在创建时手动指定 _id,并且至少有这个信息。

    1. (mongodb 4.0) 更复杂一点,此方法是利用 oplog 历史记录,并通过 startAtOperationTime 在创建时(如果可以计算的话)打开一个观察流选项。

    您需要检查您的 oplog 历史记录 可以追溯到多远,看看您是否可以使用此方法: https://docs.mongodb.com/manual/reference/method/rs.printReplicationInfo/#rs.printReplicationInfo

    注意:我使用的是 mongodb 库,而不是 mongoose

    // https://mongodb.github.io/node-mongodb-native/api-bson-generated/timestamp.html
    const { Timestamp } = require('mongodb');
    
    const MAX_TIME_SPENT_SINCE_CREATION = 1000 * 60 * 10; // 10mn, depends on your situation
    
    const cursor = db.collection('items')
      .watch([{
        $match: {
          operationType: 'delete'
        }
      }]);
    
    cursor.on('change', function(change) {
      // create another cursor, back in time
      const subCursor = db.collection('items')
        .watch([{
          $match: {
            operationType: 'insert'
          }
        }], {
          fullDocument        : 'updateLookup',
          startAtOperationTime: Timestamp.fromString(change.clusterTime - MAX_TIME_SPENT_SINCE_CREATION)
        });
    
      subCursor.on('change', function(creationChange) {
        // filter the insert event, until we find the creation event for our document
        if (creationChange.documentKey._id === change.documentKey._id) {
          console.log('item', JSON.stringify(creationChange.fullDocument, false, 2));
          subCursor.close();
        }
      });
    });
    

    【讨论】:

      猜你喜欢
      • 2018-09-17
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      • 1970-01-01
      • 1970-01-01
      • 2021-03-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多