JavaSpaces to interfejs do programowania rozproszonego oparty na modelu Lindy (wzorowane na Linda-C, ale zoptymalizowane dla Javy). Działa on w środowisku Jini w JVM. Jini to standard Sun'a dla urządzeń działających przez sieć podobny do "Plug-and-Play". Mimo że interfejs JavaSpaces zawiera niewiele prostych metod, pozwala on na współpracę nawet bardzo różniących się od siebie komputerów dzięki udostępnianiu operacji na przestrzeniach krotek, rozproszonych zdarzeń, transakcji i dzierżaw (leases). Dzięki temu łatwo jest w JavaSpaces tworzyć skalowalne systemy do rozproszonego przetwarzania danych (m. in. automatycznie uzyskujemy równoważenie obciążenia).
Z programowaniem rozproszonym związanych jest wiele problemów: współpracujące procesy muszą się komunikować i synchronizować, trzeba sobie radzić z opóźnieniami spowodowanymi komunikacją przez sieć, częściowymi porażkami (partial failure) i niekompatybilnymi językami programowania. Najbardziej znane implementacje pozwalające na programowanie rozproszone to: Remote Procedure Calls (RPCs), Distributed Component Object Model (DCOM), Common Object Request Broker Architecture (CORBA), Remote Method Invocation (RMI). Niestety te podejścia mają wady, np. głównym problemem w rozwiązaniach typu RPC jest to, że klient i serwer muszą bardzo dużo o sobie wiedzieć, żeby dobrze współdziałać. W wyniku tego są one ze sobą ściśle związane (tightly coupled). Zmiana w jednym wymusza zmiany w drugim.
W przeciwieństwie do poprzednio wymienionych technologii JavaSpaces zapewnia jednolity
mechanizm komunikacji, synchronizacji i wymiany danych, działając jako pośrednik między
procesami oferującymi usługi i tymi, które z nich korzystają. JavaSpaces jest więc minimalistycznym
modelem zupełnie zastępującym RPC. Jednocześnie większość współbieżnych czy rozproszonych problemów
można łatwo rozwiązać za pomocą tej technologii. Przestrzeń krotek, na której opiera się JavaSpaces
to rozproszona, wirtualna,
dzielona pamięć. API składa się praktycznie z 4 metod: write
(zapisanie krotki w przestrzeni), read
(odczytanie krotki, stworzenie jej kopii),
take
(zabranie krotki z przestrzeni) i notify
(informowanie o zmianach
w przestrzeni krotek). W JavaSpaces w przeciwieństwie do typowej pamięci dzielonej procesy
nie mogą bezpośrednio modyfikować krotek, kiedy są one w przestrzeni - żeby zmodyfikować obiekt
proces musi go usunąć z przestrzeni, uaktualnić i włożyć z powrotem.
Trudno byłoby wymyślić prostszy schemat rozproszonego systemu. W RPC procesy komunikują się
i synchronizują przez bezpośrednie wywoływanie swoich metod. To podejście nie skaluje się
dobrze bez dodatkowego wysiłku - im więcej procesów, tym więcej czasu zajmuje i jest bardziej
skomplikowane. W JavaSpaces to przestrzeń krotek odpowiada za synchronizację procesów i
dzięki temu rozbudowa systemu sprowadza się do uruchomienia kolejnych procesów.
Komunikujące się obiekty:
null
). Taki wzorzec przekazujemy jako jeden z parametrów operacji take
czy read
.
Obiekt z przestrzeni krotek będzie pasował do naszego wzorca, jeśli wartości jego pól będą dokładnie równe ustawionym
przez nas wartościom we wzorcu, wartości pól równych null
we wzorcu w pasującym obiekcie mogą być dowolne.
Command
- a robotnicy pobierają zlecenia i wykonują je. Kod robotnika jest niezależny
od konkretnego zadania, które oblicza. Jednocześnie, jeśli utworzymy różne podklasy klasy Command
,
ci sami robotnicy będą wykonywać różne prace.
Obiekty, które umieszczamy w przestrzeni krotek muszą implementować interfejs Entry
.
Entry
jest to tzw. marker interface - nie zawiera żadnych metod, które musielibyśmy implementować,
jedyne, co potrzebujemy to dodać implements Entry
do definicji klasy. Konwencje, jakie muszą spełniać krotki:
int
używamy Integer
)write(Entry entry, Transaction txn, long lease):
Lease
, który
zawiera informacje jak długo nasza krotka będzie trzymana w przestrzeni (w szczególności ten czas może być krótszy
niż sobie życzyliśmy).read(Entry tmpl, Transaction txn, long timeout):
take(Entry tmpl, Transaction txn, long timeout):
notify(Entry tmpl, Transaction txn, RemoteEventListener listener, long lease, MarshalledObject handback):
snapshot(Entry e):
Będziemy posługiwać się implementacją JavaSpaces o nazwie Blitz.
blitz.sh
(lub blitz.bat
pod Windowsem). Jeśli coś nie działa, to
sprawdzamy, czy w tym pliku wszystkie podane ścieżki są poprawne. Jeśli to nie pomoże, sprawdzamy ustawienia wpisane w pliku
blitz/config/start-blitz.config
jini2_1/lib/jini-core.jar
jini2_1/lib/jini-ext.jar
jini2_1/lib/reggie.jar
blitz/lib/blitz.jar
blitz/lib/blitz-dl.jar
blitz/lib/blitzui.jar
-Djava.security.policy=policy.all
.
Do tego potrzebujemy w głównym katalogu projektu plik policy.all o następującej treści:
grant { permission java.security.AllPermission; };
Lookup finder = new Lookup(JavaSpace.class); JavaSpace space = (JavaSpace) finder.getService();
hello
a w nim klasy:
Lookup
- do uzyskania dostępu do przestrzeni krotekMessage
- klasa reprezentująca pojedyncza krotkę wstawianą do przestrzeni o treści:
package hello; import net.jini.core.entry.Entry; public class Message implements Entry { public String content; // a no-arg constructor public Message() { } }
HelloWorld
- klasa testująca działanie JavaSpaces o treści:
package hello; import net.jini.core.lease.Lease; import net.jini.space.JavaSpace; public class HelloWorld { public static void main(String[] args) { try { Message msg = new Message(); msg.content = "Hello Spaces World!"; Lookup finder = new Lookup(JavaSpace.class); JavaSpace space = (JavaSpace) finder.getService(); space.write(msg, null, Lease.FOREVER); Message template = new Message(); Message result = (Message)space.read(template, null, Long.MAX_VALUE); System.out.println(result.content); } catch (Exception e) { e.printStackTrace(); } } }
blitz.bat
policy.all
-Djava.security.policy=policy.all
Mechanizm transakcji Jini pozwala na zarządzanie grupą uczestników transakcji i przeprowadzenie two-phase
commit protocol (zarządcą transakcji jest TransactionManager
, a uczestnik transakcji implementuje
interfejs TransactionParticipant
). Zapewnione są własności ACID dla rozproszonych transakcji:
Korzystanie z takich transakcji za pomocą JavaSpaces jest stosunkowo proste: tworzymy nową transakcję za pomocą
klasy TransactionFactory
- uzyskana w ten sposób transakcja będzie ważna przez pewien ustalony
przedział czasu podany przez użytkownika (jeśli transakcja nie zostanie zacommitowana przed upływem tego czasu,
to będzie automatycznie abortowana - dzięki temu nie musimy się przejmować sytuacjami, kiedy proces działający
na przestrzeni krotek przez sieć zostanie nagle odłączony). Utworzoną transakcję przekazujemy jako argument do
każdej operacji na krotkach, która ma uczestniczyć w transakcji, a na końcu wywołujemy metodę commit
.
Jeśli po drodze będą jakieś problemy, które wywołają abort
, to przestrzeń krotek będzie w takim
stanie, jak przed nieudaną traksakcją.
Jeśli wstawimy krotkę do przestrzeni, używając transakcji, będzie ona widoczna tylko dla procesów biorących udział w tej transakcji - nikt z zewnątrz jej nie zobaczy dopóki transakcja nie będzie zacommitowana. Jeśli transakcja się nie uda, to utworzone w niej krotki zostaną wyrzucone, a krotki pobrane w ramach tej transakcji zostaną zwrócone do przestrzeni.
private void createMessages() { for (int i = 0; i < numMessages; i++) { Message msg = new Message(); msg.content = "" + i; try { sourceSpace.write(msg, null, Lease.FOREVER); System.out.println("Wrote message " + i + " to " + sourceName); } catch (Exception e) { System.err.println("Cant write message " + i + ": " + e); } } }
private void relayMessages() { TransactionManager mgr = TransactionManagerAccessor.getManager(); Message template = new Message(); Message msg = null; for (int i = 0; i < numMessages; i++) { Transaction.Created trc = null; try { trc = TransactionFactory.create(mgr, 300000); } catch (Exception e) { System.err.println("Could not create transaction " + e); return; } Transaction txn = trc.transaction; try { try { template.content = "" + i; // take message under a transaction msg = (Message)sourceSpace.take(template, txn, Long.MAX_VALUE); System.out.println("Took msg " + i + " out of " + sourceName); // write message to the other spaces under a transaction for (int j = 0; j < targetSpaces.length; j++) { targetSpaces[j].write(msg, txn, Lease.FOREVER); System.out.println("Wrote message " + i + " to " + targetNames[j]); } } catch (Exception e) { System.err.println("Can't relay message " + i + ": " + e); txn.abort(); return; } txn.commit(); } catch (Exception e) { System.err.println("Transaction failed"); return; } } }
TransactionManagerAccessor
nie jest częścią JavaSpaces,
ale znacznie ułatwia korzystanie z transakcji bez znajomości szczegółów Jini.
Zaimplementuj procesy Producenta i Konsumenta
Zasymuluj za pomocą przestrzeni krotek mechanizm zdalnego wywoływania procedur. Serwer ma otrzymywać informację o nazwie metody, którą ma wykonać i tablicę jej parametrów, po czym wykonywać metodę i przesyłać wynik do Klienta . Zaimplementuj następujące metody w serwerze:
Integer iloczyn(Integer[] czynniki)
String konkatenacja(String[] napisy)
Stwórz symulację systemu, w którym znajdują się dwa typy zasobów: A i B. Istnieje M egzemplarzy zasobu A oraz N (N > M) egzemplarzy zasobu B. Ponadto w systemie działają trzy grupy procesów:
Zaimplementuj rozproszone mnożenie macierzy ustalonej wielkości. Główny proces inicjalizuje przestrzeń krotek i wypisuje obliczone elementy macierzy. Procesy robocze działają w pętli. W każdym jej obrocie wyliczają wartość jednego elementu macierzy wynikowej. Zadbaj o to, aby wszystkie procesy kończyły się po zakończeniu obliczenia.
Napisz program realizujący iteracyjny, rozproszony algorytm obliczania całki oznaczonej ustalonej funkcji f na przedziale [a, b] metodą trapezów. Kolejny krok iteracji polega na obliczeniu pola trapezu wyznaczonego przez punkty: a, b, f(a), f(b) i porównaniu go z sumą pól trapezów zbudowanych na odcinkach [a, c] i [c, b], gdzie c jest środkiem odcinka [a, b]. Jeśli różnica jest większa od zadanej wielkości ε, obliczamy oddzielnie całki na odcinkach [a, c] i [c, b] z dokładnością ε/2 każdą i sumujemy je. Obliczenie tych całek powinno odbywać się współbieżnie. Cały proces powinien być realizowany przez pewną liczbę identycznych wykonawców pobierających zlecenia z przestrzeni krotek i tam umieszczających zarówno kolejne zlecenia, jak też wyniki.
Rozwiązania znajdują się tutaj.
Źródła: