¿Patrón / algoritmo de sincronización cliente-servidor?


224

Tengo la sensación de que debe haber patrones de sincronización cliente-servidor. Pero fallé totalmente en googlear uno.

La situación es bastante simple: el servidor es el nodo central, al que se conectan varios clientes y manipulan los mismos datos. Los datos se pueden dividir en átomos, en caso de conflicto, lo que sea que esté en el servidor tiene prioridad (para evitar que el usuario pueda resolver conflictos). Se prefiere la sincronización parcial debido a cantidades potencialmente grandes de datos.

¿Existen patrones / buenas prácticas para tal situación, o si no conoce ninguno, cuál sería su enfoque?

A continuación se muestra cómo pienso resolverlo: Paralelo a los datos, se llevará a cabo un diario de modificación, con todas las transacciones con sello de tiempo. Cuando el cliente se conecta, recibe todos los cambios desde la última verificación, en forma consolidada (el servidor revisa las listas y elimina las adiciones seguidas de eliminaciones, combina actualizaciones para cada átomo, etc.). Et voila, estamos al día.

La alternativa sería mantener la fecha de modificación para cada registro y, en lugar de realizar la eliminación de datos, simplemente márquelos como eliminados.

¿Alguna idea?


27
acordó que se habla muy poco de patrones para este tipo de cosas ... a pesar de que este escenario es bastante común
Jack Ukleja el

Respuestas:


88

Debe observar cómo funciona la gestión de cambios distribuidos. Mire SVN, CVS y otros repositorios que administran el trabajo deltas.

Tienes varios casos de uso.

  • Sincronizar cambios. Su enfoque de registro de cambios (o historial delta) se ve bien para esto. Los clientes envían sus deltas al servidor; El servidor consolida y distribuye los deltas a los clientes. Este es el caso típico. Las bases de datos llaman a esto "replicación de transacciones".

  • El cliente ha perdido la sincronización. Ya sea a través de una copia de seguridad / restauración o debido a un error. En este caso, el cliente necesita obtener el estado actual del servidor sin pasar por los deltas. Esta es una copia del maestro al detalle, los deltas y el rendimiento sean condenados. Es una cosa de una sola vez; el cliente está roto; no intente optimizar esto, solo implemente una copia confiable.

  • El cliente sospecha. En este caso, debe comparar el cliente con el servidor para determinar si el cliente está actualizado y necesita alguna diferencia.

Debe seguir el patrón de diseño de la base de datos (y SVN) de numerar secuencialmente cada cambio. De esa manera, un cliente puede hacer una solicitud trivial ("¿Qué revisión debo tener?") Antes de intentar sincronizar. E incluso entonces, la consulta ("Todos los deltas desde 2149") es deliciosamente simple para que el cliente y el servidor la procesen.


¿Puede explicarme qué es exactamente un delta? Supongo que es una combinación hash / marca de tiempo ... Me gustaría saber de usted, señor.
Anis

Un delta se refiere al cambio entre dos revisiones. Por ejemplo, si el nombre de un usuario ha cambiado, el delta puede ser algo como {revisión: 123, nombre: "John Doe"}
dipole_moment

31

Como parte del equipo, realicé muchos proyectos que incluían la sincronización de datos, por lo que debería ser competente para responder esta pregunta.

La sincronización de datos es un concepto bastante amplio y hay demasiado para discutir. Cubre una gama de enfoques diferentes con sus ventajas y desventajas. Aquí está una de las posibles clasificaciones basadas en dos perspectivas: Sincrónica / Asincrónica, Cliente / Servidor / Punto a punto. La implementación de sincronización depende en gran medida de estos factores, la complejidad del modelo de datos, la cantidad de datos transferidos y almacenados, y otros requisitos. Entonces, en cada caso particular, la elección debe ser a favor de la implementación más simple que cumpla con los requisitos de la aplicación.

Basándonos en una revisión de las soluciones comerciales existentes, podemos delinear varias clases principales de sincronización, diferentes en granularidad de objetos sujetos a sincronización:

  • La sincronización de todo un documento o base de datos se usa en aplicaciones basadas en la nube, como Dropbox, Google Drive o Yandex.Disk. Cuando el usuario edita y guarda un archivo, la nueva versión del archivo se carga completamente en la nube, sobrescribiendo la copia anterior. En caso de conflicto, ambas versiones de archivo se guardan para que el usuario pueda elegir qué versión es más relevante.
  • La sincronización de pares clave-valor se puede usar en aplicaciones con una estructura de datos simple, donde las variables se consideran atómicas, es decir, no se dividen en componentes lógicos. Esta opción es similar a la sincronización de documentos completos, ya que tanto el valor como el documento se pueden sobrescribir por completo. Sin embargo, desde la perspectiva del usuario, un documento es un objeto complejo compuesto de muchas partes, pero un par clave-valor no es más que una cadena corta o un número. Por lo tanto, en este caso podemos usar una estrategia más simple de resolución de conflictos, considerando el valor más relevante, si ha sido el último en cambiar.
  • La sincronización de datos estructurados como un árbol o un gráfico se usa en aplicaciones más sofisticadas donde la cantidad de datos es lo suficientemente grande como para enviar la base de datos en su totalidad en cada actualización. En este caso, los conflictos deben resolverse a nivel de objetos, campos o relaciones individuales. Estamos enfocados principalmente en esta opción.

Por lo tanto, aprovechamos nuestro conocimiento en este artículo, que creo que podría ser muy útil para todos los interesados ​​en el tema => Sincronización de datos en aplicaciones de iOS basadas en datos básicos ( http://blog.denivip.ru/index.php/2014/04 / data-syncing-in-core-data-based-ios-apps /? lang = es )


3
^^^^^^ esta es la mejor respuesta, muchachos!
hgoebl

Estoy de acuerdo, Denis aportó mucho al tema + los enlaces del artículo son increíbles. También habla sobre el AT mencionado por DanielPaull. La respuesta de S.Lott es buena, pero esto es mucho más profundo.
Krystian

28

Lo que realmente necesita es Transformación operacional (OT). Esto incluso puede atender los conflictos en muchos casos.

Esta sigue siendo un área activa de investigación, pero existen implementaciones de varios algoritmos OT. He estado involucrado en este tipo de investigación durante varios años, así que avíseme si esta ruta le interesa y me complacerá contar con recursos relevantes.


77
Daniel, un indicador de recursos relevantes sería apreciado.
Desfile

44
Acabo de volver a leer el artículo de Wikipedia. Ha recorrido un largo camino y tiene muchas referencias relevantes al final de esa página. Te habría señalado el trabajo de Chengzheng Sun: su trabajo está referenciado en Wikipedia. en.wikipedia.org/wiki/Operational_transformation . ¡Espero que ayude!
Daniel Paull el

13

La pregunta no es clara, pero buscaría un bloqueo optimista si fuera usted. Se puede implementar con un número de secuencia que el servidor devuelve para cada registro. Cuando un cliente intenta guardar el registro, incluirá el número de secuencia que recibió del servidor. Si el número de secuencia coincide con lo que hay en la base de datos en el momento en que se recibe la actualización, se permite la actualización y se incrementa el número de secuencia. Si los números de secuencia no coinciden, la actualización no está permitida.


2
Los números de secuencia son tus amigos aquí. Piense en las colas de mensajes persistentes.
Daniel Paull el

7

Creé un sistema como este para una aplicación hace aproximadamente 8 años, y puedo compartir un par de formas en que ha evolucionado a medida que el uso de la aplicación ha crecido.

Comencé registrando cada cambio (insertar, actualizar o eliminar) de cualquier dispositivo en una tabla de "historial". Entonces, si, por ejemplo, alguien cambia su número de teléfono en la tabla "contacto", el sistema editará el campo contact.phone y también agregará un registro de historial con action = update, field = phone, record = [ID de contacto], valor = [nuevo número de teléfono]. Luego, cuando un dispositivo se sincroniza, descarga los elementos del historial desde la última sincronización y los aplica a su base de datos local. Esto suena como el patrón de "replicación de transacciones" descrito anteriormente.

Un problema es mantener las identificaciones únicas cuando se pueden crear elementos en diferentes dispositivos. No conocía los UUID cuando comencé esto, así que usé ID de incremento automático y escribí un código complicado que se ejecuta en el servidor central para verificar las nuevas ID cargadas desde los dispositivos, cambiarlas a una ID única si hay un conflicto, y dígale al dispositivo fuente que cambie la ID en su base de datos local. Simplemente cambiar las ID de los nuevos registros no fue tan malo, pero si creo, por ejemplo, un nuevo elemento en la tabla de contactos, luego creo un nuevo elemento relacionado en la tabla de eventos, ahora tengo claves externas que también necesito verificar y actualizar.

Finalmente, aprendí que los UUID podrían evitar esto, pero para entonces mi base de datos se estaba volviendo bastante grande y temía que una implementación completa de UUID creara un problema de rendimiento. Entonces, en lugar de usar UUID completos, comencé a usar claves alfanuméricas de 8 caracteres generadas aleatoriamente como ID, y dejé mi código existente para manejar conflictos. En algún lugar entre mis claves actuales de 8 caracteres y los 36 caracteres de un UUID debe haber un punto óptimo que elimine los conflictos sin una hinchazón innecesaria, pero como ya tengo el código de resolución de conflictos, no ha sido una prioridad experimentar con eso. .

El siguiente problema fue que la tabla de historial era aproximadamente 10 veces más grande que el resto de la base de datos. Esto hace que el almacenamiento sea costoso y cualquier mantenimiento en la tabla de historial puede ser doloroso. Mantener toda esa tabla permite a los usuarios revertir cualquier cambio anterior, pero eso comenzó a parecer excesivo. Así que agregué una rutina al proceso de sincronización donde si el elemento de historial que un dispositivo descargó por última vez ya no existe en la tabla de historial, el servidor no le proporciona los elementos de historial recientes, sino que le da un archivo que contiene todos los datos para esa cuenta. Luego agregué un cronjob para eliminar elementos del historial de más de 90 días. Esto significa que los usuarios aún pueden revertir los cambios de menos de 90 días de antigüedad, y si se sincronizan al menos una vez cada 90 días, las actualizaciones serán incrementales como antes. Pero si esperan más de 90 días,

Ese cambio redujo el tamaño de la tabla de historial en casi un 90%, por lo que ahora mantener la tabla de historial solo hace que la base de datos sea dos veces más grande en lugar de diez veces más grande. Otro beneficio de este sistema es que la sincronización aún podría funcionar sin la tabla de historial si fuera necesario, como si tuviera que hacer un mantenimiento que lo desconectó temporalmente. O podría ofrecer diferentes períodos de reversión para cuentas a diferentes puntos de precio. Y si hay más de 90 días de cambios para descargar, el archivo completo suele ser más eficiente que el formato incremental.

Si comenzara de nuevo hoy, omitiría la comprobación de conflictos de ID y solo apuntaría a una longitud de clave que sea suficiente para eliminar conflictos, con algún tipo de comprobación de errores por si acaso. Pero la tabla de historial y la combinación de descargas incrementales para actualizaciones recientes o una descarga completa cuando es necesario ha funcionado bien.


1

Para la sincronización delta (cambio), puede usar el patrón pubsub para publicar los cambios de nuevo en todos los clientes suscritos, servicios como push pueden hacer esto.

Para el reflejo de la base de datos, algunos marcos web usan una mini base de datos local para sincronizar la base de datos del lado del servidor con la local en la base de datos del navegador, se admite la sincronización parcial. Comprobar medidor o .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.