Modèle / algorithme de synchronisation client-serveur?


224

J'ai le sentiment qu'il doit y avoir des modèles de synchronisation client-serveur. Mais je n'ai pas réussi à en rechercher un sur Google.

La situation est assez simple - le serveur est le nœud central, auquel plusieurs clients se connectent et manipulent les mêmes données. Les données peuvent être divisées en atomes, en cas de conflit, tout ce qui se trouve sur le serveur, a la priorité (pour éviter de mettre l'utilisateur en résolution de conflits). La synchronisation partielle est préférée en raison de volumes potentiellement importants de données.

Existe-t-il des modèles / bonnes pratiques pour une telle situation, ou si vous n'en connaissez pas - quelle serait votre approche?

Voici comment je pense maintenant à le résoudre: Parallèlement aux données, un journal de modification sera tenu, avec toutes les transactions horodatées. Lorsque le client se connecte, il reçoit toutes les modifications depuis la dernière vérification, sous forme consolidée (le serveur parcourt les listes et supprime les ajouts suivis de suppressions, fusionne les mises à jour pour chaque atome, etc.). Et voila, nous sommes à jour.

Une alternative consisterait à conserver la date de modification pour chaque enregistrement, et au lieu d'effectuer des suppressions de données, il suffit de les marquer comme supprimées.

Des pensées?


27
d'accord, il est très peu question de modèles pour ce genre de choses ... même si ce scénario est assez courant
Jack Ukleja

Réponses:


88

Vous devriez regarder comment fonctionne la gestion des changements distribuée. Regardez SVN, CVS et d'autres référentiels qui gèrent le travail des deltas.

Vous avez plusieurs cas d'utilisation.

  • Synchronisez les modifications. Votre approche du journal des modifications (ou de l'historique des deltas) semble bonne pour cela. Les clients envoient leurs deltas au serveur; le serveur consolide et distribue les deltas aux clients. C'est le cas typique. Les bases de données appellent cette "réplication de transaction".

  • Le client a perdu la synchronisation. Soit par une sauvegarde / restauration ou à cause d'un bug. Dans ce cas, le client doit obtenir l'état actuel du serveur sans passer par les deltas. Il s'agit d'une copie du maître aux détails, les deltas et les performances sont sacrément bons. C'est une chose unique; le client est cassé; n'essayez pas d'optimiser cela, implémentez simplement une copie fiable.

  • Le client est suspect. Dans ce cas, vous devez comparer le client avec le serveur pour déterminer si le client est à jour et a besoin de deltas.

Vous devez suivre le modèle de conception de base de données (et SVN) de numérotation séquentielle de chaque modification. De cette façon, un client peut faire une requête triviale ("Quelle révision dois-je avoir?") Avant d'essayer de synchroniser. Et même alors, la requête ("Tous les deltas depuis 2149") est délicieusement simple à traiter pour le client et le serveur.


Pouvez-vous, monsieur, expliquer ce qu'est exactement un delta? Je suppose que c'est une combinaison de hachage / horodatage ... J'aimerais entendre de vous monsieur.
Anis

Un delta fait référence au changement entre deux révisions. Par exemple, si le nom d'un utilisateur a changé, le delta peut être quelque chose comme {révision: 123, nom: "John Doe"}
dipole_moment

31

En tant que membre de l'équipe, j'ai réalisé pas mal de projets qui impliquaient la synchronisation des données, donc je devrais être compétent pour répondre à cette question.

La synchronisation des données est un concept assez large et il y a beaucoup trop de choses à discuter. Il couvre une gamme d'approches différentes avec leurs avantages et leurs inconvénients. Voici l'une des classifications possibles basées sur deux perspectives: Synchrone / Asynchrone, Client / Serveur / Peer-to-Peer. La mise en œuvre de la synchronisation dépend fortement de ces facteurs, de la complexité du modèle de données, de la quantité de données transférées et stockées et d'autres exigences. Ainsi, dans chaque cas particulier, le choix devrait être en faveur de la mise en œuvre la plus simple répondant aux exigences de l'application.

Sur la base d'un examen des solutions existantes disponibles sur le marché, nous pouvons délimiter plusieurs classes principales de synchronisation, différentes dans la granularité des objets soumis à la synchronisation:

  • La synchronisation d'un document ou d'une base de données entière est utilisée dans les applications basées sur le cloud, telles que Dropbox, Google Drive ou Yandex.Disk. Lorsque l'utilisateur modifie et enregistre un fichier, la nouvelle version du fichier est téléchargée complètement dans le cloud, écrasant la copie précédente. En cas de conflit, les deux versions de fichier sont enregistrées afin que l'utilisateur puisse choisir la version la plus appropriée.
  • La synchronisation des paires clé-valeur peut être utilisée dans les applications avec une structure de données simple, où les variables sont considérées comme atomiques, c'est-à-dire non divisées en composants logiques. Cette option est similaire à la synchronisation de documents entiers, car la valeur et le document peuvent être complètement remplacés. Cependant, du point de vue de l'utilisateur, un document est un objet complexe composé de nombreuses parties, mais une paire clé-valeur n'est qu'une courte chaîne ou un nombre. Par conséquent, dans ce cas, nous pouvons utiliser une stratégie plus simple de résolution des conflits, en considérant la valeur plus pertinente, si elle a été la dernière à changer.
  • La synchronisation des données structurées sous forme d'arbre ou de graphique est utilisée dans des applications plus sophistiquées où la quantité de données est suffisamment importante pour envoyer la base de données dans son intégralité à chaque mise à jour. Dans ce cas, les conflits doivent être résolus au niveau des objets, des champs ou des relations individuels. Nous nous concentrons principalement sur cette option.

Nous avons donc saisi nos connaissances dans cet article qui, je pense, pourrait être très utile à tous ceux qui s'intéressent au sujet => Synchronisation des données dans les applications iOS basées sur les données de base ( http://blog.denivip.ru/index.php/2014/04 / data-syncing-in-core-data-based-ios-apps /? lang = en )


3
^^^^^^ c'est de loin la meilleure réponse, les gars!
hgoebl

Je suis d'accord, Denis a beaucoup apporté sur le sujet + les liens vers les articles sont géniaux. Parle également de l'OT mentionné par DanielPaull. La réponse de S.Lott est bonne mais c'est beaucoup plus en profondeur.
Krystian

28

Ce dont vous avez vraiment besoin, c'est de Transformation Opérationnelle (OT). Cela peut même répondre aux conflits dans de nombreux cas.

C'est toujours un domaine de recherche actif, mais il existe des implémentations de divers algorithmes OT autour. Je participe à de telles recherches depuis un certain nombre d'années maintenant, alors faites-moi savoir si cet itinéraire vous intéresse et je serai heureux de vous proposer des ressources pertinentes.


7
Daniel, un pointeur vers les ressources pertinentes serait apprécié.
Parand

4
Je viens de relire l'article de wikipedia. Il a parcouru un long chemin et a de nombreuses références pertinentes au bas de cette page. Je vous aurais indiqué le travail de Chengzheng Sun - son travail est référencé à partir de wikipedia. en.wikipedia.org/wiki/Operational_transformation . J'espère que cela pourra aider!
Daniel Paull

13

La question n'est pas limpide, mais j'examinerais le verrouillage optimiste si j'étais vous. Il peut être implémenté avec un numéro de séquence que le serveur renvoie pour chaque enregistrement. Lorsqu'un client essaie de sauvegarder l'enregistrement, il inclura le numéro de séquence qu'il a reçu du serveur. Si le numéro de séquence correspond à ce qui se trouve dans la base de données au moment où la mise à jour est reçue, la mise à jour est autorisée et le numéro de séquence est incrémenté. Si les numéros de séquence ne correspondent pas, la mise à jour est interdite.


2
Les numéros de séquence sont votre ami ici. Pensez aux files d'attente de messages persistantes.
Daniel Paull

7

J'ai créé un système comme celui-ci pour une application il y a environ 8 ans, et je peux partager quelques façons dont il a évolué à mesure que l'utilisation de l'application augmentait.

J'ai commencé par enregistrer chaque modification (insertion, mise à jour ou suppression) de n'importe quel appareil dans un tableau "historique". Ainsi, si, par exemple, quelqu'un change son numéro de téléphone dans le tableau "contact", le système éditera le champ contact.phone et ajoutera également un enregistrement d'historique avec action = update, field = phone, record = [contact ID], valeur = [nouveau numéro de téléphone]. Puis, chaque fois qu'un appareil se synchronise, il télécharge les éléments d'historique depuis la dernière synchronisation et les applique à sa base de données locale. Cela ressemble au modèle de «réplication de transaction» décrit ci-dessus.

Un problème est de garder les ID uniques lorsque des éléments peuvent être créés sur différents appareils. Je ne connaissais pas les UUID lorsque j'ai commencé, j'ai donc utilisé des ID auto-incrémentés et écrit du code alambiqué qui s'exécute sur le serveur central pour vérifier les nouveaux ID téléchargés à partir des appareils, les changer en un ID unique en cas de conflit, et dire au périphérique source de changer l'ID dans sa base de données locale. Changer simplement les ID des nouveaux enregistrements n'était pas si mal, mais si je crée, par exemple, un nouvel élément dans la table des contacts, puis crée un nouvel élément connexe dans la table des événements, maintenant j'ai des clés étrangères que je dois également vérifier et mettre à jour.

Finalement, j'ai appris que les UUID pouvaient éviter cela, mais ma base de données devenait alors assez volumineuse et j'avais peur qu'une implémentation complète des UUID crée un problème de performances. Donc, au lieu d'utiliser des UUID complets, j'ai commencé à utiliser des clés alphanumériques à 8 caractères générées de manière aléatoire comme ID, et j'ai laissé mon code existant en place pour gérer les conflits. Quelque part entre mes clés à 8 caractères actuelles et les 36 caractères d'un UUID, il doit y avoir un point idéal qui éliminerait les conflits sans ballonnement inutile, mais puisque j'ai déjà le code de résolution des conflits, il n'a pas été une priorité d'expérimenter avec cela .

Le problème suivant était que la table d'historique était environ 10 fois plus grande que le reste de la base de données. Cela rend le stockage coûteux et toute maintenance sur la table d'historique peut être pénible. Garder cette table entière permet aux utilisateurs d'annuler toute modification précédente, mais cela a commencé à sembler exagéré. J'ai donc ajouté une routine au processus de synchronisation où si l'élément d'historique téléchargé en dernier par un appareil n'existe plus dans la table d'historique, le serveur ne lui donne pas les éléments d'historique récents, mais lui donne à la place un fichier contenant toutes les données pour ce compte. Ensuite, j'ai ajouté un cronjob pour supprimer les éléments d'historique de plus de 90 jours. Cela signifie que les utilisateurs peuvent toujours annuler les modifications datant de moins de 90 jours, et s'ils se synchronisent au moins une fois tous les 90 jours, les mises à jour seront incrémentielles comme auparavant. Mais s'ils attendent plus de 90 jours,

Cette modification a réduit la taille de la table d'historique de près de 90%. Par conséquent, la maintenance de la table d'historique ne fait que rendre la base de données deux fois plus grande au lieu de dix fois plus grande. Un autre avantage de ce système est que la synchronisation pourrait toujours fonctionner sans la table d'historique si nécessaire - comme si j'avais besoin de faire une maintenance qui la mettait hors ligne temporairement. Ou je pourrais proposer différentes périodes de restauration pour les comptes à différents prix. Et s'il y a plus de 90 jours de modifications à télécharger, le fichier complet est généralement plus efficace que le format incrémentiel.

Si je recommençais aujourd'hui, je sauterais la vérification des conflits d'ID et viserais simplement une longueur de clé suffisante pour éliminer les conflits, avec une sorte de vérification d'erreur au cas où. Mais la table d'historique et la combinaison de téléchargements incrémentiels pour les mises à jour récentes ou un téléchargement complet en cas de besoin ont bien fonctionné.


1

Pour la synchronisation delta (modification), vous pouvez utiliser le modèle pubsub pour publier les modifications sur tous les clients abonnés, des services comme pusher peuvent le faire.

Pour le miroir de base de données, certains cadres Web utilisent une mini base de données locale pour synchroniser la base de données côté serveur avec la base de données locale dans le navigateur, la synchronisation partielle est prise en charge. Vérifiez mètre ou .

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.