Sincronizar bases de datos cliente-servidor

82

Estoy buscando algunas estrategias generales para sincronizar datos en un servidor central con aplicaciones cliente que no siempre están en línea.

En mi caso particular, tengo una aplicación de teléfono Android con una base de datos sqlite y una aplicación web PHP con una base de datos MySQL.

Los usuarios podrán agregar y editar información en la aplicación del teléfono y en la aplicación web. Necesito asegurarme de que los cambios realizados en un lugar se reflejen en todas partes, incluso cuando el teléfono no pueda comunicarse de inmediato con el servidor.

No me preocupa cómo transferir datos del teléfono al servidor o viceversa. Menciono mis tecnologías particulares solo porque no puedo usar, por ejemplo, las funciones de replicación disponibles para MySQL.

Sé que el problema de la sincronización de datos cliente-servidor ha existido durante mucho, mucho tiempo y me gustaría obtener información (artículos, libros, consejos, etc.) sobre los patrones para manejar el problema. Me gustaría conocer estrategias generales para lidiar con la sincronización para comparar fortalezas, debilidades y compensaciones.

Scott Saunders
fuente

Respuestas:

93

Lo primero que debe decidir es una política general sobre qué lado se considera "autorizado" en caso de cambios contradictorios.

Es decir: supongamos que el registro # 125 se cambia en el servidor el 5 de enero a las 10 p.m. y el mismo registro se cambia en uno de los teléfonos (llamémoslo Cliente A) el 5 de enero a las 11 p.m. La última sincronización fue el 3 de enero. Luego, el usuario se vuelve a conectar, digamos, el 8 de enero.

Identificar lo que debe cambiarse es "fácil" en el sentido de que tanto el cliente como el servidor conocen la fecha de la última sincronización, por lo que cualquier cosa creada o actualizada (ver más abajo para más información sobre esto) desde la última sincronización debe reconciliarse.

Entonces, suponga que el único registro modificado es el # 125. O decide que uno de los dos "gana" automáticamente y sobrescribe al otro, o necesita admitir una fase de conciliación en la que un usuario puede decidir qué versión (servidor o cliente) es la correcta, sobrescribiendo la otra.

Esta decisión es sumamente importante y debes ponderar el "rol" de los clientes. Especialmente si hay un conflicto potencial no solo entre el cliente y el servidor, sino en caso de que diferentes clientes puedan cambiar los mismos registros.

[Suponiendo que # 125 puede ser modificado por un segundo cliente (Cliente B), existe la posibilidad de que el Cliente B, que aún no se ha sincronizado, proporcione otra versión del mismo registro, haciendo que la resolución de conflictos anterior sea discutible]

Con respecto al punto " creado o actualizado " anterior ... ¿cómo puede identificar correctamente un registro si se originó en uno de los clientes (asumiendo que esto tiene sentido en el dominio de su problema)? Supongamos que su aplicación administra una lista de contactos comerciales. Si el cliente A dice que tiene que agregar un John Smith recién creado y el servidor tiene un John Smith creado ayer por el cliente D ... ¿crea dos registros porque no puede estar seguro de que no sean personas diferentes? ¿Le pedirá al usuario que también concilie este conflicto?

¿Los clientes tienen "propiedad" de un subconjunto de datos? Es decir, si el Cliente B está configurado para ser la "autoridad" sobre los datos del Área # 5, ¿el Cliente A puede modificar / crear registros para el Área # 5 o no? (Esto facilitaría la resolución de conflictos, pero puede resultar inviable para su situación).

En resumen, los principales problemas son:

  • Cómo definir la "identidad" teniendo en cuenta que es posible que los clientes separados no hayan accedido al servidor antes de crear un nuevo registro.
  • La situación anterior, sin importar cuán sofisticada sea la solución, puede resultar en la duplicación de datos, por lo que debe prever cómo resolverlos periódicamente y cómo informar a los clientes que lo que ellos consideraron como "Registro # 675" en realidad se ha fusionado o reemplazado por Registro # 543
  • Decidir si los conflictos serán resueltos por Fiat (por ejemplo, "La versión de servidor siempre triunfa sobre el cliente de si el primero ha sido actualizado desde la última sincronización") o mediante la intervención manual
  • En el caso de fiat , especialmente si decide que el cliente tiene prioridad, también debe ocuparse de cómo tratar con otros clientes aún no sincronizados que pueden tener algunos cambios más en camino.
  • Los elementos anteriores no tienen en cuenta la granularidad de sus datos (para simplificar la descripción). Baste decir que en lugar de razonar en el nivel de "Registro", como en mi ejemplo, puede que le resulte más apropiado registrar el cambio en el nivel de campo. O trabajar en un conjunto de registros (por ejemplo, registro de persona + registro de dirección + registro de contactos) a la vez, tratando su agregado como una especie de "meta registro".

Bibliografía:

(Los últimos tres son de la biblioteca digital de ACM, no tengo idea de si es miembro o si puede obtenerlos a través de otros canales).

Desde el sitio de Dr.Dobbs :

  • Creación de aplicaciones con SQL Server CE y SQL RDA por Bill Wagner 19 de mayo de 2004 (Mejores prácticas para diseñar una aplicación tanto para PC de escritorio como para PC móvil - Windows / .NET)

De arxiv.org:

  • Un tipo de datos JSON replicado libre de conflictos : el documento describe una implementación de JSON CRDT (los tipos de datos replicados libres de conflictos, CRDT, son una familia de estructuras de datos que admiten modificaciones simultáneas y que garantizan la convergencia de dichas actualizaciones simultáneas).
p.marino
fuente
Gracias por su respuesta. Estoy muy interesado en leer sobre posibles soluciones (pros, contras, comparaciones) de uso común para los problemas que describe.
Scott Saunders
Supongo que ya revisaste Wikipedia y las cosas a las que enlazan, ¿verdad?
p.marino
3
+1 Esta es una gran publicación con información muy importante sobre ese tema. Un punto que falta: sincronizar registros eliminados.
Stefan Steinegger
7
Tiendo a considerar "eliminado" como un caso especial de "actualizado", especialmente porque para este tipo de situaciones tiendo a favorecer la "eliminación lógica" en lugar de la "eliminación física". Entonces, para mí, "eliminado" en el lado del maestro o del esclavo significa que "el indicador booleano especial es-eliminado se ha cambiado" más que cualquier otra cosa.
p.marino
Gracias. He agregado un enlace más a otro artículo (dr.dobbs) y actualizaré la bibliografía si puedo encontrar algo más.
p.marino
9

Recomendaría que tenga una columna de marca de tiempo en cada tabla y cada vez que inserte o actualice, actualice el valor de marca de tiempo de cada fila afectada. Luego, itera sobre todas las tablas verificando si la marca de tiempo es más nueva que la que tiene en la base de datos de destino. Si es más nuevo, verifique si tiene que insertarlo o actualizarlo.

Observación 1: tenga en cuenta las eliminaciones físicas ya que las filas se eliminan de la base de datos de origen y debe hacer lo mismo en la base de datos del servidor. Puede resolver esto evitando eliminaciones físicas o registrando cada eliminación en una tabla con marcas de tiempo. Algo como esto: DeletedRows = (id, table_name, pk_column, pk_column_value, timestamp)Entonces, debe leer todas las filas nuevas de la tabla DeletedRows y ejecutar una eliminación en el servidor usando table_name, pk_column y pk_column_value.

Observación 2: tenga en cuenta FK ya que la inserción de datos en una tabla relacionada con otra tabla podría fallar. Debe desactivar cada FK antes de la sincronización de datos.

Francisco Goldenstein
fuente
3
Los relojes tienen que estar sincronizados
tofutim
6

Si alguien está lidiando con un problema de diseño similar y necesita sincronizar los cambios en varios dispositivos Android, le recomiendo que consulte Google Cloud Messaging para Android (GCM).

Estoy trabajando en una solución en la que los cambios realizados en un cliente deben propagarse a otros clientes. Y acabo de implementar una implementación de prueba de concepto (servidor y cliente) y funciona como un encanto.

Básicamente, cada cliente envía cambios delta al servidor. Por ejemplo, el ID de recurso ABCD1234 ha cambiado del valor 100 a 99.

El servidor valida estos cambios delta contra su base de datos y aprueba el cambio (el cliente está sincronizado) y actualiza su base de datos o rechaza el cambio (el cliente no está sincronizado).

Si el servidor aprueba el cambio, el servidor notifica a otros clientes (excluyendo al que envió el cambio delta) a través de GCM y envía un mensaje de multidifusión con el mismo cambio delta. Los clientes procesan este mensaje y actualizan su base de datos.

¡Lo bueno es que estos cambios se propagan casi instantáneamente! si esos dispositivos están en línea. Y no necesito implementar ningún mecanismo de sondeo en esos clientes.

Tenga en cuenta que si un dispositivo está fuera de línea durante demasiado tiempo y hay más de 100 mensajes esperando en la cola de GCM para su entrega, GCM descartará esos mensajes y enviará un mensaje especial cuando los dispositivos vuelvan a estar en línea. En ese caso, el cliente debe realizar una sincronización completa con el servidor.

Consulte también este tutorial para comenzar con la implementación del cliente CGM.

jogo
fuente
5

esto responde a los desarrolladores que usan el marco de Xamarin (consulte /programming/40156342/sync-online-offline-data )

Una forma muy sencilla de lograr esto con el marco xamarin es utilizar la sincronización de datos sin conexión de Azure, ya que permite enviar y extraer datos del servidor a pedido. Las operaciones de lectura se realizan localmente y las operaciones de escritura se envían a pedido; Si la conexión de red se interrumpe, las operaciones de escritura se ponen en cola hasta que se restablece la conexión y luego se ejecutan.

La implementación es bastante simple:

1) cree una aplicación móvil en Azure Portal (puede probarla gratis aquí https://tryappservice.azure.com/ )

2) conecte su cliente a la aplicación móvil. https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-xamarin-forms-get-started/

3) el código para configurar su repositorio local:

const string path = "localrepository.db";

//Create our azure mobile app client
this.MobileService = new MobileServiceClient("the api address as setup on Mobile app services in azure");

//setup our local sqlite store and initialize a table
var repository = new MobileServiceSQLiteStore(path);

// initialize a Foo table
store.DefineTable<Foo>();

// init repository synchronisation
await this.MobileService.SyncContext.InitializeAsync(repository);
var fooTable = this.MobileService.GetSyncTable<Foo>();

4) luego presione y extraiga sus datos para asegurarse de que tenemos los últimos cambios:

await this.MobileService.SyncContext.PushAsync();
await this.saleItemsTable.PullAsync("allFoos", fooTable.CreateQuery());

https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-xamarin-forms-get-started-offline-data/

Ben Ishiyama-Levy
fuente
0

Te sugiero que también eches un vistazo a Symmetricds . es una biblioteca de replicación de SQLite disponible para sistemas Android. puede usarlo para sincronizar la base de datos de su cliente y servidor, también sugiero tener bases de datos separadas en el servidor para cada cliente. Tratar de mantener los datos de todos los usuarios en una base de datos mysql no siempre es la mejor idea. Especialmente si los datos del usuario van a crecer rápidamente.

Hossein Shahdoost
fuente
0

Deja llamada que el CUDR sincronización problema (no me gusta CRUD - debido Crear / actualizar / borrar son las escrituras y deben ser emparejados)

El problema también se puede considerar desde la perspectiva de cancelar la línea primero o escribir primero en línea . El enfoque de escritura fuera de línea tiene un problema con el conflicto de identificadores únicos y también múltiples llamadas de red para la misma transacción que aumentan el riesgo (o costo) ...

Personalmente, considero que el enfoque de escribir primero en línea es más fácil de administrar (por lo que será la única fuente de verdad, desde donde se sincroniza todo lo demás). El enfoque de escritura en línea requerirá no permitir que los usuarios escriban fuera de línea primero: escribirán fuera de línea al obtener un formulario de respuesta correcto para escribir en línea.

Primero puede leer sin conexión y, tan pronto como la red esté disponible, obtenga los datos en línea y actualice la base de datos local y luego actualice la interfaz de usuario ...

Una forma de evitar el conflicto de identificador único sería usar una combinación de ID de usuario único + nombre de tabla o ID de tabla + ID de fila (generado por sqlite) ... y luego usar la columna de bandera booleana sincronizada con él ... pero aún así El registro debe realizarse en línea primero para obtener la identificación única en la que se generarán todos los demás identificadores ... aquí el problema también será si los relojes no están sincronizados, lo que alguien mencionó anteriormente ...

Fuego de Dragon
fuente
Además, el enfoque de escritura sin conexión tendrá un problema en la desinstalación de la aplicación, todos los datos que no se carguen en línea se eliminarán
DragonFire