Meteor có thể hoạt động hiệu quả như thế nào trong khi chia sẻ một bộ sưu tập khổng lồ giữa nhiều khách hàng?


100

Hãy tưởng tượng trường hợp sau:

  • 1.000 máy khách được kết nối với trang Meteor hiển thị nội dung của bộ sưu tập "Somestuff".

  • "Somestuff" là một bộ sưu tập chứa 1.000 vật phẩm.

  • Ai đó chèn một mục mới vào bộ sưu tập "Somestuff"

Chuyện gì sẽ xảy ra:

  • Tất cả các ứng Meteor.Collectiondụng khách sẽ được cập nhật tức là nội dung chèn được chuyển tiếp đến tất cả chúng (có nghĩa là một thông báo chèn được gửi đến 1.000 khách hàng)

Chi phí về CPU cho máy chủ là bao nhiêu để xác định máy khách nào cần được cập nhật?

Có chính xác không khi chỉ giá trị đã chèn mới được chuyển tiếp tới các máy khách chứ không phải toàn bộ danh sách?

Điều này hoạt động như thế nào trong cuộc sống thực? Có bất kỳ điểm chuẩn hoặc thử nghiệm nào ở quy mô như vậy không?

Câu trả lời:


119

Câu trả lời ngắn gọn là chỉ có dữ liệu mới được gửi xuống dây. Đây là cách nó hoạt động.

Có ba phần quan trọng của máy chủ Meteor quản lý đăng ký: chức năng xuất bản , xác định logic cho dữ liệu mà đăng ký cung cấp; các tài xế Mongo , mà đồng hồ cơ sở dữ liệu cho những thay đổi; và hộp hợp nhất , kết hợp tất cả các đăng ký đang hoạt động của khách hàng và gửi chúng qua mạng cho khách hàng.

Xuất bản các chức năng

Mỗi khi máy khách Meteor đăng ký một bộ sưu tập, máy chủ sẽ chạy chức năng xuất bản . Công việc của chức năng xuất bản là tìm ra tập hợp các tài liệu mà ứng dụng khách của nó phải có và gửi từng thuộc tính tài liệu vào hộp phối. Nó chạy một lần cho mỗi khách hàng đăng ký mới. Bạn có thể đặt bất kỳ JavaScript nào bạn muốn vào chức năng xuất bản, chẳng hạn như sử dụng điều khiển truy cập phức tạp tùy ý this.userId. Các công bố chức năng gửi dữ liệu vào ô merge bằng cách gọi this.added, this.changedthis.removed. Xem tài liệu xuất bản đầy đủ để biết thêm chi tiết.

Hầu hết các công bố chức năng không cần phải làm việc nhiều với mức độ thấp added, changedremovedAPI, mặc dù. Nếu một công bố chức năng trả về một con trỏ Mongo, máy chủ Meteor tự động kết nối đầu ra của người lái xe Mongo ( insert, updateremovedcallbacks) đến đầu vào của hộp merge ( this.added, this.changedthis.removed). Khá gọn gàng là bạn có thể thực hiện tất cả các kiểm tra quyền trước trong một chức năng xuất bản và sau đó kết nối trực tiếp trình điều khiển cơ sở dữ liệu với hộp hợp nhất mà không cần bất kỳ mã người dùng nào. Và khi tự động xuất bản được bật, ngay cả một chút này cũng bị ẩn đi: máy chủ tự động thiết lập truy vấn cho tất cả tài liệu trong mỗi bộ sưu tập và đẩy chúng vào hộp hợp nhất.

Mặt khác, bạn không bị giới hạn trong việc xuất bản các truy vấn cơ sở dữ liệu. Ví dụ: bạn có thể viết một hàm xuất bản đọc vị trí GPS từ một thiết bị bên trong một Meteor.setIntervalhoặc thăm dò một API REST kế thừa từ một dịch vụ web khác. Trong những trường hợp, bạn sẽ phát ra thay đổi đối với hộp kết hợp bằng cách gọi ở mức độ thấp added, changedremovedDDP API.

Người lái xe Mongo

Công việc của trình điều khiển Mongo là xem cơ sở dữ liệu Mongo để biết các thay đổi đối với các truy vấn trực tiếp. Các truy vấn này chạy liên tục và trở về bản cập nhật như sự thay đổi kết quả bằng cách gọi added, removedchangedcallbacks.

Mongo không phải là cơ sở dữ liệu thời gian thực. Vì vậy, người lái xe thăm dò ý kiến. Nó giữ một bản sao trong bộ nhớ của kết quả truy vấn cuối cùng cho mỗi truy vấn trực tiếp đang hoạt động. Trên mỗi chu kỳ bỏ phiếu, nó sẽ so sánh kết quả mới với kết quả đã lưu trước đó, tính toán các thiết lập tối thiểu added, removedchanged các sự kiện mô tả sự khác biệt. Nếu nhiều người gọi đăng ký các lệnh gọi lại cho cùng một truy vấn trực tiếp, trình điều khiển chỉ xem một bản sao của truy vấn, gọi mỗi lệnh gọi lại đã đăng ký với cùng một kết quả.

Mỗi khi máy chủ cập nhật một bộ sưu tập, trình điều khiển sẽ tính toán lại từng truy vấn trực tiếp trên bộ sưu tập đó (Các phiên bản tương lai của Meteor sẽ hiển thị API điều chỉnh tỷ lệ để giới hạn các truy vấn trực tiếp được tính toán lại khi cập nhật.) Trình điều khiển cũng thăm dò từng truy vấn trực tiếp trên bộ hẹn giờ 10 giây để bắt các bản cập nhật cơ sở dữ liệu ngoài băng tần đã bỏ qua máy chủ Meteor.

Hộp hợp nhất

Công việc của hộp kết hợp là kết hợp các kết quả ( added, changedremoved cuộc gọi) của tất cả các công bố các chức năng hoạt động của khách hàng vào một dòng dữ liệu duy nhất. Có một hộp hợp nhất cho mỗi máy khách được kết nối. Nó chứa một bản sao hoàn chỉnh của bộ nhớ cache minimongo của khách hàng.

Trong ví dụ của bạn chỉ với một đăng ký duy nhất, hộp hợp nhất về cơ bản là một chuyển tiếp. Nhưng một ứng dụng phức tạp hơn có thể có nhiều đăng ký có thể trùng lặp. Nếu cả hai đăng ký đều đặt cùng một thuộc tính trên cùng một tài liệu, hộp hợp nhất sẽ quyết định giá trị nào được ưu tiên và chỉ gửi giá trị đó cho máy khách. Chúng tôi chưa tiết lộ API để đặt mức độ ưu tiên đăng ký. Hiện tại, mức độ ưu tiên được xác định bởi thứ tự khách hàng đăng ký tập dữ liệu. Đăng ký đầu tiên mà khách hàng thực hiện có mức độ ưu tiên cao nhất, đăng ký thứ hai có mức độ ưu tiên cao nhất tiếp theo, v.v.

Vì hộp hợp nhất giữ trạng thái của khách hàng, nó có thể gửi số lượng dữ liệu tối thiểu để giữ cho từng ứng dụng được cập nhật, bất kể chức năng xuất bản cung cấp cho nó là gì.

Điều gì xảy ra trên bản cập nhật

Vì vậy, bây giờ chúng tôi đã tạo tiền đề cho kịch bản của bạn.

Chúng tôi có 1.000 khách hàng được kết nối. Mỗi người được đăng ký vào cùng một truy vấn Mongo trực tiếp ( Somestuff.find({})). Vì truy vấn giống nhau cho mỗi máy khách, trình điều khiển chỉ chạy một truy vấn trực tiếp. Có 1.000 hộp hợp nhất đang hoạt động. Và mỗi khách hàng của công bố chức năng đăng ký một added, changedremovedtrên đó truy vấn trực tiếp rằng thức ăn vào một trong các hộp merge. Không có gì khác được kết nối với các hộp hợp nhất.

Đầu tiên là trình điều khiển Mongo. Khi một trong các máy khách chèn một tài liệu mới vào Somestuff, nó sẽ kích hoạt tính toán lại. Trình điều khiển Mongo chạy lại truy vấn cho tất cả các tài liệu trong Somestuff, so sánh kết quả với kết quả trước đó trong bộ nhớ, nhận thấy rằng có một tài liệu mới và gọi mỗi trong số 1.000 lệnh insertgọi lại đã đăng ký .

Tiếp theo, các chức năng xuất bản. Có rất ít điều xảy ra ở đây: mỗi 1.000 lệnh insertgọi lại đẩy dữ liệu vào hộp hợp nhất bằng cách gọi added.

Cuối cùng, mỗi hộp hợp nhất sẽ kiểm tra các thuộc tính mới này so với bản sao trong bộ nhớ của bộ nhớ cache của khách hàng của nó. Trong mỗi trường hợp, nó nhận thấy rằng các giá trị chưa có trên máy khách và không phủ bóng giá trị hiện có. Vì vậy, hộp hợp nhất phát ra một DATAthông báo DDP trên kết nối SockJS tới máy khách của nó và cập nhật bản sao trong bộ nhớ phía máy chủ của nó.

Tổng chi phí CPU là chi phí để khác biệt một truy vấn Mongo, cộng với chi phí của 1.000 hộp hợp nhất kiểm tra trạng thái của khách hàng của họ và xây dựng trọng tải thông báo DDP mới. Dữ liệu duy nhất truyền qua dây là một đối tượng JSON duy nhất được gửi đến từng máy khách trong số 1.000 máy khách, tương ứng với tài liệu mới trong cơ sở dữ liệu, cộng với một thông báo RPC tới máy chủ từ máy khách đã thực hiện chèn ban đầu.

Tối ưu hóa

Đây là những gì chúng tôi chắc chắn đã lên kế hoạch.

  • Trình điều khiển Mongo hiệu quả hơn. Chúng tôi đã tối ưu hóa trình điều khiển trong 0.5.1 để chỉ chạy một trình quan sát duy nhất cho mỗi truy vấn riêng biệt.

  • Không phải mọi thay đổi DB đều nên kích hoạt tính toán lại một truy vấn. Chúng tôi có thể thực hiện một số cải tiến tự động, nhưng cách tiếp cận tốt nhất là một API cho phép nhà phát triển chỉ định truy vấn nào cần chạy lại. Ví dụ: rõ ràng đối với một nhà phát triển rằng việc chèn một tin nhắn vào một phòng trò chuyện sẽ không làm mất hiệu lực của một truy vấn trực tiếp cho các tin nhắn trong phòng thứ hai.

  • Trình điều khiển Mongo, chức năng xuất bản và hộp hợp nhất không cần phải chạy trong cùng một quy trình hoặc thậm chí trên cùng một máy. Một số ứng dụng chạy các truy vấn trực tiếp phức tạp và cần nhiều CPU hơn để xem cơ sở dữ liệu. Những người khác chỉ có một số truy vấn riêng biệt (hãy tưởng tượng một công cụ blog), nhưng có thể có nhiều máy khách được kết nối - những máy khách này cần nhiều CPU hơn cho các hộp hợp nhất. Việc tách các thành phần này sẽ cho phép chúng tôi chia tỷ lệ từng phần một cách độc lập.

  • Nhiều cơ sở dữ liệu hỗ trợ trình kích hoạt kích hoạt khi một hàng được cập nhật và cung cấp các hàng cũ và mới. Với tính năng đó, trình điều khiển cơ sở dữ liệu có thể đăng ký trình kích hoạt thay vì bỏ phiếu cho các thay đổi.


Có ví dụ nào về cách sử dụng Meteor.publish để xuất bản dữ liệu không phải con trỏ không? Chẳng hạn như kết quả từ một API còn lại kế thừa được đề cập trong câu trả lời?
Tony

@Tony: Nó có trong tài liệu. Kiểm tra ví dụ đếm phòng.
Mitar

Điều đáng chú ý là trong phiên bản 0.7, 0.7.1, 0.7.2 Meteor chuyển sang OpLog Quan sát driver cho hầu hết các truy vấn (trường hợp ngoại lệ là skip, $near$wherecác truy vấn chứa) là hiệu quả hơn trong tải CPU, băng thông mạng và cho phép mở rộng quy mô ra ứng dụng may chủ.
imslavko

Còn khi không phải mọi người dùng đều thấy dữ liệu giống nhau. 1. họ đã đăng ký các chủ đề khác nhau .2. họ có những vai trò khác nhau nên trong cùng một chủ đề chính, có một vài thông điệp không được phép gửi đến họ.
tgkprog

@debergalis liên quan đến bộ nhớ cache huỷ bỏ hiệu lực, có lẽ bạn sẽ tìm thấy ý tưởng từ giấy của tôi vanisoft.pl/~lopuszanski/public/cache_invalidation.pdf đáng giá
qbolec

29

Theo kinh nghiệm của tôi, việc sử dụng nhiều ứng dụng khách trong khi chia sẻ một bộ sưu tập khổng lồ trong Meteor về cơ bản là không thể hoạt động, kể từ phiên bản 0.7.0.1. Tôi sẽ cố gắng giải thích tại sao.

Như được mô tả trong bài đăng trên và cả trong https://github.com/meteor/meteor/issues/1821 , máy chủ sao băng phải giữ một bản sao của dữ liệu đã xuất bản cho từng máy khách trong hộp hợp nhất . Đây là điều cho phép phép thuật Meteor xảy ra, nhưng cũng dẫn đến việc bất kỳ cơ sở dữ liệu chia sẻ lớn nào được lưu giữ nhiều lần trong bộ nhớ của tiến trình nút. Ngay cả khi sử dụng tối ưu hóa khả thi cho các tập hợp tĩnh chẳng hạn như trong ( Có cách nào để nói sao băng là tập hợp tĩnh (sẽ không bao giờ thay đổi) không? ), Chúng tôi đã gặp phải sự cố lớn với việc sử dụng CPU và Bộ nhớ của tiến trình Node.

Trong trường hợp của chúng tôi, chúng tôi đã xuất bản một bộ sưu tập 15 nghìn tài liệu cho mỗi khách hàng hoàn toàn tĩnh. Vấn đề là việc sao chép các tài liệu này vào hộp hợp nhất của máy khách (trong bộ nhớ) khi kết nối về cơ bản đã đưa quy trình Node lên 100% CPU trong gần một giây và dẫn đến việc sử dụng thêm bộ nhớ. Điều này vốn dĩ không thể thay đổi được quy mô, bởi vì bất kỳ máy khách nào đang kết nối sẽ làm cho máy chủ dừng lại (và các kết nối đồng thời sẽ chặn lẫn nhau) và việc sử dụng bộ nhớ sẽ tăng tuyến tính theo số lượng máy khách. Trong trường hợp của chúng tôi, mỗi máy khách gây ra sử dụng thêm ~ 60 MB bộ nhớ, mặc dù dữ liệu thô được truyền chỉ khoảng 5 MB.

Trong trường hợp của chúng tôi, vì bộ sưu tập là tĩnh, chúng tôi đã giải quyết vấn đề này bằng cách gửi tất cả tài liệu dưới dạng .jsontệp, được giải nén bởi nginx và tải chúng vào một bộ sưu tập ẩn danh, dẫn đến chỉ truyền ~ 1MB dữ liệu mà không cần thêm CPU. hoặc bộ nhớ trong tiến trình nút và thời gian tải nhanh hơn nhiều. Tất cả các thao tác trên bộ sưu tập này được thực hiện bằng cách sử dụng _idcác ấn phẩm nhỏ hơn nhiều trên máy chủ, cho phép giữ lại hầu hết các lợi ích của Meteor. Điều này cho phép ứng dụng mở rộng quy mô đến nhiều khách hàng hơn. Ngoài ra, vì ứng dụng của chúng tôi chủ yếu ở chế độ chỉ đọc, chúng tôi đã cải thiện hơn nữa khả năng mở rộng bằng cách chạy nhiều phiên bản Meteor phía sau nginx với cân bằng tải (mặc dù với một Mongo duy nhất), vì mỗi phiên bản Node là một luồng.

Tuy nhiên, vấn đề chia sẻ các bộ sưu tập lớn, có thể ghi giữa nhiều máy khách là một vấn đề kỹ thuật cần được giải quyết bởi Meteor. Có lẽ có một cách tốt hơn là giữ một bản sao của mọi thứ cho mỗi khách hàng, nhưng điều đó đòi hỏi một số suy nghĩ nghiêm túc như một vấn đề hệ thống phân tán. Các vấn đề hiện tại của việc sử dụng CPU và bộ nhớ lớn sẽ không mở rộng.


@Harry oplog không thành vấn đề trong tình huống này; dữ liệu tĩnh.
Andrew Mao

Tại sao nó không làm khác các bản sao minimongo phía máy chủ? Có lẽ đó là tất cả thay đổi trong 1.0? Ý tôi là thường họ là những tôi cùng muốn hy vọng, thậm chí chức năng mà nó gọi lại sẽ tương tự (nếu tôi sau đó rằng để là cái gì đó được lưu trữ ở đó cũng và có khả năng khác nhau.)
MistereeDevlord

@MistereeDevlord Sự khác biệt của các thay đổi và bộ nhớ cache của dữ liệu khách hàng là riêng biệt ngay bây giờ. Ngay cả khi mọi người đều có dữ liệu giống nhau và chỉ cần một điểm khác nhau, bộ nhớ đệm trên mỗi máy khách sẽ khác nhau vì máy chủ không thể xử lý chúng giống nhau. Điều này chắc chắn có thể được thực hiện thông minh hơn so với triển khai hiện tại.
Andrew Mao

@AndrewMao Làm cách nào để bạn đảm bảo rằng các tệp đã nén được bảo mật khi gửi chúng đến ứng dụng khách, tức là chỉ một ứng dụng khách đã đăng nhập mới có thể truy cập nó?
FullStack

4

Thử nghiệm mà bạn có thể sử dụng để trả lời câu hỏi này:

  1. Cài đặt một thiên thạch thử nghiệm: meteor create --example todos
  2. Chạy nó trong trình kiểm tra Webkit (WKI).
  3. Kiểm tra nội dung của thông báo XHR di chuyển trên dây.
  4. Quan sát rằng toàn bộ bộ sưu tập không bị di chuyển trên dây.

Để biết các mẹo về cách sử dụng WKI, hãy xem bài viết này . Nó hơi lỗi thời, nhưng hầu như vẫn còn giá trị, đặc biệt là cho câu hỏi này.


2
Giải thích về cơ chế bỏ phiếu: eventedmind.com/posts/meteor-liveresultsset
cmather

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.