pl  |  en

Masz wiadomość? MOM

Pierwszy kontakt z systemami Message-Oriented Middleware może wywołać wrażenie “ale po co to wszystko?”. Do skomunikowania ze sobą dwóch procesów wystarczy zwykłe połączenie TCP. Co prawda sami musimy podzielić strumień danych na komunikaty, ale proste buforowanie załatwi sprawę. Jeżeli akurat serwer nie działa a nie możemy sobie po
zwolić na utratę komunikatu, przechowamy go na kliencie i spróbujemy później. Kiedy system rozrośnie się i jeden serwer przestanie wystarczać — dopiszemy równoważenie obciążenia. I najpóźniej w tym momencie powinna pojawić się myśl, że ktoś już musiał przez to wszystko przechodzić i jest dostępny jakiś gotowiec.

Owszem jest — to właśnie MOM. Oprogramowanie tego typu pozwala szybko, niezawodnie i elastycznie przekazać dane z punktu A do punktu B. Lub do C, jeżeli B jest zajęty. Ewentualnie do B, C i D jednocześnie, a jeśli dane są związane z tematem X, to również do E. Jeżeli E uległ akurat awarii, nie szkodzi — dostanie komunikat kiedy znów zacznie działać.

Modele komunikacji

Pojęcia takie jak klient czy serwer nie pasują zbyt dobrze do komunikacji z użyciem MOM. Dużo lepiej oddają ideę określenia producent (nadawca), konsument (odbiorca) i broker (pośrednik przekazujący komunikaty tam, gdzie powinny trafić). Na poziomie TCP, zarówno producenci, jak i konsumenci są klientami a broker — serwerem. Nic nie stoi na przeszkodzie, aby producent był jednocześnie konsumentem — może nawet wysyłać komunikaty sam do siebie

Wiadomości w obrębie systemu mogą być przesyłane na wiele różnych sposobów. Na przykład implementując chat dla wielu użytkowników warto użyć modelu publish-subscribe, w którym klient MOM deklaruje swoje zainteresowanie określonym strumieniem informacji (np. konkretnym pokojem chatu). Wszyscy wypowiadający się użytkownicy wysyłają swoje teksty w tym właśnie strumieniu, nie musząc znać aktualnej listy uczestników dyskusji. Z kolei mechanizm RPC skorzysta zapewne z modelu request-response, w którym żądanie zawiera “adres nadawcy”, pod który należy odesłać odpowiedź. Nic nie stoi na przeszkodzie, aby wiele serwerów oczekiwało na żądania na tym samym “kanale” — będą je one otrzymywać kolejno. Dowolny serwer może też ogłosić, że jest zajęty i chwilowo nie przyjmuje nowych żądań. W ten sposób łatwo możemy rozłożyć przetwarzanie żądań między kilka serwerów. Przyda się, jeżeli żądanie jest skomplikowane i wymaga dużych nakładów pracy — np. polega na przekodowaniu filmu.

Używane protokoły

Message-Oriented Middleware ma swoje korzenie w korporacjach, dlatego wiele protokołów nigdy nie zostało opublikowanych i pozostaje głęboko ukryte. Niemniej jednak wykształciło się kilka otwartych standardów, z których najpopularniejsze to AMQP (bogaty w możliwości i elastyczny w konfiguracji), STOMP (bardzo prosty tekstowy protokół), XMPP (oparty o XML i wykorzystywany m.in. przez protokół Jabber) oraz ZeroMQ (ekstremalnie szybki middleware do zadań specjalnych).

Routing

Wewnętrzny model brokera, który określa sposób routing komunikatów przez system, różni się między protokołami — poniższy opis dotyczy AMQP w najpopularniejszej obecnie wersji 0-8. Podstawowe elementy brokera to kolejka, z której konsumenci odbierają przeznaczone dla nich wiadomości, exchange, czyli “zwrotnica”, decydująca o tym, do których kolejek trafią poszczególne wiadomości oraz dowiązania (ang. bindings) łączące kolejki z exchange’ami.

mom-amqpBroker AMQP

Każda wiadomość oprócz treści posiada zestaw atrybutów, które decydują o jej losach w systemie: nazwę exchange’a, w którym jest publikowana, routing key, czyli (w pewnym uproszczeniu) temat wiadomości oraz zestaw nagłówków o dowolnej treści, bez określonego znaczenia w protokole AMQP. Dowiązania mogą określać swój własny routing key, który w połączeniu z typem exchange’a określa, jakie wiadomości trafią do której kolejki.

AMQP przewiduje następujące typy exchange:

      direct — routing key wiadomości musi być identyczny z routing key dowiązania
      fanout — wszystkie wiadomości trafiają do wszystkich kolejek, routing key dowiązania jest ignorowany
      topic — routing key dowiązania to prosty wzorzec, do którego dopasowywany jest routing key wiadomości, np. wiadomość z kluczem “stock.us.nasdaq.rhat” zostanie wysłana przez dowiązania z kluczami “stock.us.#” i “stock.us.*.rhat” (tematyka giełdowa często pojawia się we wszelkich opisach AMQP)
      headers — wiadomości są filtrowane na podstawie nagłówków; popularny broker RabbitMQ nie wspiera tego typu
      własne typy o nazwach zaczynających się od “x-*” — zależą tylko od inwencji i zdolności programistycznych użytkowników; RabbitMQ posiada wtyczki pozwalające na pisanie własnych typów w Erlangu, Pythonie i JavaScripcie.

Po przejściu przez exchange wiadomość trafia do określonych kolejek. Do każdej kolejki może być podłączonych wielu konsumentów. W tej sytuacji wiadomości będą rozdzielane kolejno między nimi, co daje prosty sposób na rozłożenie obciążenia. Jeżeli kilka konsumentów powinno otrzymać tę samą wiadomość, należy użyć osobnych kolejek.

Tu też MOM

Ponieważ oprogramowanie MOM rozwiązuje wiele typowych problemów w komunikacji między systemami, znajduje ono różne ciekawe zastosowania. Projekt o nazwie rabbitbal wykorzystuje AMQP do optymalnego rozłożenia ruchu HTTP między serwerami aplikacji. Mechanizm QoS (Quality of Service) protokołu AMQP zapewnia, że każdy wątek serwera aplikacji dostaje dokładnie tyle żądań, ile jest w stanie obsłużyć — nadmiarowe żądania pozostają w kolejce na brokerze, skąd mogą je pobrać inne, nieobciążone wątki. Dzięki temu load balancer nie musi wybierać konkretnego serwera na podstawie arbitralnych kryteriów a cały klaster działa z optymalną wydajnością.

Innym interesującym zastosowaniem AMQP jest nanite, czyli (wg autorów) samoorganizująca się sieć serwerów w Rubym. Serwery (aktorzy) nanite udostępniają metody stworzone przez użytkownika jak w tradycyjnym RPC, jednak nie odbierają one połączeń od klientów bezpośrednio, a przez warstwę procesów pomocniczych (mappers). Rozdzielają one żądania między aktorami na podstawie obciążenia jak typowy load balancer, ale informację o obciążeniu otrzymują od aktorów i może ona być dowolnie określana przez użytkownika systemu (domyślnym kryterium jest load average). Dodatkową przewagą nanite jest fakt, że aktorzy mogą pojawiać się i znikać bez rekonfiguracji systemu, a mappery mogą mieć bardzo ograniczoną informację na temat aktorów. Dzięki temu klaster jest bardziej elastyczny a powiększenie go o kolejne węzły jest bardzo proste — wystarczy uruchomienie na nich aktorów.

O ile rabbitbal i nanite nie mogłyby istnieć bez AMQP, o tyle serwer WWW mongrel2 bez kolejek komunikatów dalej “byłby sobą”. Użycie ZeroMQ przyniosło ze sobą obsługę “lepszych połączeń TCP”, zapewniających m.in. podział na komunikaty i większą odporność na awarie. Oprócz tego zastąpienie użycie modelu przekazywania komunikatów pozwala na rzeczy, które w typowych serwerach WWW są niemożliwe, np. wysłanie jednego żądania do wielu serwerów backendowych lub odesłanie odpowiedzi do wielu klientów. Słowami autora: “Nie jestem pewien, co można z tym zrobić, ale na pewno coś bardzo fajnego”.

Autor: Grzegorz Nosek