Windows Server 2008

Zapewnianie skalowalności aplikacji ASP.NET Udostępnij na: Facebook

Autor: Iqbal Khan

Opublikowano: 6 listopada 2009

Zawartość strony
Powody występowania tych problemów  Powody występowania tych problemów
Powody istnienia tych problemów  Powody istnienia tych problemów
Odpowiedź  Odpowiedź
Topologie buforowania  Topologie buforowania
Różne wybory  Różne wybory
W praktyce  W praktyce

 

Rozpowszechnienie ASP.NET, środowiska budowy aplikacji Web firmy Microsoft, nadal rośnie, przeskakując z poziomu indywidualnych projektantów do rangi standardu branżowego. Istnieje jednak pewien obszar, w którym technologia ta sprawia duże problemy: skalowanie aplikacji ASP.NET bez dodatkowych zabiegów po prostu nie jest możliwe.

W tym kontekście pojęcie „skalowalność” ma dwa znaczenia. Po pierwsze, potrzebujemy móc efektywnie obsłużyć szczytowe obciążenia, gdyż każda aplikacja ma okresy maksymalnego i niskiego obciążenia – w tym wypadku mamy na myśli liczbę użytkowników zalogowanych w konkretnej chwili. Podczas budowania infrastruktury trzeba zaprojektować aplikację tak, aby była w stanie obsłużyć maksymalne obciążenie równie sprawnie i szybko, jak w czasie mniejszego ruchu.

Po drugie, musimy być w stanie zwiększyć całkowitą pojemność systemu. Dziś możemy mieć zaledwie 5000 użytkowników. Pół roku później może ich być już 10, 15 lub 20 tysięcy, a w ciągu paru lat możemy dobić do stu tysięcy użytkowników. Możliwość rośnięcia wraz z liczbą użytkowników bez zgrzytów i konieczności zatrzymywania aplikacji jest właśnie tym, o co chodzi w skalowalności. Innymi słowy, musimy mieć jakiś skuteczny sposób, aby być stanie dodać użytkowników bez powodowania negatywnego wpływu na wydajność, a jeśli już degradacja wydajności jest nieunikniona, musi się ona mieścić w akceptowalnych granicach.

Typowa aplikacja ASP.NET jest zazwyczaj wdrażana na jednym lub kilku serwerach Web, połączonych razem w farmę Web, z mechanizmem równoważenia obciążeń, który rozdziela ruch na wszystkie serwery. W teorii – im więcej serwerów Web dodamy, tym więcej żądań powinniśmy móc przetworzyć w jednostce czasu. Architektura farmy Web została wymyślona właśnie po to, aby zapewnić skalowalność ASP.NET. Taka jest teoria. Rzeczywistość jest nieco odmienna.

Problem z aplikacjami ASP.NET leży w tym, że wprawdzie technologie Web zapewniają elegancką architekturę farmy Web i równoważenie obciążeń, jednak za tym rozwiązaniem nie nadążają mechanizmy magazynowania danych. Oczywiście, możemy rozbudować aplikację Web, dodając nowe serwery albo zwiększając moc indywidualnych serwerów, rozszerzając ich pamięć i liczbę procesorów.

Jednak w tym samym czasie magazyn danych nie jest w stanie rosnąć w takich samych proporcjach. Można go powiększać, ale nie aż tak, jak rośnie warstwa aplikacji sieci Web. W rezultacie każdy element aplikacji ASP.NET związany z przechowywaniem danych lub dostępem do nich staje się potencjalnym wąskim gardłem. Aby uściślić to stwierdzenie, powiedzmy wprost: serwery baz danych nie umożliwiają skalowania danych sesyjnych ani danych aplikacji.

Powody występowania tych problemów

Przyjrzyjmy się różnym działaniom dostępu lub rejestrowania danych, które występują wewnątrz aplikacji ASP.NET, zaczynając od przechowywania danych sesyjnych. Przy każdym żądaniu użytkownika dane sesji są odczytywane, po czym ponownie zapisywane na zakończenie odpowiedzi. Na początku żądania strona musi zostać wykonana, do czego potrzebuje danych sesji. Kompletne dane sesji, nazywane „obiektem sesji”, są ładowane, dzięki czemu w trakcie wykonania kodu strony aplikacja może odwoływać się do tych danych. Strona odczyta zatem pewne dane z obiektu sesji, a następnie umieści w nim nowe, zmienione dane. Wszystko to odbywa się wewnątrz procesu ASP.NET i żadne odwołania do magazynu sesji nie są wykonywane.

Gdy strona zakończy wykonywanie, niektóre rezultaty muszą zostać przesłane z powrotem do użytkownika. Sama sesja zapewne uległa zmianie w tym czasie, zatem musi zostać ponownie zapisana w magazynie. Pozostanie tam do następnego żądania użytkownika dla tej samej sesji, gdy cały proces zostanie powtórzony.

Z punktu widzenia użytkownika nastąpiło kliknięcie łącza, przycisku lub inna podobna czynność. Zanim użytkownik zobaczy stronę wynikową, sesja została odczytana, a następnie ponownie zapisana w magazynie danych. Tak więc aplikacja ASP.NET wykonała dwie „wycieczki’ do magazynu sesji.

Teraz kilka obliczeń. Jeśli mamy 10000 użytkowników uzyskujących dostęp do aplikacji w tym samym czasie, możemy mieć nawet 1000 żądań na sekundę. Każdy użytkownik kliknie coś raz na kilka sekund, zatem co sekundę możemy mieć 1000, a być może więcej żądań przekazywanych do farmy Web.

Powtórzmy – 1000 lub więcej żądań jest wysyłanych do farmy Web, a każde z nich powoduje, że serwer Web wykonuje dwa odwołania do magazynu sesji. Oznacza to ponad 2000 żądań odczytu lub zapisu danych sesji w ciągu sekundy. Teraz widać, jak szybko obciążenie może rosnąć. To jedno z miejsc, w którym może wystąpić wąskie gardło.

Wąskie gardło skalowalności może również pojawić się wtedy, gdy strona w trakcie wykonywania potrzebuje odczytać lub zapisać jakieś dane aplikacji. Rozważmy przykład dostępności miejsc w liniach lotniczych. Użytkownik klika na stronie, szukając lotów z jednej lokalizacji do drugiej, co może powodować konieczność wielu odczytów z bazy danych aplikacji. Następnie użytkownik chce zrobić rezerwację, co wymaga dodania pewnych danych do bazy. Dane te nazywamy „danymi aplikacji” i są one przechowywane w bazie danych; taka operacja zapisu również może wymagać wielokrotnych operacji zapisu, jeśli elementy danych wymagają zapisu w różnych miejscach.

W praktyce liczba „wycieczek” do bazy danych może być od pięciu do dwudziestu razy większa od liczby żądań przesyłanych przez użytkowników. Tym samym obciążenie bazy danych jest tyle razy większe, zatem tu właśnie może powstać główne wąskie gardło.

Trzecie wąskie gardło skalowalności może się pojawić przy korzystaniu ze środowiska SOA (service-oriented architecture), gdy aplikacja wykonuje wywołania do innej warstwy usługi, która może być zlokalizowana w tej samej lub innej serwerowni.

Architektura warstwy serwerowej zazwyczaj oznacza wykorzystanie farmy serwerów i tym samym zapewnia podobną skalowalność, jaką dysponuje aplikacja Web. Jednak warstwa usługowa powoduje te same utrudnienia skalowalności, gdyż zależna jest od swojej własnej bazy danych.

Tak więc aplikacja jest uzależniona od innych usług, które są zależne od swoich baz danych, a te z kolei tworzą wąskie gardła dla skalowalności – a wiadomo, że łańcuch jest tylko tak mocny, jak jego najsłabsze ogniwo. Jeżeli usługa nie daje się skalować z powodu swojej bazy danych, cała aplikacja nie da się przeskalować (patrz Rysunek 1).

Baza danych staje się wąskim gardłem miarę rozrastania się farmy Web

Rysunek 1: Baza danych staje się wąskim gardłem miarę rozrastania się farmy Web.

Nie ma większego znaczenia, czy baza danych to komputer klasy mainframe, czy też relacyjna baza danych. Odczyt lub zapis danych zwyczajnie nie da się przyśpieszyć i nie można utrzymać równego tempa ze skalowalnością samej technologii Web. Właśnie te wąskie gardła związane z magazynowaniem danych blokują możliwość skalowania aplikacji ASP.NET.

 Do początku strony Do początku strony

Powody istnienia tych problemów

Dlaczego nie da się przeskalować magazynu danych? Najpierw przyjrzyjmy się trzem opcjom przechowywania danych stanu sesji oferowanym przez firmę Microsoft: InProc, StateServer oraz SqlServer. InProc ma ograniczenia. Został zaprojektowany do stosowania w jednoserwerowym, jednoprocesowym środowisku i nie umożliwia pracy w rozproszonym środowisku ASP.NET. Sesja nie jest zachowywana.

Zobaczmy, co się dzieje. Użytkownik nawiązuje połączenie z jednym z serwerów farmy i sesja jest tworzona na nim. Jeśli mechanizm równoważenia obciążeń skieruje teraz użytkownika do innego serwera, aplikacja nie będzie mogła znaleźć sesji; z jej punktu widzenia użytkownik rozpoczyna bowiem nową sesję. Za każdym razem, gdy użytkownik kliknie cokolwiek, będzie musiał ponownie się zalogować, a tym samym nie będzie w stanie nic zrobić. W skrócie – aplikacja nie będzie działać.

Jedną z metod ominięcia tego problemu jest wykorzystanie funkcji „stałych sesji”, która pozwala kierować danego użytkownika zawsze do tego samego serwera, dzięki czemu aplikacja będzie mieć dostęp do danych sesji na tym serwerze.

Można również poradzić sobie z ograniczeniami InProc, rezygnując z tworzenia ogrodu Web na serwerze. Ogród Web występuje, gdy aplikacja używa wielu roboczych procesów ASP.NET uruchomionych na tym samym serwerze. Jeśli będziemy unikać stosowania ogrodu Web, będziemy mieli tylko jeden proces, co pozwala na użycie InProc w farmie Web.

Te dwa rozwiązania są jednak dalekie od ideału. Stałe sesje mogą same stworzyć wąskie gardło skalowalności ze względu na nierównomierność rozkładu obciążeń, gdyż czas trwania sesji użytkowników jest bardzo niejednorodny. Niektórzy użytkownicy logują się tylko na minutę, podczas gdy inni na 20 minut. Część serwerów może otrzymać wiele sesji, ale kilka z nich będzie praktycznie pustych lub nieaktywnych. Nawet jeśli dodamy więcej maszyn, wcale niekoniecznie poprawi to przepustowość.

Co więcej, InProc zawiera własne ograniczenia pamięciowe. Każda sesja w procesie ASP.NET wymaga przydziału pamięci. W miarę zwiększania liczby sesji wymagania pamięciowe procesu roboczego rosną znacząco. Na platformie 32-bitowej istnieje jednak limit 1GB pamięci dla procesu roboczego i to właśnie tworzy problem. Nie możemy powiększyć danych sesji poza rozmiar, który zmieści się w jednym gigabajcie pamięci procesu roboczego – wraz z innymi danymi i kodem aplikacji. Tak więc InProc tworzy wąskie gardło. Im więcej będzie użytkowników, tym silniej odczuwane będą te problemy.

StateServer przechowuje stan sesji w procesie oddzielnym od procesu roboczego ASP.NET, ale ma własne ograniczenia. Można skonfigurować go tak, aby każdy serwer Web zawierał swój własny StateServer albo można zadedykować do tego celu odrębny komputer i utrzymywać na nim stan sesji.

Przy pierwszej opcji nadal musimy korzystać ze stałych sesji. Gdziekolwiek sesja zostanie utworzona, musimy zawsze wrócić do tego samego miejsca przy kolejnych odwołaniach. Zatem opcja ta pozwala tylko załagodzić ograniczenia InProc dotyczące ogrodu Web. Nie pozwoli rozwiązać problemu stałych sesji, więc znowu możemy znaleźć się w sytuacji, gdy część serwerów w ogóle nie jest używana, podczas gdy inne są przeciążone. Ostatecznym efektem z punktu widzenia użytkownika może być bardzo długi czas reakcji i „powolne” działanie aplikacji.

Inną wadą tej konfiguracji jest fakt, że jeśli któryś z serwerów Web zostanie odłączony, StateServer na tym komputerze również przestanie działać i sesje przez niego przechowywane zostaną utracone. Wprawdzie nie utracimy wszystkich sesji witryny Web, a tylko te przechowywane na tym komputerze, ale i tego nie powinniśmy akceptować. Zasadniczo nie powinniśmy nigdy tracić żadnych danych sesji.

Wybór drugiej konfiguracji oferowanej przez StateServer – dedykowanego serwera – pozwala nam zapomnieć o stałych sesjach, gdyż każdy serwer Web odwołuje się do tego samego serwera StateServer. Jednak w tym momencie mamy jeszcze większy problem: jeśli ten serwer zostanie wyłączony, cała farma Web przestanie działać, gdyż każdy serwer Web będzie próbował odczytywać i zapisywać swoje sesje na tym komputerze.

To jeszcze nie koniec. Dedykowana maszyna StateServer szybko może zostać przeciążona w miarę dodawania liczby serwerów Web i eskalacji liczby transakcji na sekundę. W konsekwencji sama staje się wąskim gardłem. Tak więc problem skalowalności nie może zostać rozwiązany za pomocą StateServer w żadnej z dostępnych konfiguracji.

Przejdźmy teraz do SqlServer, który przechowuje stan sesji w bazie danych SQL Server i pod tym względem przypomina dedykowany StateServer. Jest to podstawowy serwer bazodanowy firmy Microsoft, zaprojektowany dla środowisk o wielkich obciążeniach. Jest też bardziej skalowalny niż StateServer, gdyż można utworzy klaster serwerów bazodanowych.

W konfiguracji SqlServer wszystkie serwery Web w rzeczywistości łączą się z dedykowanym komputerem SqlServer, który przechowuje dane wszystkich sesji. Jest to podobne do rozwiązania, gdy każdy serwer Web łączy się z dedykowaną maszyną StateServer. Koncepcja ta opiera się na założeniu, że SqlServer będzie bardziej skalowalny niż StateServer. Jednak SqlServer nie jest tak szybki jak State Server, gdyż StateServer polega na przechowywaniu danych w pamięci, a tym samym ma akceptowalną wydajność. Z drugiej strony SqlServer nie jest magazynem pamięciowym, ale dyskowym. Wszystkie bazy danych są przechowywane na dyskach, gdyż szybko osiągają rozmiary zbyt wielkie, aby móc je pomieścić w pamięci. Oznacza to, że baza danych przechowuje informacje na trwałym nośniku, jakim jest dysk. Ze względu na przechowywanie danych na dysku, SqlServer nie jest tak szybki, co powoduje spadek ogólnej wydajności aplikacji.

SqlServer może występować w różnych konfiguracjach. W wersji autonomicznej, która jest najczęściej spotykana, istnieje tylko jeden serwer bazy danych, z którym komunikują się wszystkie serwery Web i w miarę zwiększania rozmiarów farmy Web rośnie też obciążenie bazy danych (patrz Rysunek 2).

Sesje ASP.NET nadal tworzą wąskie gardło

Rysunek 2: Sesje ASP.NET nadal tworzą wąskie gardło.

Dodatkowo pojawi się spadek wydajności, gdyż SqlServer nie opiera się na pamięci, zaś problem skalowalności pozostanie, ponieważ bazy danych nie da się rozbudowywać tak bardzo. Można ją rozbudować, zmieniając sprzęt na bardziej wydajny (na przykład dodając procesory i pamięć), ale nie można dodawać kolejnych serwerów bazodanowych w takim samym tempie, w jakim rośnie farma Web. Można przejść z jednego na dwa lub nawet trzy serwery, wykorzystując możliwości klastrowania oferowane przez SqlServer, co zapewnia pewien poziom skalowalności nieosiągalny dla StateServer, ale nadal ograniczony.

Inny problem polega na tym, że SqlServer przechowuje dane wszystkich sesji w pojedynczej tabeli. Wzajemne blokowanie przy dostępie równoległym i jednoczesne aktualizowanie danych sesji staje się coraz bardziej oczywistym problemem w miarę rozbudowywania farmy. Zaś w miarę wzrostu liczby transakcji na sekundę pojawiać się będzie coraz więcej i więcej blokad, gdyż wszystko jest przechowywane w jednej tabeli.

Tak więc wprawdzie SqlServer umożliwia skalowalność większą niż StateServer, jednak kosztem ograniczenia wydajności, a skalowalność ta nie jest wystarczająca. Co więcej, nie następuje ona liniowo. Możliwe jest rozbudowanie farmy serwerów Web z pięciu do pięćdziesięciu czy nawet stu serwerów, przy czym sama farma Web będzie rozszerzała się w miarę „gładko”; jednak dostęp do danych nie może być powiększany w taki sam sposób. Jak wspomniałem wcześniej, baza danych jest jednym z tych rozwiązań przechowywania danych, który nie umożliwia płynnego wzrostu, zatem umieszczanie danych sesji w bazie danych nie zapewnia żadnego znaczącego usprawnienia. Jest to tylko nieznaczna poprawa w stosunku do środowiska StateServer. Co więcej, SqlServer może być wąskim gardłem także dla danych aplikacji, nie tylko danych sesji.

 Do początku strony Do początku strony

Odpowiedź

Właściwym rozwiązaniem jest mechanizm przechowywania danych sesji w pamięci, aby był maksymalnie szybki – tak jak StateServer. Z drugiej strony powinien być on w przybliżeniu liniowo skalowalny. Liniowa skalowalność oznacza, że dodanie kolejnych serwerów powinno proporcjonalnie zwiększyć pojemność. Jeśli na przykład jesteśmy w stanie obsłużyć 10000 transakcji na jednej maszynie, dodanie drugiej takiej samej powinno dać mniej więcej 20000 transakcji na sekundę. Zwróćmy uwagę na zwrot „w przybliżeniu liniowo” – nie oczekujemy, że będzie to dokładnie 20000 – może to być 19000, ale nie na przykład 12000 lub 15000. I to jest to, czego potrzebujemy: magazyn, który może rosnąć niemal liniowo i który będzie przechowywał informacje w pamięci.

Ze względu na te potrzeby nie rozmawiamy tu o trwałym magazynowaniu, które ma inne wymagania i jest zaplanowane dla długoterminowego przechowywania. Bazy danych mają przechowywać dane przez długi czas, podczas gdy magazyny pamięciowe są zawsze ulotne i tymczasowe. Jednak nasze potrzeby są właśnie tymczasowe. Potrzebujemy jedynie przechować dane na czas trwania sesji lub co najwyżej czas działania aplikacji – kilka godzin, może kilka dni, a w skrajnym wypadku kilka tygodni. Później dane mogą zostać usunięte, gdyż zawsze istnieje główny magazyn stały, gdzie jest baza danych, z której możemy ponownie załadować dane aplikacji.

Mając to wszystko na uwadze, możemy zacząć rozważać mechanizm magazynowania zwany „rozproszonym buforowaniem” – koncepcja, która stała się popularna, gdyż zapewnia korzyści wymienione wyżej, co ukazuje Rysunek 3.

Rozproszone buforowanie zmniejsza obciążenie serwera bazodanowego

Rysunek 3: Rozproszone buforowanie zmniejsza obciążenie serwera bazodanowego.

Rozproszone buforowanie wykorzystuje przechowywanie danych w pamięci, zatem jest szybkie, i zaprojektowane tak, aby mogło rosnąć niemal liniowo, szczególnie gdy wykorzysta się właściwy mechanizm dystrybucji (zwany też topologią buforowania).

Rozproszony bufor musi zapewniać wysoką wydajność i liniową skalowalność, a ponieważ istnieje tylko w pamięci, musi też umożliwiać mechanizm replikacji, dzięki któremu, jeśli któryś z komputerów zostanie odłączony (jego pamięć stanie się niedostępna), dane będą dostępne na innym komputerze i nie zostaną utracone żadne informacje. Replikacja zapewnia istnienie więcej niż jednej kopii tych samych danych rozmieszczonych w różnych lokalizacjach na różnych komputerach, dzięki czemu można osiągnąć 100-procentowy czas dostępności danych.

Rozproszony bufor przechowuje obiekty .NET lub obiekty Java albo też dowolne inne dane, takie jak dokumenty XML. Dane przechowywane są w przygotowanym formacie. Nie występują tu koncepcje tabel ani wierszy czy też kluczy głównych i obcych, znane z baz danych. Z punktu widzenia programisty rozproszony bufor to zasadniczo tabela HASH – mamy klucz, a dla każdego klucza istnieje wartość, którą w tym wypadku jest obiekt. Musimy tylko znać klucz, aby na jego podstawie zlokalizować żądany obiekt. Jest to pojedynczy logiczny bufor, rozciągający sie na wiele serwerów. Dodatkowe serwery można dołączać w celu powiększenia wielkości bufora i można je też usuwać, tym samym zmniejszając bufor, bez zatrzymywania działania całości.

 Do początku strony Do początku strony

Topologie buforowania

Istnieją różne topologie skutecznego bufora rozproszonego. Wymienić tu można bufory replikowane, partycjonowane, hybrydowe (zarazem replikowane i partycjonowane), a także klienckie lub lokalne. Idea polega na stosowaniu różnych topologii dla różnych typów wykorzystania, zapewniając maksymalną elastyczność bufora. W topologii replikowanej zawartość bufora jest powielana tyle razy, ile wynika z potrzeb (patrz Rysunek 4). Jest ona przeznaczona dla sytuacji intensywnego wykorzystania bufora do odczytów, ale niewielkiej liczby zapisów.

Replikowany bufor jest idealnym rozwiązaniem dla intensywnych odczytów

Rysunek 4: Replikowany bufor jest idealnym rozwiązaniem dla intensywnych odczytów.

Bufor partycjonowany jest wysoko skalowalną topologią dla często zmienianych danych lub dla danych transakcyjnych, które trzeba buforować. Mogą to być na przykład dane sesji ASP.NET, które są transakcyjne w wysokim stopniu. Jak wspomniałem wcześniej, przy każdym żądaniu Web dane sesji są odczytywane i aktualizowane, zatem występuje tu równa liczba odczytów i zapisów.

Topologia partycjonowana (patrz Rysunek 5) jest doskonałym wyborem dla środowisk, w których aktualizacje (zapisy) wykonywane są w podobnym tempie jak odczyty. W tej topologii bufor jest podzielony. W miarę dołączania kolejnych serwerów bufor dzielony jest dalej na (w przybliżeniu) równe części, tak więc na każdym serwerze przechowywana jest jedna N-ta (gdzie N oznacza liczbę węzłów) całkowitej pojemności bufora.

Bufor partycjonowany jest idealny dla obciążeń intensywnych pod względem zapisów

Rysunek 5: Bufor partycjonowany jest idealny dla obciążeń intensywnych pod względem zapisów.

Trzecia topologia stanowi połączenie wersji partycjonowanej i replikowanej. Bufor dzielony jest na partycje, ale jednocześnie każda z nich jest replikowana. W ten sposób można osiągnąć zalety obu rozwiązań. Można dodawać kolejne partycje i powiększać bufor, a jednocześnie mieć możliwość replikacji w celu zapewnienia, że żadne dane nie zostaną utracone (patrz Rysunek 6).

Bufor partycjonowany i replikowany jednocześnie

Rysunek 6: Bufor partycjonowany i replikowany jednocześnie.

Przy pomocy topologii partycjonowanych lub hybrydowych można niemal liniowo powiększać wielkość bufora.

Bufor kliencki lub lokalny jest czwartą, użyteczną topologią. W tym wypadku bufor zlokalizowany jest na serwerze aplikacji. Ten typ bufora jest bardzo blisko aplikacji – może to być coś podobnego do InProc. Jest to zazwyczaj drobny fragment całego wielkiego bufora rozproszonego, a jego zawartość opiera się na ostatnich żądaniach aplikacji. Czegokolwiek zażąda aplikacja, kopia tego czegoś zostaje umieszczona w buforze klienckim. Kolejnym razem, gdy aplikacja zażąda tych samych danych, zostaną one przekazane z bufora. Nie ma potrzeby odwoływać się do rozproszonego bufora, oszczędza się nawet ten ruch – ma to znaczenie, gdyż bufor rozproszony znajduje się w sieci na oddzielnym serwerze lub klastrze serwerów. Bufor kliencki zapewnia dodatkowy wzrost wydajności i ułatwia skalowalność.

Dane w buforze klienckim muszą być synchronizowane z buforem rozproszonym. Jeśli dane w buforze rozproszonym zostaną zmienione, musimy wykonać synchronizację tej zmiany w buforze klienckim. Jest to bardzo ważny aspekt – nie chcielibyśmy, aby bufor lokalny działał w oderwaniu od całości. Prowadziłoby to do utworzenia równoważnika bufora InProc, czego nie możemy zaakceptować, gdyż niszczyłoby integralność danych: moglibyśmy mieć wiele kopii tych samych danych, które nie pasują do siebie.

 Do początku strony Do początku strony

Różne wybory

Istnieje kilka rozwiązań rozproszonego buforowania. Podobnie jak dzieje się w innych sytuacjach, darmowe rozwiązania zapewniają ograniczony zbiór funkcjonalności, podczas gdy komercyjne oferują znacznie więcej opcji i funkcji.

Niezależnie od wysokiej wydajności, skalowalności i zapewniania dostępności, skuteczny bufor rozproszony musi obejmować kilka kluczowych funkcji pozwalających zapewnić aktualność danych i synchronizację danych ze źródłem, którym może być relacyjna baza danych lub mainframe. Bufor powinien też mieć opcję wygasania, aby można było nakazać mu wykonanie automatycznego czyszczenia, opartego na czasie absolutnym lub względnym. Zasadniczo, jest to czas braku aktywności: jeśli nikt nie używa danych przez pewien czas, automatycznie wygasają.

Bufor musi również być w stanie obsłużyć relacje pomiędzy różnymi typami danych. Większość danych to dane relacyjne. Na przykład, jeśli mamy klienta, mamy też zamówienia złożone przez niego, zatem istnieje relacja pomiędzy danymi klienta i danymi zamówień. Jeśli zbuforujemy dane klienta i zamówień, po czym niechcący usuniemy dane klienta z bufora, należałoby też usunąć również dane zamówień. W tym wypadku nie wiem, czy dane klienta zostały usunięte tylko z bufora, czy też na stałe. W razie trwałego usunięcia klienta zamówienia są nieważne, gdyż zamówienie musi dotyczyć jakiegoś istniejącego klienta.

Istnieją też inne, podobne typy relacji, które muszą być obsługiwane przez bufor. Jeżeli bufor sam tego nie robi, wówczas to aplikacja musi o to zadbać, co jest bardzo niewygodne. Zaprojektowany przez firmę Microsoft obiekt bufora w ASP.NET, który jest bardzo użyteczny, wykorzystuje „podejście zależności buforów”. Jedna zbuforowana rzecz jest zależna od innej. Jeśli ta druga zostanie usunięta z bufora albo nawet tylko zaktualizowana, pierwsza zostanie uznana za nieaktualną i usunięta. Ta koncepcja powinna być dostępna we wszystkich buforach, które przechowują dane relacyjne.

Synchronizacja z bazą danych jest kolejną ważną zdolnością bufora. Baza danych jest zwykle współużytkowana przez wiele aplikacji. Jeśli aplikacja używająca bufora jest jedyną, która modyfikuje zawartość bazy danych, zapewne nie będzie potrzebna funkcja synchronizacji. Jednak często zdarza się, że inne aplikacji, niekiedy innych producentów, również aktualizują dane w bazie danych, gdyż jest to wspólny magazyn, przy czym aplikacje te nie korzystają z naszego bufora. Mogą to nawet nie być aplikacje .NET, a niezależne programy, nad którymi nie mamy kontroli, ale zmieniające zawartość bazy danych. W takim wypadku może wystąpić sytuacja, że zawartość bazy została zmieniona poza naszą aplikacji, ale część danych zaktualizowanych w bazie jest również buforowana. Z tego względu bufor musi być w stanie wykonać synchronizację. Musi wiedzieć, że zawarte w nim dane nie są już takie same, jak w bazie danych. W takim wypadku musi on usunąć te dane z bufora i ewentualnie ponownie załadować najświeższą kopię z bazy danych. Synchronizacja bazy danych może być wykonywana albo poprzez zdarzenia wywoływane przez serwer bazodanowych, albo poprzez odpytywanie bazy przez bufor. Zdarzenia serwera zapewniają lepsze działanie w czasie rzeczywistym, podczas gdy odpytywanie powoduje pewne opóźnienia, jednak będzie bardziej efektywne, jeśli następują masowe zmiany w bazie danych.

Powiadamianie przez zdarzenia jest jedną z najważniejszych funkcji, którymi powinno dysponować skuteczne rozwiązanie rozproszonego bufora. Bufor może być współużytkowany przez kilka aplikacji, a także – wewnątrz aplikacji – przez wielu użytkowników. Wynika stąd potrzeba mechanizmu powiadamiania na wypadek aktualizacji lub usunięcia buforowanego obiektu. Jeśli aplikacja odwoła się ponownie do tych danych, powinna zostać powiadomiona, że musi załadować nową kopię albo z bazy danych, albo z samego bufora. Mechanizm powiadamiania usprawnia współdziałanie wielu użytkowników lub wielu aplikacji ze wspólnym buforem.

 Do początku strony Do początku strony

W praktyce

Specjaliści IT często muszą sobie radzić z problemami wydajności związanymi z bazami danych i w razie natrafienia na wąskie gardła mogą mówić o szczęściu, jeśli mogą je zgłosić programistom, a ci znajdą rozwiązanie. Niestety, twórcy programów często nie znajdują się pod ręką – znacznie częściej musimy radzić sobie z cudzymi aplikacjami.

W takiej sytuacji najlepszym miejscem, od którego można zacząć implementację rozproszonego buforowania w celu odblokowania wąskiego gardła i przyśpieszenia aplikacji, jest magazyn ASP.NET Session, ponieważ w tym wypadku nie jesteśmy zależni od programistów. Nie jest potrzebne żadne programowanie ani ingerencja w kod aplikacji. Wszystko, co trzeba zrobić, to zastąpić istniejący magazyn Session umieszczonym w pamięci buforem rozproszonym. Co więcej, implementacja rozproszonego bufora dla magazynu ASP.NET Session daje możliwość oszacowania korzyści i tego, jakie zapewnia to wydajności i skalowalności, po czym możemy zadecydować, czy zrobić to samo z danymi aplikacji.

Aby móc ocenić usprawnienie skalowalności, trzeba albo uruchomić rozproszone buforowanie w środowisku produkcyjnym, albo zasymulować takie obciążenie w środowisku testowym. Można wykorzystać tu dostęp do QA, który może pomóc w wykonaniu obciążenia testowego, aby zasymulować wielkie obciążenie przed umieszczeniem bufora rozproszonego w środowisku produkcyjnym. Większość menedżerów IT nie czuła by się pewnie, wprowadzając nowe rozwiązanie do produkcji przed jego przetestowaniem w środowisku pilotażowym, nawet jeśli nie będą mogli zasymulować takiego samego obciążenia. Od tego zatem wypadałoby zacząć.

Po skonfigurowaniu i uruchomieniu rozproszonego buforowania, a także po zebraniu wszystkich korzyści, można podzielić się wrażeniami na temat wydajności i skalowalności nowego rozwiązania ASP.NET Session z zespołem programistów – własnym czy też zewnętrznego dostawcy. Mając w ręku solidne dowody, można poprosić programistów o analizę obszarów, w których mogliby również wykorzystać rozproszone buforowanie, takich jak dane aplikacji.

Buforowanie danych aplikacji zapewni dalszy wzrost wydajności. W wielu przypadkach będzie on nawet większy niż wykorzystanie buforowania dla samego magazynu ASP.NET Session. Programiści powinni być w stanie zidentyfikować elementy danych, które są odczytywane częściej, niż są zapisywane. Nawet dane transakcyjne (takie jak dane klientów, zamówień i tak dalej) są dobrymi kandydatami na buforowanie – nawet wówczas, gdyby pozostawały w buforze zaledwie kilka minut przed wygaśnięciem. Wynika to z faktu, że w tym krótkim czasie dane mogą być ponownie odczytywane wielokrotnie i te odczyty wykonywane będą z bufora, a nie z bazy danych, znacząco zmniejszając obciążenie bazy danych.

Trzeba jednak pamiętać, że aby programiści mogli buforować dane aplikacji, będą musieli zrobić trochę programowania, wykonując wywołania API do rozproszonego bufora. Idea jest bardzo prosta. Kiedykolwiek aplikacja próbuje pobrać dane z bazy, najpierw powinna sprawdzić bufor. Jeśli zawiera on te dane, zostaną odczytane z bufora. W przeciwnym wypadku aplikacja pobiera dane z bazy danych, buforuje je i przekazuje do użytkownika. Dzięki temu dane zostaną znalezione w buforze, gdy nastąpi nowe żądanie odczytu. Analogicznie, kiedykolwiek dane modyfikowane są w bazie danych, powinny zostać również zaktualizowane w buforze. Jeśli zaś bufor rozrzucony jest po wielu serwerach, musi być automatycznie synchronizowany, aby zagwarantować, że – jeśli aplikacja działa w farmie Web – te same dane bufora dostępne są dla wszystkich serwerów farmy. Więcej informacji na temat tworzenia aplikacji wykorzystujących bufory rozproszone w celu zapewnienia skalowalności zamieszczę w kolejnym artykule w lipcowym wydaniu s MSDN Magazine.

O autorze

Iqbal Khan jest prezesem i twórcą technologii w Alachisoft, firmie udostępniającej NCache – wiodące rozwiązanie buforowania rozproszonego dla platformy .NET, zapewniające poprawę wydajności i skalowalności aplikacji korporacyjnych. Uzyskał magisterium w dziedzinie Computer Science na Uniwersytecie Indiana w Bloomington w 1990 r. Można się z nim skontaktować pod adresem iqbal@alachisoft.com.

 Do początku strony Do początku strony

Windows Server 2008