Distribuované systemy
Neustálé zvyšování hardwarových nároků vědeckých úloh žádá také neustálé zvyšování výkonu procesorů. Ne každý si může dovolit superpočítač, ale existuje i jiná cesta. Pokud úlohu vhodně rozdělíme mezi větší množství pomalejších procesorů, můžeme dosáhnout velmi velké výpočetní kapacity.
Tento nápad dostali již na začátku 70.let vědci ve výzkumném středisku Palo Alto, kteří vyvinuli červa, jenž cestoval jejich sítí čítající 100 počítačů. Na každém z nich se instaloval a poté využíval volný procesorový čas na výpočty a na zkopírování
sebe sama na další počítače, kde se proces opakoval. Nápad, jak suplovat funkci superpočítače pomocí velkého množství relativně menších jader v síti vytvořené z obyčejných počítačů byl na světě. Klíčovou podmínkou je, aby obrovskou úlohu, která stojí na začátku celého procesu, bylo možné rozdělit na velké množství relativně jednodušších úloh.
Dalším krokem je vytvoření systému, jenž umí porcovat výchozí úlohu na ony menší části a distribuovat ji obyčejným počítačům.
Toho lze v jazyce Java dosáhnout s použitím následujích technologií:
Serializace objektů, pomocí níž můžeme objekt serializovat a následně poslat na výstup, jako je například sekvenční soubor nebo roura. To zdokonaluje technologie vzdáleného volání metod (Remote Method Invocation - RMI), která celou serializaci zpřehledňuje a umožňuje serializovaný objekt posílat po síti na vzdálený počítač, kde možné k danému objektu přistupovat tak, jako by byl lokální. Tak je možné výpočet sice distribuovat na jiný počítač, ale doba výpočtu se výrazně nezmění. To nám umožní až technologie více-vláknového zpracování procesů. Před samotnou serializací výpočtu a jeho odesláním na jiný výpočetní zdroj je možné pomocí více-vláknové technologie úlohu rozdělit na dílčí úlohy, spouštěné ve vláknech, a serializovat až samotná vlákna, která je možno odeslat na více výpočetních zdrojů, čímž by se měl čas potřebný na vyřešení úlohy redukovat úměrně k počtu výpočetních zdrojů, na které byla vlákna odeslána.
Multithreading
V 80.letech běžela většina Unixovských systémů na jednoprocesorových strojích. Operační systém přesto vytvářel iluzi paralelního běhu procesů, a to tak, že mezi ně vhodným způsobem rozděloval procesorový čas. Různá meziprocesová komunikační příslušenství, jako roury, sdílené paměti, semafory, atd., umožnila vytvořit komplexní aplikace pomocí mnoha spolupracujících procesů. Nicméně, pouze s jedním společným procesorem mohly sice tyto aplikace používat víceprocesové modely, ale rychlost zpracování dat zůstávala stejná.
Teprve na začátku 90. let vznikla snaha o vybudování dostupného multiprocesorového systému. Každý program nebo proces se může skládat i z několika podprocesů neboli
vláken, která provádějí instrukce dané v kódu programu. Jako více procesů na jednom počítači, mohou běžet i vlákna zdánlivě paralelně, ale teprve když je spustíme na více-procesorovém stroji stane se tato zdánlivá paralelizace skutečnou. Narozdíl od procesů, vlákna sdílejí stejný adresový prostor, a proto mohou číst stejné proměnné a stejná data. Při psaní více-vláknových programů je třeba dát si velký pozor, aby žádné vlákno nemohlo rušit práci jiného vlákna. Takovou situaci je možne přirovnat ke kanceláři, ve které zaměstnanci pracují současně a zároveň nezávisle, až do chvíle, kdy musí použít některé ze společných prostředků nebo když spolu potřebují komunikovat.Jeden se domluví s druhým jen pokud ten druhý právě poslouchá a pokud mluví oba stejným jazykem. Zaměstnanec také může použít tiskárnu jen pokud ji právě nepoužívá někdo jiný. Programátor musí tedy zajistit, aby byla vlákna koordinována a mohla vzájemně spolupracovat.
Ve více-vláknovém programu je vlákno vybráno z fronty vláken připravených k běhu a následně spuštěno na procesoru který je k dispozici. Operační systém může vláknu procesor odebrat a poté ho vložit do fronty. Druhů front je několik, například již zmíněná fronta pro vlákna připravená k běhu, fronta pro blokovaná vlákna, atd. Také Java Virtual Machine může manipulovat s vlákny - přesouvat je z fronty vláken připravených k běhu na procesor, kde mohou začít vykonávat své instrukce.
Způsob, jakým
Java přistupuje k práci s vlákny se nazývá preemptivní multitasking. Vláknu je přidelna priorita a podle toho se pak střídají v běhu. Nejdříve je procesor přidělen těm s vyšší prioritou a postupně se na řadu
dostávají vlákna méně důležitá. Pokud má několik vláken prioritu stejnou, jejich zpracování závisí na operačním systému, který je pro běh programu používán. Například systém Windows používá metodu dávkování času, což zajišťuje střídání vláken se stejnou prioritou na procesoru v pravidelných intervalech. Vlákno může být v jednom ze čtyř možných stavů: běžící vlákno právě provádí své instrukce, pozastavené vlákno čeká a může být vráceno do stavu běžící, pokud je mu to dovoleno, blokované vlákno čeká na použití nějakého příslušentství, které je právě používáno jiným vláknem, a není k němu možný přístup, a přerušené vlakno bylo ukončeno bez možnosti návratu.
Pokud chceme multithreading v Javě používat, musíme dané třídě zajistit rozšíření třídou Thread, nebo implementaci rozhraní Runnable. Pokud naše třída rozšiřuje (extends) třídu Thread, není už možné podědit jinou třídu. Po implementování rozhraní Runnable je možné implementovat další rozhraní a nebo rozšířit třídu.
Serializace objektů
Krátce po vzniku prvních jazyků pro objektově orientované programování začali jejich vývojáři cítit potřebu persistence jejich objektů, čímž je míněno, že životní cyklus objektu není omezen na program, který ho vytvořil. Obvykle to znamená schopnost uložit jej do sekvenčního souboru a potom znovu načíst nebo třeba i převádět mezi propojenými počítači.
Jazyk Java tento problém řeší pomocí rozhraní Serializable. Toto rozhraní bylo do Javy přidáno ve verzi 1.1 za účelem podpory nových možností, především objektového modelu Java Beans a technologie
RMI. Je to poměrně jednoduchý, ale velmi účinný způsob serializace objektů, který převede objekt na posloupnost bytů tak, aby mohl být později znovu obnoven.
Tato technika funguje i v síťovém prostředí - je možné serializovat objekt na počítači s operačním systémem Windows a poté ho poslat po síti na počítač s operačním systémem Unix, kde bude objekt znovu bez problémů obnoven. Navíc serializace může být použita na objekt téměř jakékoliv systémové třídy a také na jakýkoli objekt, který programátor vytvoří, protože po rozšíření jazyka o možnost serializace bylo upraveno mnoho tříd standardních knihoven, včetně všech objektových reprezentací primitivních datových typů, všech kontejnerových tříd a řady dalších.
Za účelem serializace je nutné serializovanému objektu zajistit implementaci rozhraní Serializable. Nejprve vytvořímem objekt typu OutputStream, který následně zabalíme do objektu ObjectOutputStream. Dalším krokem je volání metody writeObject(), která náš objekt převede na posloupnost bytů a odešle ho do výstupního proudu typu OutputStream. Pokud chceme objekt znovu vyvolat, zapouzdříme nstanci třídy InputStream do objektu ObjectInputStream a zavoláme metodureadObject(). Takto získaný odkaz na objekt typu Object, musíme následně převést na správný typ.
Pro serializaci objektů mezi systémy musí být oba tyto systémy obeznámeny se třídou transportovaného objektu a oba také musí mít k dispozici stejný soubor \"*.class\", ze kterého objekt pochází. Nejobvyklejší použití serializace je přenos objektu mezi klientem a serverem, systémy jsou zde tedy myšleny dva vzájemně vzdálené systémy propojené sítí. Nicméně může nastat situace, kdy se jedná pouze o jeden systém, a persistentní objekt je rekonstruován do verze programu odlišné od té, která ho serializovala. V tom případě deserializace selže, pokud byla třída objektu změněna za určitou mez. Pokud není splněna první podmínka, bude vrácena vyjímka ClassNotFoundException, pokud není splněna druhá podmínka, vrátí kompilátor vyjímku InvalidClassException (místní třída není kompatibilní).
Jestliže je objekt serializován, je serializováno všechno uvnitř objektu, včetně jiných serializovatelných objektů, na které ukazují odkazy. Tento příjemný rys serializace ušetřuje spoustu práce a také zabraňuje přemíře chyb při kompilaci. Pokud některý z odkazovaných objektů neimplementuje rozhraní Serializable, kompilátor vrátí vyjímku NotSerializableException a stačí chybějící rozhraní doplnit. Je to mnohem jednodušší, než muset testovat rekonstruovaný objekt pro potvrzení správného průběhu serializace.
Kdy tedy můžeme serializaci použít? Důležité je hlavně zvážit, jak užitečný pro nás bude objekt po tom, co bude obnoven. Objekty, které implementují námi napsané metody mohou mít také problémy, protože stav těchto metod není součástí serializačního procesu. Ani objekty, ketré mají přímé napojení na soubory, sockety nebo na jiné vstupní a výstupní toky nejsou serializovatelné, protože jejich funkčnost je vázána na systémové zdroje, které nebudou na jiném, vzdáleném systému k dispozoci, ani není níčím zajištěno že po deserializaci budou stále tam kde byly před serializací. Většina těchto případů je zřejmá, ale je užitečné vědět, že serializace má své hranice. Pokud si nepřejeme aby mechanismus serializace automaticky ukládal a obnovoval určité podobjekty, například proto, že obsahují určité informace, které by se v žádném případě neměly dostat do nepovolaných rukou, použijeme klíčové slovo transient. Říkáme tím, že o uložení nebo obnovení daného podobjektu chceme rozhodovat sami. Po obnovení celého objektu mají tyto podobjekty hodnotu null. Další možností je implementace rozhraníExternizable. U objektu, který toto rozhraní implmentuje není automaticky serializováno nic. Jestli ale přesto chceme nějakou část objektu serializovat, uvedeme ji v metodě writeExternal().
Technologie RMI
Distribuovaný systém je program nebo sada programů, které pro svůj běh využívají více než jeden výpočetní zdroj. Metody distribuovaných výpočtů pokrývají široké spektrum, od multivláknových aplikací, přes aplikace, které pro svůj běh využívají jeden systém (například síťový klient a server na jednom počítači), až po aplikace, kde klientský program a server běží na počítačích často velmi vzdálených (webové aplikace).
Systémy pro distribuoavané výpočty existovaly už před vznikem jazyka Java. Tradiční mechanismy jsou RPC (Remote Procedure Call - vzdálené volání procedur) a CORBA (Common Object Request Broker Architecture). Java přidává technologii RMI (Remote Method Invocation), která je v podstatě objektově orientovaný RPC mechanismus. Technologie RMI byla poprvé představena ve verzi JDK 1.1 (Java Development Kit).
Zjednodušeně řečeno, vzdálené volání procedur je schopnost používat pro běh kódu jiný, vzdálený počítač, přičemž se celá sestava chová, jako by pro svůj běh používala pouze počítač, ze kterého kód spouštíme. Existují dva druhy tříd, které mohou být v technologii Java RMI použity. Prvním typem je vzdálená třída, jejíž instance může být užívána vzdáleně, přičemž na objekt takovéto třídy může být odkázáno buď uvnitř adresového prostoru, kde objekt vznikl a ten potom může být používán jako jakýkoliv běžný objekt, nebo uvnitř jiného adresového prostoru. Na takový objekt se přistupuje pomocí identifikátoru objektu. Ve většině případů se pak identifikátory objektů používají stejně jako běžné objekty. Druhým typem třídy je serializovatelná třída, jejíž instance mohou být kopírovány z jednoho adresového prostoru do jiného. Instance serializovatelné třídy se nazývá serializovatelný objekt. Pokud je serializovatelný objekt předáván jako parametr vzdáleného volání metod (
RMI), potom hodnota objektu bude kopírována z jednoho adresového prostoru do jiného. Naproti tomu, pokud je vzdálený objekt předáván jako parametr, bude kopírován pouze identifikátor objektu.
Použití serializovaných objektů ve vzdáleném volání metod je zřejmé. Jednoduše předáme objekt s použitím parametru nebo jako vracenou hodnotu. Typ parametru nebo vracené hodnoty je serializovatelná třída. Klient i server musí mít oba přístup k definicím všech serializovatelných tříd, které jsou používány. Pokud klient a server běží na různých počítačích, definice serializovatelných tříd by měly být zkopírovány z jednoho počítače na druhý.
Definovat vzdálenou třídu je poněkud složitější než definovat třídu serializovatelnou. Vzdálená třída má dvě části: rozhraní, a třídu samotnou. Vzdálené rozhraní musí v definici splňovat několik požadavků: musí být veřejné (public), musí rozšiřovat rozhraní java.rmi.Remote a každá metoda v rozhraní musí obsahovat deklaraci throws java.rmi.RemoteException. Jiné vyjímky mohou být rovněž deklarovány. Samotná vzdálená třída musí implementovat toto rozhraní, dále by měla rozšiřovat třídu java.rmi.server.UnicastRemoteObject. Objekty takové třídy existují v adresovém prostoru serveru a mohou být vzdáleně volány. Ačkoliv jsou i jiné způsoby jak definovat vzdálenou třídu, toto je asi nejjednodušší cesta jak zajistit, aby její objekty mohly být užívány jako vzdálené objekty. Vzdálená třída může obsahovat i metody, které nejsou obsaženy ve vzdáleném rozhraní. Tyto metody ale mohou být volány pouze lokálně.
Server pro svůj běh potřebuje definici jak vzdálené třídy, tak i vzdáleného rozhraní, klient používá pouze vzdálené rozhraní. Vzdálené rozhraní tak představuje něco jako identifikátor objektu, zatímco vzdálená třída reprezentuje typ objektu. Pokud je vzdálený objekt používán vzdáleně, jeho typ musí být deklarován jako typ vzdáleného rozhraní, ne typ vzdálené třídy.
Program napsaný v Javě může určit bezpečnostního manažera (security manager), který rozhodne o bezpečnostní politice. Bezpečnostní politiku lze nastavit vytvořením objektu SecurityManager a voláním metody setSecurityManager z třídy System. Důležité pro komunikaci mezi serverem a klintem je také nastavení v souboru .java.policy. Musí zde být hlavně povoleno připojení na danou adresu s použitím daného portu.
Vloženo: 08.10.2006 21:20
Přečteno:8146
Autor: strom
Hlasů: 15
Hodnocení(jako ve škole): 1.73
-
Nový Komentář