Wzorzec / algorytm synchronizacji klient-serwer?


224

Mam wrażenie, że muszą istnieć wzorce synchronizacji klient-serwer. Ale całkowicie nie udało mi się google w górę.

Sytuacja jest dość prosta - serwer jest centralnym węzłem, z którym łączy się wielu klientów i manipuluje tymi samymi danymi. Dane mogą być dzielone na atomy, w przypadku konfliktu, cokolwiek jest na serwerze, ma priorytet (aby uniknąć wciągnięcia użytkownika w rozwiązywanie konfliktów). Częściowa synchronizacja jest preferowana ze względu na potencjalnie duże ilości danych.

Czy istnieją jakieś wzorce / dobre praktyki dla takiej sytuacji, lub jeśli nie znasz żadnej - jakie byłoby twoje podejście?

Oto, jak teraz myślę, aby to rozwiązać: Równolegle do danych odbędzie się dziennik modyfikacji, z oznaczeniem czasu wszystkich transakcji. Kiedy klient się łączy, otrzymuje wszystkie zmiany od ostatniego sprawdzenia, w formie skonsolidowanej (serwer przegląda listy i usuwa dodatki, po których następuje usunięcie, łączy aktualizacje dla każdego atomu itp.). Et voila, jesteśmy na bieżąco.

Alternatywą byłoby zachowanie daty modyfikacji dla każdego rekordu, a zamiast usuwania danych, wystarczy zaznaczyć je jako usunięte.

jakieś pomysły?


27
zgodził się, że bardzo niewiele mówi się o wzorcach dla tego rodzaju rzeczy ... mimo że ten scenariusz jest dość powszechny
Jack Ukleja

Odpowiedzi:


88

Powinieneś przyjrzeć się, jak działa rozproszone zarządzanie zmianami. Spójrz na SVN, CVS i inne repozytoria zarządzające deltami.

Masz kilka przypadków użycia.

  • Synchronizuj zmiany. Twoje podejście do dziennika zmian (lub historii delta) wygląda dobrze. Klienci wysyłają swoje delty na serwer; serwer konsoliduje i dystrybuuje delty do klientów. To typowy przypadek. Bazy danych nazywają to „replikacją transakcji”.

  • Klient utracił synchronizację. Albo poprzez kopię zapasową / przywracanie, albo z powodu błędu. W takim przypadku klient musi uzyskać bieżący stan z serwera bez przechodzenia przez delty. To jest kopia od głównego do szczegółu, delty i wydajność niech będą przeklęte. To jednorazowa sprawa; klient jest zepsuty; nie próbuj tego optymalizować, po prostu zaimplementuj wiarygodną kopię.

  • Klient jest podejrzany. W takim przypadku musisz porównać klienta z serwerem, aby ustalić, czy klient jest aktualny i potrzebuje jakichkolwiek delt.

Należy postępować zgodnie z wzorcem projektowym bazy danych (i SVN) sekwencyjnego numerowania każdej zmiany. W ten sposób klient może złożyć trywialne żądanie („Jaką wersję powinienem mieć?”) Przed próbą synchronizacji. I nawet wtedy zapytanie („Wszystkie delty od 2149”) jest niezwykle łatwe do przetworzenia przez klienta i serwer.


Czy może pan wyjaśnić, czym dokładnie jest delta? Domyślam się, że to kombinacja skrótu / znacznika czasu ... Chciałbym usłyszeć od pana.
Anis,

Delta odnosi się do zmiany między dwiema wersjami. Na przykład, jeśli nazwa użytkownika uległa zmianie, delta może być czymś w rodzaju {wersja: 123, nazwa: "John Doe"}
dipole_moment

31

W ramach zespołu wykonałem sporo projektów związanych z synchronizacją danych, więc powinienem być kompetentny, aby odpowiedzieć na to pytanie.

Synchronizacja danych jest dość szeroką koncepcją i jest zbyt wiele do omówienia. Obejmuje szereg różnych podejść z ich zaletami i wadami. Oto jedna z możliwych klasyfikacji opartych na dwóch perspektywach: Synchroniczna / Asynchroniczna, Klient / Serwer / Peer-to-Peer. Synchronizacja implementacji zależy w dużym stopniu od tych czynników, złożoności modelu danych, ilości przesyłanych i przechowywanych danych oraz innych wymagań. Tak więc w każdym konkretnym przypadku wybór powinien sprzyjać najprostszej implementacji spełniającej wymagania aplikacji.

Na podstawie przeglądu istniejących gotowych rozwiązań możemy wyznaczyć kilka głównych klas synchronizacji, różniących się ziarnistością obiektów podlegających synchronizacji:

  • Synchronizacja całego dokumentu lub bazy danych jest używana w aplikacjach chmurowych, takich jak Dropbox, Dysk Google lub Yandex.Disk. Gdy użytkownik edytuje i zapisuje plik, nowa wersja pliku jest całkowicie przesyłana do chmury, zastępując wcześniejszą kopię. W przypadku konfliktu obie wersje plików są zapisywane, dzięki czemu użytkownik może wybrać, która wersja jest bardziej odpowiednia.
  • Synchronizację par klucz-wartość można stosować w aplikacjach o prostej strukturze danych, w których zmienne są uważane za atomowe, tj. Niepodzielone na logiczne elementy. Ta opcja jest podobna do synchronizacji całych dokumentów, ponieważ zarówno wartość, jak i dokument można całkowicie zastąpić. Jednak z perspektywy użytkownika dokument jest złożonym obiektem złożonym z wielu części, ale para klucz-wartość jest jedynie krótkim łańcuchem lub liczbą. Dlatego w tym przypadku możemy zastosować prostszą strategię rozwiązywania konfliktów, biorąc pod uwagę, że wartość jest bardziej odpowiednia, jeśli ostatnia uległa zmianie.
  • Synchronizowanie danych o strukturze drzewa lub wykresu jest stosowane w bardziej wyrafinowanych aplikacjach, w których ilość danych jest wystarczająco duża, aby wysłać bazę danych w całości przy każdej aktualizacji. W takim przypadku konflikty należy rozwiązać na poziomie pojedynczych obiektów, pól lub relacji. Skupiamy się przede wszystkim na tej opcji.

Wykorzystaliśmy więc naszą wiedzę w tym artykule, który moim zdaniem może być bardzo przydatny dla wszystkich zainteresowanych tematem => Synchronizacja danych w podstawowych aplikacjach iOS opartych na danych ( http://blog.denivip.ru/index.php/2014/04 / data-syncing-in-core-data-based-ios-apps /? lang = en )


3
^^^^^^, to zdecydowanie najlepsza odpowiedź, chłopaki!
hgoebl

Zgadzam się, Denis wniósł wiele do tematu + linki do artykułów są niesamowite. Mówi także o OT wspomniany przez DanielPaull. Odpowiedź S.Lott jest dobra, ale jest o wiele głębsza.
Krystian,

28

To, czego naprawdę potrzebujesz, to Operational Transform (OT). W wielu przypadkach może to nawet rozwiązać konflikty.

Jest to nadal aktywny obszar badań, ale istnieją implementacje różnych algorytmów OT. Od wielu lat jestem zaangażowany w takie badania, więc daj mi znać, czy ta trasa Cię interesuje, a chętnie udostępnię Ci odpowiednie zasoby.


7
Daniel, wskaźnik do odpowiednich zasobów będzie mile widziany.
Parand

4
Właśnie przeczytałem ponownie artykuł w Wikipedii. Przeszedł długą drogę i ma wiele istotnych odniesień na dole tej strony. Wskazałbym ci na dzieło Chengzheng Sun - jego prace są cytowane z wikipedii. en.wikipedia.org/wiki/Operational_transformation . Mam nadzieję, że to pomaga!
Daniel Paull

13

Pytanie nie jest krystalicznie jasne, ale gdybym był tobą, przyjrzałbym się optymistycznemu blokowaniu . Może być zaimplementowany z numerem kolejnym zwracanym przez serwer dla każdego rekordu. Gdy klient próbuje zapisać rekord z powrotem, będzie on zawierał numer sekwencyjny otrzymany z serwera. Jeśli numer sekwencyjny pasuje do zawartości bazy danych w momencie otrzymania aktualizacji, aktualizacja jest dozwolona, ​​a numer sekwencyjny jest zwiększany. Jeśli numery sekwencyjne nie pasują, aktualizacja jest niedozwolona.


2
Numery sekwencji są tutaj twoim przyjacielem. Pomyśl o trwałych kolejkach wiadomości.
Daniel Paull

7

Zbudowałem taki system dla aplikacji około 8 lat temu i mogę podzielić się kilkoma sposobami, które ewoluowały wraz z rozwojem aplikacji.

Zacząłem od zalogowania każdej zmiany (wstawienia, aktualizacji lub usunięcia) z dowolnego urządzenia do tabeli „historii”. Jeśli na przykład ktoś zmieni swój numer telefonu w tabeli „kontakt”, system dokona edycji pola contact.phone, a także doda rekord historii z działaniem = aktualizacja, pole = telefon, rekord = [identyfikator kontaktu], wartość = [nowy numer telefonu]. Następnie, gdy urządzenie synchronizuje, pobiera elementy historii od ostatniej synchronizacji i stosuje je do swojej lokalnej bazy danych. Brzmi to jak opisany powyżej wzorzec „replikacji transakcji”.

Jednym z problemów jest unikanie identyfikatorów, gdy elementy można tworzyć na różnych urządzeniach. Kiedy zaczynałem, nie wiedziałem o identyfikatorach UUID, więc użyłem automatycznej inkrementacji identyfikatorów i napisałem skomplikowany kod, który działa na centralnym serwerze, aby sprawdzić nowe identyfikatory przesłane z urządzeń, zmienić je na unikalne identyfikatory, jeśli wystąpi konflikt, i powiedz urządzeniu źródłowemu, aby zmieniło identyfikator w lokalnej bazie danych. Po prostu zmiana identyfikatorów nowych rekordów nie była taka zła, ale jeśli utworzę na przykład nowy element w tabeli kontaktów, a następnie utworzę nowy powiązany element w tabeli zdarzeń, teraz mam klucze obce, które muszę również sprawdź i zaktualizuj.

W końcu dowiedziałem się, że UUID może tego uniknąć, ale do tego czasu moja baza danych robiła się dość duża i bałam się, że pełna implementacja UUID może spowodować problem z wydajnością. Zamiast korzystać z pełnych identyfikatorów UUID, zacząłem używać losowo generowanych 8-znakowych klawiszy alfanumerycznych jako identyfikatorów i pozostawiłem istniejący kod na miejscu, aby poradzić sobie z konfliktami. Gdzieś pomiędzy moimi obecnymi 8-znakowymi kluczami a 36 znakami UUID musi być słaby punkt, który wyeliminowałby konflikty bez niepotrzebnego wzdęcia, ale ponieważ mam już kod rozwiązywania konfliktów, eksperymentowanie z tym nie było priorytetem .

Kolejnym problemem było to, że tabela historii była około 10 razy większa niż cała reszta bazy danych. To sprawia, że ​​przechowywanie jest drogie, a wszelkie czynności konserwacyjne w tabeli historii mogą być bolesne. Zachowanie całego stołu pozwala użytkownikom wycofać wszelkie poprzednie zmiany, ale zaczęło to wydawać się przesadą. Dodałem więc procedurę do procesu synchronizacji, w której jeśli element historii ostatnio pobrany przez urządzenie już nie istnieje w tabeli historii, serwer nie podaje mu ostatnich elementów historii, ale zamiast tego przekazuje mu plik zawierający wszystkie dane To konto. Następnie dodałem cronjob, aby usunąć elementy historii starsze niż 90 dni. Oznacza to, że użytkownicy mogą nadal wycofywać zmiany starsze niż 90 dni, a jeśli zsynchronizują co najmniej raz na 90 dni, aktualizacje będą przyrostowe, jak poprzednio. Ale jeśli czekają dłużej niż 90 dni,

Ta zmiana zmniejszyła rozmiar tabeli historii o prawie 90%, więc teraz utrzymanie tabeli historii sprawia, że ​​baza danych jest tylko dwa razy większa niż dziesięć razy większa. Kolejną zaletą tego systemu jest to, że synchronizacja może nadal działać bez tabeli historii, jeśli zajdzie taka potrzeba - na przykład gdybym musiał przeprowadzić pewne czynności konserwacyjne, które tymczasowo przełączyły ją w tryb offline. Lub mógłbym zaoferować różne okresy wycofania dla kont w różnych punktach cenowych. A jeśli do pobrania potrzeba więcej niż 90 dni, cały plik jest zwykle bardziej wydajny niż format przyrostowy.

Gdybym dzisiaj zaczynał od nowa, pomijałbym sprawdzanie konfliktu identyfikatorów i dążyłbym do uzyskania klucza o długości wystarczającej do wyeliminowania konfliktów, z pewnym rodzajem sprawdzania błędów na wszelki wypadek. Ale tabela historii i kombinacja przyrostowych pobrań dla ostatnich aktualizacji lub pełnego pobierania w razie potrzeby działa dobrze.


1

W przypadku synchronizacji delta (zmiany) możesz użyć wzorca pubsub, aby opublikować zmiany z powrotem do wszystkich subskrybowanych klientów, usługi takie jak popychacz mogą to zrobić.

W przypadku kopii lustrzanej bazy danych niektóre platformy sieciowe używają lokalnej mini bazy danych do synchronizacji bazy danych po stronie serwera z lokalną bazą danych w przeglądarce, obsługiwana jest częściowa synchronizacja. Sprawdź miernik lub .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.