Katedra Architektury Systemów Komputerowych Wydział Elektroniki

Transkrypt

Katedra Architektury Systemów Komputerowych Wydział Elektroniki
Katedra Architektury Systemów Komputerowych
Wydział Elektroniki, Telekomunikacji i Informatyki Politechniki Gdańskiej
dr inż. Paweł Czarnul
[email protected]
Integracja usług w Internecie
LABORATORIUM 6
POWIADOMIENIA ORAZ ZARZĄDZANIE ZASOBAMI W GLOBUS TOOLKIT 4
Tematem niniejszego laboratorium są:
1. powiadomienia (ang. notifications) o zmianach w zasobach tj. klient może zostać asynchronicznie powiadomiony o zmianach w zasobie np. zmianie wartości pola typu int, co więcej może (po uprzednim ustawieniu odpowiedniego parametru) odczytać z powiadomienia zarówno poprzednią jak i nową wartość takiego pola. Powiadomienia stanowią znaczne ułatwienie dla programisty jak również zwiększają wydajność i oszczędzają przepustowość łącza komunikacyjnego. Wynika to z faktu, iż alternatywne rozwiązanie – odpytywanie zasobu o zmiany (ang. polling) powoduje konieczność:
•
okresowego wysyłania zapytań do serwera – powstaje kwestia okresu – zbyt krótki spowoduje znaczne obciążenie sieci zaś zbyt długi znaczne opóźnienie w propagacji informacji o zmianie w zasobie – w konsekwencji zapewne spadek wydajności,
•
zakodowania logiki odpytującej serwer. Mechanizm powiadomień umożliwia zaś dokonanie subskrypcji informacji o zasobie i jedynie implementację metody wywoływanej po stronie klienta po wystąpieniu zmiany na serwerze.
1. zarządzanie zasobami – klient może nie tylko tworzyć zasoby jak pokazano we wcześniejszych laboratoriach, ma również możliwość ich niszczenia, w szczególności w dwóch trybach:
•
natychmiastowym (ang. immediate destruction) – zasób jest niszczony natychmiast po wywołaniu metody,
•
z opóźnieniem (ang. scheduled destruction) – zasób jest niszczony po upływie zadanego czasu.
Ze względu na znakomite przykłady dostarczone z tutorialem Globus Toolkit 4, również dotyczącymi powiadomień i zarządzania zasobami, niniejsze opracowanie rozważy problem, do rozwiązania którego wykorzystany zostanie jedynie mechanizm powiadomień. Ze względu na prostotę mechanizmu niszczenia zasobów, autor odwołuje Czytelnika do przykładów dostarczonych z ww. tutorialem. Znajdują się one pod adresem:
Rozważa się następujący problem:
Zaimplementować zlecanie zadań przez klienta serwerowi w taki sposób, aby klient zlecał nowe zadania gdy serwer zakończy zadanie(a) zlecone poprzednio. Zakłada się, iż wykonanie zadań może zająć różną ilość czasu, co więcej czas ten może być znaczący.
Zakładając dostępność mechanizmu powiadomień, wydaje się, iż problem ten rozwiązać można stosunkowo prosto tj.:
1. Serwer wykonuje zadanie po czym zmienia stan zasobu, na który klient wcześniej dokonał subskrypcji.
2. Zmiana stanu zasobu powoduje przesłanie powiadomienia do klienta, w rzeczywistości powoduje wywołanie odpowiedniej metody.
3. Klient w ww. metodzie wywołuje po raz kolejny metodę zlecającą serwerowi wykonanie kolejnego zadania. Jest to tym bardziej ułatwione, iż odpowiedni EPR jest dostępny w ww. metodzie.
Powyższe rozwiązanie, choć funkcjonalnie bez zarzutu, może zostać ulepszone ze względu na wydajność. Otóż pomiędzy momentem zakończenia wykonywania zadania a momentem rozpoczęcia wykonywania kolejnego zadania upływa czas (powiadomienie, decyzja klienta o uruchomieniu nowego zadania, przesłanie danych) – znaczący w przypadku rozważanej technologii.
Kod prezentowany poniżej rozwiązuje ten problem w jeden z możliwych sposobów tj.:
1. Po stronie serwera tworzone są dwa wątki wykonujące zadania, podobnie do rozwiązania w instrukcji do laboratorium nr 4. Każdemu z wątków przypisane jest pole zasobu (typu int). Wątek otrzymuje zadanie do wykonania, gdy nie ma zadania, wywołuje metodę wait() przechodząc w stan oczekiwania. •
w momencie wywołania metody zlecenia zadania, serwer wybiera wolny wątek i zleca mu wykonanie zadania,
•
wątek po wykonaniu zadania modyfikuje przypisane mu określone pole zasobu; powoduje to przesłanie informacji o zmianie stanu zasobu do klienta, który wcześniej dokonał subskrypcji na dany zasób – w rzeczywistości uruchamiane są dwa programy klientów, którzy odpowiednio czekają na zmiany ww. dwóch pól (innymi słowy zakończenie wykonania zadania przez dwa wątki).
Kod po stronie serwera definiuje pola zasobu wykorzystywane odpowiednio przez pierwszy i drugi wątek:
/* Resource properties */
private ResourceProperty valueRP;
private ResourceProperty value1RP;
Są one inicjalizowane jak w przykładach w poprzedzających instrukcjach. Ponadto, usługa musi implementować interfejs TopicListAccessor, dodatkowo zawiera:
/* Topic list */
private TopicList topicList;
/* Configure the Topics */
this.topicList = new SimpleTopicList(this);
valueRP = new ResourcePropertyTopic(valueRP);
((ResourcePropertyTopic) valueRP).setSendOldValue(true);
value1RP = new ResourcePropertyTopic(value1RP);
((ResourcePropertyTopic) value1RP).setSendOldValue(true);
this.topicList.addTopic((Topic) valueRP);
this.topicList.addTopic((Topic) value1RP);
Umożliwia to klientowi subskrypcję zmian ww. pól. Dodatkowo wywołanie metody setSendOldValue(true); powoduje, iż klient będzie w stanie pobrać zarówno nową wartość i jak i ustawioną poprzednio.
Kod serwera implementuje w inny sposób metodę add(int). Autor zakłada tutaj, iż metoda ta powinna przpisać dane wejściowe (w tym przypadku liczbę typu int) do wolnego wątku i zwrócić sterowanie. W rzeczywistości metoda add(int) zakodowana jest w następujący sposób:
public AddResponse add(int a) throws RemoteException {
assignDataToThread(a);
return new AddResponse();
}
zaś metoda przydzielająca wątek do danych wejściowych:
synchronized private void assignDataToThread(int a) throws RemoteException {
for (int i=0; i<computeThread.length; i++){
if ( computeThread[i].isWaitingForNewRequests()) {
// now assign the value to the resource
if (i==0) {
computeThread[i].performComputation(valueRP,a);
System.out.println("starting thread 0 with"+a);
} else
if (i==1) {
computeThread[i].performComputation(value1RP,a);
System.out.println("starting thread 1 with"+a);
}
return;
}
}
throw new RemoteException("No worker thread available");
}
Metoda performComputation(...) przyjmuje dane wejściowe oraz pola zasobu, które będą ustawiane po zakończeniu operacji na danych.
Faktyczne operacje na danych będą zakodowane w wątku obliczeniowym, który może np. sprawdzać czy dana liczba jest pierwsza czy też nie. Konkretnie, kod wątku obliczeniowego ComputeThread jest analogiczny do wątków całkujących z poprzedniego laboratorium. Po zakończeniu obliczeń zmienia wartość pola zasobu co powoduje powiadomienie klienta:
valueRP.set(0,new Integer(data));
Początkowo wartości ww. pól ustawione są na 0. Po zakończeniu pierwszych wywołań (jak opisano poniżej) wartości ustawione są na argumenty podane w wywołaniu klienta z linii poleceń, w kolejnych wywołaniach generowanych już automatycznie po otrzymaniu powiadomienia, iż wątek jest bezczynny – wartości te ustawiane są na kolejne liczby naturalne.
Oczywiście, wynik obliczeń może być ustawiany jw.
1. Kod, analogicznie do przykładu dotyczącego powiadomień w tutorialu Globus Toolkit 4, składa się z dwóch klas:
•
klient wywołujący metodę zlecającą jedno zadanie,
•
klient nasłuchujący na zmianę zasobu (klient nasłuchuje na zmianę pola typu int przypisanego do pierwszego wątku – patrz wyżej); po otrzymaniu informacji o zmianie zasobu (innymi słowy zakończeniu zadania przez pierwszy wątek) metoda ta zleca wykonanie kolejnego zadania. Kompilacja źródeł aplikacji po stronie serwera (w katalogu, który powstanie po rozpakowaniu archiwum):
globus@wolf:~/ti­lab­6$ ./globus­build­service.sh notifications
Wdrożenie aplikacji:
globus@wolf:~/ti­lab­6$ globus­deploy­gar org_globus_examples_services_core_notifications.gar
Uruchomienie serwera:
globus@wolf:~/ti­lab­6$ globus­start­container ­nosec Następnie należy skompilować źródła trzech klientów:
globus@wolf:~/ti­lab­6$ javac org/globus/examples/clients/MathService_instance_notif/ValueListener.java
globus@wolf:~/ti­lab­6$ javac org/globus/examples/clients/MathService_instance_notif/ValueListener1.java
globus@wolf:~/ti­lab­6$ javac org/globus/examples/clients/MathService_instance_notif/ClientAdd.java
Następnie należy uruchomić programy klientów (w osobnych konsolach, w podanej kolejności):
1. globus@wolf:~/ti­lab­6$ java ­DGLOBUS_LOCATION=$GLOBUS_LOCATION ­classpath
./build/stubs/classes/:$CLASSPATH org/globus/examples/clients/MathService_instance_notif/ValueListener http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService
Waiting for notification. Ctrl­C to end.
2. globus@wolf:~/ti­lab­6$ java ­DGLOBUS_LOCATION=$GLOBUS_LOCATION ­classpath
./build/stubs/classes/:$CLASSPATH org/globus/examples/clients/MathService_instance_notif/ValueListener1 http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService
Waiting for notification. Ctrl­C to end.
3. Po uruchomieniu klientów nasłuchujących, należy uruchomić klienta, który zleci zadanie do wykonania najpierw pierwszemu a następnie drugiemu wątkowi (wątki zostaną przypisane automatycznie przez metodę po stronie serwera). Argumentem wywołania jest liczba typu int, która jest przekazywana do każdego wątku.
•
globus@wolf:~/ti­lab­6$ java ­classpath ./build/stubs/classes/:$CLASSPATH org/globus/examples/clients/MathService_instance_notif/ClientAdd http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService 1000 Value RP: 0
LastOp RP: NONE
należy odczekać kilka sekund i:
•
globus@wolf:~/ti­lab­6$ java ­classpath ./build/stubs/classes/:$CLASSPATH org/globus/examples/clients/MathService_instance_notif/ClientAdd http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService 2000
Value RP: 0
LastOp RP: NONE
globus@wolf:~/ti­lab­6$
Wywołania powinny zwrócić sterowanie od razu, zaś wątki rozpoczną pracę, co zostanie udokumentowane komunikatami po stronie serwera:
starting thread 0 with1000
Thread[Thread­0,5,main].starting computations
starting thread 1 with2000
Thread[Thread­1,5,main].starting computations
Thread[Thread­0,5,main].***1000*0
Następne zadania będą już zlecane automatycznie poprzez klientów nasłuchujących (ValueListener oraz ValueListener1) tj. po wykonaniu zadania przez jeden wątek, jeden z klientów zostanie powiadomiony i prześle kolejne zadanie (w tym czasie drugi z wątków po stronie serwera będzie zajęty). To samo tyczy się drugiego wątku. Zakończenie kolejnych zadań spowoduje uruchomienie nowych itd. Komunikaty widoczne po stronie klientów nasłuchujących będą miały postać:
globus@wolf:~/ti­lab­6$ java ­DGLOBUS_LOCATION=$GLOBUS_LOCATION ­classpath
./build/stubs/classes/:$CLASSPATH org/globus/examples/clients/MathService_instance_notif/ValueListener1 http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService
Waiting for notification. Ctrl­C to end.
A notification has been delivered
Old value: 0
New value: 2000
A notification has been delivered
Old value: 2000
New value: 2001
A notification has been delivered
Old value: 2001
New value: 2002
A notification has been delivered
Old value: 2002
New value: 2003
i dla drugiego klienta:
globus@wolf:~/ti­lab­6$ java ­DGLOBUS_LOCATION=$GLOBUS_LOCATION ­classpath
./build/stubs/classes/:$CLASSPATH org/globus/examples/clients/MathService_instance_notif/ValueListener http://127.0.0.1:8080/wsrf/services/examples/core/notifications/MathService
Waiting for notification. Ctrl­C to end.
A notification has been delivered
Old value: 0
New value: 1000
A notification has been delivered
Old value: 1000
New value: 1001
A notification has been delivered
Old value: 1001
New value: 1002
A notification has been delivered
Old value: 1002
New value: 1003
Wynika to z faktu, iż klient nasłuchujący odczytuje nową wartość zmienionego zasobu, dodaje 1 i wywołuje ponownie metodę po stronie serwera:
public class ValueListener implements NotifyCallback {
/* This method is called when a notification is delivered */
public void deliver(List topicPath, EndpointReferenceType producer,
Object message) {
ResourcePropertyValueChangeNotificationElementType notif_elem;
ResourcePropertyValueChangeNotificationType notif;
notif_elem = (ResourcePropertyValueChangeNotificationElementType) message;
notif = notif_elem.getResourcePropertyValueChangeNotification();
MathServiceAddressingLocator instanceLocator = new MathServiceAddressingLocator();
if (notif != null) {
System.out.println("A notification has been delivered");
System.out.print("Old value: ");
System.out.println(notif.getOldValue().get_any()[0].getValue());
System.out.print("New value: ");
System.out.println(notif.getNewValue().get_any()[0].getValue());
try {
MathPortType math = instanceLocator.getMathPortTypePort(producer);
// Perform addition
math.add(Integer.valueOf(notif.getNewValue().get_any()[0].getValue()).intValue()
+1);
} catch (Exception e) {
System.out.println("Error");
e.printStackTrace();
}
...
Pomimo tego, iż implementacja osiągnęła cele wymienione powyżej, w rzeczywistości idea polega na uruchomieniu dwóch klientów (drugi z pewnym opóźnieniem), z których każdy jest powiadamiany o dostępności “jego” wątku i ponownie zleca mu zadanie. Uruchomienie z opóźnieniem powoduje, iż serwer jest zajęty obliczeniami drugiego wątku, gdy pierwszy przesyła kolejne dane (i odwrotnie). Autor pozostawia poniższe kwestie do analizy Czytelnikowi co powinno dalej poprawić kod tj.:
1. Dotychczasowa implementacja wykorzystuje różne kody klientów.
2. Serwer wykorzystuje dwa wątki do obliczeń. Alternatywą byłoby wykorzystanie tylko jednego wątku do obliczeń oraz umożliwienie klientowi umieszczenia nowych danych po stronie serwera w czasie gdy wątek przetwarza wcześniej zapisane dane. Wątek, po przetworzeniu danych, pobiera kolejne, które już znajdują się po stronie serwera. Tego typu techniki opisane są również w kontekście aplikacji mobilnych w technologii J2ME. Autor odsyła Czytelnika do artykułu dostępnego pod adresem: http://developers.sun.com/techtopics/mobility/ midp/articles/threading/.

Podobne dokumenty