J'ai l'impression qu'il doit exister des modèles de synchronisation client-serveur. Mais j'ai totalement échoué à en chercher un.
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 scindées en atomes. En cas de conflit, tout ce qui se trouve sur le serveur est prioritaire (pour éviter que l'utilisateur ne soit confronté à la résolution de conflit). La synchronisation partielle est préférable en raison du volume potentiellement important de données.
Existe-t-il des modèles/bonnes pratiques pour une telle situation, ou si vous n'en connaissez aucune - 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.) à jour.
L’autre solution consisterait à conserver la date de modification de chaque enregistrement et, au lieu d’effectuer des suppressions de données, il suffit de les marquer comme supprimés.
Des pensées?
Vous devriez regarder comment fonctionne la gestion du changement distribuée. Regardez SVN, CVS et autres référentiels qui gèrent le travail des deltas.
Vous avez plusieurs cas d'utilisation.
Synchroniser les modifications. Votre approche de journal des modifications (ou d'historique delta) 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 bogue. Dans ce cas, le client doit obtenir l'état actuel du serveur sans passer par les deltas. Ceci est une copie du maître au détail, des deltas et des performances à damner. C'est une chose ponctuelle; 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 client à serveur pour déterminer si le client est à jour et nécessite des deltas.
Vous devez suivre le modèle de conception de la base de données (et du SVN) consistant à numéroter séquentiellement chaque modification. De cette façon, un client peut faire une requête triviale ("Quelle révision dois-je avoir?") Avant de tenter une synchronisation. Et même alors, la requête ("Tous les deltas depuis 2149") est délicieusement simple à traiter par le client et le serveur.
Ce dont vous avez vraiment besoin est Transformation opérationnelle (OT). Cela peut même prendre en charge les conflits dans de nombreux cas.
Il s'agit toujours d'un domaine de recherche actif, mais il existe des implémentations de divers algorithmes OT. Je suis impliqué dans de telles recherches depuis un certain nombre d'années maintenant, alors laissez-moi savoir si cette voie vous intéresse et je me ferai un plaisir de vous indiquer des ressources pertinentes.
En tant que membre de l'équipe, j'ai réalisé pas mal de projets impliquant la synchronisation de données. Je devrais donc être compétent pour répondre à cette question.
La synchronisation des données est un concept assez large et il y a beaucoup trop à discuter. Il couvre une gamme d'approches différentes avec leurs avantages et 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 énormément 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. Donc, dans chaque cas particulier, le choix devrait être en faveur de la mise en œuvre la plus simple qui réponde aux exigences de l'application.
En examinant les solutions prêtes à l'emploi existantes, nous pouvons définir plusieurs grandes classes de synchronisation, différentes en termes de granularité des objets sujets à la synchronisation:
Nous avons donc intégré nos connaissances à cet article qui, selon moi, pourrait être très utile à quiconque s'intéresse au sujet => Synchronisation des données dans les applications iOS basées sur des données de base ( http://blog.denivip.ru/index.php/2014/04/synchronisation-des-données dans ios-apps-base-basées-données /? Lang = fr )
La question n’est pas limpide, mais j’examinerais le verrouillage optimiste si j’étais vous-même… .. Il peut être implémenté avec un numéro de séquence que le serveur renvoie pour chaque enregistrement. Lorsqu'un client tente de sauvegarder l'enregistrement, il inclut 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.
J'ai construit 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é avec l'utilisation croissante de l'application.
J'ai commencé par enregistrer chaque modification (insertion, mise à jour ou suppression) d'un périphérique dans un tableau "historique". Ainsi, si, par exemple, une personne modifie son numéro de téléphone dans la table "contact", le système modifiera 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]. Chaque fois qu'un appareil se synchronise, il télécharge les éléments de l'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.
L'un des problèmes est de garder les identifiants uniques lorsque des éléments peuvent être créés sur différents périphériques. Je ne connaissais pas les UUID au démarrage. J'ai donc utilisé des ID à incrémentation automatique et écrit un code compliqué qui s'exécute sur le serveur central pour vérifier les nouveaux ID chargés depuis des périphériques, les modifier en un identifiant unique en cas de conflit et Indiquez au périphérique source de modifier l'ID dans sa base de données locale. Changer les identifiants de nouveaux enregistrements n'était pas si grave, mais si je crée, par exemple, un nouvel élément dans la table des contacts, puis crée un nouvel élément associé dans la table des événements, j'ai maintenant des clés étrangères que je dois également utiliser. 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 d'UUID ne crée un problème de performances. Ainsi, au lieu d’utiliser des UUID complets, j’ai commencé à utiliser comme identifiants des clés alphanumériques à 8 caractères générées aléatoirement, et j’ai laissé mon code existant en place pour gérer les conflits. Quelque part entre mes clés actuelles à 8 caractères et les 36 caractères d’un UUID, il doit exister un point favorable qui éliminerait les conflits sans fioritures inutiles, mais comme je possède déjà le code de résolution des conflits, ce n’est pas une priorité de faire des expériences avec .
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. Le fait de conserver la totalité de la table permet aux utilisateurs d’annuler les modifications précédentes, mais cela a commencé à donner l'impression d'être excessif. J'ai donc ajouté une routine au processus de synchronisation dans laquelle, si l'élément d'historique téléchargé en dernier lieu par un périphérique n'existe plus dans la table d'historique, le serveur ne lui communique pas les éléments d'historique récents, mais lui fournit un fichier contenant toutes les données nécessaires. ce compte. Ensuite, j'ai ajouté une tâche cron pour supprimer les éléments de l'historique de plus de 90 jours. Cela signifie que les utilisateurs peuvent toujours annuler les modifications de moins de 90 jours et que, 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, l'application remplacera toute la base de données.
Cette modification a réduit la taille de la table d'historique de près de 90%. Par conséquent, maintenant la table d'historique ne rend la base de données que deux fois plus volumineuse au lieu de dix fois plus grande. Un autre avantage de ce système est que la synchronisation peut toujours fonctionner sans la table d'historique si nécessaire - comme si j'avais besoin de faire de la maintenance pour la mettre temporairement hors ligne. Ou je pourrais proposer différentes périodes de restauration pour des comptes à des prix différents. 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'identité 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 d'un téléchargement complet au besoin ont bien fonctionné.
Pour la synchronisation delta (change), vous pouvez utiliser le modèle pubsub pour publier les modifications sur tous les clients abonnés. Des services tels que pusher peuvent le faire.
Pour la mise en miroir de bases de données, certains frameworks 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 du navigateur. La synchronisation partielle est prise en charge. Vérifiez meteror .