Posts tagged service bus
Historia pewnej szyny – epilog
Jun 4th
Nieświadomy
Żyłem długo w nieświadomości istnienia komunikacji kolejkowej (asynchronicznej). To znaczy niby coś tam pamiętałem ze szkoły, że jest API JMS i są jego implementacje, ale nie rozważałem komunikacji asynchronicznej jako narzędzie w moim warsztacie deweloperskim. Przecież skoro są webserwisy, kto chciałby się bawić jakimiś kolejkami? Webserwisy rządzą!
Starcie z pierwszym problemem
I tak sobie żyłem szczęśliwie, aż tu pewnego dnia napotkałem problem: nowy projekt, którego celem była integracja dwóch systemów stworzonych w mojej firmie. Projekt trafił do mnie – miałem zaprojektować interakcję między systemami. Logicznie rzecz biorąc sprawa była prosta: w momencie, kiedy proces biznesowy w jednym z systemów zmiana stan na “wykonany”, do drugiego systemu zostaje wysłany komunikat o konieczności wykonania pewnej operacji biznesowej.
Prawdziwy problem pojawił się w momencie projektowania, jak wspomniana interakcja ma być wykonana fizycznie. Zmiana stanu procesu w pierwszym systemie przejawia się zapisem odpowiednich danych do relacyjnej bazy danych. Podobnie, wykonanie wspomnianej operacji w drugim systemie, także wiąże się z zapisem danych do bazy. Pierwszym rozwiązaniem, jakie przyszło nam do głowy, była transakcja rozproszona. Ponieważ kontrolujemy środowiska uruchomieniowe obu systemów, wpięcie ich w DTC nie stanowiłoby problemu. Okazało się jednak, że z transakcjami rozproszonymi związane są dużo większe problemy, niż początkowo myśleliśmy. Nie tylko są one wolne, a nawet WOLNE. Do tego (a było to w czasach przed WCF-em) są trudne do implementacji. Znalazłem w sieci jakiś “hack”, który pozwalał na dodanie transaction flow do Remoting-u, do webserwisu ASMX pewnie także by się dało. Ale z tym dużo i jakieś takie niepewne.
Wtedy zauważyliśmy, że wykonanie operacji w drugim systemie zawsze się udaje. To znaczy może się nie udać z przyczyn losowych (jak pad bazy danych), ale jeśli odpowiednio długo będziemy ponawiać próby, to mamy gwarancję, że w końcu się uda. To spostrzeżenie naprowadziło nas na następujące rozwiązanie. W pierwszym systemie wraz ze zmianą stanu procesu zapisujemy (w bazie danych, w ramach tej samej transakcji) informację o konieczności zlecenia operacji dla drugiego systemu. Do tego w pierwszym systemie istnieje autonomiczny proces, który okresowo sprawdza, czy są jakieś operacje do zlecenia dla drugiego systemu. Jeśli są, to wyciąga ze swojej bazy parametry wywołania i próbuje się skontaktować z drugim systemem. Jeśli dostanie odpowiedź pozytywną, zamyka zlecenie. W ten sposób każde zlecenie w końcu zostanie zrealizowane.
Rozwiązanie takie wymagało, aby istniał (po stronie pierwszego systemu) rejestr zleceń do wykonania. Skoro jest rejestr to także i GUI, które pozwoli administratorowi monitorować pracę procesu go obsługującego. Na wszelki wypadek dodaliśmy też możliwość ręcznego ponawiania i tym podobne bajery. Generalnie, bardzo dużo pracy i czasu kosztowało nas stworzenie tego rejestru. Po zakończeniu projektu stwierdziłem, że nigdy więcej. Obraziłem się na taki, nietransakcyjny, sposób komunikacji. Przecież musi istnieć coś lepszego, prawda?
Nowa nadzieja
Przyszedł czas na kolejny projekt. Ja, zaopatrzony w przekonanie, że asynchroniczna komunikacja jest jedynym słusznym rozwiązaniem (niezaprzeczalny wpływ poprzedniego TechEd-u i prezentacji Udi-ego), byłem gotowy na kolejne starcie. Tym razem w moim arsenale miałem nową tajną broń: SQL Server Service Broker – infrastruktura kolejkowa wbudowana w bazę danych. Cóż w niej takiego interesującego? Ano fakt, że umożliwia wykonywanie normalnych
operacji bazodanowych oraz operacji na kolejkach w ramach jednej lokalnej transakcji. Czyż to nie fantastyczne, móc zapisując nowy stan procesu biznesowego jednocześnie wysłać komunikat do drugiego systemu? W razie błędu lub awarii serwera obie operacje automatycznie zostaną wycofane. Fantastycznie! Z drugiej strony, system odbierający może odczytać komunikat i w ramach tej samej transakcji zapisać dane biznesowej, czyli wykonać operację. W razie awarii, nie tylko dane nie zostaną zapisane, ale także i komunikat wróci automagicznie do kolejki. Cudownie!
Stałem się fanatycznym zwolennikiem Service Broker. Nie przeszkadzał mi fakt, że SB nie jest wspierane przez praktycznie żadną liczącą się bibliotekę wymiany komunikatów (WCF, NServiceBus, MassTransit). Przecież mogę napisać swoją! No zacząłem. Stad wzięły się trzy notki, do których odwoływałem się we wstępie do tej notki. Co się okazało?
Po fazie prototypowania API własnej szyny komunikatów (do której to zatrudniłem najtęższe mózgi czytelników Zine-a oraz pracowników mojej firmy) przyszedł czas na implementację. Niestety, na to już nie starczyło sił. System, który przyszło mi tworzyć wykorzystywał wczesną, prototypową wersję biblioteki komunikacyjnej dla SB.
Upadek
Nie miałem czasu na implementację mojej szyny. Ani w pracy, ani w ramach “hobby”. Skala projektu przerosła mnie jeszcze zanim napisałem pierwszą linijką kodu (na szczęście). Ogromną ilość pracy koncepcyjnej (która zapewne przełożyłaby się na podobno ilość pracy implementacyjnej) włożyłem w opracowanie sposobu na łączenie w jednej transakcji operacji na szynie z operacjami bazodanowymi. Prototypowa biblioteka, z której wciąż nasz system korzystał nie obsługiwała takich podstawowych aspektów asynchronicznej komunikacji, jak obsługa poison messages. Domyślnym zachowaniem SB w wypadku tego typu komunikatów jest automatyczne zablokowanie kolejki po 5 nieudanych próbach odczytu. Co ciekawsze, nie da się tego mechanizmu wyłączyć! Pisanie własnej obsługi pewnie trochę by zajęło. Znalazłem się wiec w sytuacji, kiedy miałem piękną architekturę z wymianą komunikatów, ale nie dysponowałem technologią, aby tę architekturę wprowadzić w życie.
Oświecenie
I wtedy postanowiłem raz jeszcze zwrócić się o pomoc do Udi-ego. To znaczy oczywiście nie osobiście! Przetrawiłem po raz n-ty kilka postów na jego blogu. Zrozumiałem, że same podstawy mojego podejścia do tematu są błędne. Sama idea (zrodzona z trudności wynikłych przy realizacji pierwszego projektu integracyjnego), aby łączyć w jednej transakcji operacje na bazie danych i na kolejkach jest z definicji zła. A nawet ZŁA.
Rozwiązanie
Rozwiązanie problemu transakcji wymaga dwóch zmian. Obie są zmianami biznesowymi (koncepcyjnymi), a nie technicznymi. Pierwsza z nich dotyczy wysyłania komunikatu w momencie zmiany stanu procesu. Zamiast tego wprowadzanym w procesie dodatkowy stan: “gotowy do wysłania” komunikatu. Dawne przejście do stanu “wysłano”, teraz ma prowadzić do stanu “gotowy”. Obsługa procesu w tym stanie polega na podjęciu próby wysłania komunikatu. Jeśli próba ta się powiedzie, proces zmienia stan na “wysłano”. W ten sposób w końcu uda się wysłać komunikat. Efekt uboczny tego rozwiązania jest taki, że możliwe jest, że komunikat zostanie wysłany więcej niż raz. Dzieje się tak wtedy, gdy po udanym wysłaniu komunikatu nastąpi awaria bazy danych, w której przechowywany jest stan procesu. System natrafi na błąd. Po powrocie do normalnego działania proces wciąż będzie w stanie “gotowy” i system spróbuje wysłać komunikat ponownie.
Co zrobić z tymi zduplikowanymi komunikatami? Na ratunek przychodzi nam kolejna koncepcja biznesowa: idempotentność. Jest to właściwość komunikatu oznaczająca, że wysłanie tej samej wiadomości więcej niż raz nie spowoduje żadnych dodatkowych konsekwencji. Czyli w praktyce: że system odbierający zignoruje zduplikowane wiadomości.
Epilog
Jestem właśnie w trakcie eksperymentalnego przełączania mojego najnowszego systemu (integrującego się z 3 innymi systemami) z rozwiązania SB na tandem NServiceBus + MSMQ. Komunikaty, które są wymieniane między systemami już wcześniej (dobra decyzja architektoniczna:)) zaopatrzone były w unikalne identyfikatora pozwalające na odrzucanie duplikatów. Teraz tylko dodaje kod, który wykonuje rzeczywiste sprawdzenie zduplikowania. Do tej pory (ze względu na transakcyjność SB i zapisu do bazy) nie było takiej konieczności. Do tej pory spędziłem nad tym eksperymentem jakieś 8 godzin. Dziś udało mi się nawet przesłać pierwszy komunikat! Mam nadzieję, że za jakieś dwa tygodnie (pracuje nad tym w ramach firmowego Programu Innowacji, jeden dzień w tygodniu) będę mógł ogłosić pełny sukces.
Trzymajcie kciuki!
Referencje
Udi Dahan, Spectacular Scalability with Smart Service Contracts
Udi Dahan, Build Scalable Systems That Handle Failure Without Losing Data
MSDN, Optimizing Performance in a Microsoft Message Queue Server Environment
Moja własna szyna
Feb 8th
Zgodnie z obietnicą złożoną w poprzedniej notce, rozpocząłem pracę na moją własną szyną komunikatów. Dziś chciałbym podzielić się z Wami moją motywacją (w nawiązaniu do “not invented here”) oraz opowiedzieć nieco o celach, jakie sobie postawiłem.
Motywacja
Są dwie. Pierwsza to szkolenie z asynchronicznego podejścia do SOA, które to szkolenie mam zamiar przeprowadzić w mojej firmie. Inspiracją dla mnie jest nie kto inny, jak Udi Dahan i jego prezentacja, którą miałem przyjemność oglądać na zeszłorocznym TechEd-zie. Niestety jestem przekonany, że nie udałoby mi się zmusić ludzi do poświęcenia półtorej godziny na oglądanie filmu z konferencji (nota bene po angielsku oczywiście). Dlatego postanowiłem
- wyciągnąć z sesji Udiego najważniejsze fakty i przedstawić je w skondensowanej formie menedżerom
- zorganizować warsztaty dla pracowników technicznych i pokazać im naocznie to, o czym Udi tylko opowiadał
Czyli w skrócie – dla każdego coś miłego. Do tej drugiej części potrzebne mi było narzędzie – szyna komunikatów. Pomyślałem o nServiceBus, ale po przejrzeniu kodu natychmiast zrezygnowałem. Kilkadziesiąt projektów, z których duża część istnieje tylko po to, aby zdefiniować API, aby jakiś inny projekt mógł dookreślić to API, a jeszcze inny je zaimplementować. Takie coś na pewno odciągnęłoby moją audiencję od sedna sprawy i warsztaty sprowadziłyby się do tego, jak poprawnie podłączyć wszystkie wtyczki nServiceBus.
Potrzebowałem czegoś prostego (nawiązując do komentarza Arka: czegoś o 8 klasach, a nie 200).
Drugi powód to chęć poznania bliżej technologii SQLServer Service Broker. Jest to chyba jeden z mniej znanych (i wykorzystywanych) ficzerów SQLServera. Dla tych, którzy nie kojarzą, co to takiego, w telegraficznym skrócie – Service Broker to infrastruktura kolejkowa (jak MSMQ), ale wbudowana w silnik bazy danych. Ma to jedną ogromną zaletę, a mianowicie pozwala na transakcyjne korzystanie z kolejek i z silnika bazy danych wykorzystując jedynie transakcje lokalne SQLServer (a nie transakcje rozproszone).
Cele
Skoro już wiecie, jakie są powody mojej brawurowej decyzji, poznajcie teraz moje cele. Jest właściwie jeden: zrobić to, co mam zrobić, ale inaczej. Nie widzę sensu w kopiowaniu rozwiązań Udiego lub Ayende (mimo, że uważam oba za bardzo dobre). Robiąc coś zupełnie inaczej być może dokonam czegoś ciekawego. Zasługującego przynajmniej na kilka notek na Zine-ie;)
W wypadku szyny komunikatów “zrobić coś inaczej” oznacza:
- Zdefiniowanie abstrakcyjnego API dla mechanizmu wymiany komunikatów pozwalającego na wykorzystanie różnych implementacji zamiast tworzenia frameworku z tysiącem wtyczek do zaimplementowania
- Dobra integracja z innymi abstrakcyjnymi API
- dostępu do danych
- logowania
- konfiguracji
- Maksymalne wykorzystanie infrastruktury dzięki możliwości implementowania całości szyny (API szyny) z użyciem konkretnego mechanizmu kolejkowania
- wykorzystanie transakcji lokalnych
- MARS
- Udostępnienie minimalnego bezpiecznego interfejsu zamiast interfejsu ogólnego, w przypadku którego użytkownik musi się zastanawiać nad konsekwencjami (błędy?) wykonania każdej operacji.
- obsługa naraz tylko pojedynczego komunikatu
- bezwzględne zapewnienie transakcyjności
Na koniec pozostaje mi jedna do Was prośba: wish me luck!



