18/03/2009

Velocity (cz. 2)

Home

Oj długo zbierałem się do napisania tego posta chociaż po drodze pojawiło się kilka innych. Nadeszła jednak pora aby pojawił się, zresztą obiecany, post dotyczący terminologii związanej z Velocity, procesu konfigurowania i instalowania (pierwszy wpis dotyczący tej technologii znajdziemy tutaj). Dla tych, którzy zapomnieli lub nie czytali, Velocity to rozproszony cache - rozproszona pamięć podręczna. Celem Velocity, jest zwiększyć niezawodność i wydajność działania aplikacji poprzez replikację, rozproszenie oraz zapewnienie szybkiego dostępu do danych. Produkt rozwijany jest przez Microsoft i finalna jego wersja nie ujrzała jeszcze światła dziennego. Obecnie dostępna wersja to tzw. CTP2 (ang. Community Technology Preview). W poście tym nie będę opisywał wszystkiego, wgłębiał się w szczegóły bo od tego jest dokumentacja, zresztą bardzo dobra. Chciałbym napisać tylko o najważniejszych moim zdaniem elementach i skłonić do przyjrzenia się Velocity bliżej.

Przy omawianiu architektury Velocity posłużę się dwoma obrazkami, które zaczerpnąłem z dokumentu stanowiącego wprowadzenie do technologii. Znajdziemy go tutaj.



Velocity uruchamiane jest w formie kolekcji usług systemowych. W szczególności może to być pojedyncza usługa zainstalowana na jednej maszynie. Usługę taką nazywamy usługą gospodarza cache'a (ang. cache host service), a maszynę na której jest zainstalowana serwerem cache'a (ang. cache server). Zbiór takich usług nazywamy natomiast klastrem (ang. cache cluster). Aby wszystkie składowe klastra mogły się z sobą dogadać muszą mieć dostęp do wspólnej konfiguracji. Może ona zostać zapisana w bazie danych lub w pliku udostępnionym w współdzielonym katalogu. Co ważne wiele czynności o naturze administracyjnej czy też konfiguracyjnej można wykonać bez dotykania się plików konfiguracyjnych przy pomocy konsoli PowerShell. Przy instalacji Velocity rejestruje swoje własne komendy.

Monitorowaniem i zarządzaniem całego klastra zajmują się wyznaczone jego węzły tzw. główni gospodarze (ang. lead host). To ich zadaniem jest zapewnić aby klaster był cały czas dostępny, monitorowanie dostępności poszczególnych węzłów klastra czy też zapewnienie żeby dane były rozłożone równomiernie pomiędzy jego węzły. Ilość głównych gospodarzy zależy od wielkości klastra.



W obrębie klastra możemy utworzyć jeden lub więcej tzw. nazwanych cache'y (ang. named cache) lub ewentualnie skorzystać z domyślnego. Nazwany cache'y to po prostu wydzielony fragment rozproszonej pamięci, w którym można umieszczać dane. Często zamiast używać terminu nazwany cache mówi sie po prostu cache.

Można zapytać czemu warto korzystać z tej opcji skoro udostępniono cache domyślny. Otóż każdy cache nazwany jest konfigurowany oddzielnie. Konfigurowanie dotyczy natomiast tak ważnych rzecz jak to jak długo dane będą przechowywane w cache'u czy też typu cache. Dla przypomnienia, typ replikujący po umieszczeniu w nim porcji danych tworzy jej kopię i umieszcza na węzłach klastra (dobre dla danych wolno-zmiennych). Typ partycjonujący rozmieszcza dane równomiernie pomiędzy poszczególne węzły klastra (dobry dla danych szybko-zmiennych). Typ replikujący się nie jest dostępny w CTP2.

W cache'u możemy umieścić każdy serializowalny obiekt (dalej będę stosował wymiennie pojęcia obiekt/dane). Umieszczając obiekt w cache podajemy klucz, którego użyjemy następnie do jego odczytania. Istnieje również możliwość grupowania obiektów w obrębie cache'a. W nomenklaturze Velocity taką grupę obiektów nazywamy regionem (ang. region). Umożliwiają one wspólne zarządzanie większą grupą obiektów. Użycie regionów ma jednak tą wadę, że region jest przypisany do jednego gospodarza. W przypadku pojedynczych obiektów cache może je natomiast równomiernie rozłożyć pomiędzy węzły klastra. Jako podsumowanie można przyrównać nazwany cache do bazy danych. Regiony natomiast do tabel w tejże bazie.

Ale co w tym takiego specjalnego? Po pierwsze Velocity zapewnia, że jeśli umieścimy w cache'u jakieś dane to będą one dostępne z każdej maszyny, na której jest on widoczny. Nawet jeśli dane będą znajdować się fizycznie gdzie indziej, w sposób przezroczysty dla klienta zostaną odnalezione i przesłane przez sieć. Ale to nie wszystko. Velocity zapewnia również całkowicie przezroczystą dla klienta replikację danych. Możemy określić w ilu kopiach ma być przechowywana każda porcja umieszczonych w cache'u danych. Spośród wszystkich replik wyróżnia się replikę główną, na której realizowane są wszystkie aktualizacje i odczyty oraz repliki poboczne, o charakterze zapasowym.

Synchronizacja tych kopii odbywa się w sposób automatyczny i niewidoczny dla klienta! Po drugie w przypadku awarii, któregoś z węzłów z klastra i utracie dostępu do kopii głównych ich rolę przejmują kopie poboczne. Oczywiście nie można przesadzić z ilością kopii. Ich synchronizacja przecież kosztuje.

Instalacja Velocity nie jest kłopotliwa i zajmuje kilkanaście minut. W gruncie rzeczy sprowadza się do uruchomienia instalatora. W obecnej wersji automatyczna instalacja umożliwia zainstalowanie tylko jednej instancji usługi na jednej maszynie. Jeśli chodzi o konfigurowanie to wspomnę jeszcze tylko, że: konfiguracja usługi znajduje się w pliku .config w katalogu instalacyjnym Velocity na danej maszynie. Konfigurację klienta Velocity umieszczamy natomiast w pliku .config tyle, że danej aplikacji webowej lub okienkowej. Schemat konfiguracji nie jest trudny i jest dokładnie opisany w dokumentacji.

16/03/2009

TechFest 2009

Home

Możliwość uczestnictwa w konferencji TechFest 2009 bardzo mnie ucieszyła. Wiele sesji, zagraniczni uczestnicy, laboratoria no i miejsce – Hotel Gołębiewski z Wiśle. Niestety w obecnej chwili jestem daleki od zadowolenia. Czemu? Płatność zrealizowałem już ponad miesiąc temu, a od tego czasu nie dostałem potwierdzenia. Zestawu powitalnego ani widu ani słychu choć do konferencji pozostały jeszcze tylko niecałe trzy tygodnie! Kontakt z organizatorami, jaki kontakt? Brak odpowiedzi na telefony i wiadomości e-mail (no dobrze raz udało mi się dodzwonić). Dla mnie to brak kultury i dobrego wychowania oraz zwykłe lekceważenie. Mam wrażenie, że organizatorzy imprezy jeszcze długo nie będą mogli pozbyć się swoistego smrodku na jaki udało im się zapracować. Jest jeszcze szansa, że sama konferencja przyćmi dotychczasowe niepowiedzenia. Po dotychczasowych doświadczeniach jednak szczerze w to wątpię, obym się mylił.

08/03/2009

Prymitywy synchronizacyjne - jak dobrze je znamy?

Home

Ostatnio zadałem kilku osobom następujące pytanie. Spośród wymienionych poniżej prymitywów synchronizacyjnych wybierz, te które mogą zostać użyte do synchornizacji między-procesowej:
  • Klasa Monitor
  • Klasa Mutex
  • Klasa Semaphore
  • Słowo kluczowe lock
  • Klasa AutoResetEvent
  • Klasa ManualResetEvent
Sądziłem, że pytanie należy raczej do tych z kategorii łatwiejszych. Odpowiedź na nie przysporzyła jednak niestety sporo kłopotów. Na poniższej liście zostawiłem tylko elementy stanowiące poprawną odpowiedź:
  • Klasa Mutex
  • Klasa Semaphore
  • Klasa AutoResetEvent
  • Klasa ManualResetEvent
Czym różnią się wybrane prymitywy synchronizacyjne od pozostałych? Moim zdaniem można wymienić cztery zasadnicze różnice:
  • Po pierwsze klasy te dziedziczą pośrednio po klasie MarshalByRefObject i w związku z tym instancje tych klas mogą przekraczać granice po między dziedzinami aplikacyjnymi (ang. application domain).
  • Po drugie klasy te są wywiedzione z klasy WaitHandle i w związku można ich używać w podobny sposób chociaż mają zupełnie inną semantykę. W szczególności można sobie wyobrazić scenariusz, w którym wątek oczekuje równocześnie na zwolnienie semafora, mutexa i zapalenie się sygnału/zdarzenia (należy użyć metody statycznej WaitHandle.WaitAll())
  • Po trzecie klasy te stanowią w rzeczywistości opakowanie na natywne/systemowe prymitywy synchronizacyjne udostępnione w systemie operacyjny co generalnie powoduje, że są wolniejsze.
  • Po czwarte i najważniejsze z perspektywy pytania te cztery prymitywy synchronizacyjne mogą zostać nazwane. Inaczej mówiąc, w jednym procesie możemy utworzyć semafor o nazwie "MY SEMAPHORE", a w drugim procesie uzyskać dostęp do tego samego semafora posługując się podaną nazwą. W tym celu można użyć metody statycznej Semaphore.OpenExisting() lub z odpowiedniego konstruktora.
Moim zdaniem wiedza o sposobie działania, semantyce poszczególnych prymitywów synchronizacyjnych jest niezwykle ważna dla każdego programisty bez względu z jaką technologią pracuje. Błędy związane z synchronizacją są jednymi z najtrudniejszych do wykrycia i naprawienia, a wiele z nich można by unikać gdyby wszyscy programiści znali dokładnie narzędzie swojej pracy.

05/03/2009

Xceed Chart

Home

Ostatnie kilka dni w pracy spędziłem na oprogramowywaniu komponentu do rysowania wykresów firmy Xceed w wersji dla Windows Forms (jest też wersja dla ASP.NET). Wcześniej nie korzystałem z produktów tej firmy i po tych kilku dniach kontaktu z jednym z nim muszę przyznać, że pracowało mi się bardzo przyjemnie.

API nie jest bardzo trudne, a jeśli sprawia trudności zawsze można sięgnąć do bogatej biblioteki interaktywnych przykładów. Komponent wręcz przytłacza swoimi możliwościami. Mam na myśli naprawdę przeogromną liczbę opcji konfiguracyjnych. Wybierać możemy pomiędzy wykresami dwu- i trój- wymiarowymi, liniowymi, słupkowymi, radarowymi... i prawie dowolnie dostosowywać ich wygląd do swoich potrzeb. Podczas pracy komponent zachowywał się bardzo stabilnie. Jak do tej pory nie zauważyłem również problemów z wydajnością. Nadmienię jeszcze, że pracowałem z starszą wersją komponentu (4.0). Zakładam, że wersja najnowsza (4.2) musi być co najmniej tak dobra jak stara.

Jeśli ktoś potrzebuje kontrolki do rysowania wykresów to naprawdę polecam. Największy mankament to niestety cena - kilkaset dolarów. Do użytku osobistego drogo ale dla firmy nie jest to już duży wydatek.

22/02/2009

Zakamarki Visual Studio (cz. 4)

Home

W czwartym już poście dotyczącym różnych ciekawych funkcji środowiska programistycznego Visual Studio chciałbym przyjrzeć się dokładniej oknu Call Stack, a przy okazji poruszyć kilka innych tematów.

Okno Call Stack, breakpoint'y...

Czym jest breakpoint zapewne każdy programista wie, a jeśli nie to wstyd. Z drugiej strony nie jestem już taki pewny czy każdy programista zna wszystkie możliwości breakpoint'ów w Visual Studio: filtrowanie, warunki, zliczanie, uruchamiania makr czy wstawienie breakpoint'a do metody, której kodu nie znamy (o tym pisałem tutaj). Opisanie tych rzeczy to temat na oddzielny post. W tym miejscu skupmy się na użyciu pułapek w oknie Call Stack.

Po pierwsze umożliwia ono wstawianie breakpoint'ów. W tym celu wybieramy interesującą nas ramkę (metodę), zaznaczamy ją i naciskamy przycisk F9 (albo wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Breakpoint). Breakpoint pojawi się zarówno w oknie Call Stack jak i na marginesie edytora kodu. W odwrotnym scenariuszu breakpoint postawiony w kodzie pojawi się w oknie Call Stack dopiero kiedy spowoduje zatrzymanie aplikacji. W każdej metodzie może zostać umieszczony wiele pułapek i trudno by je wszystkie umiescić w oknie Call Stack.

Przy pomocy okna Call Stack można również zarządzać pułapkami czyli określić warunek zatrzymania, filtr, wyłączyć pułapkę itd. W tym celu klikamy prawym przyciskiem wybraną pułapkę, z menu kontekstowego wybieramy Breakpoint, dalej interesującą nas komendę np.: Disable Breakpoint w celu wyłączenia pułapki lub Condition... w celu określenia warunku zatrzymania się pułapki.



Okno Call Stack umożliwia również ustawienie punktu śledzenia (ang. tracepoint). Tracepoint to w gruncie rzeczy specjalny breakpoint, który nie powoduje zatrzymania wykonania ale wypisanie komunikatu do okna Output. W celu ustawienie tracepoint z poziomu okna Call Stack wywołujemy menu kontekstowe i wybieramy Breakpoint -> Insert Tracepoint. Ustawienie punktu śledzienia z poziomu edytora kodu rozpoczyna się od ustawienia zwykłego brakpoint'a. Następnie należy wywołać menu kontekstowe, dalej wybrać When Hit... (sposób ten działa również w przypadku okna Call Stack). W oknie, które pojawi się zaznaczamy pola wyboru jak na rysunku:

Jeśli nie zaznaczymy pola wyboru Continue execution utworzymy zwykły breakpoint, który dodatkowo będzie powodował wypisanie komunikatu do okna Output. Tracepoint'y reprezentowane są przez romby:

W polu tekstowym okna When Breakpoint Is Hit możemy wpisać dowolny tekst, wyświetlić dowolną zmienną lub użyć jednego ze specjalny słów kluczowych. Ale po kolei. Aby wyświetlić zmienną należy wpisać { nazwa }. Słowa kluczowe wpisujemy natomiast po znaku $. Na przykład $PID służy do wypisania identyfikatora procesu. Nie będę omawiał wszystkich słów kluczowych ponieważ, jak widać na powyższym rysunku, ich opis można znaleźć w oknie When Breakpoint Is Hit. Zwrócę tylko jeszcze uwagę na bardzo ciekawą możliwość wyświetlenia stosu przy pomocy słowa kluczowego $CALLSTACK oraz na to, że przy tworzeniu komunikatu jesteśmy ograniczeni tylko do jednej linii.

Run To Cursor

Run To Cursor to bardzo znane polecenie, które umożliwia rozpoczęcie debugowania i wskazanie miejsca w kodzie, w którym debugger ma się zatrzymać (pod warunkiem, że nie zatrzyma się wcześniej z powodu breakpoint'a). Niewiele osób jednak wie, że polecenie to jest dostępna z poziomu okna Call Stack. Pozwala wskazać, do którego miejsca chcemy zwinąć stos. Wystarczy wybrać interesującą nas ramkę/metodę i z menu kontekstowego wybrać tę komendę.

Wartość argumentów

Inną bardzo fajną cechą okna Call Stack jest to, że podaje ono wartości przekazanych do funckji argumentów. Jeśli wartości argumentów nie są widoczne powinniśmy w onie Call Stack wywołać menu kontekstowe, a następnie zaznaczyć Show Parameter Values. No dobrze, w przypadku typów prostych jest to z pewnością przydatne ale co z typami złożonymi. Domyślnie zostanie wyświetlona po prostu nazwa typu ale można to zmienić korzystając z atrybutu DebuggerDisplayAttribute. Dla tych co nie wiedzą jest to jeden z wielu atrybutów, który pozwala na dostosowanie debuggera do naszych potrzeb (kolejny dobry temat na oddzielny post, a nawet kilka :)). Najłatwiej wyjaśnić to na przykładzie. Posłużmy się kodem jak poniżej:
public class Test
{
   private int value;

   public int Value
   {
      get { return this.value; }
   }

   public Test(int value)
   {
      this.value = value;
   }
}
Wartości typu Test zostaną przedstawione w taki o to mało interesujący sposób:

ConsoleApplication.Program.Equals(a = {ConsoleApplication.Program.Test}, b = {ConsoleApplication.Program.Test})

Wystarczy jednak zmodyfikować definicję klasy Test jak poniżej:
[DebuggerDisplay("Value = {value}")]
public class Test
{
...
}
Aby osiągnąć taki efekt:

ConsoleApplication.Program.Equals(a = Value = 1, b = Value = 1

Technika ta działa nie tylko w przypadku okna Call Stack ale również w przypadku innych okien debuggera czyli np.: Quick Watch.

Unwind To This Frame

Unwind To This Frame to jedna z komend dostępna w menu kontekstowym okna Call Stack. W większości przypadków kiedy wywołamy menu jest wyszarzona. Dostępna staje się tylko w przypadku kiedy zostanie rzucony wyjątek. W momencie rzucenia wyjątki środowisko najczęściej zatrzymuje wskazując linię w kodzie, w której wyjątek został wygenerowany (w szczególności może się nie zatrzymać jeśli wyjątek został obsłużony, a my nie mamy zaznaczonej opcji zatrzymywania na wszystkich wyjątkach). W oknie Call Stack ramka, w której został rzucony wyjątek wskazywana jest przy pomocy żółtej strzałki. Jeśli wyjątek został rzucony poza naszym kodem ostatnia ramka wskazująca nasz kod zostanie oznaczona strzałką zieloną .

Kiedy środowiska zatrzyma się już z powodu wyjątku możemy w oknie Call Stack wybrać interesującą nas ramkę, wywołać menu i wybrać komendę Unwind To This Frame. Spowoduje to zwinięcie stosu do wybranej ramki (działanie podobne do Run To Cursor) i umożliwi np.: zdiagnozowanie przyczyny wyjątku, modyfikację zmiennych lokalnych tak aby wyjątek nie został rzucony itd.

Polecenie Unwind To This Frame nie jest dostępne dla wszystkich ramek. Stos można zwinąć do każdej ramki powyżej ostatniej ramki, która wskazuje miejsce w naszym kodzie (pomiędzy żółtą i zieloną strzałką :)). Czyli jeśli wyjątek został rzucony bezpośrednio w naszym kodzie polecenie będzie dostępne tylko dla jednej ramki. Z kolei dla przypadku pokazanego niżej:

System.Xml.Serialization.XmlSerializer.Deserialize(xmlReader, encodingStyle, events)
System.Xml.Serialization.XmlSerializer.Deserialize(stream)
ConsoleTest.Program.Main(args)
...


Stos możemy zwinąć do jednej z metod Deserialize() albo do metody Main() w naszym kodzie.

Omawiając tą funkcjonalność należy koniecznie napisać o exception assistant. Całkiem możliwe, że większość użytkowników Visual Studio nigdy o nim nie słyszała. W sumie nie dziwne ponieważ jest domyślnie włączony (Tools -> Options -> Debugging -> General -> Enable the exception assistant). A do czego służy? Poniżej zamieszczam okienka wyświetlone przez środowisk w momencie rzucenia wyjątku kiedy asystent jest wyłączony (pierwszy obrazek) i kiedy jest włączony (drugi obrazek):



Mam nadzieję, że zalety asystenta są oczywiste :) ale asystent to nie tylko ładne okienko. Jeśli jest włączony w momencie rzucenia wyjątki środowisko wskaże zawsze linię w naszym kodzie nawet jeżeli prawdziwe źródło wyjątku jest gdzie indziej. Dzięki temu możemy po prostu "złapać" żółtą strzałkę, wskazującą w głównym oknie ostatnio wykonaną instrukcję, i przeciągnąć ją kilka linijek wcześniej lub kilka linijek dalej. Innymi słowy możemy ominąć wyjątek lub prześledzić jak został spowodowany. Przy włączonym asystencie polecenie Unwind To This Frame staje się trochę mniej ponieważ przydatne ale czasami możemy chcieć wyłączyć asystenta. W każdym razie dobrze zdawać sobie sprawę z jego istnienia.

Opisane techniki testowałem w środowiskach Visual Studio 2005 oraz Visual Studio 2008.