Come ascoltare le modifiche a una raccolta MongoDB?


200

Sto creando una sorta di sistema di coda dei lavori in background con MongoDB come archivio dati. Come posso "ascoltare" gli inserti in una raccolta MongoDB prima di spawnare i lavoratori per elaborare il lavoro? Devo eseguire il polling ogni pochi secondi per vedere se sono state apportate modifiche dall'ultima volta o c'è un modo in cui il mio script può attendere che si verifichino inserimenti? Questo è un progetto PHP a cui sto lavorando, ma sentiti libero di rispondere in Ruby o in linguaggio agnostico.


1
Change Streams è stato aggiunto in MongoDB 3.6 per risolvere il tuo scenario. docs.mongodb.com/manual/changeStreams Inoltre, se si utilizza MongoDB Atlas, è possibile sfruttare gli Stitch Trigger che consentono di eseguire funzioni in risposta all'inserimento / aggiornamento / eliminazione / ecc. docs.mongodb.com/stitch/triggers/overview Non è più necessario analizzare l'oplog.
Robert Walters,

Risposte:


111

Quello a cui stai pensando suona molto come trigger. MongoDB non ha alcun supporto per i trigger, tuttavia alcune persone hanno "lanciato il proprio" usando alcuni trucchi. La chiave qui è l'oplog.

Quando si esegue MongoDB in un set di repliche, tutte le azioni MongoDB vengono registrate in un registro delle operazioni (noto come oplog). L'oplog è fondamentalmente solo un elenco in esecuzione delle modifiche apportate ai dati. Repliche Imposta la funzione ascoltando le modifiche in questo oplog e quindi applicando le modifiche localmente.

Questo suona familiare?

Non posso dettagliare l'intero processo qui, sono diverse pagine di documentazione, ma gli strumenti necessari sono disponibili.

Prima alcuni articoli sull'oplog - Breve descrizione - Layout della localcollezione (che contiene l'oplog)

Dovrai anche sfruttare i cursori disponibili . Questi ti forniranno un modo per ascoltare le modifiche invece di effettuare il polling per esse. Si noti che la replica utilizza cursori disponibili, quindi questa è una funzionalità supportata.


1
hmm ... non esattamente quello che avevo in mente. Sto eseguendo solo un'istanza a questo punto (senza schiavi). Quindi forse una soluzione più semplice?
Andrew

17
È possibile avviare il server con l' --replSetopzione e creerà / popolerà il file oplog. Anche senza il secondario. Questo è sicuramente l'unico modo per "ascoltare" i cambiamenti nel DB.
Gates VP,

2
Questa è una bella descrizione su come configurare oplog per la registrazione locale delle modifiche al DB: loosexaml.wordpress.com/2012/09/03/…
johndodo

Cooooool! Questo è davvero quello che voglio. E ho trovato una libreria chiamata "mongo-oplog" su npm. Così felice ~
pjincz l'

Accetto al momento della stesura di questa risposta i trigger potrebbero non essere disponibili ma per tutti coloro che atterrano qui, è disponibile un'opzione ora, controlla MongoDB Stitch ( docs.mongodb.com/stitch/#stitch ) e Stitch trigger ( docs. mongodb.com/stitch/triggers ) ..
whoami,

102

MongoDB ha ciò che viene chiamato capped collectionse tailable cursorsciò consente a MongoDB di inviare i dati agli ascoltatori.

A capped collectionè essenzialmente una raccolta di dimensioni fisse che consente solo inserimenti. Ecco come sarebbe crearne uno:

db.createCollection("messages", { capped: true, size: 100000000 })

Cursori disponibili di MongoDB ( post originale di Jonathan H. Wage )

Rubino

coll = db.collection('my_collection')
cursor = Mongo::Cursor.new(coll, :tailable => true)
loop do
  if doc = cursor.next_document
    puts doc
  else
    sleep 1
  end
end

PHP

$mongo = new Mongo();
$db = $mongo->selectDB('my_db')
$coll = $db->selectCollection('my_collection');
$cursor = $coll->find()->tailable(true);
while (true) {
    if ($cursor->hasNext()) {
        $doc = $cursor->getNext();
        print_r($doc);
    } else {
        sleep(1);
    }
}

Python (di Robert Stewart)

from pymongo import Connection
import time

db = Connection().my_db
coll = db.my_collection
cursor = coll.find(tailable=True)
while cursor.alive:
    try:
        doc = cursor.next()
        print doc
    except StopIteration:
        time.sleep(1)

Perl (di Max )

use 5.010;

use strict;
use warnings;
use MongoDB;

my $db = MongoDB::Connection->new;
my $coll = $db->my_db->my_collection;
my $cursor = $coll->find->tailable(1);
for (;;)
{
    if (defined(my $doc = $cursor->next))
    {
        say $doc;
    }
    else
    {
        sleep 1;
    }
}

Risorse addizionali:

Esercitazione su Ruby / Node.js che ti guida nella creazione di un'applicazione che ascolta gli inserti in una raccolta con limiti MongoDB.

Un articolo che parla di cursori disponibili in modo più dettagliato.

Esempi PHP, Ruby, Python e Perl sull'uso dei cursori disponibili.


70
dormire 1? veramente? per codice di produzione? come mai non è polling?
rbp,

2
@rbp haha, non ho mai detto che fosse un codice di produzione, ma hai ragione, dormire per un secondo non è una buona pratica. Abbastanza sicuro di avere quell'esempio da qualche altra parte. Non sono sicuro di come riformattare però.
Andrew,

14
@kroe perché quei dettagli irrilevanti verranno inseriti nel codice di produzione dai programmatori più recenti che potrebbero non capire perché è male.
Pesce gatto

3
Capisco il tuo punto, ma aspettarsi che alcuni nuovi programmatori aggiungano "sleep 1" alla produzione è quasi offensivo! Voglio dire, non sarei sorpreso ... Ma se qualcuno lo mette in produzione, almeno imparerà nel modo più duro e per sempre ... hahaha
kroe

19
cosa c'è di sbagliato nel fare time.sleep (1) in produzione?
Al Johri,

44

A partire da MongoDB 3.6 ci sarà una nuova API di notifica chiamata Change Streams che puoi usare per questo. Vedi questo post sul blog per un esempio . Esempio da esso:

cursor = client.my_db.my_collection.changes([
    {'$match': {
        'operationType': {'$in': ['insert', 'replace']}
    }},
    {'$match': {
        'newDocument.n': {'$gte': 1}
    }}
])

# Loops forever.
for change in cursor:
    print(change['newDocument'])

4
Perché? Puoi elaborare? Questo è il modo standard ora?
Mitar,

1
Come? non usare il polling - hai bisogno di un approccio evented invece che while loop, ecc.
Alexander Mills

3
Dove vedi il sondaggio qui?
Mitar,

Penso che si riferisca all'ultimo ciclo. Ma penso che PyMongo lo sostenga solo. Motor potrebbe avere un'implementazione asincrona / in stile listener di eventi.
Shane Hsu,

41

Dai un'occhiata a: Cambia stream

10 gennaio 2018 - Release 3.6

* EDIT: ho scritto un articolo su come fare questo https://medium.com/riow/mongodb-data-collection-change-85b63d96ff76

https://docs.mongodb.com/v3.6/changeStreams/


È nuovo in mongodb 3.6 https://docs.mongodb.com/manual/release-notes/3.6/ 2018/01/10

$ mongod --version
db version v3.6.2

Per utilizzare changeStreams il database deve essere un set di repliche

Ulteriori informazioni sui set di replica: https://docs.mongodb.com/manual/replication/

Il tuo database sarà un " Standalone " per impostazione predefinita.

Come convertire un standalone in un set di repliche: https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/


L' esempio seguente è un'applicazione pratica per come utilizzarlo.
* Specificamente per il nodo.

/* file.js */
'use strict'


module.exports = function (
    app,
    io,
    User // Collection Name
) {
    // SET WATCH ON COLLECTION 
    const changeStream = User.watch();  

    // Socket Connection  
    io.on('connection', function (socket) {
        console.log('Connection!');

        // USERS - Change
        changeStream.on('change', function(change) {
            console.log('COLLECTION CHANGED');

            User.find({}, (err, data) => {
                if (err) throw err;

                if (data) {
                    // RESEND ALL USERS
                    socket.emit('users', data);
                }
            });
        });
    });
};
/* END - file.js */

Link utili:
https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set
https://docs.mongodb.com/manual/tutorial/change-streams-example

https://docs.mongodb.com/v3.6/tutorial/change-streams-example
http://plusnconsulting.com/post/MongoDB-Change-Streams


mi dispiace per tutte le modifiche, così non mi è piaciuto il mio "Link" (ha detto che erano codice formattato in modo errato.)
Rio Weber

1
non dovresti interrogare il database, penso con watch () o simili, i nuovi dati possono essere inviati al server che sta ascoltando
Alexander Mills

22

MongoDB versione 3.6 ora include flussi di modifica che sono essenzialmente un'API in cima a OpLog che consente casi di utilizzo simili a trigger / notifiche.

Ecco un link ad un esempio Java: http://mongodb.github.io/mongo-java-driver/3.6/driver/tutorials/change-streams/

Un esempio di NodeJS potrebbe essere simile a:

 var MongoClient = require('mongodb').MongoClient;
    MongoClient.connect("mongodb://localhost:22000/MyStore?readConcern=majority")
     .then(function(client){
       let db = client.db('MyStore')

       let change_streams = db.collection('products').watch()
          change_streams.on('change', function(change){
            console.log(JSON.stringify(change));
          });
      });

JSON.stringify è molto importante per ricevere questi dati in Android Studio (App Android) ..
DragonFire

3

In alternativa, è possibile utilizzare il metodo Mongo FindAndUpdate standard e, all'interno del callback, generare un evento EventEmitter (in Node) quando viene eseguito il callback.

Qualsiasi altra parte dell'applicazione o dell'architettura che ascolta questo evento verrà informata dell'aggiornamento e anche i dati rilevanti inviati lì. Questo è un modo davvero semplice per ricevere notifiche da Mongo.


questo è molto inefficiente ... stai bloccando il db per ogni FindAndUpdate!
Yash Gupta,

1
La mia ipotesi è che Alex stesse rispondendo a una domanda leggermente diversa (non specificatamente indirizzata agli inserti) ma correlata su come attivare una sorta di notifica ai clienti quando cambia lo stato di un lavoro in coda che presumiamo debba accadere man mano che i lavori vengono generati , completare correttamente o fallire. Con i client connessi tramite nodi Web al nodo, è possibile ricevere notifiche sulle modifiche con un evento di trasmissione sul callback FIndAndUpdate che può essere chiamato quando si ricevono messaggi di modifica dello stato. Direi che questo non è inefficiente in quanto è necessario eseguire gli aggiornamenti.
Peter Scott,

3

Molte di queste risposte ti daranno solo nuovi record e non aggiornamenti e / o sono estremamente inefficaci

L'unico modo affidabile e performante per farlo è creare un cursore disponibile sulla collezione db: oplog.rs locale per ottenere TUTTE le modifiche a MongoDB e fare ciò che vuoi. (MongoDB lo fa anche internamente più o meno per supportare la replica!)

Spiegazione di cosa contiene l'oplog: https://www.compose.com/articles/the-mongodb-oplog-and-node-js/

Esempio di una libreria Node.js che fornisce un'API su ciò che è disponibile da fare con l'oplog: https://github.com/cayasso/mongo-oplog



1

C'è un esempio java funzionante che può essere trovato qui .

 MongoClient mongoClient = new MongoClient();
    DBCollection coll = mongoClient.getDatabase("local").getCollection("oplog.rs");

    DBCursor cur = coll.find().sort(BasicDBObjectBuilder.start("$natural", 1).get())
            .addOption(Bytes.QUERYOPTION_TAILABLE | Bytes.QUERYOPTION_AWAITDATA);

    System.out.println("== open cursor ==");

    Runnable task = () -> {
        System.out.println("\tWaiting for events");
        while (cur.hasNext()) {
            DBObject obj = cur.next();
            System.out.println( obj );

        }
    };
    new Thread(task).start();

La chiave è QUERY OPTIONS qui fornita.

Inoltre, è possibile modificare la query di ricerca, se non è necessario caricare tutti i dati ogni volta.

BasicDBObject query= new BasicDBObject();
query.put("ts", new BasicDBObject("$gt", new BsonTimestamp(1471952088, 1))); //timestamp is within some range
query.put("op", "i"); //Only insert operation

DBCursor cur = coll.find(query).sort(BasicDBObjectBuilder.start("$natural", 1).get())
.addOption(Bytes.QUERYOPTION_TAILABLE | Bytes.QUERYOPTION_AWAITDATA);

1

In realtà, invece di guardare l'output, perché non si nota quando viene inserito qualcosa di nuovo utilizzando il middleware fornito dallo schema di mangusta

È possibile catturare l'evento di inserire un nuovo documento e fare qualcosa dopo aver fatto questo inserimento


Colpa mia. Scusi signore.
Duong Nguyen,

0

Dopo 3.6 è consentito utilizzare il database i seguenti tipi di trigger di database:

  • trigger attivati ​​da eventi: utili per aggiornare automaticamente i documenti correlati, notificare i servizi a valle, propagare i dati per supportare carichi di lavoro misti, integrità e controllo dei dati
  • trigger pianificati: utili per i carichi di lavoro programmati di recupero, propagazione, archiviazione e analisi dei dati

Accedi al tuo account Atlas e seleziona l' Triggersinterfaccia e aggiungi un nuovo trigger:

inserisci qui la descrizione dell'immagine

Espandi ogni sezione per ulteriori impostazioni o dettagli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.