¿Es esta la arquitectura correcta para nuestro juego móvil MMORPG?

14

En estos días estoy tratando de diseñar la arquitectura de un nuevo juego móvil MMORPG para mi empresa. Este juego es similar a Mafia Wars, iMobsters o RISK. La idea básica es preparar un ejército para luchar contra tus oponentes (usuarios en línea).

Aunque anteriormente he trabajado en múltiples aplicaciones móviles, esto es algo nuevo para mí. Después de mucha lucha, se me ocurrió una arquitectura que se ilustra con la ayuda de un diagrama de flujo de alto nivel:

Diagrama de flujo de alto nivel

Hemos decidido ir con el modelo cliente-servidor. Habrá una base de datos centralizada en el servidor. Cada cliente tendrá su propia base de datos local que permanecerá sincronizada con el servidor. Esta base de datos actúa como un caché para almacenar cosas que no cambian con frecuencia, por ejemplo, mapas, productos, inventario, etc.

Con este modelo, no estoy seguro de cómo abordar los siguientes problemas:

  • ¿Cuál sería la mejor manera de sincronizar las bases de datos de servidores y clientes?
  • ¿Se debe guardar un evento en la base de datos local antes de actualizarlo al servidor? ¿Qué sucede si la aplicación finaliza por algún motivo antes de guardar los cambios en la base de datos centralizada?
  • ¿Las solicitudes HTTP simples servirán para la sincronización?
  • ¿Cómo saber qué usuarios están actualmente conectados? (Una forma podría ser que el cliente continúe enviando una solicitud al servidor después de cada x minutos para notificar que está activa. De lo contrario, considere que un cliente está inactivo).
  • ¿Son suficientes las validaciones del lado del cliente? Si no, ¿cómo revertir una acción si el servidor no valida algo?

No estoy seguro de si esta es una solución eficiente y cómo escalará. Realmente agradecería que las personas que ya han trabajado en tales aplicaciones puedan compartir sus experiencias, lo que podría ayudarme a encontrar algo mejor. Gracias por adelantado.

Información adicional:

El lado del cliente está implementado en un motor de juego C ++ llamado mermelada. Este es un motor de juegos multiplataforma, lo que significa que puede ejecutar su aplicación en todos los principales sistemas operativos móviles. Ciertamente podemos lograr el enhebrado y eso también se ilustra en mi diagrama de flujo. Estoy planeando usar MySQL para el servidor y SQLite para el cliente.

Este no es un juego por turnos, por lo que no hay mucha interacción con otros jugadores. El servidor proporcionará una lista de jugadores en línea y puedes luchar contra ellos haciendo clic en el botón de batalla y después de un poco de animación, se anunciará el resultado.

Para la sincronización de bases de datos, tengo dos soluciones en mente:

  1. Guarde la marca de tiempo para cada registro. También realice un seguimiento de cuándo se actualizó por última vez la base de datos local. Al sincronizar, solo seleccione aquellas filas que tengan una marca de tiempo mayor y envíelas a la base de datos local. Mantenga un indicador isDeleted para las filas eliminadas para que cada eliminación simplemente se comporte como una actualización. Pero tengo serias dudas sobre el rendimiento ya que para cada solicitud de sincronización tendríamos que escanear la base de datos completa y buscar filas actualizadas.
  2. Otra técnica podría ser mantener un registro de cada inserción o actualización que tenga lugar contra un usuario. Cuando la aplicación cliente solicite sincronización, vaya a esta tabla y descubra qué filas de esa tabla se han actualizado o insertado. Una vez que estas filas se transfieren correctamente al cliente, elimine este registro. Pero luego pienso en lo que sucede si un usuario usa otro dispositivo. Según la tabla de registros, todas las actualizaciones se han transferido para ese usuario, pero en realidad eso se hizo en otro dispositivo. Por lo tanto, podríamos tener que hacer un seguimiento del dispositivo también. La implementación de esta técnica lleva más tiempo, pero no estoy seguro de si supera la primera.
umair
fuente
2
Tomaría una evaluación de MsSQL y jugaría con ella: no estoy seguro de lo que MySQL proporciona en términos de replicación, pero MsSQL 2008 R2 le ofrece una GRAN cantidad de técnicas de replicación (5-6) para elegir. Solo un pensamiento, no odio a MySQL ni nada, pero Microsoft es realmente bueno en la agrupación.
Jonathan Dickinson
1
@ Jonathan Dickinson: Hay MySQL Cluster: P
Coyote
1
También mire PostgreSQL: la versión 9 tiene tecnología de replicación, PostgreSQL tiene una excelente reputación de larga data por manejar muy bien las cargas pesadas, muchos de los desarrolladores parecen estar locos por la optimización, y lo mejor de todo es que es de código abierto y completamente gratis para todos los usos (incluido comercial).
Randolf Richardson
La cuestión es que no podemos usar la misma base de datos en el lado del cliente y del servidor. Creo que la base de datos más adecuada para el cliente sería SQLite para que no consuma muchos recursos, ya que la aplicación se ejecutaría en dispositivos móviles. Si bien la persona que trabaja en la parte del servidor solo conoce MySQL, por eso hemos optado por él. También he oído que la mayoría de los juegos MMO utilizan la base de datos MySQL.
Umair
1
@umair: el lado del cliente puede ser SQLite (en el iPhone, por ejemplo, ya está disponible). Pero puede optar por almacenar simplemente los datos relevantes que necesita en un archivo (lo que SQLite hace de todos modos) y no molestarse con la base de datos del lado del cliente como lo haría con una aplicación Javascript.
Coyote

Respuestas:

15

Si no es un juego "en tiempo real" en el sentido de que los jugadores no necesitan ver el resultado inmediato de las acciones de otro jugador en una escena del juego, entonces debería estar bien con las solicitudes HTTP. Pero tenga en cuenta la sobrecarga de HTTP.

Dicho esto, usar HTTP no lo salvará de diseñar su protocolo de comunicación con cuidado. Pero si está a cargo tanto del servidor como del lado del cliente, tiene suerte, ya que puede ajustar el protocolo cuando lo necesite.

Para sincronizar entre la base de datos maestra y la base de datos del cliente, puede usar cualquier protocolo de transporte al que tenga acceso, HTTP u otros. La parte importante es la lógica detrás de la sincronización. Para un enfoque directo, simplemente agrupe desde el servidor todos los últimos cambios necesarios para el cliente desde la última marca de tiempo en la base de datos del cliente. Aplíquelo a la base de datos del cliente y vaya con eso. Si tiene más cambios en el lado del cliente, cárguelo si aún es relevante, de lo contrario, deséchelo.

Algunos juegos ni siquiera usan una base de datos local, simplemente realizan un seguimiento del estado al agrupar la información relevante del servidor cuando es necesario.

Si perder eventos locales no es aceptable, entonces sí, debe tener almacenamiento local y guardar en ese almacenamiento con la mayor frecuencia posible. Puede intentar hacerlo antes de cada envío de red.

Para verificar si hay usuarios activos, solíamos hacer ping con HTTP cada 20 segundos en un juego exitoso ... Este valor aumentó inmediatamente ya que los servidores se han sobrecargado :( El equipo del servidor no pensó en el éxito. Por lo tanto, le diría que agregue un mensaje o algún tipo de encabezado especial en su protocolo de comunicación que le permitirá reconfigurar sus clientes (para hacer ping al equilibrio de carga de frecuencia y otros valores relacionados con la comunicación).

Las validaciones del lado del cliente son suficientes si no le importan los tramposos, los piratas informáticos y otros scripts automatizados que atacan su juego. El servidor simplemente puede rechazar su acción y enviar un mensaje de error con algunos detalles. Usted maneja ese mensaje de error al revertir los cambios locales o al no aplicarlos a su almacenamiento local si puede esperar hasta que el servidor responda para realmente guardar los cambios.

Usamos el patrón de comando en nuestro juego para permitir la reversión simple y eficiente de acciones fallidas o inválidas del usuario. Los comandos se jugaron localmente enviados a los pares o traducidos al mensaje del servidor y luego se aplicaron a los pares o se verificaron en el servidor, en caso de un problema, los comandos no se jugaron y la escena del juego volvió al estado inicial con una notificación.

Usar HTTP no es una mala idea ya que, más adelante, te permitirá integrarte muy fácilmente con clientes Flash o HTML5, es flexible, puedes usar cualquier tipo de lenguaje de script de servidor y con técnicas básicas de equilibrio de carga agrega más servidores más tarde sin mucho esfuerzo para escalar tu backend.

Tienes mucho que hacer pero es un trabajo divertido ... ¡Así que disfrútalo!

Coyote
fuente
3
+1 para 'HTML probablemente sea lo suficientemente bueno' porque probablemente lo sea :).
Jonathan Dickinson
1
@ Cooyote: gracias por tomarse el tiempo y proporcionar una respuesta tan detallada. Realmente me gusta su idea HTTP para proporcionar soporte a otros clientes.
Umair
1
Que la fuerza te acompañe :)
Coyote
5

¿Cuál sería la mejor manera de sincronizar las bases de datos de servidores y clientes?

La forma más fácil es implementar la base de datos como un único archivo que puede transferir. Si intenta comparar las diferencias entre las bases de datos, entonces es un mundo de dolor, y hablo por experiencia.

Tenga en cuenta que su servidor no debe confiar en las decisiones que toma el cliente en función de esa base de datos local, porque el cliente puede cambiarla. Debe existir únicamente para detalles de presentación.

¿Se debe guardar un evento en la base de datos local antes de actualizarlo al servidor?

No. ¿Qué pasa si el servidor decide que el evento nunca sucedió? El cliente no debería tomar decisiones sobre eventos de todos modos, porque no se puede confiar en el cliente.

Noté que también está hablando de la base de datos local de dos maneras: una, para "cosas que no cambian con frecuencia" y dos, para eventos. Estos realmente no deberían estar en la misma base de datos, por las razones anteriores: no desea comenzar a intentar fusionar o diferenciar filas de datos individuales en las bases de datos. Por ejemplo, la integridad referencial se convierte en un problema cuando un cliente tiene una referencia a un elemento que usted decide eliminar del servidor. O si el cliente cambia una fila y el servidor cambia una fila, ¿qué cambio tiene prioridad y por qué?

¿Las solicitudes HTTP simples servirán para la sincronización?

Sí, siempre que sean poco frecuentes o pequeños. HTTP no es eficiente en ancho de banda, así que tenlo en cuenta.

¿Cómo saber qué usuarios están actualmente conectados? (Una forma podría ser que el cliente continúe enviando una solicitud al servidor después de cada x minutos para notificar que está activa. De lo contrario, considere que un cliente está inactivo).

Si utiliza un protocolo transitorio como HTTP, esa es una idea razonable. Cuando el servidor recibe un mensaje de un cliente, puede actualizar el tiempo "visto por última vez" para ese cliente.

¿Son suficientes las validaciones del lado del cliente? Si no, ¿cómo revertir una acción si el servidor no valida algo?

No, en absoluto. El cliente está en manos del enemigo. Cómo revertir una acción depende completamente de lo que consideres que es una acción y de los efectos que pueda tener. La ruta más fácil es no implementar la acción hasta que el servidor responda para permitirlo. Una ruta un poco más complicada es garantizar que cada acción tenga una capacidad de deshacer para deshacer la acción y almacenar en caché todas las acciones no reconocidas en el cliente. Cuando el servidor los reconozca, elimínelos del caché. Si se rechaza una acción, revierta cada acción en orden inverso hasta e incluyendo la rechazada.

Kylotan
fuente
1
Lo lograste. Tienes razón sobre la sincronización de DBs +1 El cliente nunca debe cambiar la DB por sí solo ... Simplemente llama a las funciones que se ejecutan en el servidor y actualiza la DB usando las lógicas del juego, luego recupera los resultados.
Coyote
@Kylotan: gracias por señalar flujos en mi modelo
umair
@Kylotan: "La forma más fácil es implementar la base de datos como un archivo único que puede transferir. Si intenta comparar las diferencias entre las bases de datos, entonces es un mundo de dolor, y hablo por experiencia". Esto significa que todos los datos se descargan cada vez que se inicia la aplicación. Hay cosas que no cambiarán con mucha frecuencia, por lo que es posible que no sea aceptable descargarlas cada vez. Esto también aumentará considerablemente el tiempo de lanzamiento de la aplicación. ¿Alguna idea?
umair
Si los datos son pequeños, entonces el problema desaparece. Me sorprendería si su base de datos centralizada de datos estáticos fuera muy grande. Pero de todos modos, si divide los datos estáticos en partes más pequeñas, solo necesita enviar los que han cambiado desde la última vez. Claro, puede hacer esto por fila, si está dispuesto a mantener un tiempo de modificación en cada fila. En general, prefiero tener datos estáticos fuera de la base de datos relacional, para permitirme sobrescribirlos fácilmente.
Kylotan