Monthly Archives: Październik 2014

Azure Service Bus brokered messaging – komunikacja w systemach rozproszonych

2014-10-25

Dzisiejszym wpisem chciałbym rozpocząć cykl kilku tematów poświęconych omówieniu Azure Service Bus – jednego ze składników Microsoft Azure umożliwiającego wymianę informacji w systemach rozproszonych. Azure Service Bus pozwala na komunikację w dwóch trybach: relayed messaging oraz brokered messaging. Tryb relayed polega na synchronicznej wymianie wiadomości, podczas której nadawca i odbiorca muszą być dostępni on-line (analogicznie do WebService). Z kolei tryb brokered pozwala na komunikację asynchroniczną gdzie nadawca i odbiorca pracują niezależnie, a wiadomości są kolejkowane na serwerach Azure. Znacznie ciekawszy jest drugi ze wspomnianych trybów i właśnie jemu poświęcę ten cykl. Dziś przedstawię ogólną koncepcję komunikacji w trybie brokered messaging, omówię poszczególne elementy Azure Service Bus takie jak Queue, Topic, Subscription, Brokered Message oraz pokażę kroki niezbędne do rozpoczęcia budowy konkretnego rozwiązania. W następnych wpisach znajdzie się już znacznie więcej kodu prezentującego praktyczne wykorzystanie omawianych elementów.

Jak wspomniałem wcześniej, tryb brokered messaging w Azure Service Bus pozwala na asynchroniczną wymianę informacji pomiędzy nadawcą i odbiorcą. Nadawca wysyłając wiadomość nie wie kiedy oraz przez kogo zostanie ona odczytana, jego jedynym zadaniem jest jej utworzenie i wysłanie do usługi Service Bus. Z kolei odbiorca w dowolnym momencie łączy się z usługą w celu pobrania (w kolejności FIFO) i przetworzenia oczekujących wiadomości. Największą zaletą tego rozwiązania jest fakt, że za cały mechanizm kolejkowania i zwracania wiadomości odpowiada Azure, naszą rolą jest jedynie oprogramowanie wysyłania i odbierania wiadomości. Budując rozwiązanie komunikacyjne oparte o Service Bus w trybie brokered messaging mamy do wyboru dwa podejścia: kolejki (Queues) oraz topiki z subskrypcjami (Topics, Subscriptions).

 

Queue

Poniższy schemat prezentuje zasadę komunikacji przy użyciu kolejek:

sb_queues

Źródło: MSDN

Najważniejszym elementem jest tutaj kolejka (Queue), która pełni rolę pojemnika na wiadomości. Nadrzędnym elementem jest Namespace, który może zawierać wiele kolejek (ich nazwy muszą być unikalne). Nadawcą może być dowolna aplikacja lub usługa, której rolą jest nawiązanie połączenia z usługą Azure Service Bus, utworzenie wiadomości oraz wysłanie jej do konkretnej kolejki. Odbiorcą również może być dowolny rodzaj aplikacji, a co najważniejsze z jednej kolejki może korzystać wielu odbiorców równocześnie. Jak to działa? Wiadomości zwracane są z kolejki w kolejności ich dodawania (FIFO). Po każdorazowym zgłoszeniu się odbiorcy po wiadomość usługa Azure Service Bus gwarantuje zwrócenie danej wiadomości tylko raz. W wyniku każdego kolejnego zgłoszenia otrzymamy następną wiadomość niezależnie czy będzie to ten sam odbiorca czy inny. Jak łatwo zauważyć, daje to bardzo duże możliwości skalowania naszego rozwiązania bez żadnych nakładów pracy. Jeżeli widzimy, że nasza aplikacja odbierająca nie nadąża z przetwarzaniem wiadomości, wystarczy uruchomić jej kopię na innej maszynie i automatycznie otrzymujemy wzrost wydajności dzięki zrównolegleniu operacji.

 

Topic, Subscription

Drugim podejściem jest wykorzystanie topików i subskrypcji. Oto jak wygląda schemat komunikacji w tym przypadku:

sb_topics

Źródło: MSDN

Od strony nadawcy praktycznie nic się nie zmienia w stosunku do kolejek. Tworzenie wiadomości odbywa się w identyczny sposób, a jedyną różnicą jest wysyłanie ich do topiku zamiast do kolejki. Z punktu widzenia nadawcy topik funkcjonalnie odpowiada kolejce. Większe różnice są po stronie odbiorcy. W celu odczytu wiadomości odbiorca nie odwołuje się bezpośrednio do topiku tylko korzysta z wybranej subskrypcji, która z jego punktu widzenia posiada funkcjonalność kolejki. To jakie wiadomości z topiku trafią do danej subskrypcji określane jest poprzez filtry operujące na atrybutach wiadomości. Podstawową różnicą w stosunku do kolejek jest to, iż w przypadku topiku tą samą wiadomość może otrzymać kilku odbiorców. Jeżeli dana wiadomość przesłana do topiku spełni warunki filtrów więcej niż jednej subskrypcji to jej kopia zostanie przekazana do każdej z nich. Warto dodać, że filtry subskrypcji sprawdzane są tylko w momencie pojawienia się wiadomości w topiku. Jeżeli utworzymy nową subskrypcję to mogą do niej trafić jedynie wiadomości wysłane od tego momentu, wszystkie wcześniejsze mimo spełnionych warunków filtra nie zostaną do niej przekazane. Możemy również utworzyć subskrypcję bez filtra, wówczas trafią do niej wszystkie wiadomości przesłane do topiku. Sam mechanizm odczytu wiadomości z konkretnej subskrypcji nie różni się niczym od kolejki. Z jednej subskrypcji może równocześnie korzystać wielu odbiorców, gdzie podobnie jak w przypadku kolejki gwarantowane jest dostarczenie danej wiadomości tylko do jednego z nich.

 

Brokered Message

Niezależnie od wybranego schematu komunikacji (kolejki lub topiki z subskrypcjami) wiadomość przesyłana pomiędzy nadawcą i odbiorcą ma następującą strukturę:

sb_message

Składa się ona z nagłówka (header) oraz treści (body). Nagłówek posiada szereg zdefiniowanych atrybutów, w których możemy umieścić dodatkowe informacje opisujące naszą wiadomość (m.in. MessageId, CorrelationId, SessionId, Label, Properties). Dzięki atrybutowi Properties (typu IDictionary<string, object>) do przesyłanej wiadomości możemy dołączyć listę dowolnych parametrów. Wspomniane wcześniej filtry subskrypcji decydujące o tym, które wiadomości z topiku zostaną przekazane do danej subskrypcji mogą korzystać z właściwości CorrelationId lub poszczególnych elementów właściwości Properties. Główną częścią wiadomości jest jej treść – czyli sekcja body, w której możemy umieścić konkretny obiekt. W tym miejscu trzeba zaznaczyć, iż maksymalny rozmiar przesyłanej wiadomości to 256kB, z czego 64kB przypada na nagłówek a 192kB na treść. Może się wydawać, że dla wielu scenariuszy nie jest to rozmiar wystarczający, istnieje jednak sposób na poradzenie sobie z tym ograniczeniem, co pokażę w kolejnych wpisach.

 

Konfiguracja Azure Service Bus

Przed rozpoczęciem tworzenia konkretnego rozwiązania musimy skonfigurować usługę Azure Service Bus. Logujemy się do portalu Microsoft Azure, wybieramy sekcję Service Bus i klikamy Create w celu utworzenia przestrzeni nazw (Namespace), w której będą znajdować się nasze kolejki, topiki i subskrypcje.

azure_sb

Podajemy nazwę dla tworzonego obiektu Namespace, wybieramy region i wskazujemy typ MESSAGING. Po zakończeniu operacji musimy pobrać Connection String dla utworzonej przestrzeni nazw, za pomocą którego nasze aplikacje będą łączyły się z usługą. Klikamy Connection Information i kopiujemy widoczny Connection String:

sb_namespace_cs

Składa się on z trzech elementów: adresu Namespace (Endpoint), nazwy polisy zawierającej klucz dostępu (SharedAccessKeyName) oraz samego klucza (SharedAccessKey):

Endpoint=sb://tempnamespace.servicebus.windows.net/;
SharedAccessKeyName=RootManageSharedAccessKey;
SharedAccessKey=zFUqkwWoDrem9O8UkdG0pq0sheJze0U/P93f42Aykdc=

Polisami oraz kluczami możemy zarządzać w opcji Configure naszej przestrzeni nazw:

sb_ns_configure

Jak widać domyślnie tworzona jest polisa RootManageSharedAccessKey z uprawnieniami zarządzania przestrzenią (Manage) oraz wysyłania i odbierania wiadomości (Send, Listen). Jeżeli dla danej aplikacji chcemy ograniczyć te prawa (np. tylko wysyłanie wiadomości) możemy utworzyć nową polisę i pobrać odpowiedni Connection String. Każda polisa posiada dwa klucze dostępowe (primary i secondary) działające równocześnie. Pozwala to na okresową zmianę kluczy bez konieczności natychmiastowej aktualizacji wszystkich klientów.

Przestrzeń nazw posiada także opcje Queues oraz Topics pozwalające na tworzenie kolejek, topików i subskrypcji, ale ja w kolejnych wpisach pokażę jak robić to z poziomu kodu.

 

Konfiguracja projektu Visual Studio

Ostatnim elementem jaki pozostał jest podłączenie odpowiednich referencji do naszego projektu w celu skorzystania z API usługi Azure Service Bus. W tym celu klikamy prawym klawiszem na References, wybieramy Manage NuGet Packages, wyszukujemy paczkę Microsoft Azure Service Bus i instalujemy. W efekcie do naszego projektu dodane zostaną referencje do bibliotek Microsoft.ServiceBus oraz Microsoft.WindowsAzure.Configuration.

W kolejnych wpisach znajdzie się zdecydowanie więcej kodu, a rozpocznę od pokazania jak oprogramować wysyłanie i odbieranie wiadomości do/z kolejki.

Reklamy

Zmiana Capabilities dla istniejącego agenta

2014-10-03

Podczas budowy agenta typu Extensible Connection 2.0 w naszej klasie implementujemy między innymi interfejs IMAExtensible2GetCapabilities. Wymaga to dodania do niej publicznej właściwości Capabilities zwracającej obiekt typu MACapabilities. Obiekt ten posiada szereg ustawień konfigurujących pracę naszego agenta. Oto przykładowa implementacja tej właściwości:

public MACapabilities Capabilities
{
  get
  {
    return new MACapabilities()
      {
        ConcurrentOperation = false,
        DeltaImport = false,
        DistinguishedNameStyle = MADistinguishedNameStyle.Generic,
        DeleteAddAsReplace = false,
        ExportType = MAExportType.AttributeReplace,
        NoReferenceValuesInFirstExport = false,
        SupportExport = true
      };
  }
}

Podczas dodawania agenta do Synchronization Service jednym z kroków jest Capabilities:

MaCapabilities

Właśnie w tym momencie następuje odwołanie do właściwości Capabilities w klasie naszego agenta i odczyt znajdujących się tam ustawień. Niestety tu pojawia się pewien problem. Odczyt ten następuje tylko podczas dodawania nowego agenta i pobrane ustawienia są dla niego zapamiętywane w aktualnej postaci. Jeżeli później wejdziemy w edycję istniejącego agenta to kroku Capabilities już nie zobaczymy. Co więcej, nawet jak wyeksportujemy agenta do pliku i następnie utworzymy nowego poprzez import, to ustawienia Capabilities nie zostaną wczytane z biblioteki a jedynie odtworzone z pliku eksportu. Co to oznacza? Jeżeli zmodyfikujemy ustawienia MACapabilities w naszej klasie to pomimo podmiany biblioteki agent cały czas będzie działał zgodnie z ustawieniami zapamiętanymi podczas jego tworzenia. Nie ma możliwości odświeżenia Capabilities dla istniejącego agenta więc nie jesteśmy w stanie zmienić dla niego tych ustawień (Refresh interfaces nie zadziała). Możemy oczywiście usunąć aktualnego agenta i utworzyć nowego, ale takie rozwiązanie przy rozbudowanym agencie (posiadającym kilka typów obiektów, do tego filtry oraz kilkanaście przepływów) oznacza żmudną i podatną na błędy pracę. Jaki jest więc sposób na wprowadzenie zmian w Capabilities dla istniejącego agenta? Ja stosuję następującą metodę:

  1. Modyfikujemy właściwość Capabilities w klasie agenta, kompilujemy i podmieniamy bibliotekę
  2. Dodajemy do Synchronization Service nowego agenta korzystającego z naszej biblioteki (pod dowolną nazwą, bez konfigurowania)
  3. Eksportujemy oryginalnego i nowego agenta do plików xml
  4. Usuwamy obydwu agentów z Synchronization Service
  5. Podmieniamy wartości elementów <capabilities-mask> i <capability-bits> w pliku xml oryginalnego agenta na wartości z pliku nowego agenta
  6. Importujemy oryginalnego agenta

W elementach <capabilities-mask> i <capability-bits> znajdują się ustawienia Capabilities zapisane podczas tworzenia agenta. Po zmianie ich na nowe wartości i imporcie nasz dotychczasowy agent będzie działał zgodnie z oczekiwaniami.