Posts tagged architecture

Jak zwątpiłem w transakcje

Transakcje to fajna sprawa. Polubiłem je od pierwszego użycia. Zostałem oczarowany przez ich magiczną właściwość — zwalniają z myślenia o spójności danych. Cool, przecież nie lubię myśleć. Jeszcze bardziej byłem oczarowany, gdy odkryłem transakcje rozproszone. To dopiero jazda. Mogę coś “zapdejtować” na tej bazie, na tej drugiej bazie i jeszcze wrzucić komunikat do kolejki MSMQ i wszystko wykona się transakcyjnie — w całości lub wcale.

Od dłuższego czasu zaczynam jednak wątpić w transakcje, szczególnie te rozproszone. Zbyt często ja sam lub ktoś ze znajomych wpada przez nie w pułapkę bez wyjścia. Zamiast używać najodpowiedniejszej technologii, musimy wtedy wziąć taką, która współdziała z naszym sposobem zarządzania transakcjami.

Najczęstszym problemem, na który natrafiałem jest odbieranie i wysyłanie komunikatów do kolejek z jednoczesnym zapisem danych w bazie. Pierwsza rzecz, która przychodzi w takim wypadku do głowy, to transakcja, która obejmuje zarówno infrastrukturę kolejkową, jak i RDBMS. Pierwsza nie oznacza, niestety, najlepsza.

Nieodpowiednia technologia

Wymaganie transakcyjności w komunikacji z RDBMS i kolejkami pchnęło mnie kilka lat temu do wykorzystania SQL Server Service Broker jako kolejki komunikatów. Unikalną cechą Service Broker’a jest fakt, że żyjąc wewnątrz silnika bazodanowego, jest w naturalny sposób zintegrowany z mechanizmami transakcyjnymi SQL Server. Nie ma potrzeby stosowania (wolnych) transakcji rozproszonych, aby wysłać komunikat i zrobić przysłowiowy “apdejt”.

Niestety poza tą jedną zaletą, Service Broker ma bardzo wiele wad, z których największą jest brak dobrego gotowego API w C#, o modelu usługowym (np. WCF) nie wspominając. Jest bardzo trudny w użyciu i utrzymywaniu. Definitywnie był to zły wybór technologiczny, podyktowany jedynie chęcią zastosowania mechanizmu transakcji.

Niezgodność API

W każdym nietrywialnym systemie wykorzystuje się wiele zewnętrznych bibliotek. Bardzo rzadko pochodzą one od jednego dostawcy. Zdecydowanie częściej jest to mieszanka rozwiązań open source oraz COTS. Problem pojawia się, gdy dwie z bibliotek mają współpracować w ramach jednej transakcji. Weźmy jako przykład NHibernate i NServiceBus. Ten pierwszy posiada własną abstrakcję reprezentującą transakcje, podczas gdy pod spodem korzysta z transakcji ADO.NET. Ten drugi wykorzystuje transakcje System.Transactions do dostępu do MSMQ.

W przypadku użycia obu technologii w jednej transakcji, pojawia się problem, jak sprawić, aby każda z bibliotek mogła wykorzystywać swoje API odwołując się do tej samej fizycznej transakcji. W wypadku wspomnianej pary, jedynym rozwiązaniem jest pozwolić NHibernate używać zewnętrznych transakcji System.Transactions. Jest to jednak zamiana jednego problemu na inny. Do niedawna bowiem pojawiał się w NServiceBus wyciek pamięci, ponieważ zachowanie NHibernate w wypadku współpracy z System.Transactions jest bardzo słabo udokumentowane i łatwo o błąd wynikający z niezrozumienia.

Błedne implementacje transakcyjności

Jonathan Oliver opisał swoje testy kompatybilności różnych silników bazodanowych z transakcjami rozproszonymi na swoim blogu. Wnioski nie są optymistyczne: jedynie sterowniki do SQL Server i Oracle w pełni i bez problemów je obsługują.

Z drugiej strony nie dziwie się, że mniej płatne lub darmowe rozwiązania nie wspierają rozproszonych transakcji. Zdecydowana większość systemów radzi sobie bez nich, więc zysk (w sensie pieniędzy z licencji lub satysfakcji użytkowników) z implementacji wsparcia dla nich byłby znikomy.

Proste rozwiązanie

Rozwiązanie jest oczywiście proste. Wymaga jednak nieco innego podejścia do projektowania komunikacji. Wystarczy zadbać o to, aby każdy komunikat był

  • albo idempotentny (wielokrotne przetworzenie takiego komunikatu daje taki sam efekt, jak przetworzenie jednokrotne),
  • albo jednoznacznie identyfikowalny (unikalne ID).

Drugi przypadek można sprowadzić do pierwszego dodając rejestr przetworzonych komunikatów zawierający ich unikalne ID i przed obsłużeniem komunikatu sprawdzać, czy aby nie został przetworzony wcześniej.

Po spełnieniu któregoś z powyższych warunków zyskujemy możliwość rozłącznego zarządzania transakcją związaną z obieraniem komunikatu (MSMQ, ServiceBroker) oraz transakcją bazodanową. Ta pierwsza powinna być zatwierdzana dopiero po zatwierdzeniu tej drugiej. Powoduje to, że mamy pewność, iż każdy komunikat zostanie przetworzony co najmniej raz. Z drugiej strony idempotentność gwarantuje nam, że skutki wielokrotnego przetworzenia będą takie, jak jednokrotnego. Ostatecznie więc uzyskujemy semantykę dokładnie raz — taką samą jak przy zastosowaniu transakcji rozproszonych.

Praktyka

Dokładnie taki mechanizm zastosowałem w swoim ostatnim systemie. Pozwolił mi on na użycie klienta Service Broker (użycie tej kolejki było narzucone z góry) w połączeniu z NHibernate bez konieczności integracji obu technologii. Ponieważ transakcje były rozdzielone, klient kolejek nie musiał wiedzieć nic o dostępie do danych i vice versa. Prawdopodobnie zaoszczędziło mi to kilka dni pracy przy implementacji, testach i poprawianiu bugów w warstwie integracyjnej. Nauczyłem się także, że najlepszą strategią integracji technologii jest unikanie integracji technologii, kiedy to tylko możliwe.

VN:F [1.8.7_1070]
Rating: 5.0/5 (1 vote cast)

Fractal structures

For the last few days I was mainly pair-programming (pair-refactoring to be honest) some Silverlight UI code. I must confess I really rarely write UI code these days. Actually, I don’t remember the last time I’ve written a ASP.NET control from scratch. I did write some UI for DDDSample.Net but it was not production code after all.

MVVM

When I bumped into this Model-View-View Model (MVVM) stuff, I was a little confused. Then came the moment of enlightenment. I realized I saw all of this in totally different context – in loosely coupled system architecture. Look at this:

The blue elements on the right is MVVM stuff. It contains of three classic MVVM components and an additional one — an Event Aggregator. It is used for communication between MVVM triads. Application is organized in such a way, that multiple MVVM triads form vertical slices of functionality. These slices are loosely coupled so that they can be changed independently. Actually, when you value loose coupling much, you should avoid any code sharing between slices.

The orange elements are the building blocks of DDD-based distributed application. It receives commands via Services, then it processes them in a Domain Model which is backed by a Data Store. Finally, state changes are communicated to the outside world via a Message Bus.

How do slices communicate?

They use events and (rarely) messages. The name I chose for the diagram, the Event Aggregator, is a little bit misleading, because ViewModels should be able to both publish an event (broadcast to all interested receivers) and send direct message to a concrete receiver. The only shared thing between slices is the definition of messages (sounds like SOA, isn’t it?).

Again, how does it relate to system architecture?

I think the picture says it all. View is an analog of Service interface. View Model is an analog of Domain Model. In fact, one can say that View Model is the Domain Model in the context of user interface as it models the domain of particular UI case. The model is the place where all the information lives, a Data Store if you prefer. Finally, the Event Aggregator (with mentioned earlier unicast capabilities) is an analog of a Message Bus (such as NServiceBus).

How does it work?

Let’s look at the example. There are two slices, A and B. For the purpose of the example, let A be master view (a data grid and search) and B — a details view.

User selects the row on a grid. In response to that View A to generate a command and passes it (by means of data binding) to the View Model A. The View Model validates command, processes it (updates all required properties) and, as a result, it publishes an event “SelectionChanged”.

The Event Aggregator receives the event and searches for its subscribes. It finds View Model B so it forwards the event to it. During processing of event, View Model B requests all the data it needs from the Model B and updates its own properties using retrieved values. View B is automatically updates as it is data-bound to its View Model.

Summary

As you can see, good architected applications tend to have fractal structure. Similar forms can be discovered on various levels of detail. I value such architectures very high. The coherence makes them simple and elegant.

One more thing in case you didn’t noticed. There is a trend recently to focus architecture discussion not on what’s horizontal, but on what’s vertical. As we are (hopefully and at last) moving away from waterfallish processes, we start building applications slice by slice, not layer by layer. These slices do have to have some architecture but not as much design up front. The layers are much less important. I don’t even care if all the slices have the same layers — it can be impractical. What I want to have is the conscious process of choosing the layers of each slice so it can be implemented in the simplest possible way.

VN:F [1.8.7_1070]
Rating: 0.0/5 (0 votes cast)

Sześć tygodni nietrywialnego modelu

Minęło właśnie sześć tygodni od opublikowania notki omawiającej szkic architektury systemu, nad którym właśnie pracuje. Tak, jak zakładaliśmy, wchodzimy właśnie z naszym systemem w fazę testów akceptacyjnych. Wdrożenie produkcyjne zbliża się wielkimi krokami. Przy tej okazji chciałbym Wam opowiedzieć, co zmieniło się w projekcie naszego systemu w ciągu tych pracowitych sześciu tygodni.

Architektura

Większość definicji architektury odnosi się do jej niezmienności — architektura, to ta część projektu, która jest kosztowna lub trudna do zmiany w późniejszych jego etapach. Ponieważ nie przechodziliśmy przez żaden bolesny proces przeprojektowywania, mogę z całą pewnością stwierdzić, że nasza architektura pozostała niezmieniona. Na początku projektu zdefiniowałem architekturę tak:

model zakłada istnienie anemicznych struktur danych, które są przetwarzane przez wiele następujących po sobie procesów. Każdy proces jest zaś sekwencją czynności, z których każda reaguje na konkretny rodzaj zdarzenia. Efektem wykonania czynności jest opublikowanie innego zdarzenia, aktywujące kolejną czynność.

Prawda, że minimalna ta definicja? Dobra architektura powinna być właśnie minimalna, ponieważ tylko wtedy może być stabilna. Jeśli architekt robi zbyt wiele założeń, jest więcej niż pewne, że któreś z tych założeń okaże się fałszywe w toku prac nad projektem.

Infrastruktura

Celem infrastruktury jest implementacja założeń architektury. W tym obszarze, jak w każdym innym, staram się kierować zasadą YAGNI (You Ain’t Gonna Need It). Z tego powodu infrastruktura w moim projekcie zmieniała się dosyć często.

Na początku było…

Na początku w ogóle jej nie było! Przez pierwszy tydzień lub dwa skupiliśmy się na rozwoju logiki systemu. Chcieliśmy mieć działający prototyp, który przechodzi testy “na sucho” (bez odwoływania się do bazy danych itp.). W ten sposób chcieliśmy udowodnić, że model procesowy w ogóle sprawdza się w przypadku naszego problemu. Gdyby okazało się inaczej, mielibyśmy jeszcze całkiem sporo czasu na zmianę założeń.

Pierwsza wersja

Ostatecznie przyszedł czas na pierwszą wersji infrastruktury. Napisanie jej zajęło mi jakieś 2 dni. Wersja ta obsługiwała wszystkie trzy tryby łączenia zdarzeń i czynności (natychmiastowy, z checkpointem i odroczony). Nie oferowała jedna żadnych mechanizmów kontroli stanu działania (ile zdarzeń oczekuje na przetworzenie itp.) ani historii wykonania (jakie czynności zostały wykonane w kontekście danego obiektu biznesowego). Była wystarczająca do wewnętrznych testów i o to nam chodziło.

Mechanizm był banalnie prosty. Każde zakolejkowane zdarzenie było adresowane do konkretnej docelowej czynności. W momencie publikacji odszukiwane były wszystkie zasubskrybowane czynności i do kolejki odkładane było tyle kopii zdarzenia, ile znaleziono subskrybentów. Dzięki temu różne czynności mogły subskybować to samo zdarzenie z różnym opóźnieniem.

Przetwarzanie zrealizowane było w formie pętli, która w każdym obrocie pobierała pierwsze zdarzenie z kolejki, odnajdywała jego adresata, przekazywała mu obiekt zdarzenia a następnie usuwała zdarzenie z kolejki oczekujących. Efektem takiego działania mogło być opublikowanie nowych zdarzeń. Te z nich, które były subskrybowane trwale (z checkpoint‘em) odkładane były do kolejki w bazie danych. Pozostałe kolejkowane były w pamięci do natychmiastowego przetworzenia w ramach tej samej transakcji.

Historia

Pierwszym dodatkowym wymaganiem, jakie się pojawiło, było odkładanie historii przetwarzania. “Nic prostszego!” — odpowiedziałem naszemu Product Owner‘owi. Dodanie kodu wiążącego zdarzenie z obiektem zajęło mi dosłownie chwilę. Efektem ubocznym tej poprawki była zmiana kodu usuwającego przetworzone zdarzenia na kod, który oznaczał je flagą “wykonane”. Jakie to miało konsekwencje dowiecie się niedługo.

Diagnostyka

Następna zmiana została zainicjowana przeze mnie. W pierwszej wersji, do zapisu danych związanych ze zdarzeniami, użyliśmy serializacji binarnej System.Runtime.Serialization. Dlaczego? Ponieważ ten mechanizm jest wspierany przez NHibernate out-of-the-box. Niestety ma on dwie wady. Po pierwsze, rozmiar danych po serializacji jest dosyć duży. Po drugie (i gorsze), dane te są zupełnie nieczytelne, zarówno z poziomu SQL Server Management Studio, jak technologii GUI, którą zastosowaliśmy (własne rozwiązanie 4GL). Przejście na serializację JSON (z formatowaniem) rozwiązało problem diagnostyki (wykorzystaliśmy bardzo dobrą bibliotekę JSON.NET).

Wydajność

Przetwarzanie zdarzeń oparte było o jedną tabelę w bazie danych. Zdarzenia, która są gotowe do przetworzenia wyciągane były mniej-więcej takim zapytaniem:

SELECT * FROM QueuedEvent WHERE DueDate <= @currentTime AND Processed = 0

Ten drugi warunek został dodany w momencie implementacji obsługi historii. Niestety wydajność takich zapytań, wziąwszy pod uwagę ilość zdarzeń, jakie mogą się odłożyć po roku od uruchomienia systemu, nie byłaby zapewne zadowalająca. Pierwszym rozwiązaniem, jakie się nasuwa, jest partycjonowanie. Niestety w tym wypadku zdefiniowanie funkcji partycjonującej byłoby trudne. Postanowiliśmy więc rozdzielić zdarzenia na dwie osobne tabele: jedną dla zdarzeń oczekujących i jedną dla przetworzonych. Ta pierwsza zwykle będzie zawierać nie więcej niż kilkadziesiąt rekordów, więc nie ma sensu w ogóle jej indeksować. Dzięki temu częste wstawienia i usunięcia będą tańsze.

Wydajność raz jeszcze

W końcu przyszedł czas na podejście do problemu zrównoleglenia przetwarzania. Okazało się, że czas odpowiedzi zewnętrznych systemów, z którymi się integrujemy, jest bardzo długi. Miało to bardzo niekorzystny wpływ na przepustowość naszego systemu, gdyż w podczas odczekiwania na odpowiedź dla jednego zdarzenia, inne nie były przetwarzane.

Rozwiązanie jest proste — zrównoleglijmy przetwarzanie. Prościej powiedzieć niż zrobić. Ostatecznie jednak się udało. Nasze rozwiązanie składa się z podręcznej pamięci przechowującej identyfikatory zdarzeń oczekujących na przetwarzanie i wielu wątków pobierających kolejne identyfikatory. Cache automatycznie odświeża się, gdy zostanie opróżniony.

Aby nie blokować całej tabeli ze zdarzeniami w momencie pobierania danych do cache, stosujemy tam najsłabszy poziom izolacji transakcji. Konsekwencją tego jest fakt, że nie jesteśmy pewni, czy zdarzenia, których identyfikatory pobraliśmy, są jeszcze zdatne do przetworzenia. Fakt ten jest weryfikowany przed samym przetworzeniem oraz po jego zakończeniu (optimistic concurrency).

Morał

Każda historia ma swój morał. W tym wypadku chodzi o relację architektura — design. Architektura w tym projekcie była stabilna. Z drugiej strony design (czyli głównie infrastruktura) zmieniał się praktycznie cały czas.

Stabilność architektury (zaklętej w interfejsach) pozwoliła zbudować szybko całą logikę biznesową aplikacji, bez potrzeby robienia kosztownych restrukturyzacji po każdej modyfikacji infrastruktury.

Najlepsza architektura to taka, której prawie nie widać. Najlepszy design to taki, który rozwija się wraz z projektem i jego potrzebami.

VN:F [1.8.7_1070]
Rating: 2.7/5 (3 votes cast)

Jeśli nie transakcje rozproszone, to co?

Kontynuując moje rozważania na temat transakcji rozproszonych, dziś chciałbym Wam zaproponować pewną prostą technikę, która z powodzeniem może zastąpić transakcje rozproszone w Waszych systemach. Rozwiązanie to transakcyjne kolejki komunikatów.

Co to takiego?

Transakcyjne kolejki komunikatów są praktycznie tak stare, jak same komputery. Ich historia sięga systemów IBM 360 i dalej. Sama idea jest prosta: wrzucając komunikat do kolejki zapisujemy go trwale na dysku (lub innym trwałym nośniku) uzyskując pewność, że zapis się powiódł. W dowolnym momencie później możemy komunikat odczytać. Nie jest on jednak usuwany z kolejki w chwili odczytania, ale w chwili zatwierdzenia transakcji związanej z odczytem. Co to oznacza?

  • Przy zapisie, mamy pewność, że jeśli nie wystąpił błąd, to komunikat został trwale zapisany
  • Przy odczycie mamy pewność, że jeśli wystąpił błąd, to komunikat nie został utracony

Komunikacja zdalna

Powyższa definicja nic nie mówi o tym, jak zachowuje się kolejka w środowisku rozproszonym. I słusznie. Są to już kwestie implementacyjne. Większość dostępnych infrastruktur kolejkowych (MSMQ, WebSphere MQ, Active MQ) umożliwia komunikację zdalną w środowisku rozproszonym poprzez automatyczne forwardowanie komunikatów między kolejkami znajdującymi się na różnych maszynach. Jest to naturalne rozwinięcie koncepcji kolejek o kolejną gwarancję:

  • Przy zapisie mamy gwarancję, że mając do dyspozycji nieskończeniu długi czas, infrastruktura dostarczy komunikat do kolejki docelowej.

Twierdzenie CAP

Twierdzenie o CAP (Consistency – Availability – Partitionability) mówi, że z wymienionych trzech właściwości w dowolnym systemie osiągalne są co najwyżej dwie. Transakcje rozproszone zapewniają bardzo mocne “C”, co odbywa się oczywiście kosztem “A” i “P”. Zastosowanie transakcyjnych kolejek to nie magia — zwiększenie dostępności i potencjału partycjonowania odbywa się dzięki rozluźnieniu więzów spójności. W tym obszarze jest bardzo dużo miejsca na zastosowanie kreatywnego myślenia do zaprojektowania systemu w ten sposób, aby zmniejszenie “C” było jak najmniej bolesne. Mamy więc zajęcie dla architekta:-)

Events vs compensable transactions

Istnieje wiele dobrze opisanych wzorców projektowania interakcji z wykorzystaniem transakcyjnych kolejek komunikatów.Zajmę się dwoma z nich.

Ze zdarzeniami jako sposobem komunikacji między systemami zetknąłem się pierwszy raz na prezentacji Udiego Dahana. Od tej pory ten temat bardzo mnie zainteresował. Idea polega na analizie problemu biznesowego w kategoriach autonomicznych usług reagujących na zdarzenia publikowane przez inne usługi. Podejście takie wpływa bardzo mocno na dekompozycję systemu na moduły, dzięki czemu komunikacja między modułami jest z natury asynchroniczna, a więc idealnie nadaje się do implementacji za pomocą kolejek. Niestety takie podejście sprawdza się najlepiej wtedy, gdy zaczynamy budowę systemu od podstaw.

W przeciwnym wypadku dobrym wzorcem są transakcje kompensowalne. W tym wypadku kluczowa jest rezygnacja z jednej z właściwości ACID — izolacji. Skutki transakcji kompensowalnych są widoczne dla innych równoległych transakcji. Aby sobie z tym radzić, transakcje takie wynosi się zwykle do warstwy biznesowej (w przeciwieństwie do warstwy infrastruktury, gdzie żyją transakcje ACID), gdzie jest odpowiednie miejsce, aby rozwiązywać problemy związane z brakiem izolacji.

Podsumowaie

Nie bójcie się kolejek komunikatów! Nie bójcie się rezygnacji z ACID. Nie bójcie się eventual consistency! To wszystko są narzędzia wymyślone przez ludzi dla ludzi. O ile większość obszarów w większości systemów świetnie (i tanio) można zrealizować za pomocą klasycznego zestawu RDBMS/ACID, o tyle w przypadkach szczególnych warto sięgnąć po specjalne środki. Wtedy właśnie w Waszej skrzynce na narzędzia nie powinno zabraknąć kolejek komunikatów.

VN:F [1.8.7_1070]
Rating: 5.0/5 (1 vote cast)

Dlaczego nie lubię rozproszonych transakcji

Jakiś czas temu po prostu powiedziałbym, że są niewydajne. Na szczęście, zgodnie z ideą challenge everything przeprowadziłem kilka testów, które przekonały mnie, że problem wydajnościowy wcale nie jest taki straszny. Na pewno nie na tyle, aby dyskwalifikować takie rozwiązanie. Co więc je dyskwalifikuje (w moich oczach)? Oto lista problemów:

  1. zwiększona podatność na awarię
  2. skomplikowana administracja infrastrukturą
  3. problemy związane z zaufaniem i polityką

Mam oczywiście na myśli rozwiązanie, w którym jedna transakcja rozproszona współdzielona jest przez wiele niezależnych usług/systemów/aplikacji. Wykorzystanie transakcji rozproszonych w ramach jednej aplikacji do rozwiązania kwestii technicznych (np. integracja transakcyjna bazy danych i MSMQ) jest dla mnie całkowicie OK.

Historia pewnej transakcji

Aby namierzyć problemy, prześledźmy pewien nie-całkiem-nierealny przypadek użycia: klienta łączącego się z własną bazą danych i z zewnętrzną transakcyjną usługą. Oto, co się dzieje:

  1. Klient rozpoczyna transakcję w swoim kodzie
    using (TransactionScope tx = new TransactionScope())
    
  2. Klient wykonuje operację na bazie danych. W przypadku bazy SQL Server po tym kroku transakcja jest wciąż lokalna (brak zaangażowania Distributed Transaction Coordinator, DTC). W przypadku innych silników może być inaczej
  3. Klient łączy się z usługą zdalną “A” w sposób transakcyjny (wykorzystując mechanizmy WCF). W tym momencie transakcja jest promowana i staje się transakcją rozproszoną — otrzymuje odpowiedni identyfikator.

Transakcja po promocji

W tym momencie pojawia się problem natury administracyjnej. Aby usługi DTC na różnych maszynach, być może w różnych firmach, na różnych kontynentach, mogły się porozumieć, niezbędne jest stworzenie odpowiednich kanałów (“wybicie dziur” w firewallach). Tego typu działania są ściśle związane z polityką bezpieczeństwa każdej świadomej organizacji i dlatego zwykle wymagają uruchomienia skomplikowanego i długotrwałego procesu. Każda zmiana w logicznej topologii wywoływanych usług, jakkolwiek prosta w kodzie, może się wiązać z koniecznością uruchomienia trwających tygodniami procesów w dziale IT. Ile nic to, idźmy dalej.

  1. Usługa “A” do wykonania swojego biznesu musi skorzystać z pewnej zewnętrznej funkcji udostępnianej przez usługę “B”. Oczywiście, dla utrudnienia, usługa “B” także jest transakcyjna.

Nagle i niespodziewanie pojawia się nam zależność od usługi “B”. Teoretycznie klient nie musiałby wiedzieć nic o istnieniu “B”. Teoria jednak ma się nijak do praktyki. Praktycznie bowiem, powodzenie transakcji klienta jest zależne o działania usługi “B”. Co jeśli jest ona prowadzona przez firmę konkurencyjną dla naszego klienta? Wcale nie musi jej zależeć na najwyższym poziomie obsługi. Niektóre (te najbardziej lukratywne?) transakcje mogą być umyślnie sabotowane przez zrywanie połączenia. Polityka, polityka, wszędzie polityka. Załóżmy jednak, że tym razem kwestie polityczne zostały rozwiązane pokojowo. Co dalej?

  1. Usługa “B” wykonuje swoją część pracy i zwraca sterowanie do “A”.
  2. Usługa “A” kończy pracę i zwraca sterowanie do klienta.
  3. Klient inicjuje proces zatwierdzania transakcji.

Transakcja przed zatwierdzeniem

Proces zatwierdzania składa się zwykle z dwóch faz (2-phase commit). Jest on ukoronowaniem trwającej nierzadko kilka lub nawet kilkanaście sekund interakcji mogącej dotyczyć komputerów na wielu kontynentach. Bardzo wiele rzeczy morze pójść “nie tak”. Połączenia mogą zostać zerwane, pakiety zgubione itp.

Każdy, praktycznie, problem powoduje zerwanie misternie utkanej sieci powiązań pomiędzy usługami i koordynatorami transakcji, skutkiem czego cała transakcja zostaje wycofana.

Epilog

Przekonałem Was, że transakcje rozproszone to ryzykowny biznes? Mam nadzieję, że tak. W następnym odcinku postaram się opowiedzieć o technikach, które można stosować, aby ich uniknąć.

VN:F [1.8.7_1070]
Rating: 5.0/5 (1 vote cast)