Client-Server-Synchronisationsmuster / Algorithmus?


224

Ich habe das Gefühl, dass es da draußen Client-Server-Synchronisationsmuster geben muss. Aber ich habe es total versäumt, einen zu googeln.

Die Situation ist recht einfach: Der Server ist der zentrale Knoten, über den mehrere Clients eine Verbindung herstellen und dieselben Daten bearbeiten. Daten können in Atome aufgeteilt werden. Im Falle eines Konflikts hat alles, was sich auf dem Server befindet, Priorität (um zu vermeiden, dass Benutzer zur Konfliktlösung gebracht werden). Eine teilweise Synchronisation wird aufgrund möglicherweise großer Datenmengen bevorzugt.

Gibt es Muster / bewährte Verfahren für eine solche Situation oder wenn Sie keine kennen - wie würden Sie vorgehen?

Nachfolgend denke ich, wie ich es jetzt lösen soll: Parallel zu den Daten wird ein Änderungsjournal geführt, in dem alle Transaktionen mit einem Zeitstempel versehen sind. Wenn der Client eine Verbindung herstellt, erhält er alle Änderungen seit der letzten Prüfung in konsolidierter Form (der Server durchläuft Listen und entfernt Ergänzungen, auf die Löschungen folgen, führt Aktualisierungen für jedes Atom zusammen usw.). Et voila, wir sind auf dem neuesten Stand.

Alternativ können Sie das Änderungsdatum für jeden Datensatz beibehalten und statt Datenlöschungen einfach als gelöscht markieren.

Irgendwelche Gedanken?


27
stimmte zu, dass es sehr wenig Gerede über Muster für diese Art von Dingen gibt ... obwohl dieses Szenario ziemlich häufig ist
Jack Ukleja

Antworten:


88

Sie sollten sich ansehen, wie verteiltes Änderungsmanagement funktioniert. Schauen Sie sich SVN, CVS und andere Repositorys an, die die Arbeit von Deltas verwalten.

Sie haben mehrere Anwendungsfälle.

  • Änderungen synchronisieren. Ihr Änderungsprotokoll- (oder Delta-Verlaufs-) Ansatz sieht dafür gut aus. Clients senden ihre Deltas an den Server. Der Server konsolidiert und verteilt die Deltas an die Clients. Dies ist der typische Fall. Datenbanken nennen dies "Transaktionsreplikation".

  • Der Client hat die Synchronisation verloren. Entweder durch ein Backup / Restore oder wegen eines Fehlers. In diesem Fall muss der Client den aktuellen Status vom Server abrufen, ohne die Deltas durchlaufen zu müssen. Dies ist eine Kopie vom Meister bis ins Detail, Deltas und Leistung sind verdammt. Es ist eine einmalige Sache; der Client ist kaputt; Versuchen Sie nicht, dies zu optimieren, sondern implementieren Sie einfach eine zuverlässige Kopie.

  • Der Kunde ist misstrauisch. In diesem Fall müssen Sie den Client mit dem Server vergleichen, um festzustellen, ob der Client auf dem neuesten Stand ist und Deltas benötigt.

Sie sollten dem Entwurfsmuster der Datenbank (und des SVN) folgen, bei dem jede Änderung fortlaufend nummeriert wird. Auf diese Weise kann ein Client eine triviale Anfrage stellen ("Welche Revision sollte ich haben?"), Bevor er versucht, eine Synchronisierung durchzuführen. Und selbst dann ist die Abfrage ("Alle Deltas seit 2149") für Client und Server sehr einfach zu verarbeiten.


Können Sie, Sir, erklären, was ein Delta genau ist? Meine Vermutung wäre, dass dies eine Hash / Zeitstempel-Kombination ist ... Ich würde gerne von Ihnen hören, Sir.
Anis

Ein Delta bezieht sich auf den Wechsel zwischen zwei Revisionen. Wenn sich beispielsweise der Name eines Benutzers geändert hat, kann das Delta so etwas wie {Revision: 123, Name: "John Doe"} sein
dipole_moment

31

Als Teil des Teams habe ich eine ganze Reihe von Projekten durchgeführt, bei denen es um die Synchronisierung von Daten ging. Daher sollte ich kompetent sein, diese Frage zu beantworten.

Die Datensynchronisierung ist ein ziemlich weit gefasstes Konzept und es gibt viel zu viel zu diskutieren. Es deckt eine Reihe verschiedener Ansätze mit ihren Vor- und Nachteilen ab. Hier ist eine der möglichen Klassifizierungen basierend auf zwei Perspektiven: Synchron / Asynchron, Client / Server / Peer-to-Peer. Die Implementierung der Synchronisierung hängt stark von diesen Faktoren, der Komplexität des Datenmodells, der übertragenen und gespeicherten Datenmenge und anderen Anforderungen ab. In jedem Einzelfall sollte die Wahl zugunsten der einfachsten Implementierung getroffen werden, die den App-Anforderungen entspricht.

Basierend auf einer Überprüfung bestehender Standardlösungen können wir mehrere Hauptklassen der Synchronisierung abgrenzen, die sich in der Granularität der zu synchronisierenden Objekte unterscheiden:

  • Die Synchronisierung eines gesamten Dokuments oder einer Datenbank wird in Cloud-basierten Anwendungen wie Dropbox, Google Drive oder Yandex.Disk verwendet. Wenn der Benutzer eine Datei bearbeitet und speichert, wird die neue Dateiversion vollständig in die Cloud hochgeladen, wobei die frühere Kopie überschrieben wird. Im Konfliktfall werden beide Dateiversionen gespeichert, damit der Benutzer auswählen kann, welche Version relevanter ist.
  • Die Synchronisierung von Schlüssel-Wert-Paaren kann in Apps mit einer einfachen Datenstruktur verwendet werden, bei denen die Variablen als atomar betrachtet werden, dh nicht in logische Komponenten unterteilt sind. Diese Option ähnelt der Synchronisierung ganzer Dokumente, da sowohl der Wert als auch das Dokument vollständig überschrieben werden können. Aus Anwendersicht ist ein Dokument jedoch ein komplexes Objekt, das aus vielen Teilen besteht. Ein Schlüssel-Wert-Paar ist jedoch nur eine kurze Zeichenfolge oder eine Zahl. Daher können wir in diesem Fall eine einfachere Strategie der Konfliktlösung anwenden, wobei der Wert relevanter ist, wenn er sich zuletzt geändert hat.
  • Die Synchronisierung von Daten, die als Baum oder Diagramm strukturiert sind, wird in komplexeren Anwendungen verwendet, bei denen die Datenmenge groß genug ist, um die Datenbank bei jeder Aktualisierung vollständig zu senden. In diesem Fall müssen Konflikte auf der Ebene einzelner Objekte, Felder oder Beziehungen gelöst werden. Wir konzentrieren uns hauptsächlich auf diese Option.

Daher haben wir unser Wissen in diesen Artikel eingebracht, der meiner Meinung nach für alle, die sich für das Thema interessieren, sehr nützlich sein könnte => Datensynchronisierung in zentralen datenbasierten iOS-Apps ( http://blog.denivip.ru/index.php/2014/04) / Datensynchronisierung in Kerndaten-basierten iOS-Apps /? lang = de )


3
^^^^^^ das ist bei weitem die beste Antwort, Jungs!
Hgoebl

Ich stimme zu, Denis hat viel in das Thema gebracht + die Artikel-Links sind fantastisch. Spricht auch über das von DanielPaull erwähnte OT. Die Antwort von S.Lott ist gut, aber dies ist weitaus ausführlicher.
Krystian

27

Was Sie wirklich brauchen, ist Operational Transform (OT). Dies kann in vielen Fällen sogar zu Konflikten führen.

Dies ist immer noch ein aktives Forschungsgebiet, aber es gibt Implementierungen verschiedener OT-Algorithmen. Ich bin seit einigen Jahren an solchen Forschungen beteiligt. Lassen Sie mich wissen, ob diese Route Sie interessiert, und ich werde Sie gerne auf relevante Ressourcen hinweisen.


7
Daniel, ein Hinweis auf relevante Ressourcen wäre willkommen.
Parand

4
Ich habe gerade den Wikipedia-Artikel noch einmal gelesen. Es ist ein langer Weg und hat viele relevante Referenzen am Ende dieser Seite. Ich hätte Sie auf die Arbeit von Chengzheng Sun hingewiesen - auf seine Arbeit wird aus Wikipedia verwiesen. en.wikipedia.org/wiki/Operational_transformation . Hoffentlich hilft das!
Daniel Paull

13

Die Frage ist nicht ganz klar, aber ich würde mich mit optimistischem Sperren befassen, wenn ich Sie wäre. Es kann mit einer Sequenznummer implementiert werden, die der Server für jeden Datensatz zurückgibt. Wenn ein Client versucht, den Datensatz zurückzuspeichern, enthält er die vom Server empfangene Sequenznummer. Wenn die Sequenznummer mit dem übereinstimmt, was sich zum Zeitpunkt des Empfangs der Aktualisierung in der Datenbank befindet, ist die Aktualisierung zulässig und die Sequenznummer wird erhöht. Wenn die Sequenznummern nicht übereinstimmen, ist die Aktualisierung nicht zulässig.


2
Sequenznummern sind dein Freund hier. Denken Sie an dauerhafte Nachrichtenwarteschlangen.
Daniel Paull

7

Ich habe vor ungefähr 8 Jahren ein solches System für eine App erstellt und kann einige Möglichkeiten erläutern, wie es sich mit zunehmender App-Nutzung entwickelt hat.

Ich begann damit, jede Änderung (Einfügen, Aktualisieren oder Löschen) von einem Gerät in einer "Verlauf" -Tabelle zu protokollieren. Wenn beispielsweise jemand seine Telefonnummer in der Tabelle "Kontakt" ändert, bearbeitet das System das Feld contact.phone und fügt einen Verlaufsdatensatz mit action = update, field = phone, record = [Kontakt-ID] hinzu. value = [neue Telefonnummer]. Bei jeder Synchronisierung eines Geräts werden die Verlaufselemente seit der letzten Synchronisierung heruntergeladen und auf die lokale Datenbank angewendet. Dies klingt wie das oben beschriebene Muster "Transaktionsreplikation".

Ein Problem besteht darin, IDs eindeutig zu halten, wenn Elemente auf verschiedenen Geräten erstellt werden können. Ich wusste nichts über UUIDs, als ich dies startete, also habe ich automatisch inkrementierende IDs verwendet und einen verschlungenen Code geschrieben, der auf dem zentralen Server ausgeführt wird, um neue IDs zu überprüfen, die von Geräten hochgeladen wurden, und sie bei einem Konflikt in eine eindeutige ID zu ändern Weisen Sie das Quellgerät an, die ID in seiner lokalen Datenbank zu ändern. Nur die IDs neuer Datensätze zu ändern war nicht so schlecht, aber wenn ich zum Beispiel ein neues Element in der Kontakttabelle erstelle, dann erstelle ich ein neues verwandtes Element in der Ereignistabelle, jetzt habe ich Fremdschlüssel, die ich auch brauche überprüfen und aktualisieren.

Schließlich erfuhr ich, dass UUIDs dies vermeiden konnten, aber bis dahin wurde meine Datenbank ziemlich groß und ich befürchtete, dass eine vollständige UUID-Implementierung ein Leistungsproblem verursachen würde. Anstatt vollständige UUIDs zu verwenden, habe ich zufällig generierte alphanumerische Schlüssel mit 8 Zeichen als IDs verwendet und meinen vorhandenen Code zur Behandlung von Konflikten beibehalten. Irgendwo zwischen meinen aktuellen 8-Zeichen-Schlüsseln und den 36 Zeichen einer UUID muss es einen Sweet Spot geben, der Konflikte ohne unnötiges Aufblähen beseitigt. Da ich jedoch bereits über den Konfliktlösungscode verfüge, war es keine Priorität, damit zu experimentieren .

Das nächste Problem war, dass die Verlaufstabelle etwa zehnmal größer war als der gesamte Rest der Datenbank. Dies verteuert die Lagerung und jede Wartung der Verlaufstabelle kann schmerzhaft sein. Wenn Sie die gesamte Tabelle beibehalten, können Benutzer alle vorherigen Änderungen rückgängig machen, aber das fühlte sich wie ein Overkill an. Daher habe ich dem Synchronisierungsprozess eine Routine hinzugefügt. Wenn das Verlaufselement, das ein Gerät zuletzt heruntergeladen hat, nicht mehr in der Verlaufstabelle vorhanden ist, gibt der Server ihm nicht die letzten Verlaufselemente, sondern eine Datei mit allen Daten für dieser Account. Dann habe ich einen Cronjob hinzugefügt, um Verlaufselemente zu löschen, die älter als 90 Tage sind. Dies bedeutet, dass Benutzer Änderungen, die weniger als 90 Tage alt sind, weiterhin rückgängig machen können. Wenn sie mindestens alle 90 Tage synchronisiert werden, werden die Aktualisierungen wie zuvor inkrementell durchgeführt. Aber wenn sie länger als 90 Tage warten,

Durch diese Änderung wurde die Größe der Verlaufstabelle um fast 90% reduziert. Durch die Verwaltung der Verlaufstabelle wird die Datenbank jetzt nur noch doppelt so groß anstatt zehnmal so groß. Ein weiterer Vorteil dieses Systems besteht darin, dass die Synchronisierung bei Bedarf immer noch ohne die Verlaufstabelle funktionieren kann - beispielsweise, wenn ich Wartungsarbeiten durchführen musste, die es vorübergehend offline schalteten. Oder ich könnte verschiedene Rollback-Zeiträume für Konten zu unterschiedlichen Preisen anbieten. Wenn mehr als 90 Tage lang Änderungen zum Herunterladen erforderlich sind, ist die gesamte Datei normalerweise effizienter als das inkrementelle Format.

Wenn ich heute von vorne anfangen würde, würde ich die ID-Konfliktprüfung überspringen und nur eine Schlüssellänge anstreben, die ausreicht, um Konflikte zu beseitigen, mit einer Art Fehlerprüfung für alle Fälle. Die Verlaufstabelle und die Kombination aus inkrementellen Downloads für aktuelle Updates oder einem vollständigen Download bei Bedarf haben jedoch gut funktioniert.


1

Für die Delta-Synchronisierung (Änderungssynchronisierung) können Sie das Pubsub-Muster verwenden, um Änderungen auf allen abonnierten Clients zu veröffentlichen. Dienste wie Pusher können dies tun.

Bei der Datenbankspiegelung verwenden einige Webframeworks eine lokale Minidatenbank, um die serverseitige Datenbank mit der lokalen Datenbank in der Browserdatenbank zu synchronisieren. Eine teilweise Synchronisierung wird unterstützt. Überprüfen Sie das Messgerät .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.