Jaka jest najbardziej akceptowana strategia transakcyjna dla mikrousług

80

Jednym z głównych problemów, które widziałem w systemie z mikrousługami, jest sposób działania transakcji, gdy obejmują one różne usługi. W ramach naszej własnej architektury korzystamy z transakcji rozproszonych, aby rozwiązać ten problem, ale wiążą się one z własnymi problemami. Szczególnie impasy były jak dotąd bólem.

Kolejną opcją wydaje się być niestandardowy menedżer transakcji, który zna przepływy w twoim systemie i zajmie się wycofaniem zmian jako procesem w tle obejmującym cały system (więc powie innym usługom o wycofaniu a jeśli są wyłączone, powiadom ich później).

Czy istnieje inna akceptowana opcja? Oba wydają się mieć swoje wady. Pierwszy z nich może powodować impasy i wiele innych problemów, drugi może powodować niespójność danych. Czy są lepsze opcje?

Kristof
źródło
Dla pewności, czy te mikrousługi są używane przez wielu klientów jednocześnie, czy tylko pojedynczo?
Marcel
2
Próbowałem zadać to pytanie niezależnie od tego, Marcel. Załóżmy jednak, że korzystamy z systemu wystarczająco dużego, aby to zrobić, i chcemy mieć architekturę obsługującą obie te funkcje.
Kristof
4
To źle sformułowane, ale bardzo ważne pytanie. Kiedy większość ludzi myśli o „transakcjach”, myśli prawie wyłącznie o spójności, a nie aspektach atomowości, izolacji lub trwałości transakcji. Pytanie powinno być rzeczywiście stwierdził: „W jaki sposób stworzyć system ACID zgodnego daną architekturę microservices Tylko wdrażania spójności, a nie reszta kwasu nie jest bardzo przydatne jeśli nie lubisz złych danych...
Jeff Fischer
10
Zawsze możesz spróbować zmienić moje pytanie, aby było mniej „źle sformułowane”, Jeff Fischer.
Kristof
To pytanie i dyskusja są ściśle powiązane z innym pytaniem dotyczącym SO .
Paulo Merson

Odpowiedzi:

42

Zazwyczaj stosuje się izolację mikrousług w jak największym stopniu - należy traktować je jako pojedyncze jednostki. Następnie transakcje mogą być opracowywane w kontekście usługi jako całości (tj. Nie stanowią części zwykłych transakcji DB, chociaż nadal możesz mieć transakcje DB wewnątrz usługi).

Zastanów się, w jaki sposób występują transakcje i jakie to ma sens dla twoich usług, możesz wdrożyć mechanizm wycofywania, który nie wykonuje pierwotnej operacji, lub dwufazowy system zatwierdzania, który rezerwuje pierwotną operację, dopóki nie zostanie nakazany, aby zatwierdzić na prawdziwe. Oczywiście oba te systemy oznaczają, że wdrażasz własny, ale już wdrażasz mikrousługi.

Usługi finansowe robią takie rzeczy przez cały czas - jeśli chcę przenieść pieniądze z mojego banku do twojego banku, nie ma jednej transakcji takiej jak w DB. Nie wiesz, jakie systemy działają w obu bankach, więc musisz skutecznie traktować każdy z nich jak swoje mikrousługi. W takim przypadku mój bank przeniósłby moje pieniądze z mojego konta na rachunek posiadania, a następnie powiedziałby bankowi, że mają trochę pieniędzy. Jeśli to nie powiedzie się, mój bank zwróci moje konto środkami, które próbował wysłać.

gbjbaanb
źródło
5
Zakładasz, że zwrot pieniędzy nigdy się nie powiedzie.
Sanjeev Kumar Dangi
9
@SanjeevKumarDangi jest mniej prawdopodobne, a nawet jeśli zawiedzie, można to łatwo obsłużyć, ponieważ rachunek posiadania i faktyczny rachunek znajdują się pod kontrolą banku.
gldraphael
30

Myślę, że standardową mądrością jest, aby transakcje nigdy nie przekraczały granic mikrousług. Jeśli jakikolwiek zestaw danych naprawdę musi być atomowo spójny z innym, te dwie rzeczy należą do siebie.

Jest to jeden z powodów, dla których bardzo trudno jest podzielić system na usługi, dopóki nie zostanie w pełni zaprojektowany. Co we współczesnym świecie prawdopodobnie oznacza napisane ...

soru
źródło
37
Takie podejście może bardzo dobrze doprowadzić do połączenia wszystkich mikrousług w jedną aplikację monolityczną.
Slava Fomin II
4
To jest właściwe podejście. To jest naprawdę proste: jeśli potrzebujesz transakcji między usługami, Twoje usługi są błędne: przeprojektuj je! @SlavaFominII to, co mówisz, jest prawdą tylko wtedy, gdy nie wiesz, jak zaprojektować system mikrousług. Jeśli walczysz z podejściem mikrousług, nie rób tego, twój monolit będzie lepszy niż zły projekt mikrousług. Tylko wtedy, gdy znajdziesz odpowiednie granice usług, powinieneś rozdzielić monolit na usługi. W przeciwnym razie użycie mikrousług nie jest właściwym wyborem architektonicznym, po prostu podąża za hype.
Francesc Castells,
@FrancescCastells Co zrobić, jeśli nasze usługi rzeczywiście wymagają transakcji między innymi usługami, czy masz na myśli, że powinniśmy ignorować ograniczone konteksty i modelować nasze usługi w taki sposób, aby skończyło się na pojedynczej transakcji? Jestem nowicjuszem w mikrousługach, wciąż czytam, więc wybacz moje pytanie, czy to brzmi naiwnie ....: D: D
Bilbo Baggins
@BilboBaggins Mam na myśli przeciwieństwo ignorowania ograniczonych kontekstów. Z definicji transakcje odbywają się w ograniczonym kontekście, a nie w kilku z nich. Dlatego jeśli poprawnie zaprojektujesz mikrousługi wraz z ograniczonymi kontekstami, transakcje nie powinny obejmować wielu usług. Zauważ, że pewnego czasu potrzebujesz nie transakcji, ale lepszego radzenia sobie z ostateczną konsekwencją i odpowiednich działań kompensacyjnych, gdy coś pójdzie nie tak.
Francesc Castells,
Nie rozumiem, o co chodzi, czy jest szansa, że ​​możemy to omówić na czacie?
Bilbo Baggins,
17

Myślę, że jeśli spójność jest silnym wymogiem w twojej aplikacji, powinieneś zadać sobie pytanie, czy mikrousług jest lepszym podejściem. Jak mówi Martin Fowler :

Mikrousługi wprowadzają ewentualne problemy ze spójnością ze względu na ich chwalebny nacisk na zdecentralizowane zarządzanie danymi. Za pomocą monolitu możesz aktualizować wiele rzeczy w jednej transakcji. Mikrousługi wymagają wielu zasobów do aktualizacji, a rozproszone transakcje są niezadowolone (nie bez powodu). Dlatego teraz programiści muszą zdawać sobie sprawę z problemów ze spójnością i dowiedzieć się, jak wykryć, kiedy coś nie jest zsynchronizowane, zanim zrobią cokolwiek, czego kod będzie żałował.

Ale może w twoim przypadku możesz poświęcić spójność na rzecz dostępności

Procesy biznesowe są często bardziej tolerancyjne na niespójności, niż myślisz, ponieważ firmy często cenią dostępność bardziej.

Jednak zadaję sobie również pytanie, czy istnieje strategia dla rozproszonych transakcji w mikrousługach, ale być może koszt jest zbyt wysoki. Chciałem dać wam moje dwa centy z zawsze doskonałym artykułem Martina Fowlera i twierdzeniem CAP .

gabrielgiussi
źródło
1
W rozproszonych transakcjach biznesowych spójność jest czasem korygowana przez dodanie warstwy aranżacyjnej, takiej jak silnik przepływu pracy lub starannie zaprojektowana kolejka. Ta warstwa obsługuje wszystkie dwie fazy zatwierdzania i wycofywania oraz pozwala mikrousługom skupić się na określonej logice biznesowej. Ale wracając do CAP, inwestowanie w dostępność i spójność sprawia, że ​​wydajność jest ofiarą. Jak mikrousługi porównują się do wielu oddzielonych od siebie klas składających się na Twój biznes w OOP?
Greg
Możesz użyć RAFT za pośrednictwem mikrousług, aby zająć się spójnością FWIW
f0ster
1
+1. Często zastanawiam się, dlaczego w kontekście mikrousług w ogóle pożądane są transakcje, skoro wszystkie widoki danych typu „pull” można wdrożyć jako widoki zmaterializowane. Na przykład, jeśli jedna mikrousługa wdroży obciążenia z jednego konta, a inne kredyty na inne konto, widoki sald uwzględniłyby tylko pary kredytów i debetów, przy czym niedopasowane kredyty i debaty nadal znajdowałyby się w buforach zmaterializowanego widoku.
Sentinel
16

Jak zasugerowano w co najmniej jednej z odpowiedzi tutaj, ale także w innych miejscach w sieci, możliwe jest zaprojektowanie jednej mikrousługi, która utrzymuje jednostki razem w ramach normalnej transakcji, jeśli potrzebujesz spójności między tymi dwiema jednostkami.

Ale jednocześnie może się zdarzyć, że podmioty tak naprawdę nie należą do tej samej mikrousługi, na przykład ewidencji sprzedaży i ewidencji zamówień (kiedy zamawia się coś, aby zrealizować sprzedaż). W takich przypadkach możesz potrzebować sposobu zapewnienia spójności między dwiema mikrousługami.

Stosowano tradycyjnie rozproszone transakcje i według mojego doświadczenia działają one dobrze, dopóki nie zostaną skalowane do rozmiaru, w którym blokowanie staje się problemem. Możesz rozluźnić blokadę, aby tak naprawdę tylko odpowiednie zasoby (np. Sprzedawany przedmiot) były „blokowane” za pomocą zmiany stanu, ale w tym momencie zaczyna się robić trudniej, ponieważ wchodzisz na terytorium, na którym musisz zbudować wszystkie logika, aby zrobić to sam, zamiast powiedzieć, że baza danych sobie z tym poradzi.

Współpracowałem z firmami, które poszły drogą budowania własnych ram transakcyjnych do obsługi tego złożonego problemu, ale nie polecam go, ponieważ jest drogi i wymaga dojrzałości.

Istnieją produkty, które można przykręcić do systemu i które zapewniają spójność. Dobrym przykładem jest silnik procesów biznesowych, który zazwyczaj radzi sobie z konsekwencją ostatecznie i za pomocą kompensacji. Inne produkty działają w podobny sposób. Zazwyczaj kończy się warstwa oprogramowania w pobliżu klienta (ów), która zajmuje się spójnością oraz usługami (mikro) transakcji i połączeń w celu przeprowadzenia rzeczywistego przetwarzania biznesowego . Jednym z takich produktów jest ogólne złącze JCA, którego można używać z rozwiązaniami Java EE (dla przejrzystości: jestem autorem). Zobacz http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html, aby uzyskać więcej szczegółów i głębsze omówienie poruszonych tutaj problemów.

Innym sposobem obsługi transakcji i spójności jest zawinięcie połączenia z mikrousługą w połączenie z czymś transakcyjnym, takim jak kolejka komunikatów. Weź powyższy przykład rekordu sprzedaży / zamówienia - możesz po prostu pozwolić mikrousługi sprzedaży wysłać komunikat do systemu zamówień, który jest zatwierdzony w tej samej transakcji, która zapisuje sprzedaż do bazy danych. Rezultatem jest rozwiązanie asynchroniczne, które skaluje się bardzo dobrze. Korzystając z technologii takich jak gniazda sieciowe, można nawet obejść problem blokowania, który często wiąże się ze skalowaniem rozwiązań asynchronicznych. Aby uzyskać więcej pomysłów na takie wzory, zobacz inny z moich artykułów: http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html .

Niezależnie od tego, które rozwiązanie wybierzesz, ważne jest, aby pamiętać, że tylko niewielka część twojego systemu będzie pisać rzeczy, które muszą być spójne - większość dostępu będzie prawdopodobnie tylko do odczytu. Z tego powodu wbuduj zarządzanie transakcjami tylko w odpowiednie części systemu, aby nadal można było dobrze skalować.

Ant Kutschera
źródło
W międzyczasie powiedziałbym, że należy poważnie rozważyć przekształcenie procesu w proces asynchroniczny, w którym każdy etap procesu jest w pełni transakcyjny. Zobacz szczegóły: blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html
Ant Kutschera
„Utwórz przykładowy rekord sprzedaży / rekord zamówienia z góry - możesz po prostu pozwolić mikrousługi sprzedaży wysłać komunikat do systemu zamówień, który jest zatwierdzony w tej samej transakcji, która zapisuje sprzedaż do bazy danych”. Nie jestem pewien, czy to, co sugerujesz, jest w zasadzie transakcją rozproszoną, co powiedziało, jak poradziłbyś sobie w tym przypadku ze scenariuszem wycofania? Np. Komunikat zostaje przydzielony do kolejki komunikatów, ale został wycofany po stronie DB.
sactiw
@sactiw nie jestem pewien, czy mógłbym mieć na myśli zatwierdzanie dwufazowe, ale uniknę tego teraz i zamiast tego zapisz moje dane biznesowe oraz fakt, że komunikat musi zostać dodany do kolejki, w jednej transakcji do mojej bazy danych mikrousług . „Fakt”, czyli „polecenie”, jest następnie przetwarzany asynchronicznie po zatwierdzeniu transakcji, przy użyciu automatycznego mechanizmu ponownych prób. Zobacz przykład na blogu od 10.03.2018. Jeśli to możliwe, unikaj wycofania lub kompensacji na rzecz strategii forward, ponieważ łatwiej ją wdrożyć.
Ant Kutschera
1

Istnieje wiele rozwiązań, które kompromisują bardziej, niż czuję się swobodnie. To prawda, że ​​jeśli twoja sprawa użycia jest złożona, na przykład przenoszenie pieniędzy między różnymi bankami, przyjemniejsze alternatywy mogą być niemożliwe. Ale spójrzmy na to, co możemy zrobić we wspólnym scenariuszu, w którym użycie mikrousług zakłóca nasze przyszłe transakcje w bazie danych.

Opcja 1: Jeśli to możliwe, unikaj potrzeby transakcji

Oczywiste i wspomniane wcześniej, ale idealne, jeśli damy radę. Czy komponenty faktycznie należały do ​​tej samej mikrousługi? Czy możemy przeprojektować system (systemy) tak, aby transakcja stała się niepotrzebna? Być może akceptacja braku transakcji jest najbardziej przystępną ofiarą.

Opcja 2: Użyj kolejki

Jeśli istnieje wystarczająca pewność, że druga usługa odniesie sukces we wszystkim, co chcemy, możemy wywołać ją za pomocą jakiejś formy kolejki. Element w kolejce nie zostanie odebrany do później, ale możemy upewnić się, że element jest w kolejce .

Powiedzmy na przykład, że chcemy wstawić podmiot i wysłać wiadomość e-mail jako pojedynczą transakcję. Zamiast dzwonić na serwer pocztowy, umieszczamy wiadomość w kolejce w tabeli.

Begin transaction
Insert entity
Insert e-mail
Commit transaction

Oczywistą wadą jest to, że wiele mikrousług będzie potrzebowało dostępu do tej samej tabeli.

Opcja 3: Wykonaj zewnętrzną pracę, tuż przed sfinalizowaniem transakcji

Podejście to opiera się na założeniu, że popełnienie transakcji jest bardzo mało prawdopodobne.

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

Jeśli zapytania nie powiodą się, połączenie zewnętrzne jeszcze się nie odbyło. Jeśli połączenie zewnętrzne nie powiedzie się, transakcja nigdy nie zostanie zatwierdzona.

Takie podejście wiąże się z ograniczeniami polegającymi na tym, że możemy wykonać tylko jedno połączenie zewnętrzne i musi być wykonane na końcu (tzn. Nie możemy wykorzystać jego wyniku w naszych zapytaniach).

Opcja 4: Twórz rzeczy w stanie oczekiwania

Jak opublikowano tutaj , możemy mieć wiele mikrousług tworzących różne komponenty, każdy w stanie oczekiwania, bez transakcji.

Sprawdzanie poprawności jest wykonywane, ale nic nie jest tworzone w stanie ostatecznym. Po pomyślnym utworzeniu wszystkiego każdy komponent jest aktywowany. Zwykle ta operacja jest tak prosta, a prawdopodobieństwo, że coś pójdzie nie tak, jest tak małe, że możemy nawet zdecydować się na aktywację bez transakcji.

Największą wadą jest prawdopodobnie to, że musimy uwzględnić istnienie oczekujących przedmiotów. Każde wybrane zapytanie musi rozważyć, czy dołączyć oczekujące dane. Większość powinna to zignorować. A aktualizacje to zupełnie inna historia.

Opcja 5: Pozwól mikroserwisowi udostępnić swoje zapytanie

Żadna z pozostałych opcji tego nie robi dla Ciebie? Zatem bądźmy niekonwencjonalni .

W zależności od firmy ta może być nie do przyjęcia. Jestem świadomy. To jest niekonwencjonalne. Jeśli to nie do przyjęcia, wybierz inną trasę. Ale jeśli pasuje to do Twojej sytuacji, rozwiązuje problem prosto i skutecznie. To może być najbardziej akceptowalny kompromis.

Istnieje sposób na przekształcenie zapytań z wielu mikrousług w prostą, pojedynczą transakcję bazy danych.

Zwraca zapytanie zamiast go wykonywać.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

Jeśli chodzi o sieć, każda mikrousługa musi mieć dostęp do każdej bazy danych. Należy o tym pamiętać, również w odniesieniu do skalowania w przyszłości.

Jeśli bazy danych biorące udział w transakcji znajdują się na tym samym serwerze, będzie to zwykła transakcja. Jeśli są na różnych serwerach, będzie to transakcja rozproszona. Kod jest taki sam, niezależnie od tego.

Otrzymujemy zapytanie, w tym jego typ połączenia, parametry i ciąg połączenia. Możemy zawinąć je w czystą, wykonalną klasę Command, dzięki czemu przepływ będzie czytelny: Wywołanie mikrousług powoduje powstanie polecenia, które wykonujemy w ramach naszej transakcji.

Ciąg połączenia jest tym, co daje nam inicjująca mikrousługa, więc dla wszystkich celów i celów kwerenda jest nadal uważana za wykonaną przez tę mikrousługę. Po prostu fizycznie kierujemy go przez mikrousługę klienta. Czy to robi różnicę? Pozwala nam to umieścić tę samą transakcję z innym zapytaniem.

Jeśli kompromis jest akceptowalny, to podejście daje nam prostą transakcyjność aplikacji monolitowej w architekturze mikrousługowej.

Timo
źródło
0

Zacznę od dekompozycji przestrzeni problemowej - identyfikacji granic usług . Po prawidłowym wykonaniu nigdy nie będziesz musiał zawierać transakcji między usługami.

Różne usługi mają własne dane, zachowanie, siły motywacyjne, rząd, reguły biznesowe itp. Na dobry początek należy wymienić możliwości wysokiego poziomu, jakie posiada przedsiębiorstwo. Na przykład marketing, sprzedaż, księgowość, wsparcie. Kolejnym punktem wyjścia jest struktura organizacyjna, ale należy pamiętać, że istnieje pewne zastrzeżenie - z pewnych powodów (na przykład politycznych) może to nie być optymalny schemat dekompozycji przedsiębiorstw. Bardziej rygorystycznym podejściem jest analiza łańcucha wartości . Pamiętaj, że twoje usługi mogą obejmować również ludzi, nie jest to wyłącznie oprogramowanie. Usługi powinny komunikować się ze sobą za pośrednictwem wydarzeń .

Następnym krokiem jest rzeźbienie tych usług. W rezultacie otrzymujesz względnie niezależne agregaty . Reprezentują one jednostkę spójności. Innymi słowy, ich elementy wewnętrzne powinny być spójne i ACID. Agregaty komunikują się także ze sobą poprzez zdarzenia.

Jeśli uważasz, że Twoja domena wymaga najpierw spójności, pomyśl jeszcze raz. Żaden z dużych i krytycznych systemów nie został zbudowany z myślą o tym. Wszystkie są rozproszone i ostatecznie spójne. Sprawdź klasyczny papier Pat Helland .

Oto kilka praktycznych wskazówek na temat budowy systemu rozproszonego.

Zapadło
źródło