2008.05_Oracle Coherence część 2 _[Bazy Danych].pdf

(233 KB) Pobierz
441071828 UNPDF
Data
Grid
Michał Kuratczyk
Oracle Coherence część 2
W poprzednim artykule zajmowaliśmy się pod-
stawową funkcjonalnością Coherence, ja-
ką jest przechowywanie obiektów w pamię-
ci operacyjnej klastra. Teraz, gdy wiemy już w jaki spo-
sób wstawiać, pobierać i wyszukiwać dane, możemy za-
jąć się bardziej zaawansowaną funkcjonalnością – roz-
proszonym przetwarzaniem danych przechowywanych
w gridzie.
Przetwarzanie danych znajdujących się w przestrze-
niach Coherence można porównać do procedur skła-
dowanych w bazie danych. W obu przypadkach cho-
dzi o to, by przetwarzać dane tam, gdzie one się znaj-
dują, a do aplikacji zwracać jedynie wyniki, jeśli takowe
są potrzebne. Ze względu na rozproszoną naturę Cohe-
rence, takie przetwarzanie możemy wykonywać równo-
legle – każdy węzeł klastra przetwarza dane, za które
jest odpowiedzialny. Im bardziej rozproszymy zatem da-
ne (przez połączenie większej ilości serwerów w klaster),
tym szybciej będziemy mogli je przetwarzać. Zacznijmy
jednak od najprostszego przypadku – przetwarzania po-
jedynczej wartości:
Przetwarzanie danych
Wiemy już jak wywoływać agentów, którzy będą prze-
twarzać nasze dane. Teraz zajmiemy się ich możliwościa-
mi, implementacją i zasadami działania.
Agent musi implementować interfejs EntryProcessor ,
który definiuje dwie proste metody: process(Entry en-
try) oraz processAll(Set entries) . Metoda processAll()
jest zaimplementowana w klasie AbstractProcessor , więc
zazwyczaj wystarczy po prostu rozszerzyć tę klasę i za-
implementować metodę process() . Jako przykład może-
my utworzyć agenta, który wywołany dla określonego re-
kordu, pobiera jego wartość, inkrementuje ją i zapisuje
zmienioną wartość tak jak na Listingu 2.
Jak widać modyfikowanie wartości musi zostać wy-
konane wprost (metodą setValue() ) - jeśli o tym zapo-
mnimy, to nie zauważymy efektów działania agenta. W
powyższym przepadku nie zwracamy żadnej wartości.
Możliwe jest jednak zwrócenie wyniku dowolnego typu,
który można zserializować.
Agent może nie tylko modyfikować rekord, dla które-
go został wykonany, ale także usunąć go lub utworzyć
zupełnie nowy. Potencjalnie może również wywołać in-
nego agenta, ale jest to obarczone ograniczeniami, aby
uniknąć zapętleń oraz dead-locków . Więcej informacji na
temat tego specyficznego (i rzadko stosowanego w prak-
tyce) przypadku można znaleźć w dokumentacji.
Bardzo ważną cechą całego mechanizmu jest fakt,
że agenci wywoływani na tym samym rekordzie są ko-
lejkowani. Nie musimy zatem martwić się o blokowanie
danych w naszym kodzie. Zminimalizowany jest także
czas sekcji krytycznej, gdyż rekord zablokowany jest
dokładnie wtedy, kiedy zablokowany być musi. W połą-
czeniu z faktem, że agent wykonywany jest na węźle od-
powiedzialnym za dany rekord, oznacza to bardzo wyso-
ką przepustowość.
Ciekawą możliwością jest wywoływanie agentów na
rekordach, które w ogóle jeszcze nie istnieją. Możemy
zatem tworzyć dane w gridzie w sposób rozproszony,
a zarazem zorganizowany i spójny, gdyż również w tym
przypadku Coherence troszczy się o blokowanie i ko-
lejkowanie wywołań na wirtualnym kluczu. Oczywiście
agent ma możliwość sprawdzić, czy przetwarzany rekord
istnieje czy nie – wystarczy sprawdzić wynik metody In-
vocableMap.Entry.isPresent() .
Object result = cache.invoke(key, agent);
Obiekt agent implementuje bardzo prosty interfejs Entry-
Processor (zazwyczaj poprzez dziedziczenie z Abstract-
Processor ). Powyższe wywołanie spowoduje, że Cohe-
rence ustali węzeł, który jest właścicielem rekordu o po-
danym kluczu i przekaże mu kod agenta, a ten wykona
go i zwróci wynik. Aby wykonać agenta na wielu rekor-
dach również wystarczy jedna linia kodu:
Map results = cache.invokeAll(keys, agent);
Jak widać używamy do tego celu metody invokeAll() i
podajemy jej kolekcję kluczy. Metoda ta ma również dru-
gą postać, w której zamiast kluczy podajemy filtr określa-
jący warunki, które musi spełnić rekord, by zostać prze-
tworzony (Listing 1).
Powyższy kod spowoduje, że każdy węzeł (równole-
gle) wyszuka u siebie rekordy spełniające warunki i wy-
kona kod agenta dla każdego z nich. Szczególnym przy-
padkiem filtra jest AlwaysFilter lub po prostu null . W pro-
sty sposób możemy zatem wykonać jakąś operację na
wszystkich rekordach:
Sytuacje wyjątkowe
Jeśli w czasie wykonywania agenta zostanie rzucony
wyjątek, rekord nie zostanie zmodyfikowany, zaś wyją-
Map results = cache.invokeAll((Filter) null, agent);
Michał Kuratczyk, senior presales consultant, Oracle Polska
Kontakt z autorem: Michal.Kuratczyk@oracle.com
Artykuł sponsorowany
74
www.sdjournal.org
Software Developer’s Journal 5/2008
441071828.001.png
Oracle Coherence
A R T Y K U Ł S P O N S O R O W A N Y
tek zostanie opakowany i rzucony ponownie w wątku, który wy-
wołał agenta. W przypadku poważnej awarii, na przykład padu
maszyny wirtualnej, agent zostanie wykonany na innym węźle
– tym, który przejmie odpowiedzialność za dany rekord. Wyko-
nanie agenta jest zatem gwarantowane. W związku z tym każda
implementacja EntryProcessor.process() musi być powtarzalna
(ang. idempotent ), to znaczy jej ponowne wykonanie nie powinno
powodować efektów ubocznych (chodzi głównie o zapisy do baz
danych – gdyby wykonując operację dwukrotnie, stan bazy da-
nych był inny niż po pierwszym wykonaniu, to możemy mieć pro-
blem w przypadku awarii węzła podczas wykonywania agenta).
Listing 1. Filtry określające warunki które musi spełnić
rekord
Filter ilter = new AndFilter (
new EqualsFilter ( "getCustomer" , customerId ) ,
new EqualsFilter ( "getStatus" , Status . OPEN ));
Map results = cache . invokeAll ( ilter , agent );
Listing 2. Zapis zmienionej wartości
public static class IncrementProcessor extends
AbstractProcessor {
Agregaty
Oprócz przetwarzania danych w gridzie, możliwe są również róż-
ne operacje agregujące. Również i w tym przypadku, możliwe jest
wykonywanie ich równolegle na wielu węzłach. Agregaty możemy
pisać sami w podobny sposób co agentów (dziedzicząc z Abstrac-
tAggregator ), możemy także skorzystać z wielu gotowych agrega-
tów – zliczających, sumujących, znajdujących minimalną lub mak-
symalną wartość, itp. Załóżmy na przykład, że nasze obiekty ma-
ją metodę getPrice() i chcemy zsumować ceny dla wszystkich
obiektów w naszej przestrzeni. Wystarczy wówczas wykonać na-
stępującą operację:
public Object process ( InvocableMap . Entry entry ) {
Integer i = ( Integer ) entry . getValue ();
entry . setValue ( new Integer ( i . intValue () + 1 ));
return null ;
}
}
Listing 3. Przykładowa klasa reagująca na zdarzenia
import com . tangosol . util . Base ;
import com . tangosol . util . MapEvent ;
import com . tangosol . util . MapListener ;
Double result = (Double) cache.aggregate((Filter) null, new
DoubleSum("getPrice"));
public static class EventPrinter extends Base implements
MapListener {
Wszystkie agregaty dostępne standardowo w Coherence działają
w sposób rozproszony. Powyższa instrukcja zostanie zatem wyko-
nana znacznie szybciej, niż gdybyśmy ręcznie sumowali wartości
w pętli, wywołując getPrice() dla każdego obiektu po kolei.
public void entryInserted ( MapEvent event ) {
out ( event );
}
public void entryUpdated ( MapEvent event ) {
out ( event );
}
public void entryDeleted ( MapEvent event ) {
out ( event );
}
}
Śledzenie zdarzeń
Coherence może informować nas o zdarzeniach, które zachodzą
w przestrzeniach danych. Możemy zatem na przykład zainicjować
czasochłonny proces obliczeniowy i zamiast czekać na jego za-
kończenie – zarejestrować odpowiednią metodę, która zostanie
wykonana, gdy pojawią się wyniki tych obliczeń. Przykładowa kla-
sa, która reaguje na zdarzenia może wyglądać tak jak na Listingu
3. Aby ją zarejestrować, by wypisywała informację o każdym zda-
rzeniu wystarczy wykonać:
używany przede wszystkim w systemach tradingowych – po-
zwala przetwarzać duże ilości napływających żądań i wyko-
nywać odpowiednie działania. Dzięki Real Time Client , anality-
cy mogą na bieżąco śledzić operacje finansowe oraz wszelkie
wskaźniki. W przypadku firm ubezpieczeniowych, głównym za-
stosowaniem są systemy analizy ryzyka, które wymagają skom-
plikowanych obliczeń matematycznych. Częstym zastosowa-
niem są także systemy logistyczne - Coherence jest na przy-
kład podstawą dla systemu zarządzania dystrybucją przesyłek
w firmie FedEx. Dużą grupę stanowią także popularne serwisy
internetowe – systemy aukcyjne, bukmacherskie oraz portale.
cache.addMapListener(new EventPrinter());
W bardzo podobny sposób możemy nasłuchiwać na określonym
kluczu:
cache.addMapListener(new EventPrinter(), new Integer(123), false);
Ostatni parametr oznacza tutaj, że chcemy, by nasza metoda mia-
ła dostęp do starej i nowej wartości. Jeśli nie ma takiej potrzeby –
ustawiamy ten parametr na true, by umożliwić dodatkowe optyma-
lizacje. Oczywiście możemy także oczekiwać jedynie na wybrane
zdarzenie – stosując podobne filtry jak przy wyszukiwaniu.
Podsumowanie
Coherence, to nie tylko cache dla obiektów, ale potężne narzę-
dzie pozwalające w prosty sposób implementować rozproszone
przetwarzanie danych. Wszędzie tam, gdzie wydajność i skalo-
walność są kluczowe dla powodzenia projektu, należy zastano-
wić się nad zaprojektowaniem aplikacji w sposób możliwie roz-
proszony, by operacje wykonywane były równolegle w wielu miej-
scach. Unikamy w ten sposób pojedynczych punktów awarii i wą-
skich gardeł, które uniemożliwiają skalowanie systemu. n
Przykłady zastosowań
Aby lepiej uzmysłowić sobie potencjalne zastosowania rozwią-
zań typu data grid, warto zobaczyć kto i do czego używa obec-
nie Coherence. Duża część obecnych klientów to instytucje fi-
nansowe – banki i firmy ubezpieczeniowe. Coherence jest tam
Software Developer’s Journal 5/2008
www.sdjournal.org
75
441071828.002.png
Zgłoś jeśli naruszono regulamin