La respuesta corta es que solo se envían nuevos datos por el cable. Así es como funciona.
Hay tres partes importantes del servidor Meteor que administran las suscripciones: la función de publicación , que define la lógica de los datos que proporciona la suscripción; el controlador Mongo , que vigila la base de datos en busca de cambios; y el cuadro de combinación , que combina todas las suscripciones activas de un cliente y las envía a través de la red al cliente.
Publicar funciones
Cada vez que un cliente de Meteor se suscribe a una colección, el servidor ejecuta una
función de publicación . El trabajo de la función de publicación es averiguar el conjunto de documentos que su cliente debe tener y enviar cada propiedad del documento al cuadro de combinación. Se ejecuta una vez por cada nuevo cliente suscrito. Puede poner cualquier JavaScript que desee en la función de publicación, como el uso de control de acceso arbitrariamente complejo this.userId
. El publican función envía los datos en la caja de combinación llamando this.added
, this.changed
y
this.removed
. Consulte la
documentación de publicación completa para obtener más detalles.
La mayoría de publicar funciones no tienen que ensuciar alrededor con el bajo nivel
added
, changed
y la removed
API, sin embargo. Si una publican función devuelve un cursor Mongo, el servidor Meteor conecta automáticamente la salida del controlador de Mongo ( insert
, update
y removed
devoluciones de llamada) a la entrada de la caja de combinación ( this.added
, this.changed
y this.removed
). Es bastante bueno que pueda hacer todas las comprobaciones de permisos por adelantado en una función de publicación y luego conectar directamente el controlador de la base de datos al cuadro de combinación sin ningún código de usuario en el camino. Y cuando la publicación automática está activada, incluso esta pequeña parte está oculta: el servidor configura automáticamente una consulta para todos los documentos de cada colección y los inserta en el cuadro de combinación.
Por otro lado, no está limitado a publicar consultas de bases de datos. Por ejemplo, puede escribir una función de publicación que lea una posición GPS de un dispositivo dentro de un Meteor.setInterval
, o sondee una API REST heredada de otro servicio web. En esos casos, usted emiten cambios en el cuadro de combinación llamando al bajo nivel added
, changed
y removed
DDP API.
El conductor de Mongo
El trabajo del conductor de Mongo es observar la base de datos de Mongo para ver si hay cambios en las consultas en vivo. Estas consultas se ejecuten de forma continua y actualizaciones a medida que cambian los resultados vuelven llamando added
, removed
y changed
las devoluciones de llamada.
Mongo no es una base de datos en tiempo real. Así que el conductor realiza encuestas. Mantiene una copia en memoria del último resultado de la consulta para cada consulta activa en vivo. En cada ciclo de sondeo, se compara el nuevo resultado con el resultado guardado anterior, calcular el conjunto mínimo de added
, removed
y changed
acontecimientos que describen la diferencia. Si varias personas que llaman registran devoluciones de llamada para la misma consulta en vivo, el conductor solo ve una copia de la consulta, llamando a cada devolución de llamada registrada con el mismo resultado.
Cada vez que el servidor actualiza una colección, el controlador vuelve a calcular cada consulta en vivo en esa colección (las versiones futuras de Meteor expondrán una API de escalado para limitar qué consultas en vivo se recalculan en la actualización). El controlador también sondea cada consulta en vivo en un temporizador de 10 segundos para detectar actualizaciones de base de datos fuera de banda que eludieron el servidor Meteor.
El cuadro de combinación
El trabajo del cuadro de combinación es la combinación de los resultados ( added
, changed
y removed
llamadas) de todas las funciones publicar activos de un cliente en un único flujo de datos. Hay un cuadro de combinación para cada cliente conectado. Contiene una copia completa de la caché minimongo del cliente.
En su ejemplo con una sola suscripción, el cuadro de combinación es esencialmente una transferencia. Pero una aplicación más compleja puede tener varias suscripciones que pueden superponerse. Si dos suscripciones establecen el mismo atributo en el mismo documento, el cuadro de combinación decide qué valor tiene prioridad y solo lo envía al cliente. Aún no hemos expuesto la API para establecer la prioridad de suscripción. Por ahora, la prioridad está determinada por el orden que el cliente suscribe a los conjuntos de datos. La primera suscripción que realiza un cliente tiene la prioridad más alta, la segunda suscripción es la siguiente más alta, y así sucesivamente.
Debido a que el cuadro de combinación contiene el estado del cliente, puede enviar la cantidad mínima de datos para mantener a cada cliente actualizado, sin importar la función de publicación que lo alimente.
Qué sucede en una actualización
Así que ahora hemos preparado el escenario para su escenario.
Tenemos 1.000 clientes conectados. Cada uno está suscrito a la misma consulta de Mongo en vivo ( Somestuff.find({})
). Dado que la consulta es la misma para cada cliente, el controlador solo ejecuta una consulta en vivo. Hay 1000 cuadros de combinación activos. Y la función de publicación de cada cliente registró un added
, changed
y
removed
en esa consulta en vivo que se alimenta en uno de los cuadros de combinación. Nada más está conectado a los cuadros de combinación.
Primero el controlador de Mongo. Cuando uno de los clientes inserta un nuevo documento en Somestuff
, se activa un nuevo cálculo. El controlador de Mongo vuelve a ejecutar la consulta para todos los documentos en Somestuff
, compara el resultado con el resultado anterior en la memoria, encuentra que hay un documento nuevo y llama a cada una de las 1,000 insert
devoluciones de llamada registradas .
A continuación, las funciones de publicación. Aquí sucede muy poco: cada una de las 1,000 insert
devoluciones de llamada empuja los datos al cuadro de combinación mediante una llamada added
.
Por último, cada cuadro de combinación comprueba estos nuevos atributos con su copia en memoria de la caché de su cliente. En cada caso, encuentra que los valores aún no están en el cliente y no sombrean un valor existente. Entonces, el cuadro de combinación emite un DATA
mensaje DDP en la conexión SockJS a su cliente y actualiza su copia en memoria del lado del servidor.
El costo total de la CPU es el costo de diferenciar una consulta de Mongo, más el costo de 1,000 cuadros de combinación que verifican el estado de sus clientes y construyen una nueva carga útil de mensajes DDP. Los únicos datos que fluyen a través del cable son un único objeto JSON enviado a cada uno de los 1,000 clientes, correspondiente al nuevo documento en la base de datos, más un mensaje RPC al servidor desde el cliente que hizo la inserción original.
Optimizaciones
Esto es lo que definitivamente hemos planeado.
Conductor Mongo más eficiente. Hemos
optimizado el controlador
en 0.5.1 sólo para ejecutar un solo observador por consulta distinta.
No todos los cambios en la base de datos deberían desencadenar un nuevo cálculo de una consulta. Podemos realizar algunas mejoras automatizadas, pero el mejor enfoque es una API que permite al desarrollador especificar qué consultas deben volver a ejecutarse. Por ejemplo, es obvio para un desarrollador que insertar un mensaje en una sala de chat no debería invalidar una consulta en vivo para los mensajes en una segunda sala.
El controlador de Mongo, la función de publicación y el cuadro de combinación no necesitan ejecutarse en el mismo proceso, ni siquiera en la misma máquina. Algunas aplicaciones ejecutan consultas complejas en vivo y necesitan más CPU para observar la base de datos. Otros tienen solo unas pocas consultas distintas (imagine un motor de blog), pero posiblemente muchos clientes conectados, estos necesitan más CPU para los cuadros de combinación. Separar estos componentes nos permitirá escalar cada pieza de forma independiente.
Muchas bases de datos admiten activadores que se activan cuando se actualiza una fila y proporcionan las filas nuevas y antiguas. Con esa función, un controlador de base de datos podría registrar un disparador en lugar de sondear los cambios.
skip
,$near
y$where
las consultas que contienen) que es mucho más eficiente en la carga de la CPU, el ancho de banda de la red y permite el escalado de aplicaciones servidores.Desde mi experiencia, usar muchos clientes mientras se comparte una gran colección en Meteor es esencialmente inviable, a partir de la versión 0.7.0.1. Intentaré explicar por qué.
Como se describe en la publicación anterior y también en https://github.com/meteor/meteor/issues/1821 , el servidor de meteoritos debe guardar una copia de los datos publicados para cada cliente en el cuadro de combinación . Esto es lo que permite que suceda la magia del meteorito, pero también hace que las grandes bases de datos compartidas se guarden repetidamente en la memoria del proceso del nodo. Incluso cuando usamos una posible optimización para colecciones estáticas como en ( ¿Hay alguna manera de decirle a meteor que una colección es estática (nunca cambiará)? ), Experimentamos un gran problema con el uso de CPU y memoria del proceso Node.
En nuestro caso, estábamos publicando una colección de 15k documentos para cada cliente que era completamente estática. El problema es que copiar estos documentos en el cuadro de combinación de un cliente (en la memoria) después de la conexión básicamente llevó el proceso de nodo al 100% de la CPU durante casi un segundo y dio como resultado un gran uso adicional de memoria. Esto es inherentemente no escalable, porque cualquier cliente que se conecte pondrá de rodillas al servidor (y las conexiones simultáneas se bloquearán entre sí) y el uso de la memoria aumentará linealmente en el número de clientes. En nuestro caso, cada cliente provocó un uso adicional de ~ 60 MB de memoria, a pesar de que los datos sin procesar transferidos eran solo de unos 5 MB.
En nuestro caso, debido a que la colección era estática, resolvimos este problema enviando todos los documentos como un
.json
archivo, que fue comprimido con gzip por nginx, y cargándolos en una colección anónima, lo que resultó en una transferencia de datos de solo ~ 1 MB sin CPU adicional o memoria en el proceso del nodo y un tiempo de carga mucho más rápido. Todas las operaciones sobre esta colección se realizaron utilizando_id
correos electrónicos de publicaciones mucho más pequeñas en el servidor, lo que permitió conservar la mayoría de los beneficios de Meteor. Esto permitió que la aplicación escale a muchos más clientes. Además, debido a que nuestra aplicación es principalmente de solo lectura, mejoramos aún más la escalabilidad al ejecutar varias instancias de Meteor detrás de nginx con equilibrio de carga (aunque con un solo Mongo), ya que cada instancia de Node es de un solo subproceso.Sin embargo, el problema de compartir grandes colecciones de escritura entre varios clientes es un problema de ingeniería que debe ser resuelto por Meteor. Probablemente haya una manera mejor que guardar una copia de todo para cada cliente, pero eso requiere un pensamiento serio como un problema de sistemas distribuidos. Los problemas actuales del uso masivo de CPU y memoria simplemente no se escalarán.
fuente
El experimento que puede utilizar para responder a esta pregunta:
meteor create --example todos
Para obtener consejos sobre cómo usar WKI, consulte este artículo . Está un poco desactualizado, pero en su mayoría sigue siendo válido, especialmente para esta pregunta.
fuente
Esto todavía tiene un año y, por lo tanto, creo que el conocimiento previo a "Meteor 1.0", ¿es posible que las cosas hayan cambiado nuevamente? Todavía estoy investigando esto. http://meteorhacks.com/does-meteor-scale.html conduce a un "¿Cómo escalar Meteor?" artículo http://meteorhacks.com/how-to-scale-meteor.html
fuente