MongoDB / Mongoose ¿Cómo emparejo dos entradas de db sin conflictos?

Consulte esta pregunta para obtener información adicional:

MongoDB La mejor manera de emparejar y eliminar entradas de base de datos secuenciales


De acuerdo, estoy haciendo un juego de “guerra ciega”, si quieres. Basicamente funciona de esta manera:

Hay 10,000 usuarios jugando al mismo tiempo. No hay cuentas de usuario, solo un alias que elijan al visitar mi sitio web. Un usuario recibe una cantidad aleatoria de tropas en la visita. Un usuario puede enviar cualquier cantidad de sus tropas para ir a la “guerra ciega”.

El juego funciona así: el usuario A envía x cantidad de tropas. (diga 100) El usuario B envía x cantidad de tropas (digamos 72) (todas estas tropas se llevan a cabo en el “grupo de tropas gigantes”)

La aplicación empareja a estos dos usuarios. El usuario A gana y le da el paquete a sus tropas más las tropas del usuario Bs. (alias 172 tropas) El usuario B pierde y no se le devuelve nada.

Aquí está mi problema: ¡potencialmente podría tener 2,000 usuarios enviando tropas en relativamente al mismo tiempo! ¡Necesito asegurarme de que tres o más usuarios nunca se comparen en un accidente!

Estaba pensando en hacer algo como esto:

Duels .Where('_id').lt('objectId') .Where('User').ne('A') .exec(callback) function callback(err,doc){ /* Do troops comparison & other stuff here */ doc.remove }; 

pero me temo que esto puede terminar en documentos que toman dos del mismo documento antes de eliminarlos. Haciendo que el grupo de tropas esté sobregirado …

¿Hay alguna manera de evitar que esto suceda?

Siguiendo con mi respuesta original , esto es, de nuevo, algo en lo que pueden surgir algunas ideas diferentes en su ayuda. Y como tal, esto parece tener más que ver con la architecture que decir que la implementación de su código “de cierta manera” será la mejor manera de hacerlo.

Por tu comentario sobre eso y tu pregunta aquí, parece que el problema que debes resolver es cómo ajustar el conteo de tropas para el otro usuario que juega el movimiento. Comencemos mirando de nuevo los mismos datos:

 { "_id" : ObjectId("531cf5f3ba53b9dd07756bb7"), "user" : "A", "units" : 50 } { "_id" : ObjectId("531cf622ba53b9dd07756bb9"), "user" : "B", "units" : 62 } 

Haciendo el “movimiento”

Entonces, la situación aquí es que el usuario “B” acaba de hacer su movimiento y cometió 62 unidades en ese movimiento. En la publicación anterior, expliqué cómo recuperar el movimiento para el usuario correspondiente “A” y, por lo tanto, puede “emparejarlos” y determinar la ganancia.

Tomando eso más en cuenta, considere lo que sucedió en la solicitud. Usuario “B” enviado, luego inserta el documento para su movimiento, luego lee el correspondiente. Así que ahora mismo tienes ambos documentos en memoria para la solicitud. Si considera los datos de la sesión, entonces podría tener algo como esto (de una manera muy breve):

 { currentUnits: 100 } 

Llamemos a eso la cuenta inicial. Entonces, cuando envías un movimiento del usuario, simplemente disminuyes el número de tropas que tienen. Así que al hacer el inserto de 62 tropas, el contador va a esto:

 { currentUnits: 38 } 

Es una buena práctica, ya que lo hace en el reconocimiento de inserción dentro del movimiento. Pero luego, dentro de esa callback, harás el hallazgo como dije, y eso solo devuelve un documento. Ahora tienes la información que puedes comparar y hacer tus cálculos. El usuario “B” gana para que pueda ajustar el valor de su sesión:

 { currentUnits: 150 } 

Así que eso debería abarcar todo para el traslado del usuario “B”. Quitó las unidades cuando se jugó un movimiento, igualó al otro jugador, luego “hizo los cálculos” y ajustó sus resultados. ¡Hecho! Ah, y guardaste todos los datos de la sesión en un almacén persistente, ¿no? Nod si. Y también que los datos de la sesión están vinculados al identificador del usuario (o el usuario es, de hecho, el identificador de la sesión) para poder acceder a su modificación.

Todo lo que queda es “notificar” al otro jugador.


Contándole a alguien más las noticias.

Esta parte debe ser simple. Así que no lo estoy codificando para ti. Está utilizando socket.io para su aplicación, por lo que todo esto se reduce a enviar un mensaje. Eso significa que los datos que “emites” le dicen al otro usuario del cliente que “perdieron sus tropas”, sin embargo, debes lidiar con ellos. Pero también recuerde que usted “quitó” esas unidades cuando se envió su movimiento . En todos los casos, es asegurarse de que nadie pueda cometer más de lo que tienen.

Lo único que se puede hablar aquí es escalar su aplicación más allá de una instancia. Así que puede hablar alegremente con los eventos en “nodo”, todos trabajando en una instancia de servidor, pero para “escalar” necesitaría pasar mensajes entre diferentes instancias.

Una forma de manejar esto usando MongoDB puede ser con colecciones limitadas .

Aparte de lo que generalmente hacen las colecciones con límite en la forma de mantener un tamaño establecido para una colección de documentos, hay una cosa más que ofrecen, y es un cursor que se puede enviar . Una forma bastante atípica de crear uno con el controlador de nodo sería:

 var options = { tailable: true, awaitdata: true, numberOfRetries: -1 }; var cursor = collection.find(query, options).sort({ $natural: 1 }); 

Las opciones completas se enumeran en la sección Cursor () de la página del manual del controlador. Puede obtener estos métodos “nativos” en la mongoose por la forma típica.

Para lo que se configura un cursor de “tailable” es “seguir” el documento “último insertado” en la colección y puede sentarse y “seguir” de esta manera con una encuesta uniforme, como en:

  (function more() { cursor.nextObject(handle(function(doc) { if (!doc) return setTimeout(poll, self.wait); callback(doc); latest = doc._id; more(); })); })(); 

Por lo tanto, dentro de esa construcción, “encuentra” el documento recién insertado y pasa a su callback interna la información a procesar, donde “envía” mensajes a los clientes, actualiza las cosas y cualquier otra cosa que desee hacer.

De vuelta a su “solicitud” real, entonces emitiría un inserto después de que “hiciera sus cálculos” a la “colección limitada” separada. Usted querría algo significativo por breve como:

 { "player": "B", "vsplayer": "A", "win": 50, "loss": 62 } 

Y de nuevo estos son solo inserciones. Por lo tanto, debe configurar un índice TTL para manejar las eliminaciones a lo largo del tiempo y al limitarse, las entradas antiguas se agotarán naturalmente al ser “expulsadas” de las entradas presentes en la colección.

En el lado del “cliente”, cada aplicación de usuario conectada realiza un seguimiento del valor “último _id” revelado. Por lo tanto, las entradas recién insertadas son siempre mayores en valor a las “anteriores”.

Por lo tanto, hay “una forma” de usar MongoDB para crear una cola persistente que puede procesar de manera secuencial para compartir el paso de mensajes entre varias instancias de servidores de aplicaciones.


Ultimas palabras

Con todo lo dicho para implementar un cursor “capaz de hacer cola” de esta manera, por mi dinero estaría usando zeromq o algo parecido. Pero es posible que el método MongoDB sea más adecuado para usted si no desea profundizar en otra tecnología. O tal vez este tipo de “escalabilidad” no sea necesario para su aplicación (al menos en esta etapa) y el simple hecho de pasar a los métodos “socket.io” dentro de la solicitud sería suficiente. Depende de usted.

Sin embargo, en gran medida, parece que todavía está “colgado” de sus conceptos de “combinación” y “eliminación”. Esta fue la intención de cubrir en la última respuesta y fue decir que la eliminación de documentos cuando se procesan no es necesaria . El proceso descrito garantiza que nunca volverá a recibir el “mismo par” en ninguna solicitud.

Le animaría a “releer” esa información y realmente entender el proceso. Y pregunta si tienes preguntas. De lo que se ha discutido allí, la analogía de su patrón de acceso a datos es más como “jugar en una stack” que “emparejar pares”.

Entonces, lo que recibió como respuesta, siguiendo con la lógica que se describe aquí, es todo lo que necesita para configurar sus patrones de acceso a datos. Su otro componente será, por supuesto, el mensaje, pero esto le da acceso a los datos que necesita.