Làm cách nào để lắng nghe những thay đổi đối với bộ sưu tập MongoDB?


200

Tôi đang tạo một loại hệ thống hàng đợi công việc nền với MongoDB là kho lưu trữ dữ liệu. Làm cách nào tôi có thể "lắng nghe" để chèn vào bộ sưu tập MongoDB trước khi sinh ra công nhân để xử lý công việc? Tôi có cần thăm dò ý kiến ​​vài giây một lần để xem liệu có bất kỳ thay đổi nào từ lần trước không, hoặc có cách nào để kịch bản của tôi có thể chờ đợi các lần chèn xảy ra không? Đây là một dự án PHP mà tôi đang làm việc, nhưng hãy trả lời bằng Ruby hoặc bất khả tri ngôn ngữ.


1
Luồng thay đổi đã được thêm vào trong MongoDB 3.6 để giải quyết tình huống của bạn. docs.mongodb.com/manual/changeStreams Ngoài ra, nếu bạn đang sử dụng MongoDB Atlas, bạn có thể tận dụng Stitch Triggers cho phép bạn thực thi các chức năng để đáp ứng với việc chèn / cập nhật / xóa / vv. docs.mongodb.com/stitch/triggers/overview Không cần phải phân tích cú pháp nữa.
Robert Walters

Câu trả lời:


111

Những gì bạn đang nghĩ về âm thanh rất giống như kích hoạt. MongoDB không có bất kỳ sự hỗ trợ nào cho các trình kích hoạt, tuy nhiên một số người đã "tự lăn" bằng một số thủ thuật. Chìa khóa ở đây là oplog.

Khi bạn chạy MongoDB trong Bộ bản sao, tất cả các hành động MongoDB được ghi vào nhật ký hoạt động (được gọi là oplog). Các oplog về cơ bản chỉ là một danh sách đang chạy của các sửa đổi được thực hiện cho dữ liệu. Bản sao Đặt chức năng bằng cách lắng nghe các thay đổi trên oplog này và sau đó áp dụng các thay đổi cục bộ.

Điều này nghe có vẻ quen thuộc phải không?

Tôi không thể chi tiết toàn bộ quá trình ở đây, nó là một vài trang tài liệu, nhưng các công cụ bạn cần có sẵn.

Đầu tiên một số bài viết trên oplog - Mô tả ngắn gọn - Bố cục của localbộ sưu tập (có chứa oplog)

Bạn cũng sẽ muốn tận dụng các con trỏ có sẵn . Những thứ này sẽ cung cấp cho bạn một cách để lắng nghe những thay đổi thay vì bỏ phiếu cho chúng. Lưu ý rằng sao chép sử dụng các con trỏ có sẵn, vì vậy đây là một tính năng được hỗ trợ.


1
hmm ... không chính xác những gì tôi đã nghĩ trong đầu Tôi chỉ chạy một ví dụ tại thời điểm này (không có nô lệ). Vì vậy, có thể một giải pháp cơ bản hơn?
Andrew

17
Bạn có thể khởi động máy chủ với --replSettùy chọn và nó sẽ tạo / điền vào oplog. Ngay cả khi không có thứ cấp. Đây chắc chắn là cách duy nhất để "lắng nghe" những thay đổi trong DB.
Gates VP

2
Đây là một mô tả hay về cách thiết lập oplog để ghi nhật ký thay đổi vào DB cục bộ: Loosexaml.wordpress.com/2012/09/03/ mẹo
johndodo 30/12/14

Thôi nào! Đó thực sự là những gì tôi muốn. Và tôi đã tìm thấy một thư viện có tên 'mongo-oplog' vào npm. Thật hạnh phúc ~
pjincz

Tôi đồng ý vào thời điểm viết trình kích hoạt câu trả lời này có thể không khả dụng nhưng với tất cả những người hạ cánh ở đây, hiện có một tùy chọn, hãy xem MongoDB Stitch ( docs.mongodb.com/stitch/#stitch ) & Stitch kích hoạt ( docs. mongodb.com/stitch/triggers ) ..
whoami

102

MongoDB có những gì được gọi capped collectionstailable cursorscho phép MongoDB đẩy dữ liệu đến người nghe.

A capped collectionthực chất là một bộ sưu tập có kích thước cố định và chỉ cho phép chèn thêm. Đây là những gì nó sẽ trông giống như để tạo ra một:

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

MongoDB Các con trỏ có sẵn ( bài gốc của Jonathan H. Wage )

Hồng ngọc

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

Con trăn (của 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 (bằng 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;
    }
}

Tài nguyên bổ sung:

Hướng dẫn về Ruby / Node.js hướng dẫn bạn thông qua việc tạo một ứng dụng nghe các phần chèn trong bộ sưu tập có giới hạn MongoDB.

Một bài báo nói về con trỏ có sẵn chi tiết hơn.

Các ví dụ về PHP, Ruby, Python và Perl về việc sử dụng các con trỏ có sẵn.


70
ngủ 1? có thật không? cho mã sản xuất? Làm thế nào mà không bỏ phiếu?
rbp

2
@rbp haha, tôi chưa bao giờ nói đó là mã sản xuất, nhưng bạn nói đúng, ngủ trong một giây không phải là một thực hành tốt. Khá chắc chắn tôi đã lấy ví dụ đó từ một nơi khác. Không chắc chắn làm thế nào để cấu trúc lại nó mặc dù.
Andrew

14
@kroe vì những chi tiết không liên quan đó sẽ được đưa vào mã sản xuất bởi các lập trình viên mới hơn có thể không hiểu tại sao nó xấu.
Cá trê

3
Tôi hiểu quan điểm của bạn, nhưng hy vọng một số lập trình viên mới thêm "ngủ 1" vào sản xuất là gần như gây khó chịu! Ý tôi là, tôi sẽ không ngạc nhiên ... Nhưng nếu ai đó đưa nó vào sản xuất, ít nhất sẽ học được cách khó khăn và mãi mãi .. hahaha
kroe

19
Có gì sai khi làm time.s ngủ (1) trong sản xuất?
Al Johri

44

Vì MongoDB 3.6 sẽ có một API thông báo mới có tên Change Streams mà bạn có thể sử dụng cho việc này. Xem bài đăng blog này cho một ví dụ . Ví dụ từ nó:

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
Tại sao? Bạn có thể xây dựng? Đây là cách tiêu chuẩn bây giờ?
Mitar

1
làm sao? không sử dụng bỏ phiếu - bạn cần một cách tiếp cận có sự kiện thay vì vòng lặp, v.v.
Alexander Mills

3
Bạn thấy bỏ phiếu ở đâu?
Mitar

Tôi nghĩ rằng anh ấy / cô ấy đang đề cập đến vòng lặp cuối cùng. Nhưng tôi nghĩ PyMongo chỉ hỗ trợ điều đó. Motor có thể có cách triển khai kiểu trình nghe không đồng bộ / sự kiện.
Shane Hsu

41

Kiểm tra điều này: Thay đổi luồng

Ngày 10 tháng 1 năm 2018 - Phiên bản 3.6

* EDIT: Tôi đã viết một bài viết về cách thực hiện điều này https://medium.com/riow/mongodb-data-collection-change-85b63d96ff76

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


Nó mới trong mongodb 3.6 https://docs.mongodb.com/manual/release-notes/3.6/ 2018/01/10

$ mongod --version
db version v3.6.2

Để sử dụng ChangeStreams , cơ sở dữ liệu phải là Bộ sao chép

Tìm hiểu thêm về Bộ sao chép: https://docs.mongodb.com/manual/replication/

Cơ sở dữ liệu của bạn sẽ là " Độc lập " theo mặc định.

Cách chuyển đổi độc lập thành một bộ bản sao: https://docs.mongodb.com/manual/tutorial/convert-standalone-to-replica-set/


Ví dụ sau đây là một ứng dụng thực tế cho cách bạn có thể sử dụng cái này.
* Cụ thể cho Node.

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

Các liên kết hữu ích:
https://docs.mongodb.com/manual/tutorial/convert-standopol-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


xin lỗi về tất cả các chỉnh sửa, SO không thích "Liên kết" của tôi (cho biết chúng là mã được định dạng không chính xác.)
Rio Weber

1
bạn không cần phải truy vấn cơ sở dữ liệu, tôi nghĩ với watch () hoặc tương tự, dữ liệu mới có thể được gửi đến máy chủ đang lắng nghe
Alexander Mills

22

MongoDB phiên bản 3.6 hiện bao gồm các luồng thay đổi, về cơ bản là một API trên OpLog cho phép các trường hợp sử dụng giống như kích hoạt / thông báo.

Đây là một liên kết đến một ví dụ Java: http://mongodb.github.io/mongo-java-do/3.6/do/tutorials/change-streams/

Một ví dụ NodeJS có thể trông giống như:

 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.opesify rất quan trọng để nhận dữ liệu này trong Android Studio (Ứng dụng Android) ..
DragonFire

3

Ngoài ra, bạn có thể sử dụng phương thức Mongo FindAndUpdate tiêu chuẩn và trong cuộc gọi lại, kích hoạt một sự kiện EventEuctor (trong Nút) khi cuộc gọi lại được chạy.

Bất kỳ phần nào khác của ứng dụng hoặc kiến ​​trúc nghe sự kiện này sẽ được thông báo về bản cập nhật và bất kỳ dữ liệu liên quan nào cũng được gửi ở đó. Đây là một cách thực sự đơn giản để đạt được thông báo từ Mongo.


điều này rất không hiệu quả..bạn đang khóa db cho mỗi FindAndUpdate!
Yash Gupta

1
Tôi đoán là Alex đã trả lời hơi khác (không giải quyết cụ thể các phần chèn) nhưng câu hỏi liên quan như làm thế nào để loại bỏ một số loại thông báo cho khách hàng khi trạng thái của một công việc xếp hàng mà chúng tôi cho rằng sẽ cần phải xảy ra khi công việc được sinh ra , hoàn thành thành công hoặc thất bại. Với các máy khách được kết nối bằng cách sử dụng websockets đến nút, tất cả đều có thể được thông báo về các thay đổi với sự kiện phát trên cuộc gọi lại FIndAndUpdate có thể được gọi khi nhận được thông báo thay đổi trạng thái. Tôi sẽ nói rằng điều này không hiệu quả vì các cập nhật cần phải được thực hiện.
Peter Scott

3

Nhiều câu trả lời trong số này sẽ chỉ cung cấp cho bạn hồ sơ mới, không cập nhật và / hoặc cực kỳ thiếu hiệu quả

Cách duy nhất, đáng tin cậy để thực hiện việc này là tạo một con trỏ khả dụng trên bộ sưu tập db: oplog.rs cục bộ để nhận TẤT CẢ các thay đổi đối với MongoDB và làm theo ý bạn. (MongoDB thậm chí còn thực hiện điều này trong nội bộ ít nhiều để hỗ trợ nhân rộng!)

Giải thích về những gì oplog chứa: https://www.compose.com/articles/the-mongodb-oplog-and-node-js/

Ví dụ về thư viện Node.js cung cấp API xung quanh những gì có sẵn để thực hiện với oplog: https://github.com/cayasso/mongo-oplog


2

Có một bộ dịch vụ tuyệt vời có sẵn được gọi là MongoDB Stitch . Nhìn vào chức năng khâu / kích hoạt . Lưu ý đây là dịch vụ trả phí dựa trên đám mây (AWS). Trong trường hợp của bạn, khi chèn, bạn có thể gọi một hàm tùy chỉnh được viết bằng javascript.

nhập mô tả hình ảnh ở đây


stackoverflow.com/users/486867/manish-jain - bạn có ví dụ về cách sử dụng khâu để thông báo cho ứng dụng REACT rằng dữ liệu được chèn vào bảng không?
MLissCetrus

1

Có một ví dụ java hoạt động có thể được tìm thấy ở đây .

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

Điều quan trọng là NHIỀU LỰA CHỌN được đưa ra ở đây.

Ngoài ra, bạn có thể thay đổi tìm truy vấn, nếu bạn không cần tải tất cả dữ liệu mỗi lần.

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

Trên thực tế, thay vì xem đầu ra, tại sao bạn không nhận được thông báo khi một cái gì đó mới được chèn bằng cách sử dụng kho trung gian được cung cấp bởi lược đồ mongoose

Bạn có thể bắt sự kiện chèn một tài liệu mới và làm một cái gì đó sau khi chèn xong


Lỗi của tôi. Xin lỗi sếp.
Dương Nguyên

0

Sau 3.6 người ta được phép sử dụng cơ sở dữ liệu các loại kích hoạt cơ sở dữ liệu sau:

  • kích hoạt theo sự kiện - hữu ích để tự động cập nhật các tài liệu liên quan, thông báo các dịch vụ hạ nguồn, truyền dữ liệu để hỗ trợ khối lượng công việc hỗn hợp, toàn vẹn dữ liệu & kiểm toán
  • kích hoạt theo lịch trình - hữu ích cho các công việc truy xuất, truyền bá, lưu trữ và phân tích dữ liệu theo lịch trình

Đăng nhập vào tài khoản Atlas của bạn và chọn Triggersgiao diện và thêm trình kích hoạt mới:

nhập mô tả hình ảnh ở đây

Mở rộng từng phần để biết thêm cài đặt hoặc chi tiết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.