วิธีฟังการเปลี่ยนแปลงคอลเลกชัน MongoDB อย่างไร


200

ฉันกำลังสร้างระบบคิวงานแบ็คกราวน์ที่มี MongoDB เป็นแหล่งข้อมูล ฉันจะ "ฟัง" เพื่อแทรกการรวบรวม MongoDB ก่อนที่จะวางไข่เพื่อประมวลผลงานได้อย่างไร ฉันต้องสำรวจทุกสองสามวินาทีเพื่อดูว่ามีการเปลี่ยนแปลงใด ๆ จากครั้งที่แล้วหรือไม่หรือมีวิธีที่สคริปต์ของฉันสามารถรอการแทรกเกิดขึ้นได้หรือไม่? นี่เป็นโปรเจ็กต์ PHP ที่ฉันกำลังทำงาน แต่รู้สึกฟรีที่จะตอบใน Ruby หรือผู้ไม่เชื่อเรื่องภาษา


1
Change Streams ถูกเพิ่มเข้ามาใน MongoDB 3.6 เพื่อจัดการกับสถานการณ์ของคุณ docs.mongodb.com/manual/changeStreams นอกจากนี้หากคุณใช้ MongoDB Atlas คุณสามารถใช้ประโยชน์จากทริกเกอร์ Stitch ซึ่งช่วยให้คุณสามารถใช้งานฟังก์ชั่นเพื่อตอบสนองต่อการแทรก / อัพเดต / ลบ / ฯลฯ docs.mongodb.com/stitch/triggers/overviewไม่จำเป็นต้องแยก oplog
Robert Walters

คำตอบ:


111

สิ่งที่คุณกำลังคิดฟังดูเหมือนเป็นทริกเกอร์ MongoDB ไม่ได้รับการสนับสนุนใด ๆ สำหรับทริกเกอร์ แต่บางคนมี "รีดของตัวเอง" โดยใช้เทคนิคบางอย่าง กุญแจนี่คือ oplog

เมื่อคุณเรียกใช้ MongoDB ในชุดแบบจำลองการกระทำ MongoDB ทั้งหมดจะถูกบันทึกลงในบันทึกการทำงาน (หรือที่เรียกว่า oplog) oplog นั้นเป็นเพียงรายการทำงานของการแก้ไขที่ทำกับข้อมูล ตั้งค่าแบบจำลองโดยฟังการเปลี่ยนแปลงใน oplog นี้จากนั้นใช้การเปลี่ยนแปลงแบบโลคัล

เสียงนี้คุ้นเคยไหม?

ฉันไม่สามารถบอกรายละเอียดของกระบวนการทั้งหมดได้ที่นี่เป็นเอกสารหลายหน้า แต่มีเครื่องมือที่คุณต้องการ

เริ่มแรกเขียนบทความบน oplog - คำอธิบายสั้น ๆ - เค้าโครงของlocalคอลเลกชัน (ซึ่งมี oplog)

คุณจะต้องการใช้ประโยชน์เคอร์เซอร์ที่มีอยู่ สิ่งเหล่านี้จะช่วยให้คุณมีวิธีรับฟังการเปลี่ยนแปลงแทนการลงคะแนนเลือกตั้ง โปรดทราบว่าการจำลองแบบใช้เคอร์เซอร์ที่ใช้งานได้ดังนั้นนี่คือคุณสมบัติที่ได้รับการสนับสนุน


1
อืม ... ไม่ใช่สิ่งที่ฉันมีอยู่ในใจ ตอนนี้ฉันใช้งานอินสแตนซ์เดียวเท่านั้น (ไม่มีทาส) ดังนั้นอาจเป็นทางออกพื้นฐานเพิ่มเติมหรือไม่
Andrew

17
คุณสามารถเริ่มต้นเซิร์ฟเวอร์ที่มี--replSetตัวเลือกและมันจะสร้าง / oplogเติม แม้ไม่มีรอง นี่เป็นวิธีเดียวที่จะ "รับฟัง" การเปลี่ยนแปลงในฐานข้อมูล
Gates VP

2
นี่เป็นคำอธิบายที่ดีเกี่ยวกับวิธีตั้งค่า oplog สำหรับการบันทึกการเปลี่ยนแปลงไปยังฐานข้อมูลภายใน: loosexaml.wordpress.com/2012/09/03/…
johndodo

Cooooool! นั่นคือสิ่งที่ฉันต้องการ และฉันพบห้องสมุดชื่อ 'mongo-oplog' ในเวลา 23.00 น. มีความสุขมาก ~
pjincz

ฉันเห็นด้วยตามเวลาที่เขียนทริกเกอร์คำตอบนี้อาจไม่สามารถใช้ได้ แต่สำหรับทุกคนที่ลงจอดที่นี่มีตัวเลือกให้ใช้งานในขณะนี้ลองดู MongoDB Stitch ( docs.mongodb.com/stitch/#stitch ) & ทริกเกอร์ Stitch ( เอกสาร) mongodb.com/stitch/triggers ) ..
whoami

102

MongoDB มีสิ่งที่เรียกว่าcapped collectionsและtailable cursorsช่วยให้ MongoDB สามารถส่งข้อมูลไปยังผู้ฟังได้

A capped collectionเป็นหลักชุดที่มีขนาดคงที่และอนุญาตให้ใส่เท่านั้น นี่คือสิ่งที่ดูเหมือนว่าจะสร้าง:

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

MongoDB เคอร์เซอร์ใช้งานได้ ( โพสต์ต้นฉบับโดย Jonathan H. Wage )

ทับทิม

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 (โดย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 (โดยสูงสุด )

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;
    }
}

แหล่งข้อมูลเพิ่มเติม:

Ruby / Node.js บทแนะนำที่จะนำคุณไปสู่การสร้างแอปพลิเคชันที่รับฟังการแทรกในคอลเลกชันที่ปกคลุมด้วย MongoDB

บทความที่พูดถึงเคอร์เซอร์ที่ใช้งานได้ในรายละเอียดเพิ่มเติม

ตัวอย่าง PHP, Ruby, Python และ Perl ของการใช้เคอร์เซอร์ที่มีอยู่


70
นอนหลับ 1 จริงๆ? สำหรับรหัสการผลิต? เป็นวิธีที่ไม่ได้สำรวจ?
rbp

2
@rbp ฮ่า ๆ ฉันไม่เคยบอกว่ามันเป็นรหัสการผลิต แต่คุณพูดถูกการนอนเป็นครั้งที่สองนั้นไม่ใช่วิธีปฏิบัติที่ดี ค่อนข้างแน่ใจว่าฉันได้รับตัวอย่างจากที่อื่น ไม่แน่ใจว่าจะปรับโครงสร้างอย่างไร
แอนดรู

14
@kroe เพราะรายละเอียดที่ไม่เกี่ยวข้องเหล่านั้นจะได้รับการใส่รหัสการผลิตโดยโปรแกรมเมอร์รุ่นใหม่ที่อาจไม่เข้าใจว่าทำไมมันถึงไม่ดี
ปลาดุก

3
ฉันเข้าใจประเด็นของคุณ แต่คาดว่าผู้เขียนโปรแกรมใหม่บางคนจะเพิ่ม "sleep 1" ในการผลิตเป็นเรื่องที่น่ารังเกียจ! ผมหมายถึงผมจะไม่แปลกใจ ... แต่ถ้าทำให้คนนี้ในการผลิตอย่างน้อยจะได้เรียนรู้วิธีที่ยากและตลอดไป .. ฮ่าฮ่าฮ่า
kroe

19
เกิดอะไรขึ้นกับการลงเวลานอน (1) ในการผลิต
Al Johri

44

ตั้งแต่ MongoDB 3.6 จะมี API การแจ้งเตือนใหม่ชื่อ Change Streams ซึ่งคุณสามารถใช้สำหรับสิ่งนี้ ดูโพสต์บล็อกนี้เป็นตัวอย่าง ตัวอย่างจากมัน

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
ทำไม? คุณสามารถทำอย่างละเอียด? นี่เป็นวิธีมาตรฐานแล้วหรือยัง?
Mitar

1
อย่างไร อย่าใช้การเลือกตั้ง - คุณจำเป็นต้องมีวิธีการที่เป็นเหตุการณ์แทนที่จะเป็นลูปเป็นต้นขณะนี้
Alexander Mills

3
คุณเห็นการสำรวจที่นี่ที่ไหน
Mitar

ฉันคิดว่าเขา / เธอหมายถึงวงสุดท้าย แต่ฉันคิดว่า PyMongo สนับสนุนเท่านั้น มอเตอร์อาจมีการใช้งานแบบ async / event listener
Shane Hsu

41

ลองดูสิ่งนี้: เปลี่ยนสตรีม

10 มกราคม 2018 - เผยแพร่ 3.6

* แก้ไข: ฉันเขียนบทความเกี่ยวกับวิธีการทำhttps://medium.com/riow/mongodb-data-collection-change-85b63d96ff76

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


มันใหม่ในmongodb 3.6 https://docs.mongodb.com/manual/release-notes/3.6/ 2018/01/10

$ mongod --version
db version v3.6.2

เพื่อที่จะใช้changeStreamsฐานข้อมูลจะต้องเป็นชุดการจำลองแบบ

เพิ่มเติมเกี่ยวกับชุดการจำลอง: https://docs.mongodb.com/manual/replication/

ฐานข้อมูลของคุณจะเป็น " สแตนด์อโลน " โดยค่าเริ่มต้น

วิธีการแปลงสแตนด์อโลนเป็นชุดจำลอง: https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/


ตัวอย่างต่อไปนี้เป็นแอปพลิเคชันที่ใช้งานได้จริงสำหรับวิธีที่คุณใช้
* เฉพาะสำหรับโหนด

/* 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 */

ลิงค์ที่มีประโยชน์:
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


ขออภัยเกี่ยวกับการแก้ไขทั้งหมดดังนั้นจึงไม่ชอบ "ลิงก์" ของฉัน (กล่าวว่าเป็นโค้ดที่จัดรูปแบบไม่ถูกต้อง)
Rio Weber

1
คุณไม่จำเป็นต้องค้นหาฐานข้อมูลฉันคิดว่าด้วย watch () หรือคล้ายกันข้อมูลใหม่สามารถส่งไปยังเซิร์ฟเวอร์ที่กำลังฟังได้
Alexander Mills

22

ตอนนี้ MongoDB เวอร์ชั่น 3.6 รวมถึงกระแสการเปลี่ยนแปลงซึ่งเป็นหลักของ API ที่อยู่ด้านบนของ OpLog เพื่อให้สามารถใช้กรณีเรียกใช้ / แจ้งเตือนได้

นี่คือลิงค์ไปยังตัวอย่าง Java: http://mongodb.github.io/mongo-java-driver/3.6/driver/tutorials/change-streams/

ตัวอย่างของ NodeJS อาจมีลักษณะดังนี้:

 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 เป็นสิ่งสำคัญมากในการรับข้อมูลนี้ใน Android Studio (Android App) ..
DragonFire

3

อีกวิธีหนึ่งคุณสามารถใช้วิธี Mongo FindAndUpdate มาตรฐานและภายในการโทรกลับให้เริ่มต้นเหตุการณ์ EventEmitter (ในโหนด) เมื่อมีการเรียกใช้การเรียกกลับ

ส่วนอื่น ๆ ของแอปพลิเคชั่นหรือสถาปัตยกรรมที่รับฟังเหตุการณ์นี้จะได้รับแจ้งการอัพเดตและข้อมูลที่เกี่ยวข้องใด ๆ ที่ส่งไปที่นั่นด้วย นี่เป็นวิธีที่ง่ายมากในการรับการแจ้งเตือนจาก Mongo


นี้ไม่มีประสิทธิภาพมาก .. คุณกำลังล็อคฐานข้อมูลสำหรับ FindAndUpdate แต่ละรายการ!
Yash Gupta

1
ฉันเดาว่าอเล็กซ์ตอบแตกต่างกันเล็กน้อย (ไม่ใช่ที่อยู่ที่แทรกโดยเฉพาะ) แต่คำถามที่เกี่ยวข้องเช่นเดียวกับวิธีการแจ้งเตือนลูกค้าบางประเภทเมื่อสถานะของงานที่ถูกจัดคิวเปลี่ยนแปลงซึ่งเราคิดว่าจะต้องเกิดขึ้นเนื่องจากงานกลับกลาย เสร็จสมบูรณ์หรือไม่สำเร็จ ด้วยไคลเอนต์ที่เชื่อมต่อโดยใช้ websockets ไปยังโหนดพวกเขาทั้งหมดสามารถได้รับแจ้งการเปลี่ยนแปลงด้วยเหตุการณ์ออกอากาศในการเรียกกลับ FIndAndUpdate ซึ่งสามารถเรียกได้เมื่อได้รับข้อความการเปลี่ยนสถานะ ฉันจะบอกว่ามันไม่ได้ผลเพราะจำเป็นต้องทำการอัพเดท
ปีเตอร์สกอตต์

3

คำตอบเหล่านี้หลายคำตอบจะให้บันทึกใหม่แก่คุณเท่านั้นไม่ใช่อัปเดตและ / หรือไม่มีประสิทธิภาพมากนัก

วิธีเดียวที่เชื่อถือได้และมีประสิทธิภาพในการทำเช่นนี้คือการสร้างเคอร์เซอร์ที่ใช้งานได้บนคอลเลกชัน db: oplog.rs เพื่อรับการเปลี่ยนแปลงทั้งหมดไปยัง MongoDB และทำตามที่คุณต้องการ (MongoDB ทำสิ่งนี้ภายในมากขึ้นหรือน้อยลงเพื่อรองรับการจำลองแบบ!)

คำอธิบายของสิ่งที่ oplog มี: https://www.compose.com/articles/the-mongodb-oplog-and-node-js/

ตัวอย่างของไลบรารี Node.js ที่ให้ API เกี่ยวกับสิ่งที่สามารถทำได้ด้วย oplog: https://github.com/cayasso/mongo-oplog


2

มีชุดที่น่ากลัวของการบริการที่มีเรียกว่าเป็นMongoDB Stitch มองเข้าไปในฟังก์ชั่นตะเข็บ / ทริกเกอร์ หมายเหตุนี่เป็นบริการชำระเงินบนระบบคลาวด์(AWS) ในกรณีของคุณในส่วนแทรกคุณสามารถเรียกใช้ฟังก์ชันที่กำหนดเองที่เขียนด้วยจาวาสคริปต์

ป้อนคำอธิบายรูปภาพที่นี่


stackoverflow.com/users/486867/manish-jain - คุณมีตัวอย่างของวิธีการใช้ Stitch เพื่อแจ้งให้แอปพลิเคชัน REACT ทราบว่ามีการแทรกข้อมูลลงในตารางหรือไม่
MLissCetrus

1

มีตัวอย่างจาวาทำงานซึ่งสามารถพบได้คือที่นี่

 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();

กุญแจสำคัญคือQUERY OPTIONS ที่ให้ไว้ที่นี่

นอกจากนี้คุณยังสามารถเปลี่ยนข้อความค้นหาหากคุณไม่จำเป็นต้องโหลดข้อมูลทั้งหมดทุกครั้ง

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

ที่จริงแล้วแทนที่จะดูผลลัพธ์ทำไมคุณไม่สังเกตเห็นเมื่อมีสิ่งใหม่แทรกโดยใช้มิดเดิลแวร์ที่ให้ไว้โดยคีโน mongoose

คุณสามารถติดตามเหตุการณ์การแทรกเอกสารใหม่และทำบางสิ่งหลังจากการแทรกนี้เสร็จสิ้น


ความผิดฉันเอง. ขออภัยค่ะ
Duong Nguyen

0

หลังจาก 3.6 หนึ่งได้รับอนุญาตให้ใช้ฐานข้อมูลประเภททริกเกอร์ฐานข้อมูลต่อไปนี้:

  • ทริกเกอร์ที่ขับเคลื่อนด้วยเหตุการณ์ - มีประโยชน์ในการอัปเดตเอกสารที่เกี่ยวข้องโดยอัตโนมัติแจ้งบริการแบบดาวน์สตรีมเผยแพร่ข้อมูลเพื่อรองรับปริมาณงานที่หลากหลายความสมบูรณ์ของข้อมูลและการตรวจสอบ
  • กำหนดการทริกเกอร์ - มีประโยชน์สำหรับการดึงข้อมูลตามกำหนดเวลาการเผยแพร่การเก็บถาวรและปริมาณงานการวิเคราะห์

ลงชื่อเข้าใช้บัญชี Atlas ของคุณแล้วเลือกTriggersอินเทอร์เฟซและเพิ่มทริกเกอร์ใหม่:

ป้อนคำอธิบายรูปภาพที่นี่

ขยายแต่ละส่วนเพื่อดูการตั้งค่าหรือรายละเอียดเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.