Miskolci Egyetem Gépészmérnöki és Informatikai Kar Általános Informatikai tanszék
Java Lokális Erőforrás Ütemező ( DGRID )
Diplomamunka
Készítette: Szabó Endre
Témavezető: Elek Tibor
Neptun kód: D4FPDI
Miskolc 2013
Java Lokális Erőforrás Ütemező
Tartalomjegyzék
1.
Bevezető ........................................................................................................................ 5 Köszönetnyilvánítás ...................................................................................................... 6
2.
3.
Használt technológiák.................................................................................................... 7 2.1.
Spring framework ................................................................................................... 7
2.2.
Logback ................................................................................................................ 16
2.2.1.
Konfiguráció .................................................................................................. 16
2.2.2.
Apenderek ...................................................................................................... 17
2.3.
Maven ................................................................................................................... 21
2.4.
Java Concurrent csomag (java.util.concurrent) .................................................... 24
2.4.1.
Atomikus típusok........................................................................................... 24
2.4.2.
Executors ....................................................................................................... 24
2.4.3.
Queues ........................................................................................................... 26
2.4.4.
Timing............................................................................................................ 26
2.4.5.
Synchronizers ................................................................................................ 26
2.4.6.
Concurrent Collections .................................................................................. 27
Java Lokális Erőforrás ütemező (DGRID) .................................................................. 28 3.1.
Alapkoncepció ...................................................................................................... 28
3.2.
Osztálydiagram és leírás ....................................................................................... 33
3.2.1.
Project struktúra és a projectek közötti függőségek ...................................... 33
3.2.2.
A teljes Common projekt csomag struktúra .................................................. 35
3.2.3.
A teljes Common projekt osztály struktúra ................................................... 36
3.2.4.
Common projekt részekre bontott osztálydiagram ........................................ 37
2
Java Lokális Erőforrás Ütemező 3.2.5.
A teljes szerver csomag struktúra .................................................................. 44
3.2.6.
A teljes szerver osztály struktúra ................................................................... 45
3.2.7.
Szerver részekre bontott osztálydiagram ....................................................... 46
3.2.8.
A teljes Kliens csomag struktúra ................................................................... 55
3.2.9.
A teljes Kliens osztály struktúra .................................................................... 56
3.2.10.
Kliens részekre bontott osztálydiagram ..................................................... 57
4.
Használt könyvtárak listája névvel és verziószámmal ................................................ 59
5.
Szerver konfigurációja és használata ........................................................................... 60 server.properties ........................................................................................................... 60 Main.java ..................................................................................................................... 61 CustomDistributedOperationControl........................................................................... 61 CustomSpringBeanConfig ........................................................................................... 62 pom.xml ....................................................................................................................... 62 Kliens használata ......................................................................................................... 63
6.
server.properties ........................................................................................................... 63 Main.java ..................................................................................................................... 64 7.
A rendszer felhasználása .............................................................................................. 67 7.1.
Példa alkalmazás ................................................................................................... 67
7.2.
Éles felhasználás ................................................................................................... 67
7.2.1. 8.
Feladat ismertetése ........................................................................................ 67
Tesztek ......................................................................................................................... 71 8.1.
Egygépes konfiguráció (átlagos teljesítményű processzoron) .............................. 72
8.2.
Egygépes konfiguráció (átlag feletti teljesítményű processzoron) ....................... 73
8.3.
Miskolci Egyetem Informatikai tanszék laboratóriumában végzett teszt ............. 73
8.4.
Eredmények összehasonlítása ............................................................................... 73
9. 10.
Továbbfejlesztési javaslatok ........................................................................................ 75 Összegzés ................................................................................................................ 76
3
Java Lokális Erőforrás Ütemező 11.
Summary................................................................................................................... 78
12.
Források .................................................................................................................... 80
13.
Melléklet –A CD melléklet tartalma ......................................................................... 81
4
Java Lokális Erőforrás Ütemező
1. Bevezető A GRID rendszerek létrejöttét a kisebb számítógépek alacsony kihasználtsága valamint az adattárolási vagy adatátvitelből adódó kapacitásnövekedés és a nagy számítást igénylő feladatok párhuzamosításának igénye motiválta. A rendszer lehetővé teszi az erőforrások és szolgáltatások adott cél szerinti felhasználását, ezáltal hatékony erőforrásgazdálkodás érhető el. Kialakulását a dinamikusan fejlődő adatátviteli eljárások segítették elő. Univerzális felhasználhatóságának egyik alapja, hogy nyílt szabványokat és protokollokat alkalmaz az interneten keresztül az erőforrás-megosztás megvalósításához. Diplomamunkám célja egy olyan rendszer kialakítása Java alapokon, ami nagy számítást igénylő párhuzamosítható feladatok elvégzésére alkalmas. A rendszer a GRID irányelveknek megfelelően „általános” célú, bár néhány kritériumot meg kell jegyeznem. Bárki felhasználhatja a keretrendszert, akinek egy komplex párhuzamosítható feladatot kell megoldania, de a feladatnak kellően szétteríthetőnek kell lennie, csak kevés szinkronicáziós pontot tartalmazhat, és a részfeladatoknak CPU igényesnek kell lenniük. Az utóbbi két megszorítás azért szükséges, mert ha túl sok szinkronizációs pontot tartalmazna a feladat, vagy a részfeladatok csak kis mértékben használnák ki az erőforrásokat, könnyen előfordulhat, hogy több időbe kerülne ilyen módon az összetett feladat megoldása a kliens és az erőforrások közötti kommunikáció miatt, mintha csak egy erőforrás végezné el a feladatot. A felhasználónak, ahhoz hogy alkalmazni tudja a keretrendszert a saját céljainak megvalósításához, csak a specifikus részeket kell megírnia a felhasználásához. A rendszer a gépek csatlakozását, feladatok szétosztását, eredmények gyűjtését és összesítést, gépek kiesését, hibakezelést és a kommunikációs feladatokat elvégezné, ezzel is tehermentesítve a felhasználót. Tömören megfogalmazva a rendszer a számítás lebonyolításáért lenne felelős a felhasználó pedig a feladat helyes specifikálásáért. A választásomat és a fejlesztést az alábbi pontokban megfogalmazott gondolatok támasztják alá. A lehető legegyszerűbb megvalósítás a cél. Illetve a felhasználó minél könnyebben, gyorsabban tudja igénybe venni, átlátható és gyors legyen. Kihasználhatnánk a manapság elterjedt mobileszközök, tabletekben rejtőző számítási igényeket is, ezzel egyedülállóként képes lenne ezeket az eszközöket is munkára fogni. Hiába tolódik el az IT világ a Cloud computing felé, az egyetemeken nagy számban
5
Java Lokális Erőforrás Ütemező találhatók
több
magos
viszonylag
nagy
teljesítményű
számítógépek.
A
legkülönfélébb kutatásokban lehetne alkalmazni a fejlesztendő rendszert, meggyorsítva a számításokat, nagyobb anyagi befektetés nélkül. A nagy számítást igénylő párhuzamosítható feladatokra a nagy bonyolultság jellemző. Rengeteg terhet vesz le a felhasználó válláról, hogy a konkrét számítás lebonyolításáért a fejlesztendő rendszer gondoskodik. Saját elképzelés, saját megvalósítás, személyes kihívás, komplexitása a témakörnek eléri a kívánt szintet. Diplomamunkám első részében bemutatásra kerülnek a rendszer fejlesztése közben használt technológiák, azok elméleti hátterei, rövid szemléltetések a könnyebb érthetőségért, majd dolgozatom második felében az általam létrehozott keretrendszer fejlesztésének lépéseit fogom taglalni, valamint szintén a saját fejlesztésű példaprogramot, ami elengedhetetlen volt a fejlesztés során és a tesztelések lefuttatásához. A dolgozat gyakorlatias jellegű. Ebből következik, hogy szinte mindenhol példákkal illusztrálom az elmondottakat. Olykor az is előfordul, hogy először a példa kerül ismertetésre és ebből következik a magyarázat. Azért választottam ezt az utat, mert szerintem így sokkal érthetőbb, rávezetőbb a leírás.
Köszönetnyilvánítás Köszönöm Elek Tibor tanár úrnak a segítségét, mely nélkül nem készülhetett volna el a Diplomamunka. Továbbá Édesanyámnak, Páromnak, akik minden helyzetben támogattak, erőt adtak.
6
Java Lokális Erőforrás Ütemező
2. Használt technológiák A fejezet a felhasznált technológiákat sorakoztatja fel rövid magyarázatokkal kiegészítve. Az alfejezetek a technológiák fontosabb részeit és az alapfelépítést részletezi kiemelve a kitűzött cél megvalósításához szükséges fontosabb ismereteket és a rájuk esett választásomnak okait.
2.1. Spring framework A Spring framework a fejlesztés szinte minden területén nyújt hatékony, professzionális segédeszközöket. Az alábbiakban a teljesség igénye nélkül felsoroltam az általam legfontosabbnak ítélt moduljait az alkalmazásnak. Spring Web MVC: Kiváló model view controller tervezési mintát követő keretrendszer a webes alkalmazásokhoz. Spring Security: Webes alkalmazásaink biztonságának kialakításában segédkezik (Authentikáció, jogkörökön alapuló hozzáférés szabályozás) Spring
Mobile:
Okostelefonok
és
tabletek
elterjedésével
fokozottan
megnövekedett az igény a mobileszközökre optimalizált weblapok iránt. A Spring Mobile segítségével könnyedén azonosíthatjuk, hogy milyen típusú eszköz kívánja megtekinteni az adott oldalt, ennek megfelelően szolgálhatjuk ki a kérést. Spring AOP: Aspektus orientált programozásnál vehetjük hasznát. Általában a különböző profiling témakörökben használják (például meg szeretnénk tudni, hogy egy az alkalmazásunkban a megvizsgálni kívánt metódus a futás során hányszor hívódott meg) Megjegyzendő, hogy a modulok rendkívül jól kombinálhatóak egymással, sőt integrálhatóak más keretrendszerekkel (pl. Quartz, Struts) A rendszer megvalósításánál központi szerephez jut a felsorolásból szándékosan kihagyott Spring Bean. Az alábbiakban ennek a modulnak a részletesebb bemutatása található. Mi is a Spring bean? Legrövidebb meghatározása az IoC konténer. Tehát a különböző java 7
Java Lokális Erőforrás Ütemező osztályok példányosítását szabályozhatjuk, menedzselhetjük a segítségével. Nagy előnyként megemlítendő a singletonok (az adott osztályból egyetlen példány jön létre az alkalmazás futása során, aminek a legnagyobb előnye, hogy erőforrás-megtakarítás érhető el és központilag elérhető a kód bármely területéről) könnyű, egyszerű használata. Az alapértelmezett bean definíció is singleton. Használatuk előnyei elvitathatatlanok, természetesen a prototípus (ebben az esetben a Spring Bean konténeren keresztüli lekéréskor az adott osztályból mindig egy új példány jön létre a futás során) tervezési minta is elérhető. A Spring 3.0 legnagyobb újításai között van az annotáció vezérelt lehetőségek bevezetése. Egyre több területen megjelentek az annotációk, amik és az XML alapú konfigurációt kezdték kiszorítani. Természetesen a visszafelé kompatibilitás megmaradt, így a fejlesztőkre marad a döntés melyik módot választják, de a kevert használat is megengedett (ebből következik, hogy a régebbi fejlesztésű projekteket nem muszáj alapjaiban átírni). Mint ahogyan a fentebbiekből kiolvasható, a spring konfigurációját többféle módon elvégezhetjük: XML; Annotációk; Java config (itt annotációkat is kell használnunk). A rendszer konfigurációjakor a Java config lehetőséget választottam. Tisztázni kell azonban, hogy a java config egy speciális annotáció vezérelt konfiguráció. Lényege, hogy egy (vagy több) java osztályban leírjuk, hogy milyen osztályok állnak rendelkezésre, és hogyan kezelje azokat a spring bean menedzser. Tehát ha ez megvan, akkor már nem kell a továbbiakban a példányosítással foglalkoznunk, csak el kell kérnünk az adott beant. Következzen egy példa a spring bean java konfiguráció lehetőségeinek bemutatására. A példa kitér a singleton és prototype beanek közötti különbségre, közös interfész esetén hogyan kérhetjük le a konkrét implementációt a spring bean konténer segítségével. Továbbá a kódolást egyszerűsítő és hatékonyan használható annotációkat is bemutatja. Közös interface: Egyszerű példa, valaminek a számolására lehet használni az implementációkat.
8
Java Lokális Erőforrás Ütemező package hu.dsoftware.grid.common.springjavaconfig; /** * * @author Endre */ public interface Counter { public int getCounter(); public void setCounter(int value); }
Singleton bean: Singleton osztály, mely implementálja a Counter interfészt. Semmi szokatlan nincs benne, egy egyszerű adattaggal rendelkezik, és persze az interfész által kényszerített metódusokból. A konkrét implementáció szerint a getCounter metódus visszaadja a counter adattagot, míg a setCounter segítségével beállíthatjuk az értékét. Tipikus getter/setter metóduspár. package hu.dsoftware.grid.common.springjavaconfig; /** * * @author Endre */ public class BeanSingleton implements Counter { private int counter = 1; public BeanSingleton() { } public int getCounter() { return counter; } public void setCounter(int counter) { this.counter = counter;
9
Java Lokális Erőforrás Ütemező } }
Prototype bean Prototype osztály, mely implementálja a Counter interfészt. Teljes egészében megegyezik a BeanSingleton osztállyal, csupán a spring bean konténer fogja másképpen kezelni a példányosításukat. package hu.dsoftware.grid.common.springjavaconfig; /** * * @author Endre */ public class BeanPrototype implements Counter { private int counter = 1; public BeanPrototype() { } public int getCounter() { return counter; } public void setCounter(int counter) { this.counter = counter; } }
Spring Java Config A Java konfigurációt bemutató osztály. package hu.dsoftware.grid.common.springjavaconfig;
10
Java Lokális Erőforrás Ütemező
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; /** * * @author Endre */ @Configuration public class SpringBeanConfig { @Bean(name="beanSingleton") public Counter beanSingleton() { return new BeanSingleton(); } @Bean(name="beanPrototype") @Scope("prototype") public Counter beanPrototype() { return new BeanPrototype(); } @Bean public AutowiredExample autowiredExample() { return new AutowiredExample(); } }
Az osztály definíció fölé kötelezően a @Configuration annotációt ki kell raknunk. Bean definiálására a @Bean annotációt használhatjuk metódusokon. A spring értelmező reflection API használatával feldolgozza a metódust és a konténer bean lekéréskor visszatérési értéknek megfelelő típust ad. További kérdés merülhet fel, ha belegondolunk, hogy interfész vagy közös ős esetén ugyanaz lehet több annotált metódus visszatérési típusa. Ekkor a konténer nem tud dönteni sem fordítási sem futási időben, hogy melyik beant szeretnénk lekérni. A megoldás, hogy egyedi névvel kell ellátnunk ilyen esetben a 11
Java Lokális Erőforrás Ütemező beaneket. Ezt az annotáció name attribútumával tehetjük meg. @Bean(name="beanSingleton")
Az alapértelmezett scope a singleton. Ha nem ezt szeretnénk használni, akkor a @Scope annotációval változtathatunk ezen. A value attribútumát szükséges beállítanunk. Tehát prototype bean esetén: @Scope("prototype")
Léteznek még egyéb scope-ok, például a request vagy session. Mivel ezekre nem lesz szükség és webes fejlesztéseknél lehet létjogosultsága, ezért a részletezéstől eltekintek.
App.java Használja a fentebb ismertetett osztályokat a spring bean konténeren keresztül package hu.dsoftware.grid.common.springjavaconfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplication Context; /** * Hello world! * */ public class App { public static void main( String[] args ) { ApplicationContext appCtx = new AnnotationConfigApplicationContext(SpringBeanConfig.class); Counter beanSingleton1 = appCtx.getBean("beanSingleton",Counter.class); Counter beanSingleton2 = appCtx.getBean("beanSingleton",Counter.class);
12
Java Lokális Erőforrás Ütemező System.out.println(beanSingleton1.getCounter()); System.out.println(beanSingleton2.getCounter()); beanSingleton1.setCounter(2); System.out.println(beanSingleton1.getCounter()); System.out.println(beanSingleton2.getCounter()); Counter beanPrototype1 = appCtx.getBean("beanPrototype",Counter.class); Counter beanPrototype2 = appCtx.getBean("beanPrototype",Counter.class); System.out.println(beanPrototype1.getCounter()); System.out.println(beanPrototype2.getCounter()); beanPrototype1.setCounter(2); System.out.println(beanPrototype1.getCounter()); System.out.println(beanPrototype2.getCounter()); AutowiredExample autowiredExample = appCtx.getBean(AutowiredExample.class); System.out.println(autowiredExample.delegateCounter()); } Futtatható belépési ponttal rendelkező osztály. Itt használjuk az eddig létrehozott osztályokat és itt válik érthetővé a spring bean konténer singleton és prototype példányosítás mechanizmusa. Az első lépés az ApplicaionContext megszerzése (egyben a bean konténer is). Az ApplicationContext AnnotationConfigApplicationContext implementációját kell választanunk, ha Java konfigurációt használunk. Természetesen a régebbi és egyébként mai napig sokszor alkalmazott
xml
konfiguráció
esetében
a
ClassPathXmlApplicationContext
vagy
a
FileSystemXmlApplicationContext osztály között kell választanunk. Kiemelve még egyszer a szükséges kódrész: ApplicationContext appCtx = new AnnotationConfigApplicationContext(SpringBeanConfig.class); A konstruktor argumentuma a SpringBeanConfig osztály. Megjegyzendő, hogy akár több osztályban is szétoszthatjuk valamilyen logika szerint a teljes konfigurációt. A következő sorokban pedig a bean konténer használata látható. A getBean metódussal történik a lekérés. Ha egyértelmű az osztálydeklaráció, akkor elég csak az osztály típusát megadnunk.
13
Java Lokális Erőforrás Ütemező Ellenkező esetben a bean nevét Stringként is meg kell adnunk, hogy egyértelmű legyen a konténer számára, melyik osztályt kértük el. Az eredmény a megadott osztállyal egyező lesz. Tehát semmiféle castolást nem kell végeznünk. Az App.java az ApplicationContext megszerzése után először lekéri a BeanSingleton osztályt kétszer (beanSingleton1, beanSingleton2). Majd ezután a beanSingleton1 változónak beállítjuk a counter mezőjét 2-es értékre. Kiíratjuk mindkét BeanSingleton counter mezőjének értékét. Azt kell látnunk, hogy mind a két esetben 2-es jelent meg a standard outputon. A magyarázat pedig, hogy a BeanSingleton osztály csak egy példányban létezhet a program futása során, ezért mind a két lekéréskor a konténer ugyanazon példánnyal tért vissza. Ezáltal a két változó referenciája ugyanarra az objektumra mutat. A program második felében ugyanaz a forgatókönyv zajlik le, csak a BeanPrototype-ot kérjük le kétszer (beanPrototype1, beanPrototype2). Ugyanúgy beállítjuk a beanPrototype1 counter értékét 2-re. Majd kiíratjuk mindkettőnél. Az eredmény eltérő az előbb futtatotthoz képest. beanPrototype1 esetében: 2 beanPrototype2 esetében: 1 A magyarázat, hogy mivel prototípusról van szó, ezért a konténer minden kéréskor egy új példánnyal tér vissza. A két változónak emiatt eltérő az objektum referenciája.
Autowired példa: Az Autowired annotáció használatát szemlélteti. Segítségével a különböző osztályok könnyedén injektálhatóak, megkönnyítve és lerövidítve a programozást. Nem utolsósorban a kód átláthatósága is nagyságrendekkel jobb lesz, ha effektíven használjuk a spring által kínált lehetőségeket. package hu.dsoftware.grid.common.springjavaconfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; /** * * @author Endre */ public class AutowiredExample {
14
Java Lokális Erőforrás Ütemező @Autowired @Qualifier("beanSingleton") private Counter beanSingleton; public int delegateCounter() { return beanSingleton.getCounter(); } }
A BeanSingleton példányáról referenciát szerezhetünk a konténeren keresztül történő direkt lekérésen kívül egy másik módszerrel is. Adattagként deklaráljuk a példában Counter láthatósági típussal beanSingleton néven, de valójában azt szeretnénk, ha a BeanSingleton objektumáról tartalmazna referenciát. Ekkor a mezőt annotálnunk kell és ez lesz az @Autowired megjelölés. Alapesetben ennyi is elég lenne és megtörténne az injektálás, de itt még ez alapján nem tud dönteni a konténer. Ezért kell a bean nevére hivatkoznunk,
melyet
egy
másik
annotációval
tehetünk
meg.
Ez
a
@Qualifier(„beanName”). Az injektálás ebben az esetben akkor következik be, amikor az ApplicationContextet megszerezzük. Egész pontosan mire visszakapjuk már mindenféle előfeldolgozás megtörtént.
15
Java Lokális Erőforrás Ütemező
2.2. Logback Alkalmazásával a futás közbeni kommunikáció és események naplózását lehet megoldani. A népszerű Log4J logging framework utódja, továbbfejlesztése. Használata nagyban hasonló hozzá, viszont rengeteg helyen újragondolták, csiszolták. Ennek eredményeképpen logikusabb,
könnyebben elsajátítható.
Több részből
tevődik össze, amiket
az
alfejezetekben fejtettem ki. Mivel implementálja a SLF4J API-t, ezért könnyedén válthatunk a logger frameworkök között (Log4J, java.util.logging)
2.2.1. Konfiguráció A logback.properties, vagy a manapság elterjedt és széles körben alkalmazott XML formátummal valósítható meg a konfiguráció. A fájl név kötött: logback.xml, amit automatikusan megtalál, ha a classpath-on van. Kódból is megadhatjuk a fájlt, ekkor már saját nevet is adhatunk: LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); JoranConfigurator jc = new JoranConfigurator(); try { jc.setContext(lc); lc.reset(); jc.doConfigure(LOGBACK_CONFIG_FILE_PATH); } catch (JoranException ex) { log.error("Error during load logback configuration\nTrying to load default settings"); ContextInitializer ci = new ContextInitializer(lc); lc.reset(); try { ci.autoConfig(); } catch (JoranException ex1) { log.error("Autoconfig failed"); }
16
Java Lokális Erőforrás Ütemező } //Sátusz kiírása StatusPrinter.print(lc);
2.2.2. Appenderek Az appender egy különálló logolási egységet jelöl. Például a standard outputra ír egy beállított minta alapján. A keretrendszer számunkra többféle lehetőséget biztosít. Egy típus szerepelhet többször is, de eltérő paraméterezéssel. Például bizonyos logolási szinteket elkülöníthetünk külön fájlokba vagy a rendszer bizonyos komponenseinek logjait rendszerezhetjük. A főbb appenderek, és egy példa logback.xml részlet:
ConsoleAppender A standard outputra logol.
name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%-4relative
[%thread]
%msg %n
17
%-5level
%logger{35}
-
Java Lokális Erőforrás Ütemező
FileAppender Megadott fájlba logol.
testFile.log true <encoder> <pattern>%-4relative
[%thread]
%-5level
%logger{35}
-
%msg%n
Speciális példa: minden bejegyzés időbélyeg alapján egyedi fájlba kerül
log-${bySecond}.txt
18
Java Lokális Erőforrás Ütemező <encoder> <pattern>%logger{35} - %msg%n
Speciális FileAppender: RollingFileAppender Segít elkerülni az óriásira növő log fájlokat. Bizonyos feltétel bekövetkezésekor a logolás egy másik fájlban folytatódik, az addig aktív lezáródik. Megadott konvenciót követve átnevezhető. A konkrét alkalmazása lehet dátum alapú szétvágás vagy maximális fájlméret alapú. Az első esetben például napi szinten létrehozható új fájl és az archív fájlok neveiben szerepelhet a dátum. Ebben az esetben megkönnyíti a hibakeresést, elemzést, ha csak egy bizonyos napi eseményekre vagyunk kíváncsiak. Továbbá beállítható, hogy a log fájlok mennyi ideig maradjanak meg, így megtehetjük, hogy az egy hónapnál régebbi bejegyzések automatikusan törlésre kerüljenek. A két említett eset kombinálható is egymással. Tehát létrehozható olyan szabály, miszerint a napi bontásban szerepelnek a log fájlok, de ha esetleg egy nap a 100 MB-os határt túllépné a fájl mérete, akkor a dátumon kívül még egy sorszámmal lesznek szeparálva.
Példa RollingFileAppenderre: Méret és idő szerint történő szétvágás Minden nap új fájl keletkezik és a fájl neve az aktuális dátum lesz. Valamint még ezen belül, hogy ha egy fájl mérete meghaladná a kritikus szintet, akkor automatikusan új üres fájlba folytatódik a logolás.
mylog.txt
19
Java Lokális Erőforrás Ütemező class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> mylog-%d{yyyy-MMdd}.%i.txt <maxFileSize>100MB <encoder> <pattern>%msg%n
20
Java Lokális Erőforrás Ütemező
2.3. Maven Java alkalmazásoknál használatos build eszköz. Jól megfigyelhető tendencia, hogy a közép és nagyméretű rendszerek fejlesztéséhez egyre inkább használnak mavent. A maven projekt lelke a pom.xml nevű fájl. Ebben írhatjuk le a projektünk azonosítóját (group id, artifact id, verzió). Valamint itt kell felsorolnunk a projektünk függőségeit <dependencies> tag között Példa pom.xml: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0
hu.dsoftware.grid.client <artifactId>GRIDClient
1.0-SNAPSHOT <packaging>jar
GRIDClient http://maven.apache.org <properties> <project.build.sourceEncoding>UTF8 <spring.version>3.1.2.RELEASE <dependencies> <dependency>
junit <artifactId>junit
21
Java Lokális Erőforrás Ütemező
3.8.1 <scope>test <dependency>
hu.dsoftware.grid.common <artifactId>GRIDCommon
1.0-SNAPSHOT <scope>compile <dependency>
ch.qos.logback <artifactId>logback-classic
1.0.6 <dependency>
org.springframework <artifactId>spring-beans
${spring.version} <dependency>
org.springframework <artifactId>spring-core
${spring.version} <dependency>
org.springframework <artifactId>spring-context
${spring.version} <dependency>
cglib <artifactId>cglib
22
Java Lokális Erőforrás Ütemező
2.2.2
A függőségek nem a saját gépeken vannak tárolva, hanem a maven központi könyvtárában. Nem kell letöltenünk kézzel a függőségeket, automatikusan menedzseli a maven és a szükséges függőségek letöltődnek a lokális maven repositoryba. Az utóbbi években robbanásszerűen nőtt a központi repositoryba feltöltött elemek száma. Mindezek mellett, ha mégsem találjuk meg a számunkra fontosat, akkor könnyedén létrehozhatunk saját maven repositoryt. Ehhez csak egy FTP tárhelyre van szükségünk. Ekkor azonban meg kell adnunk a pom.xml-ben a saját könyvtárunk elérhetőségét a
tagek között. Első buildeléskor a maven automatikusan ellenőrzi a dependenciákat, és letölti a saját gépünkre a maven home könyvtárába. A további futtatásokkor már nem szükséges ez a művelet, így csak az első buildelés tarthat viszonylag hosszabb ideig. Ahogy már említettem pozitívum, hogy nem kell magukkal a dependált jarokkal foglalkoznunk a csapatmunka során. Nem mellékes, hogy SVN vagy más verziókezelő rendszerbe szintén nem kell ezeket a jar fájlokat commitálnunk, sőt még az aktuális IDE-ben szereplő speciális projektfájlokat sem. Továbbá különböző profilokat hozhatunk létre, pillanatok alatt megoldható, hogy éppen most saját gépen tesztelünk vagy a más kontextust kívánó tesztszerverre rakjuk ki programunkat. Buildelésnél unit teszteket, integrációs teszteket futtathatunk, így nyomon követhetjük, biztosíthatjuk az új programrészek megfelelő működését, a régebbi részek viselkedésének állandóságát (tehát az új részek jól működnek és nincs káros mellékhatásuk a korábban már tesztelt jól működő elemekre). A népszerű java fejlesztőkörnyezetek (Netbeans, Eclipse), teljes maven támogatást nyújtanak.
23
Java Lokális Erőforrás Ütemező
2.4. Java Concurrent csomag (java.util.concurrent) Az alap Java része. Magas szintű API-t nyújt számunkra a többszálú programozás megkönnyítésére.
2.4.1. Atomikus típusok AtomicBoolean; AtomicInteger ; AtomicLong. Közös jellemzőjük, hogy az érték lekérdezés és beállítás atomikus műveletként hajtódik végre. Tehát „lost update” és „dirty read” jelenségek nem fordulhatnak elő, amennyiben több szál próbál hozzáférni. Minden esetben konzisztens adatokat szolgáltat.
2.4.2. Executors Más néven végrehajtók. Arra szolgál, hogy számítási egységeket adjunk át, majd pedig elindítva azokat menedzseli a feldolgozást. A számítási egységek rendszerint külön szálon fognak futni. Példányosításuk egy gyártó osztályon keresztül történik (Executors). Fontos megjegyezni, hogy az így kapott konkrét implementáció az ExecutorService interface-t implementálja, ami az Executor interface leszármazottja. Többféle végrehajtó közül választhatunk. Így természetesen a felmerülő probléma határozza meg melyik lesz a legmegfelelőbb (éppen mire van szükség). Egy új szálon történő végrehajtás esetén az Executors.newSingleThreadExecutor() hívást célszerű választanunk. Végső soron nevezhetjük ebben az esetben egy elemű threadpoolnak is. Indításhoz kétféle lehetőségünk van. Az egyik az execute metódus, mely egy Runnable interface implementációt vár paraméterként. Visszatérési érték nélküli futásról van szó, az elindított szál elvégzi a dolgát, eredményt nem szolgáltat az executor. Másik lehetőség a submit metódus, mely Callable generikus interface implementációt
24
Java Lokális Erőforrás Ütemező vár. A forgatókönyv hasonló az execute metóduséhoz, azzal a különbséggel, hogy itt van visszatérő érték: Future objektum. Mint ahogyan a neve is jelzi, egy jövőbeli eseményről van szó. Egész pontosan arról van szó, hogy a submit metódus nem blokkol. Ezért azonnal visszaadja a válasz objektumot, és folytatódhat a futás immáron két szálon, egymástól függetlenül. Tehát amikor megtörténik a visszatérés, akkor még valójában nincs is meg az elindított Callable eredménye, éppen feldolgozás alatt áll (vagy feldolgozásra vár). Így tehát a kapott objektum neve arra utal, hogy majd valamikor a jövőben fog előállni a tényleges eredmény. Szóval a submit hívása után folytathatjuk a munkát az indító szálon és amikor szükségünk van a tényleges eredményre, akkor meg kell hívnunk a Future objektum get() metódusát, ami T típusú objektummal tér vissza. Ez egy nagyon fontos rész, ugyanis a get metódus viselkedése a következő: abban az esetben, ha már befejeződött a szál futása, ami szolgáltatja az eredményt, a get egyből visszatér, de ha még tart a számítás, akkor blokkol és csak a befejezés után tér vissza a tényleges eredmény objektummal. Jogos a felvetés, hogy sokszor nem egy szállal kell dolgoznunk, rengeteg kis feladatot kell elindítanunk, illetve ez a megoldás nyilvánvalóan a bonyolultság robbanásszerű növekedésével jár, azonban a mai több magos és több szálú párhuzamos feldolgozást támogató rendszereknél hatalmas teljesítménybeli javulással kecsegtet. Manapság egyértelműen ez az irányelv már teljesen megszokott, nem találkozunk olyan megoldásokkal, ami a háttérben ne több szállal dolgozna. Threadpoolok
létrehozására
a
következő
lehetőségek
adódnak:
Executors.
newFixedThreadPool(int poolSize). Ekkor poolSize méretű threadpoollal dolgozó executor példányosodik. A feldolgozandó szálakat vagy egyesével, vagy akár egy listában összegyűjtve egy függvényhívással adhatjuk át. Ekkor a feldolgozás menete úgy alakul, hogy elindul egyszerre poolSize számú feldolgozás (természetesen csak akkor, ha van annyi feldolgozandó feladat), majd befejeztével ha maradt még elindítatlan szál, akkor azokat is elkezdi futtatni. Fontos megjegyezni, hogy ilyenkor új szálak keletkeznek, de mindig csak maximum poolSize szál fut egyszerre. A visszatérés eredményei szintén egy Future listában kapjuk meg. A további felhasználás megegyezik a fentebb leírtakkal. Másik lehetőség az Executors. newCachedThreadPool(). Használata megegyezik a FixedThreadPool-éval, azonban a belső végrehajtás eltérő. Lényege, hogy amikor új szálra van szükség, akkor először megnézi, hogy a már létrehozott szálak között van-e olyan, ami már befejezte tevékenységét, ekkor abban a szálban indítja a végrehajtást. Ha nincs szabad, 25
Java Lokális Erőforrás Ütemező akkor új szál kreálódik. Az implementáció gondoskodik a memória felszabadításról is. Amennyiben egy szál alapesetben 60 másodpercig tétlen és nem kerül felhasználásra, a menedzser megszünteti. Így elkerülhető, hogy egy nagyobb terheltség esetén felszökő szál mennyiség a későbbiekben indokolatlanul foglalja a memóriát. Ez az implementáció akkor hoz látványos teljesítménynövekedést, ha sok, rövid aszinkron műveletet kell végrehajtanunk. (Vannak még egyéb lehetőségek, például ütemezett végrehajtású threadpool létrehozása.)
2.4.3. Queues Alapvetően két részre bonthatjuk a csomagban található sorok szerkezetét. Blokkoló és nem
blokkoló
implementációk.
Nem
blokkoló
szálbiztos
megoldás
esetén
a
ConcurrentLinkedQueue osztályt érdemes választanunk. FIFO (First In First Out) sort valósít meg. Blokkoló sor esetében a BlockingQueue interface öt implementációja közül választhatunk (LinkedBlockingQueue, ArrayBlockingQueue, SynchronousQueue, PriorityBlockingQueue, és DelayQueue). Ezen implementáció is FIFO sort valósítanak meg. Amennyiben szükségünk lenne LIFO (Last In First Out) megoldásra, akkor a BlockingDeque egyetlen implementációja marad.
2.4.4. Timing Időzítések megvalósítására szolgál. Egész pontosan idő alapú műveletek indítására alkalmas. Bizonyos időegység eltelte után valamilyen feladatot elindít, jelez. Az időegységet meghatározhatjuk akár nanosecundumokban is. Amint a beállított idő eltelt a lehető legrövidebb késleltetést produkálva következik be a jelzés és értesítés.
2.4.5. Synchronizers Alapvető
szinkronizációs
megvalósításokat
biztosít.
Például
Semaphore,
vagy
CountDownLatch. Ezek mind egyszerű megvalósítások, gyakorlatilag addig blokkolnak, amíg nem kapnak egy speciális szignált, esetleg bizonyos feltétel nem teljesül. CountDownLatch esetében pedig egy bizonyos szám eléréséig (például események hatására csökken egy számláló, és ha eléri a nullát, akkor megszűnik a blokk). 26
Java Lokális Erőforrás Ütemező
2.4.6. Concurrent Collections ConcurrentHashMap: Szálbiztos Map megvalósítás. Konstruktorának speciális paramétere segítségével megadhatjuk, hogy mennyi konkurens hozzáférést engedélyezhetünk. ConcurrentSkipListMap ConcurrentSkipListSet CopyOnWriteArrayList CopyOnWriteArraySet A felsorolt elemek közös jellemzője, hogy biztosítják az elvárt konkurens hozzáférést, és konzisztens adatokat szolgáltatnak.
27
Java Lokális Erőforrás Ütemező
3. Java Lokális Erőforrás ütemező (DGRID) 3.1. Alapkoncepció Szerver oldal: Core: A rendszer lelke. Feladata, hogy menedzselje a számítást. A kliensekkel kiépíti és tartja a kapcsolatot. FragmentStore: osztott erőforrásként jelenik meg. Tárolja, a rendszerben megjelenő töredék számításokat (vagy számítási kérelmeket), továbbá nyilvántartja, hogy az adott pillanatban mennyi töredék vár munkára, mennyi van éppen feldolgozás alatt, pontosan melyik töredék melyik kliensnek van kiosztva. ResourcesManager: Erőforrás regisztráló és értesítő szerepet tölt be. (Tehát alapadatokat gyűjt a kliensről, majd a tényt, hogy csatlakozni szeretne egy erőforrás továbbítja azon résztvevőknek, melyek feliratkoztak az eseményre.) CentralResources: Központi erőforrás nyilvántartó. Mindig pontos képet kaphatunk az aktuális kapcsolódó erőforrásokról, azok tulajdonságairól. Több fajta klienst különböztetünk meg a szerepkörük szerint: Producer: Meghatározza a részfeladatokat (egy önálló elvégzendő számítási egység). Worker: Számítási feladatot végez. Összesítő: Az elvégzett részfeladatokat összegzi, ez adja a számítás végeredményét. Az elgondolás alapötlete, hogy egy kitüntetett számítógép (szerver) menedzseli az elosztott számítást. A kommunikáció hálózat útján történik. A jelenlegi fejlesztett rendszer TCP/IP alapon kommunikál, de a rendszer olyan interfészeket és struktúrát kínál, mely segítségével könnyedén létrehozható hozzá más üzenetküldési mód (Például UDP). A szerver végzi a kliensek szerepköreinek szétosztását. Ez a szerver első válasza a kliensekhez, majd utána bizonyos inicializáló adatokat küld, mely nélkülözhetetlen a kliens későbbi munkavégzése során. Az adatok változatosak lehetnek, minden problémánál más és más.
28
Java Lokális Erőforrás Ütemező
Fontos eleme a műveletnek, hogy valamilyen egységes módon lemérjük a kliensek processzor teljesítményét. Ez abban az optimalizációs törekvésben segít, hogy erősebb processzor egységnyi idő alatt több számítást tud elvégezni. Nem is beszélve arról a tényről, hogy manapság ritka az egymagos processzor, ezért további párhuzamosítást hajthatunk végre, ha az adott erőforrás összes magját ki tudjuk használni, cél a 100%-os processzorterhelés (illetve minél jobban közelíteni). A benchmarkhoz a Scimark2 nevezetű ingyenes java könyvtárat használom (a Scimark2 csak az egymagos teljesítményt méri). A teszt elvégzése után egy kalkulált pontszámot küld vissza a kliens, illetve néhány dolgot a processzor paraméterei közül. Egyik nagyon fontos adat a magok száma. A benchmark pontszám és a magok száma alapján a szerver különböző kiegészítő számításokat végez, a valódi számítási teljesítmény pontos meghatározása végett. Az utolsó lépés, hogy az így kapott eredményt átkonvertálja töredék számmá. A töredék szám azt jelzi, hogy a kliens egy számítás alkalmával hány darab töredéket fog kapni egyetlen számításhoz. Tehát ezeket egyszerre kell feldolgoznia, és csak akkor jelez a szervernek, ha mindet feldolgozta. Értelemszerűen a válasza több feldolgozott töredék eredményei lesznek. Ezen kitérő után visszatérek a feldolgozás menetének az ismertetéséhez (az inicializálás, és erőforrás teljesítmény meghatározása után következő feladatokhoz). A kliensek várakoznak a szerver parancsára. Amikor az erőforrások mennyisége elér egy meghatározott minimális szintet, akkor kezdődik meg a tényleges számítási művelet. A kitüntetett gép sorban elindítja a producereket, workereket és az összesítőt. A producerek termelik a feldolgozandó töredékeket, melyet küldenek a szerver felé. A szerver berakja egy osztott sorba, ahonnan a workerek veszik le ezeket a töredékeket. Egy workernek annyit próbál juttatni a szerver, mint amennyi a meghatározott töredék pontszáma. Ha nincs elegendő töredék, akkor kevesebbet kénytelen feldolgozni, ha egyáltalán nincs töredék, akkor pedig várakozik, ameddig termelődik. Ez az állapot a számítás elején valószínűsíthető, később a producereknek meg kell előzniük a workerek fragment feldolgozó képességét, és mindig lenni kell elég töredéknek, hogy maximalizált legyen a feldolgozás sebessége. Az nem probléma, ha a producerek aránytalanul gyorsabbak, mert a sor fölülről korlátolt, és ha betelik, akkor várakoznak, míg üresedés nem következik be. Fontos a gördülékeny munkavégzéshez az optimális paraméterek meghatározása. A workerek munkájuk végeztével, visszatérnek az eredményekkel. A szerver azonnal 29
Java Lokális Erőforrás Ütemező berakja az összesítendő töredékeket tartalmazó sorba, a workernek új munkát oszt ki, amennyiben még van feldolgozandó töredék. Az ideális, hogyha mindig van elegendő kapacitás, és üresedésre történő várakozás nem alakul ki. Az összesítési procedúrának nagyságrendekkel kisebb erőforrás igényűnek kell lennie, mint a workerek feladatának. További optimalizációs eljárás, hogy az összesítő, nem a meghatározott pontszámának megfelelő töredéket emel le, hanem üríti a sort és minden benne lévő darabot elkezd feldolgozni. Ha ezzel végzett, akkor visszaadhat némi részeredményt, de csak abból a célból, hogy esetleges végzetes hiba esetén vissza lehessen állni egy bizonyos feldolgozottsági szintre, ne kelljen elölről kezdeni a számítást. Végezetül, ha minden egyes töredék összesítése befejeződött, akkor a teljes kalkuláció végeredményével tér vissza. Ezután lehetőségünk van az eredmények prezentálására. Megfelelő kiterjesztési pontot nyújt a szerver, melyet természetesen a felhasználónak kell implementálni. A későbbiekben ismertetek két megoldást is, az egyik a standard outputra, míg a másik egy fájlba menti az eredményt. Ezután a szerver bontja a kapcsolatokat, végére értünk a feldolgozásnak, nem maradt egyéb elvégzendő feladat. Egyéb események is történhetnek a feldolgozás közben, például kieshetnek erőforrások. Érdemes a producereknek és az összesítőnek megbízható gépeket választani. Ezek kiesése esetén nem folytatódhat a számítás. Amikor azonban worker kliens szakad le valamilyen küldő hiba okán, akkor a szerver érzékeli, hogy mi történt, és megteszi a megfelelő lépéseket. A feldolgozás alatt álló töredék tárolóban találhatóak kliensekhez rendelve a munka alatt álló részek. Innen törlőnek és visszakerülnek a feldolgozandó töredékek tartalmazó sorba. Ezután már csak az erőforrások nyilvántartásából kell eltávolítani a leszakadt gép jellemzőit. A részletezett mechanizmusnak köszönhetően, nem lesz feldolgozatlan töredék, hiszen azon elemek, melyek elvesztek, újra bekerültek a feldolgozandók közé, és majd egy másik kliens újból meg fogja kapni. A folyamat leírása A szerver elindul. A hallgató portot tetszés szerint beállíthatjuk. Várakozik a gépek érkezésére. Ezután csatlakozhatnak a kliensek. A futás első fázisában a szerver csak gyűjti az erőforrásokat. Erre azért van szükség, mert csak úgy van értelme a tényleges számítást elkezdeni, ha rendelkezünk egy bizonyos minimális erőforrással. A minimális erőforrás infrastruktúra beállítása egy konfigurációs fájlon keresztül történik. Itt megadhatjuk, hogy hány darab producer, worker szükséges a tényleges számítás elindításához. Fontos
30
Java Lokális Erőforrás Ütemező megjegyezni, hogy a jelenlegi koncepcióban csak egy darab összesítő lehetséges, ugyanis több engedélyezése felvet egy olyan problémát, hogy még ezen feldolgozók eredménye sem végleges, azokat újból további összesítésnek kell alávetni és így tovább. Így tehát az egyszerűsítés jegyében és abból kiindulva, hogy az összesítés (hasonlóan a töredékek gyártásához) kis erőforrás igényű feladat, melyet egy gép is el tud végezni, nem lesz szűk keresztmetszet. Azonban azt kontrollálhatjuk, hogy mely gép legyen az összesítő. Ezt szintén a konfigurációs állományban állíthatjuk be. A tervezéskor elsődleges szempont volt, hogy egy olyan balanszot lehessen kialakítani a számítás futtatására, mely a hálózati kommunikációt minimalizálja, a workerek processzor terhelését maximalizálja. A workereknek ne kelljen a producerekre várniuk. Mindig legyen feldolgozandó elem, amíg a számítás tart. Az összesítőknél pedig az a kívánatos, hogy rövid idő alatt képesek legyenek kiüríteni (és a tartalmát alacsony szinten tartani) az összegzésre váró sort. Ha egy csatlakozási kérelem érkezik, akkor a szerver regisztrálja. Tételesen ellenőrzi, hogy milyen pozíciók vannak már betöltve, és ennek megfelelően dönt a szerepkörről. Az elsődleges az összesítő ellenőrzése, hiszen dedikált gép töltheti be ezt a szerepet, ezért logikailag így helyes, hogy először azt nézzük meg, hogy az összesítő már csatlakozott-e. Ha nem akkor megvizsgáljuk a címét egyezik-e a megadottal. Igen esetén regisztrálásra kerül, mint összesítő. Ezután érkező gépek a gyártó helyeket töltik fel. Mikor már minden hely betelt (ezt szintén konfigurációs fájlból szabályozhatjuk), akkor innentől kezdve már minden csatlakozó erőforrás a worker szerepkört tartalmazza. Még az az említett ellenőrzés zajlik ebben az állapotban, hogy a workerek száma elére-e a minimális szintet. Amikor még nem, akkor a szerver gyűjtő fázisa zajlik. Amikor pontosan megvan a minimális infrastruktúra, egy figyelő értesíti erre az eseményre feliratkozott résztvevőket. Egy ilyen résztvevő mindenképpen a központi mag lesz, ami ilyenkor elindítja a tényleges számítást. Ezután a szerver gyűjtő fázisa véget ér, és innentől kezdve az újonnan érkező erőforrásokat a számításhoz csatlakoztatja. A szerver a klienseknek egyedi azonosítót állít elő (nem globálisan egyedi id, csak a rendszeren belül egyedi) melyek alapján tárolja azokat. Egész pontosan egy ConcurrentHashMap-ben, melynek kulcsa az egyedi id, és válaszul kapjuk a kliens paramétereit. A három szerepkörnek megfelelően három darab Mapet használok. A 31
Java Lokális Erőforrás Ütemező részfeladatok tárolása sorokban történik, a konkrét implementáció a Concurrent csomag LinkedBlockingQueue osztályával történik. Látható, hogy ezek rendkívül fontosak a konzisztens szerver kontextus biztosításához. Minden olyan helyen, ahol konkurens hozzáférés történik, atomikus osztályokat használok, illetve ahol több műveletnek szükséges lefutnia egyszerre, ott szinkronizációt alkalmazok. Általános törekvés, hogy a szinkronizációs blokkok a lehető legkisebbek legyenek, továbbá inkább alkalmazzunk több különálló lockot elkerülve azt a hibát, hogy amikor csak egy speciális szakaszt kell védenünk, akkor az egész objektumot lezárjuk, és más egyéb metódusokhoz történő hozzáférés esetén is várakoztatva lesz az adott szál, holott ez nem lenne szükséges. Általánosságban elmondható, hogy a magas szintű API használatával kisebb a deadlock (két erőforrás kölcsönös egymásra várása, ezáltal a zárolás sosem szűnik meg) bekövetkezésének valósznűsége. Véleményem arra alapozom, hogy sokkal egyszerűbben programozhatunk multithreading megoldásokat. Egyszerűbb átlátni a programsorokat, egyre inkább a logikára koncentrálhatunk. Továbbá a modern IDE-kben már van automatikus deadlock detektálás. Természetesen még ilyenkor is alapos ismeretek szükségesek ebben a témakörben.
32
Java Lokális Erőforrás Ütemező
3.2. Osztálydiagram és leírás A bemutatás a durvább felbontástól a részletesebb felé történik. A projekt szinttől kezdve eljutunk az egyes osztályok közötti kapcsolatokig, ismertetésre kerülnek a működési logikák.
3.2.1. Project struktúra és a projectek közötti függőségek
1. ábra
Szerver oldal: server_parent GRIDServer GRIDCommon ServerUsageDemo KörforgalomSzerver
33
Java Lokális Erőforrás Ütemező
Kliens oldal: client_parent GRIDClient GRIDCommon ClientUsageDemo KorforgalomKliens A server_parent és a client_parent maven gyűjtő projektek. Mindösszesen egy pom.xml-t tartalmaznak. Felsoroljuk benne az egyéb projekteket, melyek összetartoznak (modulok). Ezzel kapcsolhatóak össze a különálló részek, illetve azt is lehetővé teszi, hogy a dependenciákat megadhassuk két projekt között (ezt az aktuális projekt pom.xml-jében tehetjük meg). Egyéb hasznos funkciója, hogy ennek az egy projektnek segítségével a benne lévő modulokat is le tudjuk buildelni. Átvizsgálja a modulok közötti hierarchiát, és a megfelelő sorrendben történik a fordítás. Fordítás előtt a megfelelő külső függőségek ellenőrzése is megtörténik, szükség esetén letöltődnek. További egyedi előfordító tevékenységeket is végez (például profil szerinti property kicserélések, stb.). GRIDCommon projekt, mint ahogy az elnevezésből is látszik, a közös interfészeket és osztályokat tartalmazza. Tehát olyan részeket, melyekre a szerver és kliens részről is szükség van, melyekkel mind két fél operál. Bevezetésével elkerülhetőek a redundáns osztályismétlődések és a cirkuláris dependenciák. Ide tartoznak a kommunikációt megvalósító, az egyes gépek teljesítményének felmérésére szolgáló, valamint a három alapvető klienst leíró programrészek. Speciális rész a hu.dsoftware.grid.common.config csomag. Ebben található a property fájlokat menedzselő osztály, illetve a @Log egyedi annotáció és a feldolgozója. GRIDClient és GRIDServer projektek funkciója értelemszerű. Bővebb részletezésére a későbbiekben kerül sor. Továbbá két-két összetartozó projekt a ServerUsageDemo <--> ClientUsageDemo valamint a KörforgalomSzerver <--> KorforgalomKliens. Az előbbi egy példa alkalmazás, szinte triviális probléma. Ezért összességében rendelkezik a rendszer által teremtett
34
Java Lokális Erőforrás Ütemező követelményekkel (tehát elosztható producer, worker, összesítő feladatokra). Egyrészt szemlélteti működés közben a rendszert, másrészt fejlesztéskor is rendkívül hasznos volt. Az egyes részfeladatok implementálása után biztosította a tesztlehetőséget. A másik projekt kettős pedig egy gyakorlati alkalmazás, mely már valós probléma alapján hasznos munkát végez, nevezhetjük az első éles alkalmazásnak. Ez a két projekt kellő betekintést enged, hogy esetlegesen egy további felhasználás esetén milyen lépésekre van szükség, hogyan kell az alap rendszert egyedire szabni.
3.2.2. A teljes Common projekt csomag struktúra
2. ábra
hu.dsoftware.grid.common.bean.clients:
a
csatlakozott
kliensek
közös
tulajdonságait foglalja magában. hu.dsoftware.grid.common.benchmark: gépek
processzor teljesítményének a
meghatározására szolgál. Lehetőség van többfajta módszer implementálására is. 35
Java Lokális Erőforrás Ütemező hu.dsoftware.grid.common.communication: a gépek közötti kommunikáció alap programrészei közös
hu.dsoftware.grid.common.config:
konfigurációs
programrészek
(PropertesManager, speciális @Log annotáció) hu.dsoftware.grid.common.messages: kommunikáció során az üzenetek struktúráját határozza meg. Szerepköröket tartalmazó csomagok o hu.dsoftware.grid.common.producer o hu.dsoftware.grid.common.summarizer o hu.dsoftware.grid.common.worker
3.2.3. A teljes Common projekt osztály struktúra
3. ábra
A teljes nagyságú kép megtalálható a linkre kattintva. Azért került be a teljes struktúrát ábrázoló osztálydiagram, mert így nyomon követhetőek a kapcsolódások, csomagok elrendeződése és tartalma, valamint az osztályhierarchia.
36
Java Lokális Erőforrás Ütemező
3.2.4. Common projekt részekre bontott osztálydiagram
4. ábra
A közös csatlakozott egység jellemzői. A ROLE enumeráció foglalja magába a szerepköröket. A ConnectedMember interfész biztosítja a megfelelő metódusokat és az absztrakciót. Egy alap implementációja a DefaultConnectedMember. Lényege, hogy tartalmaz egy ConnectionProperties nevű adattagot, mely a kapcsolat illetve a kliens egyéb lényeges tulajdonságait tárolja. Továbbá a hálózati kapcsolatot megvalósító taggal rendelkezik. A szerepkör is meg van határozva, tehát amikor a bean létrejön, már megtörtént a tényleges besorolása, hogy milyen tevékenységet fog végezni. ProcessorParameter osztály pár alapvető és fontos információt tárol. Processzor megnevezése, processzor magok száma. A score adattag csupán a benchmark eredményt tartalmazza. Ezeket a számokat a csatlakozás után a kliens küldi el, amikor véget ér a tesztelő algoritmus. A konkrét rendszerbeli erőviszonyát pedig a szerver számítja ki. Ez jelen esetben annak a mérőszáma, hogy hány darab számítási töredéket fog kapni, amit
37
Java Lokális Erőforrás Ütemező egyszerre kell feldolgoznia. Miután végzett ismét a szerverhez fordul, és ugyanennyi töredéket fog kapni. Értelemszerű, hogy minél nagyobb ez a szám, annál nagyobb teljesítményű számítógépről beszélünk. Fontos megjegyezni, hogy mivel ezek tipikusan adattároló osztályok, ezért prototípus scope-al rendelkeznek.
5. ábra
Az előző ábra kiegészítése. Látható, hogy a három szerepkör, három interfészként jelenik meg. Közös ős a ConnectedMember. Továbbá egy-egy konkrét megvalósításra is szükség volt. Ezek a Default előtaggal ellátott osztályok. Az adott interface implementálásán túl kiterjesztik a DefaultConnectedMember osztályt. A tényleges felhasználáskor ezeket a legalul elhelyezkedő osztályokat kell kiterjeszteniük az egyedi számítást végző kliensek implementálásakor. Ez a hierarchia biztosítja a szükségszerű absztrakciót. Tehát az új szerepköröket (amelyek tényleges számítást végeznek) nem is szükséges a szerver oldalra is továbbvinni, hiszen a szerver csak mint absztrakt interfész látja a résztvevőket. Sőt akár 38
Java Lokális Erőforrás Ütemező a ConnectedMember megjelenési formát is felvehetik, így ahol lehetséges egységesen is kezelhetőek. A rendszer fejlesztésekor mindig a lehető legkisebb láthatósági körrel használjuk a szerepkörök tagjait.
6. ábra
Üzenetek alaposztálya, és üzenet típusok. Alapvetően az üzenet két dolgot kell hogy tartalmazzon. Az egyik egy int mező, mely az üzenet típusát jelöli a másik pedig maga az üzenet. Az értelmező a típus alapján következtet az üzenet törzsére. Megkülönböztetünk 39
Java Lokális Erőforrás Ütemező két fajta csoportot. Szerver üzenetek: ASSIGN_ROLE INITIALIZE_DATA DO_JOB FINISH_JOB Kliens üzenetek SCORE_REPORT WORK_FRAGMENT WORK_RESULT_FRAGMENT SUM_RESULT A felsorolás tulajdonképpen idősorrendben van, tehát egy normál végrehajtás során ezek kerülnek elküldésre. Elsőként az ASSIGN_ROLE, melyet a kliens csatlakozása után küld a szerver. Meghatározza mi lesz a szerepköre az újonnan érkezett erőforrásnak, és ezzel az üzenettel tudatja a klienssel. Sikeres fogadás után azonnal egy újabbat küld, melyben a szükséges inicializációs adatok szerepelnek. Ennek a típusa az INITIALIZE_DATA. Miután az inicializálás rendben lezajlott a kliens lefuttatja a processzor benchmarkot, és az eredménnyel tér vissza (SCORE_REPORT). Ezután a szerver ellenőrzi, hogy van-e még munka, nyílván ez a csatlakozás utáni pillanatokban még valószínűsíthetően lesz, de az algoritmus működése miatt fontos. Abban az esetben, ha van munka a következő üzenet típus a DO_JOB lesz. A DO_JOB típus üzenet objektuma eltérő lehet. Egyrészt problémánként eltérő, másrészt szerepkörtől is függ. Ahogyan az is, hogy a munka végeztével a kliens milyen üzenettel tér vissza. A közös az, hogy a szerver ismét ellenőrzi, hogyan áll a teljes számítás adott szerepköri része, ha nincs munka, akkor a FINISH_JOB üzenetet küldi végül a szerver, mely után a kliens lekapcsolódik. Szerepkörök szerint a kliens üzenetek a DO_JOB fogadása és az elvégzett munka után azonnal megpróbálja prezentálni a szerver felé. Producer esetében az eredmény egy feldolgozandó töredék vagy töredékek lesznek. Az üzenet típusa WORK_FRAGMENT. Értelemszerűen a worker esetében WORK_RESULT_FRAGMENT lesz, míg összesítők esetében SUM_RESULT.
40
Java Lokális Erőforrás Ütemező
7. ábra
Alapvető hálózati kommunikációt támogató interfész és absztrakt megvalósítás. Arra kényszeríti a fejlesztőt, hogy még származtasson le egy osztályt a NetworkCommunicationből. Jelenleg a ServerSocketCommunication osztály az egyetlen ilyen osztály a rendszerben.
8. ábra
A számítógépek teljesítményének felmérésére szolgáló interfész és megvalósítása. Az interfész lehetővé teszi, hogy akár többféle implementációt is megvalósíthatunk. Az
41
Java Lokális Erőforrás Ütemező alapértelmezett implementáció a Scimark2 ingyenes java library segítségével valósítottam meg. A Scimark2 egy összetett Java benchmark a processzor teljesítményének mérésére. A tesztelő numerikus algoritmusokat használ, melyek előfordulnak napjaink matematikai és mérnöki szoftvereiben. Alapvetően öt különböző tesztet hajt végre: Gyors Fourier transzformáció (FFT = Fast Fourier Transform); Jacobi Successive Over-relaxation (SOR); Monte Carlo integráció; Ritka mátrix szorzás (Sparse matrix multiply); Sűrű LU faktorizáció (dense LU matrix factorization); A felsorolt öt darab teszt mindegyike egy bizonyos pontszámmal tér vissza. Minél nagyobb ez a pontszám, annál nagyobb a processzor teljesítménye. Fontos megjegyezni, hogy a teszt csak az egy magos teljesítményt méri. A szerver a tényleges teljesítmény méréséhez ezt az eredményt még megszorozza egy magonkénti súlyozott értékkel.
9. ábra
A speciális @Log annotáció és a feldolgozó osztálya (LoggerPostProcessor). Lényege, hogy az SLF4J Logger interfészét használva, egyszerűsítsük a következő tipikus
42
Java Lokális Erőforrás Ütemező deklarálást: private static final Logger log=LoggerFactory.getLogger(ClassName.class);
A @Log annotáció csak egy jelzés, melyet a BeanPostProcessor osztály megkeres és az adott mezőbe injektálja az típusának megfelelő loggert. A művelet Reflection API használatával történik. Megjegyzendő, hogy a BeanPostProcessor interfészt a Spring framework biztosítja, így könnyen, egyszerűen kezelhetjük az annotációkat. Tehát csak regisztrálnunk kell a feldolgozót a Spring Java configban és a konkrét metódus hívásokról (postProcessBeforeInitialization,
postProcessAfterInitialization)
a
keretrendszer
gondoskodik. Az egyszerűsített Logger deklaráció: @Log private static Logger log;
A konkrét implementáció nem követi el azt a hibát, hogy minden egyes @Log annotáció megtalálásakor egy új Logger példányt injektál be, hanem egy központi Hashmapben tárolja a Loggereket (, Logger>) és csak akkor hoz létre új példányt, ha a HashMapben nem található. Ekkor ez a példány bekerül. Találat esetén a kapott példány lesz az annotált mező referenciája. PropertiesManager feladata, hogy egyszerűsítse és megkönnyítse a property fájl olvasását, az értékek felhasználását. Megadható a kívánt állomány helye, melyet felolvas. Az értékek kinyerésére a findProperty metódust használhatjuk.
43
Java Lokális Erőforrás Ütemező
3.2.5. A teljes szerver csomag struktúra
10. ábra
hu.dsoftware.grid.server.communication: Kommunikációhoz szükséges osztályokat és interfészeket tartalmaz. hu.dsoftware.grid.server.core: Központi csomag, a szerver legfontosabb komponensei találhatóak benne. hu.dsoftware.grid.server.core.listener hu.dsoftware.grid.server.fragment hu.dsoftware.grid.server.network.listener hu.dsoftware.grid.server.operationcontrol hu.dsoftware.grid.server.resources.config hu.dsoftware.grid.server.resources.config.bean: Konfigurációs beállításokat tartalmazó Java osztályok. hu.dsoftware.grid.server.utility: Szerverre jellemző általános metódusok és konstansok gyűjtőhelye. A teljes nagyságú kép megtalálható a linkre kattintva.
44
Java Lokális Erőforrás Ütemező
3.2.6. A teljes szerver osztály struktúra
11. ábra
A teljes nagyságú kép megtalálható a linkre kattintva. Azért került be a teljes struktúrát ábrázoló osztálydiagram, mert így nyomon követhetőek a kapcsolódások, csomagok elrendeződése és tartalma, valamint az osztályhierarchia.
45
Java Lokális Erőforrás Ütemező
3.2.7. Szerver részekre bontott osztálydiagram
Kommunikació
12. ábra
46
Java Lokális Erőforrás Ütemező
13. ábra
14. ábra
47
Java Lokális Erőforrás Ütemező
15. ábra
48
Java Lokális Erőforrás Ütemező
16. ábra
Csak egy példányban létezik a rendszerben. Osztott erőforrásokat tárol, ezért az érzékeny adatok szálbiztos (kölcsönös kizárást és konzisztenciát biztosító) objektumokban tárolódnak. A feldolgozásra váró fragmentek a DistributedFragmentQueue, az összesítésre váró töredékek pedig a ResultFragmentQueue sorban tárolódnak. További adattagok szükségesek a feldolgozás kontextus nyilvántartásához. produceFragmentOnExecuting, WorkFragmentOnExecuting,
SummarizeFragmentOnExecuting
ConcurrentHashMapek
tárolják a kliensnél lévő töredékeket. Segédmetódusok és változók mutatják, hogy az adott pillanatban mennyi töredék van végrehajtás alatt és mennyi feldolgozása fejeződött be.
49
Java Lokális Erőforrás Ütemező
17. ábra
Nyilvántartja a kapcsolódó erőforrásokat, illetve mint egy delegált képes lekérdezni a FragmentStore-on keresztül a végrehajtási állapotokat (Szerepkörönként mennyi az elkészült töredék, és éppen mennyi feldolgozása van folyamatban). Csak egy példányban lehet jelen. Mivel ezek az adatok szinte az egész rendszer területéről elérhetőeknek kell lennie, ezért az erőforrások ConcurrentHashMap-ben tárolódnak, minden egyéb számlálót pedig az atomikus osztályok reprezentálnak annak érdekében, hogy a többszálú hozzáférések mellett is az adat konzisztencia megmaradjon.
50
Java Lokális Erőforrás Ütemező
18. ábra
Az erőforrásokkal kapcsolatos események a ResourceManager-en keresztül futnak át. Az egyik ilyen esemény az új erőforrás csatlakozása. A működése két fázisra bontott. Az első, amikor az frissen indított szerver nem rendelkezik elegendő erőforrással, hogy a tényleges elosztott számítás megkezdődhessen. Ekkor az erőforrás gyűjtés fázisában vagyunk (collectResources). Új erőforrás csatlakozásakor a ResourceManager regisztrálja a megfelelő pozícióba. Majd ellenőrzi, hogy a minimális erőforrás szint betöltődött-e az újonnan
regisztrált
klienssel.
Ha
igen,
akkor
minden
feliratkozott
ResourceAvailableListenert értesít erről az eseményről, és innentől a második fázis lép életbe. Ha a regisztrációval még nem tesz eleget a jelenlegi erőforrás infrastruktúra a minimális követelményeknek, akkor folytatódik az első fázis. A második fázis tulajdonképpen abból áll, hogy a már futó szimulációhoz kell hozzácsatolni újonnan érkezett erőforrásokat (attachResourceToTheActiveCalculation). Itt történik a szerepkör megválasztása és a klienshez rendelése is. A ResourceManager 51
Java Lokális Erőforrás Ütemező singleton.
19. ábra
A rendszer központi. Feladata, hogy menedzselje a számítást. A kliensekkel kiépíti és tartja a kapcsolatot. A főbb komponensek mind kapcsolódnak a Core-hoz. A rendszerben bekövetkező események kezelésében részt vesz.
52
Java Lokális Erőforrás Ütemező
20. ábra
A szerver főbb komponensei. A ClientDisconnectInspector a kliens lecsatlakozása után megállapítja, hogy a mi az oka. Ha a számítás befejezése miatt fejeződött be a kommunikáció, akkor a NORMAL válasszal tér vissza. Minden egyéb helyzetben a lecsatlakozás hiba eredménye. Ekkor a LOST (elveszett a kliens) válasszal tér vissza. A PerformanceCalculator a kliens által végzett benchmark pontszáma és a magok száma alapján meghatároz egy újabb pontszámot, majd ezt átszámítja fragment darabszámmá. Fontos, hogy a kalkuláció során a töredék szám minimuma egyenlő magok számával.
53
Java Lokális Erőforrás Ütemező
21. ábra
A szerver főbb komponensei és a köztük lévő kapcsolat (21. ábra)
54
Java Lokális Erőforrás Ütemező
3.2.8. A teljes Kliens csomag struktúra
22. ábra
hu.dsoftware.grid.client.resources.config:
Konfigurációs
osztályok
találhatóak
benne. hu.dsoftware.grid.client.utility: Kliensre jellemző egyszerű metódusok, konstansok. hu.dsoftware.grid.client.work: A kliens munkafolyamatainak vezérléséért felelős interfészek, osztályok.
55
Java Lokális Erőforrás Ütemező
3.2.9. A teljes Kliens osztály struktúra
23. ábra
A teljes nagyságú kép megtalálható a linkre kattintva. Azért került be a teljes struktúrát ábrázoló osztálydiagram, mert így nyomon követhetőek a kapcsolódások, csomagok elrendeződése és tartalma, valamint az osztályhierarchia.
56
Java Lokális Erőforrás Ütemező
3.2.10.
Kliens részekre bontott osztálydiagram
24. ábra
A kliens szerkezeti felépítése és az osztályok közötti kapcsolat. A jelzett komponensek már korábban részletezésre kerültek.
57
Java Lokális Erőforrás Ütemező
25. ábra
Kliens oldali Spring bean konfigurációs fájl. Illetve a Constants osztály, mely jelen pillanatban csak egy bejegyzést tartalmaz. A klienshez tartozó property fájl elérési útját.
58
Java Lokális Erőforrás Ütemező
4. Használt könyvtárak listája névvel és verziószámmal aopalliance-1.0 asm-3.3.1 cglib-2.2.2 commons-io-2.4 commons-logging-1.1.1 logback-classic-1.0.10 logback-core-1.0.10 scimark-2.0 slf4j-api-1.7.3 spring-aop-3.1.2.RELEASE spring-asm-3.1.2.RELEASE spring-beans-3.1.2.RELEASE spring-context-3.1.2.RELEASE spring-core-3.1.2.RELEASE spring-expression-3.1.2.RELEASE
59
Java Lokális Erőforrás Ütemező
5. Szerver konfigurációja és használata A szerver használatát bemutató ServerUsageDemo projekt célja, hogy szemléltesse, hogyan tudjuk a szervert egyedi problémák megoldására felkészíteni. A következő sorokban bemutatom a kezdeti lépéseket és inicializálást, majd indítást. Látható, hogy mindösszesen három darab .java és egy property fájl szükséges a teljes konfigurációhoz és a teljes értékű futtatáshoz. Természetesen nem feledkezhetünk meg a pom.xml-ről sem, amennyiben mavenes project keretein belül szeretnénk felhasználni a rendszert (ajánlott). Egyéb esetben a project libraryk közé fel kell venni a GRIDServerwith-dependecies.jar vagy a GRIDServer.jar állományt. A két verzió csak a függőségek tartalmazásában tér el. Tehát az utóbbi esetben a hibamentes futtatáshoz nélkülözhetetlen az összes függőség linkelése is.
server.properties A tényleges számítás csak azután kezdődhet meg, ha kiépült egy minimális erőforrás infrastruktúra. Ilyen és ehhez hasonló property-k definiálására használható az állomány. A részletes lista: server.port: minimal.producer: A producerek minimális száma minimal.worker: A workerek minimális száma minimal.summarizer: Az összesítők minimális száma. Figyelem jelen állapotban ez a szám csakis 1 lehet. distributed.operation.producelimit: Abban az esetben szükséges megadni, ha nem készítünk külön CustomDistributedOperationControl megvalósítást. Ekkor a DefaultDistributedOperationControl osztályt fogja a Core használni a számítás lebonyolításához, és ez az osztály a fenti property-t keresi. conventional.resource.performance: Szabadon változtatható érték. Lényege, hogy ez a pontszám jelenti, hogy egy darab fragmentet vehet le az adott kliens a számára megfelelő sorról. Értelemszerű, hogy ha a teljesítmény mérőszáma kétszer nagyobb, akkor kettő darab töredéket kap egy számítás során. A pontos szabályokat és a pont 60
Java Lokális Erőforrás Ütemező alapján történő töredék átszámítás algoritmusát már az előző fejezetekben részleteztem. Saját számításom alapján egy átlagos számítógép esetében ~1365 körül mozog teljesítmény értéke. core.multiplicator: Az a szorzó tényező, mely a magok számának súlyozására szolgál. Például a tényleges teljesítmény számításakor az alap pontszámot nem többszörözhetjük meg szimplán a magok számával, mert esetleges szinkronizáció, processzor kommunikáció, a memóriához és egyéb erőforrásokhoz történő hozzáférések miatt a tényleges teljesítmény csökkenhet. Ennek a differenciálására szolgál a core.multiplicator. Az alapértelmezett ajánlás a 0,9-es érték. summarizer.address: Az összesítő IP címe. Ez alapján fogja kiosztani a szerver a csatlakozott kliensek között az összesítő szerepkört.
Main.java ApplicationContext appCtx = new AnnotationConfigApplicationContext(CustomSpringBeanConfig.class); DGridServer dGridServer = appCtx.getBean(DGridServer.class); dGridServer.startup();
Mindösszesen egy public static void main(String[] args) metódusból áll és az is rendkívül rövid és egyszerű. Először az applicationcontext-et kell példányosítanunk. Argumentuma a spring java configot tartalmazó osztály. A konstruktorának bármely változatát választhatjuk. Talán ez a megoldás a legegyszerűbb, hiszen elég egy paramétert átadnunk. Ekkor kötelező a saját spring konfigurációnkat kiterjeszteni a server alap konfigurációs osztályából. Ha nincs szükségünk egyáltalán erre, akkor a szerverét is megadhatjuk. Említést érdemel még az a megoldás is, amikor két (vagy több) különálló állományt adunk meg. Minden megközelítés kérdése, a konkrét használat már szubjektív döntés.
CustomDistributedOperationControl Egy példa, a saját DistributedOperationControl interfész megvalósítására és a @hu.dsoftware.grid.server.resources.config.CustomDistributedOperationControl
61
Java Lokális Erőforrás Ütemező annotáció használatára. Tehát ebben az osztályban implementált logika fogja a műveleteket (produce, work, summarize) kontrollálni. Injektálásra kerül a szerver Core osztályának distributedOperationControl adattagjába (mivel ezen az adattagon található meg a @DistributedOperationController annotáció).
CustomSpringBeanConfig A szerver SpringBeanConfig osztályának kiterjesztése. Szerepe azért fontos, mert itt regisztrálhatjuk az általunk írt osztályokat a spring bean container számára. Természetesen a
scope-ját
is
tetszés
szerint
variálhatjuk.
Jelen
esetben
a
CustomDistributedOperationControl beant definiáljuk: @Bean(name="customDistributedOperationControl") public DistributedOperationControl customDistributedOperationControl() { return new CustomDistributedOperationControl(); }
Csak ebben az esetben fogja a rendszer BeanPostProcessora megtalálni a felannotált osztályt és csak így tudja elvégezni a szükséges injektálást a szerver Core osztályába.
pom.xml Az egyetlen említésre méltó bejegyzés a függőségek között van, mégpedig a fejlesztett szerver maven függősége. <dependency> hu.dsoftware.grid <artifactId>GRIDServer 1.0-SNAPSHOT <scope>compile
62
Java Lokális Erőforrás Ütemező
6. Kliens használata A kliens használatát bemutató ClientUsageDemo projekt célja, hogy szemléltesse, hogyan tudjuk a klienst egyedi problémák megoldására felkészíteni. A következő sorokban bemutatom a kezdeti lépéseket és inicializálást, majd indítást. Természetesen rendkívül fontos, hogy a szerverrel szinkronban legyen a kliens, minden problémához külön szerverkliens páros tartozik. Látható, hogy mindösszesen öt darab .java és egy property fájl szükséges a teljes konfigurációhoz és a teljes értékű futtatáshoz. Természetesen nem feledkezhetünk meg a pom.xml-ről sem, amennyiben mavenes project keretein belül szeretnénk felhasználni a rendszert (ajánlott). Egyéb esetben a project libraryk közé fel kell venni a GRIDClientwith-dependecies.jar vagy a GRIDClient.jar állományt. A két verzió csak a függőségek tartalmazásában tér el. Tehát az utóbbi esetben a hibamentes futtatáshoz nélkülözhetetlen az összes függőség linkelése is.
client.properties Nagyon egyszerű és kevés adatra van szükség. server.address: A szerver IP címe. server.port: A szerver kommunikációs portja. Komplex probléma esetén természetesen az adott implementáció által megkövetelt egyéb property-k helye is lehet, de akár máshol is definiálhatóak, teljesen szabad kezet kapnak fejlesztők.
Szerepkörök saját implementációja A kliensnek központi eleme, hogy biztosítson módot a szerepkörök (gyártó, worker, összesítő) egyéni igényekre történő szabására. Mindegyik szerepkörnek megvan az alap osztálya, melyet ősként kell használnunk az implementáció során. Ezek az osztályok sokat segítenek, sok rész implementáció található bennük, így a lényegi egyéni logikai részre koncentrálhatunk. Sorrendben az osztályok: DefaultProducer: startInitialize, initialize, produce, result metódusokat kell
63
Java Lokális Erőforrás Ütemező implementálnunk. A startInitialze, initialize, result: DefaultWorker: Implementálandó metódusok: startInitialize, initialize, work, result. A work metódus felelős a számítási töredék feldolgozásáért. Tehát az egyik legfontosabb egység, amiért az egész rendszert életre hívtuk. Itt történik meg a hosszadalmas számítás szétterített feldolgozása, egyben a legtöbb processzoridőt igénylő rész. DefaultSummarizer: A három alapmetóduson kívül az eltérő az estimate. A részeredmények összesítése történhet ebben a blokkban. A startInitialize, initialize, result metódusok minden szerepkörnél megtalálhatóak. A startInitialize csak egyszer fut le a kliens életciklusa alatt. Ezért a minden számításhoz szükséges, ugyanakkor megegyező paramétereket és adatokat itt érdemes átadni. Az initialize pedig minden számítás előtt lefut. Itt állíthatjuk be az adott számításra jellemző dolgokat. Tipikusan a producerek által létrehozott fragmentek egyedi jellemzőit. A result metódus célja, hogy visszatérési értéke szolgáltassa a számítás végeredményét, ami visszakerül a szerverhez. Minden implementálandó metódus hívásáról a WorkManager gondoskodik. Nekünk csak a megfelelő és hibamentes működésről kell gondoskodnunk, illetve a paraméterek helyes megválasztáról.
Main.java Mindösszesen egy public static void main(String[] args) metódusból áll és az is rendkívül rövid és egyszerű. Először az applicationcontext-et kell példányosítanunk. Argumentuma a spring java configot tartalmazó osztály. A konstruktorának bármely változatát választhatjuk. Talán ez a megoldás a legegyszerűbb, hiszen elég egy paramétert átadnunk. Ekkor kötelező a saját spring konfigurációnkat kiterjeszteni a kliens alap konfigurációs osztályából. ApplicationContext appCtx = new AnnotationConfigApplicationContext(ClientUsageSpringConfig.class); DGridClient dGridClient = appCtx.getBean(DGridClient.class);
64
Java Lokális Erőforrás Ütemező Az ApplikacationContext példányosítás után máris lekérjük a DGridClientet. Ezen keresztül végezhetünk indítás előtti inicializációt, majd az indítást. A következő lépés, hogy a lekérjük a CustomizedProcessingUnitContainer osztályt. Segítségével injektálhatjuk be a gyártó, worker, összesítő általunk kiterjesztett megvalósítását. Ezen beaneket definiálnunk kell a ClientUsageSpringConfig osztályban, és névvel kell ellátnunk. @Bean(name = "distributedPrimeOperationProducer") @Scope("prototype") public Producer distributedPrimeOperationProducer() { return new DistributedPrimeOperationProducer(); } @Bean(name = "distributedPrimeOperationWorker") @Scope("prototype") public Worker distributedPrimeOperationWorker() { return new DistributedPrimeOperationWorker(); } @Bean(name = "distributedPrimeOperationSummarizer") @Scope("prototype") public Summarizer distributedPrimeOperationSummarizer() { return new DistributedPrimeOperationSummarizer(); }
Fontos, hogy prototype beanként definiáljuk az osztályokat. Ezek alapján visszatérek a Main.java
ismertetéséhez.
CustomizedProcessingUnitContainer
Ott
tartottunk,
osztályt,
és
hogy
definiáltunk
lekértük egyedi
a
szerepkör
megvalósításokat a saját Spring konfigurációs osztályban. Az injektálást oly módon végezhetjük, hogy a megfelelő metódus hívás paraméterének átadjuk a bean nevét. A WorkManager majd ez alapján operál a bean conténerrel. customizedProcessingUnitContainer.setCustomizedProducerQualifier(" distributedPrimeOperationProducer"); customizedProcessingUnitContainer.setCustomizedWorkerQualifier("di stributedPrimeOperationWorker");
65
Java Lokális Erőforrás Ütemező CustomizedProcessingUnitContainer.setCustomizedSummarizerQualifier ("distributedPrimeOperationSummarizer");
Nem maradt más hátra, mint a kliens indítása dGridClient.start();
Az utóbbi két fejezet minden szükséges információt tartalmaz (konvenciók, konfigurálás, alapvető struktúra). Ezek birtokában bárki felhasználhatja a rendszert saját megoldandó számításaihoz.
66
Java Lokális Erőforrás Ütemező
7. A rendszer felhasználása Az alábbi fejezetben bemutatjuk az elkészült rendszer két felhasználását. Az egyik egy egyszerű, de jól szemléltető alkalmazás, a másik egy tényleges gyakorlati implementáció valós problémával. A leírásból kiderül, hogy a két eset jelentősen eltér egymástól, mégis jól alkalmazható a DGRID. Ez két tekintetből is fontos. Egyrészt a leírás és forráskód áttanulmányozása után, már bárki használhatja, saját igényeire formálhatja, új problémák megoldására készítheti fel a rendszert. Másrészt bizakodást adhat, hogy egyéb területeken is bevethető, akár egyetemi kereteken belül segítheti a kutatásokat a jövőben.
7.1. Példa alkalmazás Példa alkalmazásként a következő, egyszerű, mégis megfelelő szemléltetést nyújtó problémát választottam: A producerek egy előre meghatározott véletlen számot generálnak. A szám nagyságát jelző határok is előre beállítottak. Szóval a producer visszaküldi a generált számot, amit a szerver regisztrál és belerak az osztott sorba. Ezeket az elemeket kiosztja a workereknek, melyek feladata, hogy eldöntsék a kapott számról, hogy prím-e. A választ (0 vagy 1) visszaküldik, mire az bekerül egy másik osztott sorba, amelyeket az összegzők dolgoznak fel, nevezetesen, összeadják az értékeket. Végeredményként, megkapjuk, hogy a generált számok között, hány darab prímszám van.
7.2. Éles felhasználás 7.2.1. Feladat ismertetése A téma a Körforgalmi szimuláció. Szakdolgozat, melyet még Programtervező Informatikus BSc-n írtam. A könnyebb érthetőség kedvéért átemelném szakdolgozatom bevezetését idézet formájában. „Mindenki találkozhat egy utazás során a kedvelt forgalomszervezési megoldással, a körforgalommal. A szakdolgozat bemutatja a lépéseit, gondolatmenetét egy körforgalmi
67
Java Lokális Erőforrás Ütemező szimuláció számítógépi megvalósításának. Először tisztázzuk a szabályokat, törekszünk a valós körforgalomban jelen lévőket betartani, illetve beépíteni a saját rendszerünkbe, néhol azonban lesznek eltérések, elsősorban a komplexitás miatt. Felállítunk egy valóságot közelítő modellt és egy teljesen más elgondoláson alapulót. A két körforgalmi modell logikai hátterét és számítógépi megvalósítását kifejtjük. Végezetül teszteket végzünk, a kapott eredményeket értelmezzük, a két körforgalmi modellt összehasonlítjuk.” Pontos magyarázat és teljes ismertetés az eredeti szakdolgozatban: Miskolci Egyetem Szabó Endre Programtervező Informatikus BSc Matematikai Modellek szakirány Körforgalom Szimuláció 2010 A BSc-n készült szakdolgozatom fő feladata két modell felállítása, és köztük lévő különbségek feltárása, összehasonlítása. A diplomamunkámba azonban csak az egyik modellt emeltem át, a folytonos modellt. Ennek alapvető célja, hogy bár erőforrás igényes, de a valóságot próbálja közelíteni. Ennek érdekében bevezetésre kerültek különböző tulajdonságok, változók. Mint már említettem az alap témakör a Körforgalmi szimuláció. A folytonos modellben a szabályok megegyeztek a valós KRESZ szabályokkal. A vizsgált útszakasz két részből áll. Az egyik a bekötő útszakaszok, a másik a körgyűrű, melyhez a bekötő útszakaszok kapcsolódnak. A számuk, hosszuk, és kapcsolódási pontjuk megadható, valamint a körgyűrű nagysága is. Az utaknak még egy jellemzőjük van, mégpedig a járművek érkezési intenzitása. A konkrét implementációban exponenciális eloszlást követ a két jármű érkezése között eltelt idő eloszlása. Az exponenciális eloszlás lambda paramétere szabadon állítható (minden útszakaszra külön-külön). További paraméterek, melyek bevezetésre kerültek: Két autó között lehető legkisebb távolság álló helyzetben. A körforgalomban engedélyezett maximális sebesség. Haladó járművek esetén a követési távolság.
68
Java Lokális Erőforrás Ütemező Reakció idő: A sofőrökre jellemző némi késleltetés, amíg érzékelik a kialakult helyzetet és ezután reagálnak. A járművek gyorsulás értékei. A járművek lassulási értékei (fékezés intenzitása). A járművek hosszai. A szimuláció továbbá azon alapult, hogy a futás előtt meg kellett adni, hogy mennyi időpillanatig (másodperc) végezze a program a számítást. Másik fontos dolog, hogy egy darab futási eredményből még nem vonhatunk le érdemi konzekvenciákat, ezért többszöri futás esetén az eredmények átlagaival érdemes számolni. Beszédes számadat még ilyenkor az eredmények szórása (minél kisebb annál pontosabbnak nevezhető a szimuláció). A program a szimuláció alatt a következő adatokat és jellemzőket figyeli, melyeket összegez, és végül prezentál (a teljesség igénye nélkül): Átlagsebesség; Átlagos áthaladási idő; Átlagos várakozás; A rendszer kihasználtsága; Várakozósorok nagysága. Ahol lehetséges az eredmények az egyes útszakaszokra is külön számításra kerülnek a teljes rendszer mellett. A párhuzamosítás alapötlete abban rejlik, hogy több szimulációt futtatunk (minél többet), melyek egymástól függetlenek. Tehát adódik az ötlet, hogy egy szimuláció feleljen meg egy számítási töredéknek. A szimulációs rész teljes egészében át lett emelve így a fejlesztett rendszer hatékonysága jól összehasonlíthatóvá válik (ugyanaz a szimuláció futtatása párhuzamosítás nélkül és a DGRID). Tehát az elosztott számítás a következő logikát követi: A gyártók a számítási paramétereket rendezik egy fragmentbe. A workerek pedig ez alapján lefolytatják a szimulációt, mely közben gyűjti az adatokat a figyelt paramétereket. Az összegző ezen részek alapján fogja kiszámítani kategóriánként az átlagot és a szórást. A szimuláció végeredménye egy szöveges állomány lesz. Az eredeti szakdolgozati programot módosítottam, bekerült egy új
69
Java Lokális Erőforrás Ütemező menüpont. (26. ábra)
26. ábra
Az új menüpont azt a célt szolgálja, hogy amit a DGRID rendszer előállít végeredményként, azt fel tudja dolgozni, és grafikusan szemléltetni.
27. ábra
70
Java Lokális Erőforrás Ütemező
8. Tesztek A rendszert az Elosztott Körforgalmi Szimulációs kliens/szerver párossal teszteljük. Az alapötlet, hogy meghatározunk egy körforgalmi pályát, hozzá fix lambda értékeket. Szükséges még a szimuláció időtartama és a szimulációk száma. Természetesen több beállítás is tartozik a számításhoz (gyorsulás, maximális sebesség a körforgalomban, maximális sebesség a bekötő útszakaszokon, reakcióidő, követési távolság stb.) Ezeket is rögzítjük (az alapértelmezett beállításokat használjuk). A választott paraméterek: Pálya: Belső méret: 100 Becsatlakozó utak száma: 4 0. út kapcsolódása: 0 1. út kapcsolódása: 25 2. út kapcsolódása: 50 3. út kapcsolódása: 75 Becsatlakozó utak hossza: 500 Lambda értékek: 0. út: 20 1. út: 5 2. út: 30 3. út: 40 Szimuláció hossza: 10 000 000 másodperc Szimulációk száma: 1000 darab Az előtesztelések és a tapasztalatok alapján határoztam meg a fentebbi értékeket (szimulációs hossz és szimulációk száma). Ez az az értékpáros, amivel már használható adatok állíthatók elő, és a tesztelés szempontjából is látható, hogy a 10 000 000 másodperc rendkívül nagy számítási igényű, ráadásul 1000-szer kell elvégeznünk. Könnyedén
71
Java Lokális Erőforrás Ütemező léphetnénk még egy fokkal magasabb dimenzióba követve azt az alapelvet, hogy több adatból pontosabb következtetéseket tudunk levonni (például 100 millió másodperc 100000-szer) de ehhez nem állt rendelkezésre megfelelő infrastruktúra/idő. Viszont a fejlesztett rendszer képességeit a választott beállítások is kellőképpen bemutatják. A teszt egyik része az egygépes szimulációra koncentrál. Az alapelgondolás, hogy a fentebb részletezett beállításokkal lefuttatjuk a szimulációt egy átlagosnak (vagy talán manapság már átlag alattinak) nevezhető egygépes konfiguráción (egy szálon párhuzamosítás nélkül), illetve a DGRID rendszert használva a Miskolci Egyetem Informatikai épületének 101. termében levő számítógépes laborjának teljes erőforrását bevonva. Összehasonlítjuk a kapott eredményeket és a szimuláció futás idejét. A teszteknél a processzor számít a memória mennyisége nem releváns, mivel feltesszük, hogy a tesztgépekben legalább 1GB memória található és az adott programot (szerver vagy kliens) 512 MB maximális heap space-el tudjuk futtatni. A kliens és a szerver rendkívül kevés memória igénnyel beérik (kb. 30 MB) és maga a szimuláció is processzorigényes művelet, szintén kevés memóriát igényel. Abban a szellemben, hogy inkább több szabad memória jusson a virtuális gépnek, mint kevesebb, ezért indítjuk 512 MB maximális heap space-el.
8.1. Egygépes konfiguráció (átlagos teljesítményű processzoron) A gép teszt szempontjából fontosabb paraméterei: Intel Pentium 4 2,4 GHz 1,5 GB Memória Windows XP A tesztgépen a szimuláció nem futott végig, mert túl sokáig tartott volna. Ezért meg változtattunk egy paramétert, még pedig a szimuláció számot. 1000 helyett 100 darabra csökkent. Így a szimuláció 255 perc 20 másodpercig futott. Könnyen belátható, hogy mivel a többi paraméter változatlan maradt, tehát az egy szimuláció számítási igénye nem változott, ezért az erőforrásigény változás lineáris. Ebből következik, hogy 1000 darab esetében durván 2550 percre lett volna szükség.
72
Java Lokális Erőforrás Ütemező
8.2. Egygépes konfiguráció (átlag feletti teljesítményű processzoron) A gép teszt szempontjából fontosabb paraméterei: Intel Core i5 650 3.2 GHz 4 mag Windows 7 A tesztgépen a szimuláció nem futott végig, az ok megegyezik az előbb említettel. 100 szimuláció végrehajtásánál 81 perc 12 másodperc alatt végzett a feladattal. Átszámítva 100 esetre ~810 perc a szimuláció időtartama.
8.3. Miskolci Egyetem Informatikai tanszék laboratóriumában végzett teszt A DGRID rendszer számításában részt vevő gépek tulajdonságai: Intel Core i5 650 3.2 GHz 4 mag Windows 7 A klaszter homogén számítógépekből állt. Összesen 11 gép vett részt a számításban. Értelemszerűen egy darab volt a szerver, viszont ugyanez a gép töltötte be a gyártó és összesítő kliens szerepét. Ebből következik, hogy a másik 10 gép worker szerepben végezte a tevékenységet. A szimuláció időtartama alatt a workerek processzora az idő nagy részében 100%-on dolgozott, legalábbis a feladatkezelő tanúsága szerint. A számítás 1000 esetre véget ért 14 perc 11 másodperc alatt. Az időt attól a pillanattól mértem, amikor a szerver elindult, tehát ebben a kicsit több mint 14 percben benne van az is, hogy a kliensek csatlakoznak és a tényleges munka előtt lefuttatják a benchmarkot, szóval a teljes infrastruktúra kiépítése, erőviszonyok meghatározása. Ráadásul a kliens gépeken kézzel, egyesével történt az indítás, amely még plusz késleltetést jelentett.
8.4. Eredmények összehasonlítása A tesztek eredményei nagyban megegyeznek. Azonban fontos megjegyeznem, hogy csak a DGRID szimulációban volt lehetőség 1000 szimuláció végrehajtására, a másik két tesztben
73
Java Lokális Erőforrás Ütemező csak 100 darabra volt időbeli lehetőség. Fentebbi részben kifejtettem, hogy mivel a szimulációk egymástól függetlenek, és ha tízszer többet hajtunk végre belőlük, akkor tízszer több idő alatt fog a számítás elkészülni. Tehát azonos paraméterek mellett a szimuláció mennyiség és az idő között lineáris viszony áll fenn. Így tehát kis hibahatárral kiszámítható, hogy 1000 szimuláció végrehajtásához mennyi időre lett volna szükség a két kisebb szimuláció számmal operáló tesztesetben. A következőkben ezeket az eredményeket fogom közölni. Konfiguráció
Szimuláció végrehajtás ideje
Egy szálas végrehajtás egy átlagosnak
~2550 perc
mondható gépen (Normal (AV)) Egy szálas végrehajtás felsőkategóriás
~810 perc
processzoron (Normal) DGRID rendszer 11 számítógép, melyek felsőkategóriás processzorral voltak
~14,5 perc
felszerelve (DGRID)
28. ábra Teszt futási idők arányosítva
74
Java Lokális Erőforrás Ütemező
9. Továbbfejlesztési javaslatok A továbbfejlesztés területén egy ekkora programcsomag esetében, szinte korlátlan lehetőségek vannak. Elsőként megemlítenem, a szerver időközönkénti állapot mentési lehetőséget. Így ha a szerver sérül valamilyen oknál fogva (áramszünet, esetleges gép hiba), akkor célszerű lenne, ha a szimuláció, amely megszakadt bizonyos ponttól folytatható lenne, és a program kontextus visszaállítható lenne, minimalizálva ezzel a veszteséget. A producerek munkájuk befejeztével csatlakozhatnának a számítás lebonyolításához is. Ebből adódik az a igény, hogy a szerver futás közben adhasson szerepkör módosító parancsot a klienseknek. Webes felület kialakítása a szerver részére. Ezáltal távolról is nyomon követhető lenne a számítás állása. A menedzselés is megvalósítható lenne. Ehhez érdemes lenne a Spring Web MVC-t használni, mivel jól integrálhatóak egymással a különböző Spring komponensek. Elképzelhető, hogy asztali alkalmazást is lehetne fejleszteni a szerver monitorozás céljából. Ekkor is érdemes lenne. A Web MVC használata és a monitorozó alkalmazás és a szerver közötti kommunikáció Restful web szolgáltatás formájában zajlana. Az összesítők munkáját is lehetne párhuzamosítani. Ebből következik, hogy akár több összesítő is csatlakozhatna a rendszerhez. Ez sok új problémát vetne fel, csak egyet említve például, ha az összesítők visszaküldenék a részösszegeket, akkor azoknak újra be kellene kerülnie egy osztott sorba (talán vissza az összesítendő töredékeket tartalmazóba) és azokat újra összesíteni kellene, szóval problematikus a teljes összesítés kinyerése és a leállási feltétel korrekt implementálása.
75
Java Lokális Erőforrás Ütemező
10. Összegzés Saját megvalósítás útján elkészült egy Java alapú lokális erőforrás ütemező. A dolgozat keretein belül ismertetésre került a feladat konkrét elképzelése és a kitűzött célok. Utána a használt technológiák bemutatása következett. A fontosabb részek példákkal lettek illusztrálva és kiegészítő magyarázat is található hozzájuk. A következő rész a programcsomag bemutatása. Ismertetésre kerülnek a funkciók, elvárt működések, kitűzött célok, illetve az egyes kiterjesztési pontok. További információk találhatóak a szerver illetve kliens oldal konfigurálásával kapcsolatban. A programkódok magyarázata osztálydiagramokkal szemléltetve történt. A részletezés a teljes rendszert távolból indulva egyre közelebbről ismertette meg. Kiemelt fontosságú volt a csoportosítás, tehát a logikailag összeillő, illetve egymáshoz kapcsolódó részek felismerése, összekapcsolása, rendszerezett ismertetése. Egy szemléltető és egy valós problémát megoldó alkalmazás készült el a rendszer felhasználásával. A két koncepció, problémakör teljesen eltérő, ebből látható, hogy a jelenlegi állapotban is könnyen egyedi igényekre szabható a rendszer. A valós felhasználás témáját a saját Programtervező Informatikus BSc képzésen készített szakdolgozat a Körforgalom Szimuláció adta. Lehetőségem volt a Miskolci Egyetem egyik számítógépes laborjában teszteket végezni, egész pontosan 11 gépen. A Körforgalom szimulációt úgy paramétereztem, hogy ténylegesen kihasználja a rendszer adta lehetőségeket és mai modern több magos processzorok teljesítményét. A teszt pozitív eredménnyel zárult, ugyanis összehasonlításra került a több gépes elosztott számítás és párhuzamosítás nélkül futtatott. A számítások logikájában, paraméterezésében és a probléma nagyságában megegyeztek. Szekvenciális végrehajtás esetében nagyságrendileg 42 óra alatt (átlagos processzor), illetve 810 perc alatt (felsőkategóriás processzor) ért véget a számítás, a DGRID rendszert futtató gépeknek ezzel szemben nagyjából 15 percre volt szüksége. A rendszer továbbfejlesztésére számos lehetőség mutatkozik, pár elképzelés ismertetésre került. (Monitorozó felület kialakítása, összesítési folyamat párhuzamosítása, dinamikus szerepkör módosítás, szerver program kontextus mentés és szükség esetén visszaállítás).
76
Java Lokális Erőforrás Ütemező
Nem kerülnek elhallgatásra a limitációk sem, hiszen a rendelkezésre álló időkeretbe egy ekkora rendszer teljes implementálása nem volt lehetséges (több funkció a kódminőség rohamos csökkenésével járt volna). Mindazonáltal a cél egy igényes munka elkészítése volt, mely legfőképpen a mai nagyvállalati fejlesztői eszközök és keretrendszerek használatával történt. Továbbá nagy hangsúly volt a kód tisztaságán, a megfelelő logikai részek elkülönítésén, az elnevezések és programozási konvenciók betartásával és megfelelő programozási
minták
alkalmazásával
könnyen
értelmezhető
és
jól
átlátható,
öndokumentáló programsorok előállításán. Végszóként engedjen meg egy személyes véleményt az olvasó: Remélem, hogy az ismertetett fejlesztés ezzel nem zárul le, és bizonyos körök érdeklődhetnek, felfigyelhetnek a benne lévő lehetőségre és a jövőben akár több valós kutatásban segíthetné az egyetemeken folyó munkát.
77
Java Lokális Erőforrás Ütemező
11.
Summary
A personal implementation of a Java-based local resource scheduler was successfully developed during the thesis’ design. The motivation and the idea behind the scheduler application is presented to the reader in the beginning sections of the thesis along with the goals that were set before the start of the actual development. Later on the technologies that were used to implement the program are introduced. Important sections are illustrated with examples with additional explanations supplied where necessary.
The next part of the thesis deals with the software packages the application comprises. Functions are explained thoroughly with their intentions and goals alongside with any possible extension points. Additional information is given on server and client-side configuration options and possibilities. Class diagrams are supplied to help interpret the related source code. At first the software is described as a whole, then the thesis’s emphasis gradually shifts and dwells into the details of the software components in a more fine-grained manner. As such identifying the logically related functionalities of the software was important for discussing its inter-connected components in a systematical manner.
By using the software two applications were created: the first serves demonstration purposes only, while the second involves a real-world problem. The two scenarios are completely different, which shows that the software in its current state can be easily tailored to different needs. The idea for the real-world scenario is based on my BSc thesis Traffic Circle Simulation. I had the opportunity to use one of the computer laboratories at the University of Miskolc to carry out tests and simulations on eleven machines. I parameterized the traffic circle simulations so that they would fully utilize the multi-core processing capabilities of the modern hardware at hand. The test simulations were carried out successfully. Given that the parameters that were used to configure the simulations and size of the problem to solve was the same the outcome of the tests in view of the execution environment are as follows: on a serial execution environment using an ordinary processing unit calculations took approximately 42 hours, while using a high-end unit resulted in a shorter execution time of 810 minutes. Execution times reduced dramatically on machines running the DGRID system; finishing the calculations took about 15 minutes.
78
Java Lokális Erőforrás Ütemező A few concepts out of the numerous possibility that lends itself as further development ideas were discussed on the previous pages, such as the development of a monitoring interface, the parallelization of the summation process, dynamic role modifications and the server-side context store/restore functionality.
It must be noted however that while the software performed well during the aforementioned tests some limitations remain in the program. Given the timeframe available a complete implementation could not be produced without hindering codequality. Nonetheless the primary goal was to deliver a quality work - which was done using well-recognized development tools. It was an important part of the development process to write readable clean, self-documenting, well-structured code using well-known programming conventions and patterns.
Finally, I hope that development efforts that were described in this thesis won’t come to a halt and interested parties will find the software and see its potential so that it could be used in real-world academic experiments in the future.
79
Java Lokális Erőforrás Ütemező
12.
Források
http://www.springsource.org/ http://www.mkyong.com/ http://hu.wikipedia.org/wiki/Spring_keretrendszer http://logback.qos.ch/ http://docs.oracle.com/javase/tutorial/essential/concurrency/ http://www.vogella.com http://math.nist.gov/scimark2/ http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html
80
Java Lokális Erőforrás Ütemező
13.
Melléklet – A CD melléklet tartalma
+---DGRID | +---Forras | | +---ClientUsageDemo | | +---client_parent | | +---GRIDClient | | +---GRIDCommon | | +---GRIDServer | | +---Korforgalom | | | +---Korforgalmi_szimulacio_Reloaded_GRID | | | +---KorforgalomKliens | | | +---KorforgalomSzerver | | | \---Test | | |
+---Result_average_1_core
| | |
\---Result_DGRID
| | +---ScimarkTester | | +---ServerUsageDemo | | \---server_parent | +---Futtathato | | +---Korforgalom | | | +---KorforgalomClient | | | +---KorforgalomServer | | | \---Korforgalom_szimulacio_GUI | | \---UsageDemo | |
+---Client
| |
\---Server
| \---Teszt eredmenyek |
+---Result_average_1_core
|
\---Result_DGRID
+---Dolgozat | \---Kepek |
+---common_parts
81
Java Lokális Erőforrás Ütemező |
+---kliens_parts
|
\---server_parts
\---Java Runtime Environment
82