tag:blogger.com,1999:blog-72714550307349722712024-03-14T13:25:52.783+01:00jpatternjava and the othersmhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.comBlogger48125tag:blogger.com,1999:blog-7271455030734972271.post-46937676102933721362018-03-02T22:57:00.000+01:002018-03-02T22:57:19.309+01:00Fájl feltöltése Github tárolóbaEgy átlagos napon, egy átlagos feladaton dolgoztam, amikor egy teljesen átlagos, mondhatni triviális dolgot kellett megoldanom. Konkrétan: le kellett tárolni egy állományt egy megadott git tárolóba. Mi sem egyszerűbb, gondoltam naivan, de nem eszik olyan forrón a kását! Nem mennék bele túl mélyen a részletekbe, talán annyi is elég, hogy rendszeresen szoktunk sütni kép-fájlokat különböző felhőkbe. Természetesen az egész sütési folyamat automatizálva van, az eredmény pedig egy katalógusba kerül, ahol a kedves felhasználó ízlése szerint válogathat közülük. Kezdjük hát az egy pont nullával:
<br />
<pre>git add metadata ; git commit -m"New image available" ; git push
</pre>
<br />
Két probléma is adódott:<br />
<ul>
<li>Egyfelől nagyon ronda, persze működik, és a Jenkins-nek is megvan a joga írni a tárolót, de én valami kifinomultabb megoldásra vágytam.</li>
<li>Másfelől pedig maga a sütés egy másik git tárolóból indul ki, szóval vagy valami ideiglenes könyvtárba dolgozok (/tmp, ram disk), aminek az életciklusát kezelni kell, vagy a git tárolóban a git tároló problémájával küzdök, és kényesen ügyelek rá, hogy mindkét tárolóba csak az oda illő dolgok kerüljenek be.</li>
</ul>
Elvetettem a git parancsok használatát, úgyis <a href="https://github.com/">Github</a>-on tartjuk a forrást, van annak saját <a href="https://en.wikipedia.org/wiki/Application_programming_interface">API</a>-ja, megoldom elegánsan a kérdést. A dokumentációt olvasva elég hamar megtaláltam, hogy a v1-es API-ban elérhető direkt fájl feltöltést kompletten kihajították, így a legfrissebb API verzióban nincs ilyenre lehetőség. Persze tudunk fájt feltölteni, viszont ki kell hozzá nyitni a motorháztetőt, azaz a .git könyvtárban kell műveleteket végezni az API segítségével. Lássunk is neki:
<br />
<br />
<script src="https://gist.github.com/mhmxs/c7cdd648879ed227714f7d0cf541d6cc.js"></script>
A script első felében a szükséges változókat definiáltam. A <b>GITHUB_API_KEY</b>-nek egy <a href="https://github.com/settings/tokens">Personal Access Token</a>-t generáltam a Github felületén. Következő lépésben kiderítettem az utolsó <i>commit</i> azonosítóját, és helyét. A <a href="https://en.wikipedia.org/wiki/JSON">JSON</a> válaszok értelmezésére a <a href="https://stedolan.github.io/jq/">Jq</a> nevű parancs-soros eszközt választottam.<br />
<br />
<script src="https://gist.github.com/mhmxs/74eb23fa75def8f3be2428e8c6835887.js"></script>
A git fa szerkezetben tárolja kódunk különböző állapotait, ezért egyel tovább kell mennünk, és meg kell tudnunk, hogy a <i>commit</i> melyik fához tartozik.
<br />
<br />
<script src="https://gist.github.com/mhmxs/bbab08fe3f34ffadc883bc192d953e28.js"></script>
Most, hogy tudjuk az azonosítóját a fának, feltölthetjük az új fájlt, vagyis a <i>blob</i>-ot.
<br />
<br />
<script src="https://gist.github.com/mhmxs/119cf9be79b257b08790b8307597aebe.js"></script>
Kezd izgalmassá válni a dolog. Ahogy említettem a .git könyvtárban kézi-munkázunk, ami azt jelenti, hogy az imént feltöltött állomány pillanatnyilag sehová sem tartozik, ahhoz ugyanis egy fához kell csatolni azt.
<br />
<br />
<script src="https://gist.github.com/mhmxs/ccdc1f6d27f1f74fac39e1ba56772211.js"></script>
Majd készítünk egy új <i>commit</i>-ot, amit összekapcsolunk az utolsó <i>commit</i>-tal, és az imént kreált fára irányítjuk.
<br />
<br />
<script src="https://gist.github.com/mhmxs/66cf300dcbb9b8949b54b9d2b63765ae.js"></script>
Egy utolsó lépés maradt hátra, a <i>master</i> ágon a <b>HEAD</b> címkét át kell helyezni az újdonsült elkészült <i>commit</i>-ra.
<br />
<br />
<script src="https://gist.github.com/mhmxs/f8cd4147267148f62a7e9a6017fcb319.js"></script>
Nem állítom, hogy pilóta vizsga kell egy egyszerű fájl feltöltéséhez, de azért nem árt ismerni, hogy miként is működik a git, hogyan tárolja és azonosítja az objektumokat, és nem utolsó sorban miként kezeli a címkéket. A teljes script elérhető <a href="https://gist.github.com/mhmxs/ac0a82fff7e510d7f9435120dcc63ee8">itt</a>. mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com3tag:blogger.com,1999:blog-7271455030734972271.post-85470790463411022482017-09-26T14:53:00.000+02:002017-09-26T14:53:19.367+02:00Kiterjesztések írása Go nyelvi környezetbenMunkám során elkerülhetetlenné vált, hogy kicsit közelebbről is megismerjem a Go nyelv kiterjesztési képességeit. Konkrétan azt az aspektusát, amikor egy adott problémára több megoldás/kiterjesztés is létezik, és a programunknak futás időben kell a megfelelő implementációt felhasználnia. Tipikus példája ennek, amikor ugyanazon az interfészen keresztül különböző adatbazisokhoz tudunk kapcsolódni, tehát van az általanos dolgokat tartalmazó <a href="https://golang.org/pkg/database/sql/">database/sql</a> csomag, és a gyártó specifikus működést biztosító <a href="https://github.com/Go-SQL-Driver/MySQL/">go-sql-driver/mysql</a>. Az én esetemben azt kellett megoldani, hogy egy felhő alapú infrastruktúra automatizációs eszköz képes legyen szolgáltató specifikus ellenőrzéseket végezni.<br />
<br />
Első lépésben hozzunk létre egy cloud nevű <i>packaget</i> és definiáljuk a közös interfészt, amit meg kell valósítaniuk az egyes kiterjesztéseknek. A példában egyetlen metódus szerepel, a <b>CloudPlugin.Validate()</b>.<br />
<br />
<script src="https://gist.github.com/mhmxs/887edb48ce47f9224fef0f1ec715a86d.js"></script>
Ezután deklaráljunk egy map típust, ami az elérhető kiterjesztéseket gyűjti, és egy metódust amin keresztül a kiterjesztések regisztrálni tudják magukat.<br />
<br />
<script src="https://gist.github.com/mhmxs/30b6a99e73e73ed6229a209496bf268b.js"></script>
Valamint írjunk pár segédfüggvényt, hogy a kliens kód is hozzáférjen a regisztrált kiterjesztésekhez.<br />
<br />
<script src="https://gist.github.com/mhmxs/216be0e2287c020a1aab626b4aae724c.js"></script>
Miután a közös résszel végeztünk, ideje egy konkrét implementaciót megvalósítani.<br />
<br />
<script src="https://gist.github.com/mhmxs/dbed8e6d4c4f87c5f4d44fd8048dbd83.js"></script>
Amikor a Go <i>runtime</i> betolt egy <i>package</i>t, akkor szépen sorban meghívja a fájlokban szereplő <b>init()</b> metódusokat, ezt használjuk fel arra, hogy a kiterjesztés beregisztrálja magát.<br />
<br />
<script src="https://gist.github.com/mhmxs/ef37f27d6aa1ff6f98280c86b8b59128.js"></script>
Nincs más dolgunk mint felhasználni a kiterjesztést, amihez szinték kihasználjuk a Go nyelv egyik adottságát. Ahogyan metódus hívásnál alulvonással helyettesithetjük azt a változót, amivel nem szeretnénk foglalkozni, az importálásnál is lehetőségünk nyílik berántani egy <i>packaget</i> anélkül, hogy használnánk azt.<br />
<br />
<script src="https://gist.github.com/mhmxs/4048721c6cfe5f541c68421a74788b39.js"></script>
Az eredmény pedig:
<br />
<pre># go run main.go
openstack validator
</pre>
Egy dologról szeretnék még szót ejteni. A <b>cloud.Register()</b> függvényt ha közelebbről megvizsgáljuk láthatjuk, hogy egyáltalán nem szál biztos, ami a példa program esetben nem okoz gondot. Viszont ha ismeretlen forrásból érkezhet regisztráció, akkor egy <a href="https://golang.org/pkg/sync/#Mutex">Mutex</a>el garantálnunk kell a szálbiztos működést. Ezzel a témával a G<a href="http://jpattern.blogspot.com/2016/10/go-konkurencia-kezeles-gyorstalpalo.html">o konkurencia kezelés gyorstalpaló</a> cikkben foglalkoztam részletesebben.
<br />
A példaprogram teljes <a href="https://github.com/mhmxs/go-plugins-tutorial">forráskódja</a> a hiányzó RedHat implementációval együtt megtalálható GitHubon.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-24453916841671531042016-10-19T11:59:00.000+02:002016-12-23T19:16:37.133+01:00Go konkurencia kezelés gyorstalpalóMikor elkezdtem ezt a blogot írni, érdeklődésem előterében a Java alapú technológiák voltak. Rengeteg téma merült fel az eltelt 7 évben, amiről szívesen írtam volna, de mivel nem kapcsolódtak szorosan az eredeti tematikához, ezek a bejegyzések sosem kerültek implementálásra. Szakítva ezzel a (rossz) hagyománnyal szeretnék az informatika világának szélesebb spektrumával foglalkozni ezen túl. Első Java mentes bejegyzésem témájának pedig a Go nyelv konkurencia kezelését választottam.
<br />
<br />
A Go nyelv körül elég nagy a sürgés-forgás mostanában, nem tudom pontosan miért örvend ekkora népszerűségnek. Számtalan jó tulajdonsága van, de szerintem mind közül a legfontosabb szempont, hogy a nyelv egyszerű. Nincsenek mögötte "bonyolult" ideológiák mint például az objektum orientált programozásban, vagy nem lehet benne egy dolgot száz féleképpen csinálni mint mondjuk a funkcionális nyelvekben. Kutyaközönséges, de modern procedurális nyelv annak minden előnyével és hátrányával. Akit részletesebben érdekel ez a terület érdemes Rob Pike <a href="https://www.youtube.com/watch?v=rFejpH_tAHM">előadását</a> meghallgatni. Témánkra rátérve ugyanez az egyszerűség jellemzi a Go konkurencia kezelését is. Vannak az un. goroutinok, ezek segítségével lehet párhozamosítani a végrehajtást, és vannak a chanelek, amiken keresztűl zajlik a kommunikáció. Utóbbiból van szinkron meg aszinkron.
<br />
<br />
<script src="https://gist.github.com/mhmxs/449d71d879a91acd9dc988ee1fd23415.js"></script>
A program létrehoz egy c szinkron chanelt, majd elindít egy goroutint, és kiolvassa a chanleből az értéket, amit a goroutin beletesz. Mivel szinkron módban működik egyik végrehajtás sem megy tovább amíg a kommunikáció meg nem történik. Aszinkron channelt a méretének megadásával tudunk létrehozni. Kész is, mindent tudunk :) Akit részletesebben érdekel, mi történik a motor-háztető alatt, annak ajánlom a Go memória kezeléséről szóló <a href="https://golang.org/ref/mem">cikket</a>. Természetesen ennyivel nem ússzuk meg a dolgot, komolyabb alkalmazásnál szükségünk van meg egy pár dologra, hogy biztosítani tudjuk programunk helyes működését. Két csomagot fogunk felhasználni, az egyik a <a href="https://golang.org/pkg/sync/">sync</a>, ami szinkronizációs problémákra nyújt megoldást, illetve a <a href="https://golang.org/pkg/sync/atomic/">sync/atomic</a>, melynek segítségével elemi műveleteket végezhetünk. A Go nyelv dokumentációját olvasva számtalan helyen olvashatjuk, hogy nincs garancia, hogy adott dolog hogyan viselkedik mikor több végrehajtó szál birizgálja egy időben (nem 1 az 1-hez a goroutine és szál leképzés, de ettől most tekintsünk el). Alapvetően érték szerint adódik át minden, így a legtöbb esetben nincs is szükség különösebb szinkronizációra, de ha mégis, magunknak kell gondoskodni róla.
<br />
<br />
Kitaláltam egy egyszerű kis feladatot, amin keresztűl bemutatok pár fontos eszközt működés közben.<br />
<ul>
<li>Legyen egy szál típus, ami 1000 véletlen számot generál, és a legnagyobbat elküldi feldolgozásra.</li>
<li>Egy másik az előzőtől kapott értékből kiindulva generál még 500 véletlen számot, a legnagyobbat gyűjti egy tömbbe és tovább küldi.</li>
<li>A harmadik feldolgozó nem csinál mást, mint számolja, hogy a kapott érték hány esetben volt az utolsó a tömbben.</li>
<li>Szabályozható legyen a goroutinok száma, a generálóból hány példány futhat egyidőben, valamint hány értéket generáljon összesen.</li>
<li>Mérjük a végrehajtás idejét a generálás kezdetétől.</li>
</ul>
A feladatnak semmi értelme, és biztos egyszerűbben is meg lehetett volna oldani, de igyekeztem minél több módszert belezsúfítani a megoldásba.
<br />
<br />
Első lépésben inicializáljuk a konstansokat, valamint az eredmény tömböt. Mivel az eredménnyel több szál is fog egy időben dolgozni szükségünk lesz valami zárolási logikára. Tekintettel arra, hogy az egyik szál írja a másik pedig olvassa én a <a href="https://golang.org/pkg/sync/#RWMutex">sync.RWMutex</a>-et választottam.
<br />
<br />
<script src="https://gist.github.com/mhmxs/010a0152516e0d01bbebd797ad1a9df2.js"></script>
A következő kódblockban implementálásra kerül a statisztikázó egység.
<br />
<br />
<script src="https://gist.github.com/mhmxs/967877dc9b426278f8db600b637c1824.js"></script>
Majd megírjuk a feldolgozó egységet.
<br />
<br />
<script src="https://gist.github.com/mhmxs/cfd823106a28ff6b07b1485721540224.js"></script>
Na most kezd igazán érdekes lenni a dolog. A generátor esetében tudni kell szabályozni, hogy hány darab fut egyidőben. A legkézenfekvőbb döntés, ha létrehozunk egy chanelt adott méretben, és beleteszünk egy értéket amikor indítunk egy új generátort, és kiveszünk egyet ha végzett a generátor. Ennek eredméyeképpen amikor megtelik a chanel programunk várakozik.
<br />
<br />
<script src="https://gist.github.com/mhmxs/05021c4f0f4366789b3e8f07c2837292.js"></script>
Miután elindítottuk a generáló szálakat szeretnénk megvárni amíg az összes végez. Ennek érdekében bevezetünk egy <a href="https://golang.org/pkg/sync/#WaitGroup">sync.WaitGroup</a>-ot.
<br />
<br />
<script src="https://gist.github.com/mhmxs/05a66d949c596cebd531844999ab6007.js"></script>
Egy utolsó dolog maradt a generátorral kapcsolatban, hogy szeretnénk az eltelt időt mérni a lehető legközelebb a generáláshoz. Ennek biztosítására én a <a href="https://golang.org/pkg/sync/#Once">sync.Once</a>-ot választottam, ami garantálja ezt a működést.<br />
<br />
<script src="https://gist.github.com/mhmxs/885f5f71e668c047166d6a364eb9c84e.js"></script>
Nincs más dolgunk mint megvárni és kiírni az eredményt.
<br />
<br />
<script src="https://gist.github.com/mhmxs/f40f1b08162384160fd19f1b85154fd1.js"></script>
A teljes forráskód elérhető <a href="https://gist.github.com/mhmxs/302792bde11f6511391357056290e891">itt</a>.
<br />
<br />
Programunk gyönyörűen fut, de vajon mi történik a háttérben? Fordítsuk le a programot a Go beépített verseny helyzet elemzőjével.
<br />
<pre>go build -race
</pre>
Ez után futtatva valami hasonló kimenetet kapunk:
<br />
<pre>==================
WARNING: DATA RACE
Read at 0x00c42000c2e8 by main goroutine:
main.main()
/Users/rkovacs/asd/src/asd/main.go:92 +0x731
Previous write at 0x00c42000c2e8 by goroutine 7:
sync/atomic.AddInt32()
/usr/local/Cellar/go/1.7.1/libexec/src/runtime/race_amd64.s:269 +0xb
main.main.func1()
/Users/rkovacs/asd/src/asd/main.go:37 +0x191
Goroutine 7 (running) created at:
main.main()
/Users/rkovacs/asd/src/asd/main.go:42 +0x31b
==================
time spent: 1121ms
last:not ratio: 499:1
Found 1 data race(s)
</pre>
Futás idejű analízisből kiderült, hogy volt értelme elemi műveletben növelni a számlálót, hiszen verseny helyzet alakult ki, és máskülönben megjósolhatatlan eredménnyel zárult volna a futás.
<br />
<br />
A téma még nem merült ki ennyivel, de remélem sikerült egy gyors képet adnom a Go konkurencia kezeléséről. Ajánlom továbbá figyelmetekbe a <a href="https://blog.golang.org/concurrency-is-not-parallelism">Concurrency is not parallelism</a> és a <a href="https://www.youtube.com/watch?v=f6kdp27TYZs">Go concurrency patterns</a> előadásokat.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-1555309459895773452016-09-30T16:01:00.000+02:002016-10-17T18:00:42.406+02:00Üzenet hitelesítése Java és Go szervizek közöttJava fejlesztés mellett időm egy részét Go programozással töltöm, és egy olyan feladaton volt szerencsém dolgozni, amely mindkét platformot érintette. Napjaink modern alkalmazásai kisebb szervízekre vannak bontva, és igen gyakori, hogy az egyes szervízek eltérő technológiával kerülnek implementálásra. Konkrét esetben az volt az elvárás, hogy a szervízek közti kommunikációt aláírással hitelesítsem, a küldő fél Javaban, míg a fogadó Goban írodott. Mivel nem valami egzotikus kérést kellett megvalósítani gondoltam másoknak is hasznos lehet a megoldás. Előljáróban még annyit kell tudni a rendszer architektúrájáról, hogy a Java kód indít virtuális gépeket, és az ezeken a gépeken futó Go szolgáltatáson keresztül végez beállítási műveleteket, ráadásul mindkét komponens nyílt forráskódú. Ezen két adottságból adódóan nem volt mód sem szimetrikus titkosítást használni, vagy egyéb más érzékeny adatot eljuttatni a futó virtuális gépre, sem pedig valami közös "trükköt" alkalmazni. Maradt az aszinkron kulcspárral történő aláírás, mi az <a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA</a>-t választottuk. Nem is szaporítanám a szót, ugorjunk fejest a kódba.<br />
<br />
Kezdjük a fogadó féllel. A Go nyelv dokumentációját olvasva hamar ráakadhatunk, hogy létezik beépített <a href="https://golang.org/pkg/crypto/rsa">crypto/rsa</a> csomag. Nem bővelkedik a lehetőségekben, ugyanis csak <a href="https://en.wikipedia.org/wiki/PKCS_1">PKCS#1</a>-et támogat. Remélem nem spoiler, de a Go lesz a szűk keresztmetszet választható sztenderdek közül. Létezik persze külső csomag pl. PKCS#8 támogatással, de mi a biztonsági kockázatát kisebbnek ítéltük a beépített bár gyengébb eljárásnak, mint a külső kevesek által auditált megoldásnak. A crypto/rsa csomagnál maradva az egyetlen lehetőségünk, hogy PSS (Probabilistic signature scheme) aláírásokat hitelesítsünk a <a href="https://golang.org/pkg/crypto/rsa/#VerifyPSS">VerifyPSS</a> metódussal. Szóval nincs más dolgunk mint az RSA kulcspár publikus részét eljuttatni a virtuális gépre, és már mehet is a hitelesítés.<br />
<br />
<script src="https://gist.github.com/mhmxs/cc36025d31e39893a302ffe93e130616.js"></script>
<br />
Küldés során a kérés teljes törzsét írtuk alá, így nincs más dolgunk mint a kérésből kibányászni a törzset és ellenőrizni a hitelességét.
<br />
<script src="https://gist.github.com/mhmxs/9b7f8ec791c82605ef628b7fa2c7fb87.js"></script>
<br />
Valamint implementálni és regisztrálni a kérés feldolgozót.
<br />
<script src="https://gist.github.com/mhmxs/3fc17bb16e5c464f024e55cd48187d1d.js"></script>
<br />
Természetesen tesztet is írtam az aláírás ellenőrzésére.
<br />
<script src="https://gist.github.com/mhmxs/48594667fd91c6436a9a02f41719b6a8.js"></script>
<br />
Miután megvagyunk a hitelesítéssel jöhet az aláírás Java oldalon. Kutattam egy darabig hogyan lehet PSS aláírást Java SE-vel generálni, de mivel a projektünknek már része volt a <a href="http://bouncycastle.org/">Bouncy Castle Crypto API</a>, így kézenfekvő volt, hogy azt használjam fel.
<br />
<script src="https://gist.github.com/mhmxs/5b8b3f7ea2e124d3d3249f8798c7430a.js"></script>
<br />
A Java oldali kulcspár generálással tele van az internet, azzal nem untatnák senkit.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-74103718960941773622016-01-23T20:27:00.000+01:002016-01-26T14:13:30.340+01:00Big Data trendek 2016Remélem nem vagyok még nagyon elkésve egy ilyen témájú bejegyzéssel, csak most kezdődik az év, úgy gondolom belefér egy kis jövendő mondás. Nem kell megijedni, nem a kristály-gömbömből fogok olvasni, hanem a szakma neves képviselőinek jóslatait igyekszem közvetíteni. A <a href="http://www.meetup.com/Big-Data-Meetup-Budapest/" target="_blank">Budapest Big Data Meetup</a> idei első találkozója alkalmából a szervezők három embert kértek fel, hogy osszák meg gondolataikat a közösséggel, én pedig szeretném összefoglalni az ott hallottakat. Mielőtt az előadások elkezdődtek volna volt pár köz érdekű információ, melyek közül szerintem a legfontosabb, hogy megalakult a <a href="http://www.meetup.com/Budapest-Spark-Meetup/" target="_blank">Budapest Spark Meetup</a>. Csatlakozzatok, terjesszétek, stb.<br />
<br />
Az első előadást <a href="https://twitter.com/rgabo" target="_blank">Rátky Gábor</a> tartotta "<a href="http://slides.com/sspinc/big-data-2016#/" target="_blank">Climbing the slope of enlightment</a>" címmel. Rövid bemutatkozás után felvázolta, hogy szerinte pontosan a <a href="http://www.gartner.com/technology/research/methodologies/hype-cycle.jsp" target="_blank">hype ciklus</a> melyik szakaszában jár a "big data", őszintén remélem igaza van Gábornak, mert meglátása alapján már felfelé mozgunk a völgyből, és lassan megtaláljuk helyét a világban a technológiáknak.<br />
A figyelmet érdemlő technológiák közül elsőként az Amazon <a href="https://aws.amazon.com/about-aws/whats-new/2015/07/amazon-emr-release-4-0-0-with-new-versions-of-apache-hadoop-hive-and-spark-now-available/" target="_blank">EMR 4</a>-es verzióját említette meg, ami gyakorlatilag egy <a href="https://hadoop.apache.org/">Hadoop</a> as a Service (HaaS) szolgáltatás, ahol az infrastruktúrárol az Amazon gondoskodik, a fejlesztőnek csak a feladatok megírásával, futtatásával kell törődnie, meg persze a számla kiegyenlítésével hónap végén. Legfontosabb tulajdonságainak az alábbiakat gyűjtötte össze:<br />
<ul>
<li>Az EMR a legnagyobb Hadoop distribúció piaci részesedés alapján</li>
<li>Nagyon jó a többi Amazon szolgáltatással az integráció, mint például <a href="https://aws.amazon.com/ec2/" target="_blank">EC2</a>, <a href="https://aws.amazon.com/s3/" target="_blank">S3</a> vagy <a href="http://docs.aws.amazon.com/redshift/latest/mgmt/welcome.html" target="_blank">Redshift</a></li>
<li><a href="http://bigtop.apache.org/" target="_blank">Apache Bigtop</a>-re épül</li>
<li>Könnyen lehet ideiglenes (ephemeral) fürtöket létrehozni és megszüntetni</li>
<li>Intelligens skálázhatóság</li>
<li><a href="http://spark.apache.org/" target="_blank">Spark</a> 1.5 támogatással rendelkezik, és gyorsan adoptálják az újabb verziókat</li>
<li>Az EMR homokozóban friss technológiákat lehet kipróbálni mint az <a href="https://zeppelin.incubator.apache.org/" target="_blank">Apache Zeppelin</a></li>
<li>Igazán azoknak ajánlott, akik egyéb Amazon szolgáltatásokat is használnak, vagy nem okoz problémát elköteleződni egy beszállító felé</li>
</ul>
<div>
Megkerülhetetlen szereplő az Apache Spark, ha 2016-os trendekről beszélünk, Gábor sem hagyhatta figyelmen kívűl. Csapatuk, a <a href="http://www.secretsaucepartners.com/" target="_blank">Secure Sauce Partners</a> is aktívan használja, dübörögő technológia, sok területen hódít, és nem méltán örvend ekkora népszerűségnek. Egy mondatban összefoglalva a Spark egy olyan keretrendszer, amivel elosztott alkalmazásokat lehet fejleszteni, és megfelelő programozással az elosztott futtatásról a Spark gondoskodik. Első pontnak is ezt emelte ki, de lássuk mi volt a többi:</div>
<div>
<ul>
<li>Ugyanaz a kód fut az egy gépestől a sok gépes rendszerig, "Write once, run anywhere"</li>
<li>Hatalmas lendülettel fejlődik, köszönhetően a felhajtásnak is ami körülveszi</li>
<li>Szaporodnak a Spark primitívekre épülő keretrendszerek, mint például <a href="http://spark.apache.org/docs/latest/sql-programming-guide.html#dataframes" target="_blank">DataFrame</a>, <a href="http://spark.apache.org/mllib/" target="_blank">MLLib</a></li>
</ul>
<div>
Gábor személyes kedvencére tért még ki az előadásában az Apache Zeppelinre, ami egy hiánypótló, web alapú, interaktív adat elemző eszköz. A projekt jelenleg az Apache inkubátorban fejlődik roham léptekkel. Gábor véleménye szerint az egész adat analízis eddig a programozói interfészeken keresztűl történt, történik, és ő reméli, hogy a 2016-os év az ökoszitéma köré épülő felhasználói felületek éve lesz. Ennek a területnek egyik úttörője a Zeppelin, mert:</div>
</div>
<div>
<ul>
<li>Feladatoknak 80%-a az adat <a href="https://en.wikipedia.org/wiki/Mung_(computer_term)" target="_blank"><i>munging</i></a>, és ezt (is) könnyíti meg a projekt</li>
<li>Legjobb alkalmazás csoportban adat-halmazokon dolgozni</li>
<li>Egyszerű(bb)en elvégezhető vele az adat-halmazok definiálása, a lépések és az eredmény megosztása</li>
<li><a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop" target="_blank">REPL</a>-ben jártas adat-elemzőknek otthonos környezet</li>
</ul>
<div>
Végül pár pontban összeszedte mit vár a 2016-os évtől:</div>
</div>
<div>
<ul>
<li>Konszolidáción mennek keresztül az eszközök, és így az egész ökoszisztéma</li>
<li>Az <a href="https://en.wikipedia.org/wiki/SQL">SQL</a> alapú megoldások erősödni fognak</li>
<li>A már említett kiegészítő eszközök sokasodnak</li>
<li>+1-nek pedig az <a href="https://en.wikipedia.org/wiki/Internet_of_Things">IoT</a></li>
</ul>
</div>
<div>
A következő előadást a <a href="https://rapidminer.com/" target="_blank">RapidMiner</a> képviseletében <a href="https://twitter.com/prekopcsak" target="_blank">Prekopcsák Zoltán</a> tartotta. Sajnos írásos anyag híján csak az emlékezetemre támaszkodhatok, hogy miről is beszélt pontosan.<br />
<br />
Téma volt a Spark, aktív felhasználói a keretrendszernek, és két fontos dolgot is mondott ezzel kapcsolatban:<br />
<ul>
<li>Reméli a hiba javítás nagyon nagy fókuszt kap a jövőben, mert véleménye szerint ezen a területen nagyon sok munka van</li>
<li>Az a tapasztalata, hogy sok vállalat, akik letették a voksukat a Spark mellett, nem félnek frissíteni a rendszert, és bátran ugranak bele egy-egy újabb verzióba</li>
</ul>
<div>
Zoltán szerint is az SQL technológiák nagyobb térnyerése lesz jellemző az évre, valamint a biztonságot - mind adat, mind rendszer szinten - célzó fejlesztések lesznek a közép pontban.</div>
</div>
<div>
<br /></div>
<div>
Harmadikként <a href="https://twitter.com/matyix_" target="_blank">Mátyás János</a> adott elő a <a href="http://hortonworks.com/" target="_blank">Hortonworks</a> színeiben. A Hortonworks egy teljesen nyílt forrású Hadoop disztribúció, a <a href="http://hortonworks.com/hdp/whats-new/" target="_blank">HDP</a> fejlesztésére és támogatására szakosodott szilícium-völgyi vállalat. A szokásos bemutatkozás után János összefoglalta miről is szólt a 2015-ös év big data szempontból:</div>
<div>
<ul>
<li>Az Apache Spark éve volt</li>
<li>Az SQL még mindig nagy dolog, miért is dobnánk ki az ablakon több évtizednyi tudást</li>
<ul>
<li><a href="https://hive.apache.org/" target="_blank">Apache Hive</a> <a href="http://hortonworks.com/innovation/stinger/" target="_blank">Stinger</a> kezdeményezése</li>
<li><a href="https://phoenix.apache.org/" target="_blank">Apache Phoenix</a> egy <a href="https://hbase.apache.org/" target="_blank">HBase</a>-re épülő relációs réteg</li>
</ul>
<li>Az adatok mozgatása a Hadoop fürtökbe nagy kérdés volt/lesz, és ez adott lét jogosultságot olyan alkalmazásoknak mint az:</li>
<ul>
<li><a href="http://kafka.apache.org/" target="_blank">Apache Kafka</a></li>
<li><a href="https://nifi.apache.org/" target="_blank">Apache NiFi</a></li>
</ul>
</ul>
<div>
A 2016-os előrejelzések terén rengeteg tényezőt kell figyelembe venni, amit külön nehezít, hogy a HDP több mint 22 Apache projektet ölel körbe, ezért János inkább technológiai szempontból szedte össze az érdekesebb trendeket.<br />
<br />
Elsőként az adatok tárolása körüli munkákat emelte ki mint például az <a href="https://issues.apache.org/jira/browse/HDFS-7285" target="_blank">Erasure Coding</a> a <a href="https://hadoop.apache.org/docs/r1.2.1/hdfs_design.html#Introduction" target="_blank">HDFS</a> fájlrendszerben és az archív adatokat tároló rendszerek támogatása. Mindkettő fejlesztésnek a költség hatékonyság a fő mozgató rugója.<br />
<br />
A HDFS az EC bevezetése előtt csak teljes blokk replikációt támogatott, ami azt jelenti, hogy a replikációs faktornak megfelelő darab számban minden adat tárolásra került. Az újításnak köszönhetően alap értelmezetten 6 adat blokkhoz 3 paritás blokk tartozik. Ennek hála tárhelyet spórol az eljárás, és megnöveli az írási sebességet, miközben a hiba tűrés ugyanúgy megmarad. Amíg az EC szoftveresen javítja a tároló kapacitás kihasználását, addig a másik fejlesztés hardveresen igyekszik a költségek minimalizálására. A hozzáférés gyakorisága szerint három csoportba rendezi az adatokat, és a ritkán használt, régi adatokat olcsóbb, de lassabb tárolókra lehet szervezni.</div>
</div>
<div>
<br /></div>
<div>
Következőkben az adat trendekről beszélt az előadó, úgy mint:<br />
<ul>
<li>A modern alkalmazásokban az adatok elemzése elsődleges funkció</li>
<li>A gyakorlat azt mutatja, hogy a szolgáltatásokat és adat elemző rétegeket együttesen használják</li>
<ul>
<li>Spark + HBase</li>
<li><a href="https://flume.apache.org/" target="_blank">Flume</a> + <a href="http://storm.apache.org/" target="_blank">Storm</a> + Hive + Tableau</li>
</ul>
<li>Alapvető elvárások ezen a területen:</li>
<ul>
<li>Könnyen felhasználható legyen és könnyen lehessen menedzselni</li>
<li>Biztonságos legyen</li>
<li>Reprodukálható legyen</li>
</ul>
</ul>
<div>
Ezután az előadó a <a href="https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html" target="_blank">YARN</a> fontosságát emelte ki, a YARN.next már a hagyományos konténerek mellett <a href="https://www.docker.com/" target="_blank">Docker</a> konténerek használatát is támogatja, és ezzel lehetővé vált, hogy ne csak Hadoop komponensek futását kezelje a YARN, hanem bármilyen más konténerizált alkalmazásét. Ilyen alkalmazások lehetnek a <a href="http://tomcat.apache.org/" target="_blank">Tomcat</a>, <a href="https://www.mysql.com/" target="_blank">MySQL</a>, vagy akár saját alkalmazásaink. Ez egy nagyon fontos lépés volt, hiszen ezzel az újítással fürtjeinket nem csak Hadoop műveletekre tudjuk használni, hanem gyakorlatilag a YARN-on keresztűl teljes erőforrás menedzsmentet kapunk.</div>
<div>
<br /></div>
<div>
A YARN után a Spark került elő, "Miért szeretjük a Sparkot a Hortonworksnél" címmel. Nekem talán a legfontosabb üzenet az volt ezzel a témával kapcsolatban, hogy ha már a Spark "YARN ready", és az alkalmazásunk egyébként is kapcsolódik más (Hadoop) szolgáltatásokkal, akkor nyugodtan bízzuk a YARN-ra a Spark erőforrások kiosztását is, mert meghálálja magát a dolog. De mi is jellemzi a Sparkot:</div>
<div>
<ul>
<li>Elegáns fejlesztői <a href="https://en.wikipedia.org/wiki/Application_programming_interface" target="_blank">API</a></li>
<ul>
<li>DataFrame-k</li>
<li>Gépi tanulás</li>
<li>SQL</li>
</ul>
<li><i>Data sience</i>-eknek készült</li>
<ul>
<li>A alkalmazások kiszámíthatóan tudnak skálázódni, és megfelelően lehet őket tagolni</li>
</ul>
<li>Demokratizálja a gépi tanulást</li>
<ul>
<li>A Spark azt csinálja a gépi tanulással a Hadoopon belül, amit a Hive csinált az SQL-el</li>
</ul>
<li>Közösség</li>
<ul>
<li>Széles körben fejlesztik, és nagy érdeklődés övezi, nem csak a fejlesztők irányából</li>
</ul>
<li>Realizálja az adat operációs rendszerek értékét</li>
<ul>
<li>Kulcs fontosságú eszköz a Hadoop érában</li>
</ul>
</ul>
<div>
A 2016-os évben nagy erőkkel fog zajlani a Spark mélyebb integrációja a Hadoop ökoszisztémával, számtalan fejlesztésre kitért az előadó (a teljesség igénye nélkül):</div>
</div>
<div>
<ul>
<li>Szorosabb <a href="http://spark.apache.org/docs/latest/programming-guide.html#resilient-distributed-datasets-rdds">RDD</a> és HDFS együtt működés</li>
<li>Dinamikus végrehajtás <a href="http://www.hadoopinrealworld.com/data-locality-in-hadoop/" target="_blank"><i>data-locality</i></a> alapján</li>
<li><a href="http://atlas.incubator.apache.org/" target="_blank">Atlas</a> integráció</li>
<li>Hbase konnektor</li>
<li>NiFi <i>stream</i>-ek</li>
<li>Biztonsági fejlesztések</li>
<ul>
<li>SparkSQL Security</li>
<li>Wire Encryption</li>
</ul>
<li>Spark fejlesztés és vizualizáció Zeppelinben</li>
</ul>
<div>
Utolsó témakörként a biztonság került még szóba. Erről a témáról nem lehet eleget beszélni, és rengeteg fronton helyt kell állni. Négy főbb projektet emelt ki az előadó, melyek együttes használatával fel lehet adni a leckét a támadónak, és az adatok belső védelme is megoldható.</div>
</div>
<div>
<br /></div>
<div>
Az <a href="http://ranger.apache.org/" target="_blank">Apache Ranger</a> egy központosított biztonsági keretrendszer, ami az alábbiakról gondoskodik:</div>
<div>
<ul>
<li>Authorizáció</li>
<li>Authentikálás</li>
<li>Audit</li>
<li>Adat titkosítás</li>
<li>Biztonsági beállítások kezelése</li>
</ul>
<div>
Az <a href="https://knox.apache.org/" target="_blank">Apache Knox</a> a <a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" target="_blank">HTTP</a> és <a href="https://en.wikipedia.org/wiki/Representational_state_transfer" target="_blank">REST</a> interfészeket hivatott megvédeni. Egy Hadoop fürt védelmében kulcs szerepet játszik, hogy a tűzfalon minden befelé irányuló porton tiltani kell az adat forgalmat, elzárva a rendszert a külvilágtól. Amennyiben felhasználóink mégis szeretnének a rendszerhez hozzáférni, a Knox egy biztonságos átjárót biztosít külső hálózatok felé.</div>
</div>
<div>
<ul>
<li>Egyszerűsíti a hozzáférést, mert elfedi a <a href="http://web.mit.edu/kerberos/#what_is" target="_blank">Kerberos</a>t és nem szükséges annak telepítése a kliens gépeken</li>
<li>Fokozza a biztonságot azáltal, hogy <a href="https://en.wikipedia.org/wiki/SSL" target="_blank">SSL</a> csatornán kommunikál</li>
<li>Központosítja a vezérlést, mivel lehet benne irányítani a kéréseket akár több Hadoop fürt felé</li>
<li>Nagy vállalati integrációt biztosít <a href="https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol" target="_blank">LDAP</a> és <a href="https://en.wikipedia.org/wiki/Active_Directory" target="_blank">Active Directory</a> támogatással</li>
</ul>
<div>
Szóba került az előadásban az Apache Atlas projekt is, ami egy integrációs keretrendszer, és nagy vállalati rendszereket segít Hadoop-pal összekötni, valamint az <a href="https://metron.incubator.apache.org/" target="_blank">Apache Metron</a>, amely jelenleg még <a href="https://wiki.apache.org/incubator/MetronProposal" target="_blank">inkubátor</a> projekt, és biztonsági analízist végez, ami alapján értesítéseket tud küldeni.</div>
</div>
<div>
<br /></div>
<div>
Az előadása végén János pár szóban beszélt a Hortonworks szárnyai alatt Magyar országon fejlesztett HaaS megoldásról a <a href="http://hortonworks.com/hadoop/cloudbreak/" target="_blank">Cloudbreak</a>ről. A támogatott felhő platformok között nem kisebb nevek szerepelnek mint az <a href="https://aws.amazon.com/" target="_blank">Amazon AWS</a>, <a href="https://cloud.google.com/compute/" target="_blank">Google GCE</a>, <a href="https://azure.microsoft.com/en-us/" target="_blank">Microsoft Azure</a>, vagy az <a href="https://www.openstack.org/" target="_blank">OpenStack</a>. A Cloudbreak lényegében minimális felhasználói interakcióval gondoskodik az infrastruktúra beüzemelésétől, az <a href="https://ambari.apache.org/" target="_blank">Apache Ambari</a> fürt beállításán át, a teljes Hadoop környezet felépítéséig mindenről.</div>
<div>
<br /></div>
<div>
Az előadások végeztével megkezdődött a kötetlen beszélgetés, pizza sör ilyenek :). Sokan részt vettek a meetupon, és jó volt látni, hogy ennyi embert foglalkoztatnak az idei trendek. Ha idáig eljutottál, valószínűleg te is közéjük tartozol, ne habozz megosztani a véleményed!</div>
</div>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-18573772239274862402015-06-05T16:35:00.001+02:002015-06-05T19:24:24.873+02:00Konténerezett Hadoop és Cassandra cluster konfigurálása - harmadik részA sorozat előző részeiben (<a href="http://jpattern.blogspot.com/2015/05/kontenerezett-hadoop-es-cassandra.html">1</a>, <a href="http://jpattern.blogspot.com/2015/05/kontenerezett-hadoop-es-cassandra_25.html">2</a>) Vagrantos környezetben felépítettünk egy Hadoop clustert. Ebben a befejező cikkben egy Cassandra fürtöt fogunk telepíteni, majd egy map/reduce jobot futtatunk a teljes clusteren. Izgalmasan hangzik, vágjunk is bele.
<br />
<br />
<h2>
Előkészítés</h2>
Első dolgunk, hogy meglévő projektünket frissítjük a megfelelő verzióra, és építsük újra a konténereket:
<br />
<pre>cd docker-cassandra && git checkout 2.6.0-cassandra && git pull origin 2.6.0-cassandra
cd ../hadoop-docker && git checkout 2.6.0-cassandra && git pull origin 2.6.0-cassandra
cd .. && git checkout 2.6.0-cassandra && git pull origin 2.6.0-cassandra
</pre>
Élesszük fel a gépeket:
<br />
<pre>vagrant halt && vagrant up
</pre>
Majd a futó Vagrantos környezetben építsük újra a konténereket:
<br />
<pre>vagrant provision master slave1 slave2 slave3
</pre>
Lépjünk be a virtuális gépekre, és töröljünk minden futó konténert:
<br />
<pre>docker rm -f $(docker ps -qa)
</pre>
Ha mindezzel végeztünk, hozzuk létre ismét a Weave hálózatot és a Swarm clustert, a Hadoop konténereket egyelőre hagyjuk parlagon.
<br />
<br />
<h2>
Változások</h2>
Csökkentettem a master nevű gép memória igényét, mivel a továbbiakban csak mesterként fog szolgálni a benne futó Hadoop konténer, és ezzel egy időben növeltem a slaveX nevű gépek memóriáját, mert az eddigi beállítás ki volt hegyezve a Hadoopra, mostantól viszont a Cassandrának is kell helyet szorítani. A fejlesztés előrehaladtával a gépemben lévő 8 Gb RAM már sokszor kevésnek bizonyult, elkezdte a Vagrant aktívan használni a swapet, ami igen rossz hatással van a jobok futtatására, lépten nyomon elhasaltak. Én átmenetileg a 3-s számú slave gépet kikapcsoltam. Összességében 4 virtuális gépből és 15 konténerből áll a cluster, szóval személy szerint nem is csodálkozom, hogy ilyen mértékben megnövekedett a gép igény.
<br />
<br />
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-cassandra/bootstrap.sh">bootstrap.sh</a>
<br />
<pre>if [ -n "$MASTER_IS_SLAVE_TOO" ]; then
echo $HOST_NAME > $HADOOP_PREFIX/etc/hadoop/slaves
else
echo "" > $HADOOP_PREFIX/etc/hadoop/slaves
fi
</pre>
Bevezettem egy környezeti változót (<i>MASTER_IS_SLAVE_TOO</i>) melynek hatására a mester konténer szolga is lesz egyben, a változó nélkül csak mesteri teendőit látja el.
<br />
<br />
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-cassandra/Dockerfile">Dockerfile</a>
<br />
<pre>RUN sed -i "s|^# Extra Java CLASSPATH.*|&\nexport HADOOP_CLASSPATH=/usr/share/cassandra/*:/usr/share/cassandra/lib/*:\$HADOOP_CLASSPATH|" $HADOOP_PREFIX/etc/hadoop/hadoop-env.sh
</pre>
Javítottam a <i>HADOOP_CLASSPATH</i>on, hiányzott egy Cassandrás függőség.
<br />
<br />
<a href="https://github.com/mhmxs/docker-cassandra/blob/2.6.0-cassandra/cassandra-cluster/scripts/cassandra-clusternode.sh">cassandra-clusternode.sh</a>
<br />
<pre>if [ -n "$PUBLIC_INTERFACE" ]; then
IP=$(ifconfig $PUBLIC_INTERFACE | awk '/inet addr/{print substr($2,6)}')
PUBLIC_IP=$IP
fi
if [ -n "$PUBLIC_IP" ]; then
sed -i -e "s/^# broadcast_address: 1.2.3.4/broadcast_address: $PUBLIC_IP/" $CASSANDRA_CONFIG/cassandra.yaml
fi
</pre>
A Cassandrás konténerben egy új környezeti változóval, név szerint <i>PUBLIC_INTERFACE</i>, megoldottam, hogy a Cassandra a megfelelő IP címet használja minden nemű kommunikációhoz.
<br />
<pre>if [ -n "$CASSANDRA_SEEDS" ]; then
for a in $(echo $CASSANDRA_SEEDS | sed 's/,/ /g'); do CASSANDRA_SEEDS=$(echo $CASSANDRA_SEEDS | sed "s/$a/$(ping -c1 $a | grep PING | awk '{ print $3 }' | sed "s/(//;s/)//")/"); done
fi
</pre>
Mivel a konténerek dinamikusan kapnak IP címet, a Cassandra viszont csak IP alapján tud kapcsolódni a <i>seed</i> szerverekhez, ezért meg kellett trükköznöm a <i>CASSANDRA_SEEDS</i> változót, domain neveket és IP címeket is elfogad egyaránt, majd a Cassandra indítása előtt feloldja a domain neveket IP címekre.
<br />
<br />
<h2>
Futtatás</h2>
<br />
slave1
<br />
<pre>nohup docker -H tcp://192.168.50.15:1234 run --name cassandra-slave1 --dns 192.168.50.15 -h cassandra1.lo -e "PUBLIC_INTERFACE=eth0" -e "CASSANDRA_CLUSTERNAME=HadoopTest" -e "CASSANDRA_TOKEN=-9223372036854775808" -t mhmxs/cassandra-cluster > cassandra.log 2>&1 &
docker -H tcp://192.168.50.15:1234 run --name hadoop-slave1 --dns 192.168.50.15 -h slave1.lo -e "MASTER=master.lo" -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
A Cassadnrás konténer logját a cassandra.log fájlban találjuk, érdemes a Hadoop cluster elindítása előtt összeállítani a Cassandra clustert (vagy külön terminálban nohup nélkül indítani), mert ha valami időzítési vagy hálózati probléma miatt nem találták meg egymást a nodeok, akkor elég kényelmetlen a Swarm clusterből törölgetni, majd újraindítgatni a megfelelő konténereket. Sokszor kellett a fejlesztés alatt hasonlót csinálnom, és a rendszer sem túl hiba tűrő, úgyhogy rászoktam, hogy minden lépés előtt ellenőrzöm, hogy az elemek a helyükre kerültek-e.
<br />
<br />
slave2
<br />
<pre>nohup docker -H tcp://192.168.50.15:1234 run --name cassandra-slave2 --dns 192.168.50.15 -h cassandra2.lo -e "PUBLIC_INTERFACE=eth0" -e "CASSANDRA_CLUSTERNAME=HadoopTest" -e "CASSANDRA_SEEDS=cassandra1.lo" -e "CASSANDRA_TOKEN=-3074457345618258603" -t mhmxs/cassandra-cluster > cassandra.log 2>&1 &
docker -H tcp://192.168.50.15:1234 run --name hadoop-slave2 --dns 192.168.50.15 -h slave2.lo -e "MASTER=master.lo" -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
slave3
<br />
<pre>nohup docker -H tcp://192.168.50.15:1234 run --name cassandra-slave3 --dns 192.168.50.15 -h cassandra3.lo -e "PUBLIC_INTERFACE=eth0" -e "CASSANDRA_CLUSTERNAME=HadoopTest" -e "CASSANDRA_SEEDS=cassandra1.lo" -e "CASSANDRA_TOKEN=3074457345618258602" -t mhmxs/cassandra-cluster > cassandra.log 2>&1 &
docker -H tcp://192.168.50.15:1234 run --name hadoop-slave3 --dns 192.168.50.15 -h slave3.lo -e "MASTER=master.lo" -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
master
<br />
<pre>docker -H tcp://192.168.50.15:1234 run --name hadoop-master --dns 192.168.50.15 -h master.lo -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -v /vagrant:/vagrant -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
<br />
<h2>
Job</h2>
<br />
A hozzuk létre a projekt könyvtárban a KeyCollector.java fájlt az alábbi tartalommal:
<br />
<pre class="brush: java">import java.io.IOException;
import java.util.*;
import java.nio.ByteBuffer;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.hadoop.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.utils.ByteBufferUtil;
public class KeyCollector {
public static void main(String[] args) throws IOException {
if (args.length != 1) {
System.err.println("Usage: KeyCollector <output path>");
System.exit(-1);
}
JobConf conf = new JobConf(KeyCollector.class);
conf.setJobName("KeyCollector");
ConfigHelper.setInputInitialAddress(conf, "cassandra1.lo");
ConfigHelper.setInputColumnFamily(conf, "HadoopTest", "content");
ConfigHelper.setInputPartitioner(conf, "org.apache.cassandra.dht.Murmur3Partitioner");
SlicePredicate predicate = new SlicePredicate().setColumn_names(Arrays.asList(ByteBufferUtil.bytes("text")));
ConfigHelper.setInputSlicePredicate(conf, predicate);
conf.setInputFormat(ColumnFamilyInputFormat.class);
conf.setMapperClass(KeyCollectorMapper.class);
FileOutputFormat.setOutputPath(conf, new Path(args[0]));
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setReducerClass(KeyCollectorReducer.class);
JobClient.runJob(conf);
}
public static class KeyCollectorMapper extends MapReduceBase implements Mapper<ByteBuffer, Map<ByteBuffer, BufferCell>, Text, IntWritable> {
public void map(ByteBuffer key, Map<ByteBuffer, BufferCell> columns, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException {
String textKey = ByteBufferUtil.string(key);
output.collect(new Text(textKey), new IntWritable(1));
}
}
public static class KeyCollectorReducer extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> {
public void reduce(Text key, Iterator<IntWritable> values, OutputCollector<Text,IntWritable> output, Reporter reporter) throws IOException {
int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
output.collect(key, new IntWritable(sum));
}
}
}
</pre>
Valószínűleg ez a világ legértelmetlenebb map/reduce jobja, összegyűjti a <i>column familybe</i> lévő kulcsokat, de ez tűnt a legegyszerűbb implementációnak. Természetesen lett volna lehetőség a job eredményét a Cassandrába tárolni, de a letisztultság jegyében, én a fájl rendszert preferáltam. Fordítsuk le az osztályt és csomagoljuk be egy jar-ba a mester Hadoop konténerben.
<br />
<pre>yum install -y java-1.8.0-openjdk-devel
cd /vagrant
mkdir build
classpath=.
for jar in /usr/share/cassandra/*.jar; do classpath=$classpath:$jar; done
for jar in /usr/share/cassandra/lib/*.jar; do classpath=$classpath:$jar; done
for jar in `find /usr/local/hadoop/share/hadoop/ *.jar`; do classpath=$classpath:$jar; done
javac -classpath $classpath -d build KeyCollector.java
jar -cvf KeyCollector.jar -C build/ .
</pre>
Következő lépés, hogy ellenőrizzük a cluster működését, és teszt adattal töltjük fel az adatbázist, szintén a mester konténerből.
<br />
<pre>nodetool -h cassandra1.lo status
cassandra-cli -h casandra1.lo
create keyspace HadoopTest with strategy_options = {replication_factor:2} and placement_strategy = 'org.apache.cassandra.locator.SimpleStrategy';
use HadoopTest;
create column family content with comparator = UTF8Type and key_validation_class = UTF8Type and default_validation_class = UTF8Type and column_metadata = [ {column_name: text, validation_class:UTF8Type} ];
set content['apple']['text'] = 'apple apple red apple bumm';
set content['pear']['text'] = 'pear pear yellow pear bumm';
</pre>
Elérkezett a várv várt pillanat, futtathatjuk a jobot.
<br />
<pre>$HADOOP_PREFIX/bin/hadoop jar KeyCollector.jar KeyCollector output
$HADOOP_PREFIX/bin/hdfs dfs -cat output/*
</pre>
A kimenetben láthatjuk, hogy egy darab apple és egy darab pear kulcs van az adatbázisban.
<br />
<pre>SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/usr/local/hadoop-2.6.0/share/hadoop/common/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/usr/share/cassandra/lib/logback-classic-1.1.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
apple: 1
pear: 1
</pre>
<br />
<h2>
Teljesítmény</h2>
<br />
Befejezésül ejtsünk pár szót a rendszer teljesítmény optimalizálásáról.
<br />
<br />
A Cassandra elég korai verziójában bevezetésre kerültek az un. <i>vNode</i>ok, amik azt biztosítják, hogy egy Cassandra valós node a token tartomány több szeletét is birtokolhassa egyszerre. A Cassandra oldaláról számos előnye van ennek a megoldásnak, viszont a Hadoop felöl érkezve kifejezetten káros hatása van. A vNodeok száma kihatással van a szeletek (split) számára, ami annyit tesz, hogy annyi darab szelet egészen biztos lesz, ahány vNode van engedélyezve.
<br />
<br />
A következő paraméter, amire érdemes figyelni, az a szeletek mérete. Az alapértelmezett szelet mérete 64K, ha mondjuk van 5,000,000,000 sor az adatbázisban, akkor 64,000-el számolva 78,125 szeletben fogja elvégezni a műveletet a Hadoop, ami szeletenkénti 10 másodperccel szorozva száz óra körüli futás időt eredményez. Az alábbi sorral konfigurálhatjuk a szeletek méretét.
<br />
<pre class="brush: java">ConfigHelper.setInputSplitSize(job.getConfiguration(), 10000000);
</pre>
Mivel a feldolgozó egységekben korlátozott mennyiségű memória áll rendelkezésre, javasolt finom-hangolni az egyszerre feldolgozott adatok számát, a példában 100-as kötegekben fogja a CQL driver a sorokat prezentálni, az alapértelmezett 1000 darab helyett.
<br />
<pre class="brush: java">CqlConfigHelper.setInputCQLPageRowSize(job.getConfiguration(), Integer.toString(100));
</pre>
A negyedik megkerülhetetlen téma az un. <i>data locality</i>. A jó teljesítmény eléréséhez elengedhetetlen, hogy az adatokat a legkisebb mértékben kelljen mozgatni. A Cassandra fejlesztői az <a href="https://github.com/facebookarchive/hadoop-20/blob/master/src/mapred/org/apache/hadoop/mapreduce/InputFormat.java" target="_blank">InputFormat</a> implementálása során szerencsére gondoltak erre a probléma forrásra, annyi a dolgunk, hogy ugyanarra a hostra telepítjük a Cassandrát, és a Hadoop JobTrackert. Sajnos Docker konténereket használva ezt bukjuk, mert a két konténer mindig két különálló host lesz, gyakorlatilag mintha külön gépen futnának a szolgáltatások. Megtehetnénk, hogy összeházasítjuk a két konténert, de akkor a Docker szemlélettel mennénk szembe, miszerint minden szolgáltatás egy önálló konténer legyen. Ha ezt el szeretnénk kerülni, akkor bizony be kell koszolnunk a kezünket. Én első lépésben megnyitottam a legfrissebb <a href="https://github.com/apache/cassandra/blob/9718e13d7ec5d7cf88095d3db81091ca75247e15/src/java/org/apache/cassandra/hadoop/ColumnFamilyInputFormat.java" target="_blank">dokumentációt</a>. Láthatjuk, hogy semmi nem található benne a témával kapcsolatban, de ne keseredjünk el, ugyanis van egy őse ennek a dokumentumnak, nyissuk meg a <a href="https://github.com/apache/cassandra/blob/9718e13d7ec5d7cf88095d3db81091ca75247e15/src/java/org/apache/cassandra/hadoop/AbstractColumnFamilyInputFormat.java" target="_blank">AbstractColumnFamilyInputFormat</a> fájlt is. A <span class="pl-k" style="background-color: white; box-sizing: border-box; color: #a71d5d; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;">public</span><span style="background-color: white; color: #333333; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;"> </span><span class="pl-k" style="background-color: white; box-sizing: border-box; color: #a71d5d; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;">List<<span class="pl-smi" style="box-sizing: border-box; color: #333333;">InputSplit</span>></span><span style="background-color: white; color: #333333; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;"> </span><span class="pl-en" style="background-color: white; box-sizing: border-box; color: #795da3; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;">getSplits</span><span style="background-color: white; color: #333333; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;">(</span><span class="pl-smi" style="background-color: white; box-sizing: border-box; color: #333333; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;">JobContext</span><span style="background-color: white; color: #333333; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;"> </span><span class="pl-v" style="background-color: white; box-sizing: border-box; color: #ed6a43; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;">context</span><span style="background-color: white; color: #333333; font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 12px; line-height: 15.27272605896px; white-space: pre;">) </span>metódusban történik a csoda, itt állítja össze a Hadoop a szeleteket, minden egyes szeletet egy <a href="http://hadoop.apache.org/docs/r2.6.0/api/org/apache/hadoop/mapred/InputSplit.html" target="_blank">InputSplit</a> objektum reprezentál, és tartalmazza azt/azokat a host neveket, ahol az adat megtalálható.<br />
<br />
A jelen cluster egy szoftveres hálózaton kommunikál, így ebben az esetben nincs jelentősége az adatok tényleges helyének, nem is bonyolítanám tovább a rendszert saját InputFormat írásával és/vagy különböző DNS trükkök bevezetésével. A jó megoldást mindenféleképpen az adott hálózati architektúrának megfelelően kell kialakítani. Akit érdekelnek további a teljesítménnyel kapcsolatos kérdések <a href="http://apmblog.dynatrace.com/2013/07/17/top-performance-problems-discussed-at-the-hadoop-and-cassandra-summits/" target="_blank">itt</a> talál pár választ.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-11429144558557600802015-05-25T21:00:00.000+02:002015-06-05T16:36:17.673+02:00Konténerezett Hadoop és Cassandra cluster konfigurálása - második részAz <a href="http://jpattern.blogspot.com/2015/05/kontenerezett-hadoop-es-cassandra.html">előző</a> részben sikeresen elindítottuk a Hadoop clustert, annyi szépség hibával, hogy a Docker konténerek a Vagrantos gépek IP címeire telepedtek --net=host kapcsolóval, ami bizonyos körülmények között elfogadható, de mindenképpen szeretném elkerülni a host IP-k használatát. A most következő részben három dologgal fogom felturbózni az infrastruktúrát. Szeretném a gépeket <a href="https://docs.docker.com/swarm/">Swarm</a> clusterben összefogni, hogy DNS segítségével találják meg egymást, egy privát hálózaton. A Swarm a <a href="https://docs.docker.com/">Docker Engine</a> képességeit terjeszti ki egy egész gép csoportra, lényegében a megszokott szolgáltatásokat kapjuk, csak nem egy gépre levetítve, hanem egy egész fürtre. A <a href="https://github.com/weaveworks/weave" target="_blank">Weave</a> gondoskodik a konténer közi hálózatról, és bár rendelkezik saját DNS megoldással, számomra a <a href="https://github.com/iverberk/docker-spy" target="_blank">Docker-spy</a> szimpatikusabb választásnak tűnt, mert egyszerűbb telepíteni és üzemeltetni.<br />
<br />
<h2>
Előkészítés</h2>
Első dolgunk, hogy meglévő projektünket frissítjük a megfelelő verzióra:
<br />
<pre>cd hadoop-docker && git checkout 2.6.0-dns && git pull origin 2.6.0-dns
cd .. && git checkout 2.6.0-dns && git pull origin 2.6.0-dns
</pre>
A Docker Swarmot és egyéb szükséges szolgáltatásokat egy újabb virtuális gépre telepítettem, kénytelen voltam a hálózati címeket megváltoztatni, mert a Swarm menedzser nem volt hajlandó az 1-es végű gépre csatlakozni, kitöröltem a hostnév konfigurációt, hiszen többé nem lesz rá szükség, illetve a Weave hálózathoz szükséges újabb IP-ket konfiguráltam:
<br />
<pre>config.vm.define "swarm" do |sw|
sw.vm.network "private_network", ip: "192.168.50.15"
sw.vm.provision :shell, inline: "sh /vagrant/swarm.sh 10.2.0.15/16 10.2.15.0/24"
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "512"]
end
end
</pre>
Élesszük fel a gépeket:
<br />
<pre>vagrant halt && vagrant up
</pre>
Majd a futó Vagrantos környezetben építsük újra a konténereket illetve eszközöljük az egyéb változtatásokat:
<br />
<pre>vagrant provision master slave1 slave2 slave3
</pre>
Lépjünk be a virtuális gépekre, és töröljünk minden futó konténert:
<br />
<pre>docker rm -f $(docker ps -qa)
</pre>
<br />
<h2>
Változások</h2>
Mielőtt nekilátnánk a szolgáltatások elindításához vegyük sorra pontosan miket kellett átalakítani a kódban.
<br />
<br />
<a href="https://github.com/mhmxs/vagrant-host-hadoop-cassadra-cluster/blob/2.6.0-dns/common.sh">common.sh</a>
<br />
<pre>sed -i "s/exit 0//" /etc/rc.local
if [ -z "$(cat /etc/rc.local | grep 'docker restart')" ]; then
echo "service docker restart" >> /etc/rc.local
fi
</pre>
Biztos ami biztos a Dockert kényszerítem, hogy elinduljon.
<br />
<pre>cat > /etc/network/interfaces.d/weave.cfg << EOF
auto weave
iface weave inet static
address $(echo $1 | sed "s|/.*||")
network 10.2.0.0
netmask 255.255.0.0
broadcast 0.0.0.0
bridge_ports none
bridge_maxwaitout 0
EOF
</pre>
Létrehoztam egy hálózati hidat a Weave számára az első paraméterben (pl. 10.2.0.15/16) megadott IP címmel.
<br />
<pre>apt-get update && apt-get -y install bridge-utils curl docker.io
echo "" > /etc/default/docker
service docker restart
</pre>
Telepítem a bridge-utils csomagot ami elengedhetetlen a perzisztens hálózati hídhoz, majd kényszerítem a Dockert, hogy alap beállításokkal induljon el.
<br />
<pre>if [ ! -f /usr/local/bin/weave ]; then
wget -O /usr/local/bin/weave https://github.com/weaveworks/weave/releases/download/latest_release/weave
chmod a+x /usr/local/bin/weave
fi
if [ -z "$(ifconfig | grep weave)" ]; then
weave create-bridge
ip addr add dev weave $1
fi
</pre>
Telepítem a Weavet, és mivel az előzőekben definiált hálózati híd a következő újraindítás során válik csak perzisztenssé, ezért most az egyszer kézzel létrehozom azt a Weave beépített eszközével.
<br />
<pre>echo "DOCKER_OPTS='-H tcp://$(ifconfig eth1 | awk '/inet addr/{print substr($2,6)}'):2375 -H unix:///var/run/docker.sock --bridge=weave --fixed-cidr=$2'" > /etc/default/docker
service docker restart
</pre>
Az eth1 nevű interfésszel összekapcsolom a Dockert, így az kívülről is elérhetővé válik, továbbra is használatban marad a Unix socket, definiálom a hidat mint hálózati csatolót, és garantálom, hogy a Docker minden konténernek teljesen egyedi IP-t adjon cluster szerte a második paraméterben (pl. 10.2.15.0/24) megadott IP tartománnyal.
<br />
<br />
A konténer konfigurációjában is kellett kicsit piszkálni, a <a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-dns/bootstrap.sh">bootstrap.sh</a>-ban lecseréltem a statikus IP konfigurációt host nevekre:
<br />
<pre>if [ -z "$MASTER" ]; then
HOST_NAME=$(hostname)
echo $HOST_NAME > $HADOOP_PREFIX/etc/hadoop/masters
echo $HOST_NAME > $HADOOP_PREFIX/etc/hadoop/slaves
if [ -n "$SLAVES" ]; then
echo "$SLAVES" | sed -e "s/,/\n/g" >> $HADOOP_PREFIX/etc/hadoop/slaves
fi
for a in `ls $HADOOP_PREFIX/etc/hadoop/*site.xml`; do sed -i "s/master/$HOST_NAME/g" $a; done
else
if [ -n "$MASTER" ]; then
for a in `ls $HADOOP_PREFIX/etc/hadoop/*site.xml`; do sed -i "s/master/$MASTER/g" $a; done
fi
fi
</pre>
Többé nem a mesterrel kell környezeti változóval tudatni, hogy mester módban induljon el, hanem a szolgáknak kell megadni a mester host nevét. A mester kicseréli a *.site.xml konfigurációs állományokban a master nevet a saját host nevére, a szolgák pedig a megadott névre.
<br />
<br />
<h2>
Futtatás</h2>
A rendszer automatizálása eddig a pontig tartott, az egyes komponenseket már kézzel kell elindítani. A swarm nevű gépen indítsuk el a Weave hálózatot, hozzuk létre a Swarm cluster és indítsuk el a menedzsert:
<br />
<pre>weave launch
docker run --rm swarm create > /vagrant/currenttoken
docker run -d -p 1234:2375 swarm manage token://$(cat /vagrant/currenttoken)
</pre>
Majd a többi gépen csatlakozzunk a hálózathoz és a clusterhez:
<br />
<pre>weave launch 192.168.50.15
docker run -d swarm join --addr=$(ifconfig eth1 | awk '/inet addr/{print substr($2,6)}'):2375 token://$(cat /vagrant/currenttoken)
</pre>
Ellenőrizzük, hogy a Swarm cluster megfelelően jött-e létre:
<br />
<pre>docker -H tcp://192.168.50.15:1234 info
</pre>
Látnunk kell, hogy 3 gépből áll a fürt (ne essünk kétségbe, ha rögtön nem jelennek meg a gépek, van egy kis késleltetése a rendszernek). A feladat első részével végeztünk is, ha megfelelően paraméterezve elindítjuk a konténereket, akkor a hálózaton látni fogják egymást. Nagyobb cluster méret esetén érdemes még un. <a href="https://docs.docker.com/swarm/discovery/">Service discovery</a> réteggel kibővíteni a rendszert. Támogatott implementáció akad bőven <a href="https://github.com/coreos/etcd">Etcd</a>, <a href="https://www.consul.io/">Consul</a>, <a href="https://zookeeper.apache.org/">Zookeeper</a>, nem szeretnék a részletekbe belemenni, mindenki használja a saját preferáltját. A SD-nek két fontos szerepe van: az egyik, hogy a Swarm menedzser azon keresztül találja meg a gépeket a clusterben, a másik, hogy a gépek egymást is azon keresztül találják meg a hálózaton. A jelenlegi gép méret az első opciót nem igazán indokolja, DNS szolgáltatásnak pedig mondhatni ágyúval verébre esete áll fenn, így valami egyszerűbb megoldást választottam. Számtalan DNS projekt érhető el Docker ökoszisztémában, a legtöbb arra alapozza a működését, hogy listenereket aggat a Docker registryre és elcsípik amikor egy konténer létrejön vagy megszűnik. Ahogy említettem és a Docker-spyt választottam, és ennek megfelelően indítsuk is el azt a swarm nevű gépen.<br />
<pre>docker run --name docker-spy -e "DOCKER_HOST=tcp://192.168.50.15:1234" -e "DNS_DOMAIN=lo" -p 53:53/udp -p 53:53 -v /var/run/docker.sock:/var/run/docker.sock iverberk/docker-spy
</pre>
Ez a konténer nem csatlakozik a Swarm clusterhez, viszont a cluster eseményeire reagál, és az <i>lo</i> végződésű host neveket regisztrálja a memóriában, a többi kérést pedig a Google népszerű név feloldó szervere felé továbbítja, így nem kell aggódnunk, hogy csak a saját neveinket oldja fel.<br />
<br />
Nincs más dolgunk, mint elindítani a Hadoop konténereket.
<br />
<br />
slave1
<br />
<pre>docker -H tcp://192.168.50.15:1234 run --name hadoop-slave1 --dns 192.168.50.15 -h slave1.lo -e "MASTER=master.lo" -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
slave2
<br />
<pre>docker -H tcp://192.168.50.15:1234 run --name hadoop-slave2 --dns 192.168.50.15 -h slave2.lo -e "MASTER=master.lo" -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
slave3
<br />
<pre>docker -H tcp://192.168.50.15:1234 run --name hadoop-slave3 --dns 192.168.50.15 -h slave3.lo -e "MASTER=master.lo" -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
master
<br />
<pre>docker -H tcp://192.168.50.15:1234 run --name hadoop-master --dns 192.168.50.15 -h master.lo -e "SLAVES=slave1.lo,slave2.lo,slave3.lo" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
Ha mindent jól csináltunk, akkor a docker-spy konténer logjában látnunk kell, ahogyan regisztrálja a host neveket, kedvünre futtathatjuk a nekünk tetsző map/reduce jobot.
<br />
<br />
A <a href="http://jpattern.blogspot.hu/2015/06/kontenerezett-hadoop-es-cassandra.html" target="_blank">következő részben</a> egy Cassandra cluster beüzemelésének lépéseit tervezem bemutatni, némi teljesítmény hangolással, és egy map/reduce job futtatásával.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-37609115188524002992015-05-19T06:25:00.000+02:002015-05-25T21:01:20.604+02:00Konténerezett Hadoop és Cassandra cluster konfigurálása - első részA következő cikk-sorozatban egy <a href="http://hadoop.apache.org/">Hadoop</a> cluster telepítését mutatom be <a href="http://cassandra.apache.org/">Cassandra</a> támogatással, a méltán népszerű <a href="http://www.docker.com/">Docker</a> konténerekbe zárva. Ebben a cikkben a Hadoop telepítése és konfigurálása kerül terítékre. Bár mindkét rendszer elindítható egyke módban, azért az elsődleges cél területük a nagy mennyiségű adat kezelés elosztott rendszereken, ha pedig a skálázhatóság is fontos szerepet játszik a kiszolgálásban, akkor a Docker kézenfekvő és divatos megoldás. A konténereket ésszerűen nem 0-ról építettem fel, hanem újrahasznosítottam a <a href="https://github.com/sequenceiq/hadoop-docker/tree/2.6.0">SequenceIQ Hadoop</a>, és a <a href="https://github.com/spotify/docker-cassandra">Spotify Cassandra</a> konténerét. Mindkét csapatról elmondható, hogy van sejtésük :) a témáról, így biztos alapnak éreztem az ő megoldásaikból kiindulni. A cikk készítése során fontos szempontnak tartottam, hogy bárki, aki megfelelő mennyiségű memóriával rendelkezik, a saját gépén próbálhassa ki a Hadoop és Cassandra házasságát,
és ne kelljen valamilyen olcsó vagy drága felhő szolgáltatást előfizetnie. Választásom a Vagrantra esett, és segítségével virtualizálom a gépeket, a hálózatot, és egyéb erő forrásokat, és készítek privát hálózatot, amin keresztül a gépek elérik egymást. További előnye a döntésnek, hogy így a Hadoop memória kezelésébe is el kell mélyednünk, hiszen mindent minimalizálni kényszerültem, mivel a Hadoop alap konfiguráció óriási méretekre van optimalizálva, a konténerenknéti maximális 8192 Mb memória és 8 vcore legalábbis erről árulkodik.
<br />
<br />
A teljes projekt forrását elérhetővé tettem, így az első lépés a forrás megszerzése, és a Vagrant gépek elindítása. Az egyes gépek között 4.5 Gb memóriát osztottam szét (+oprendszer, +vagrant,+a böngésző amiben olvasod a cikket), amennyiben nincs elegendő memória, kézzel kell a főbb gépeket elindítani. Türelmetlenek a <i>cassandra.sh</i> fájlban letilthatják a Cassandra konténerek építését, egy darabig úgysem lesz rájuk szükség.
<br />
<pre>git clone https://github.com/mhmxs/vagrant-host-hadoop-cassadra-cluster.git
cd vagrant-host-hadoop-cassadra-cluster
git submodule update --init
cd hadoop-docker && git checkout 2.6.0-static-ip
cd .. && git checkout 2.6.0-static-ip
vagrant up</pre>
Amíg a letöltés, telepítés, és egyéb gyártási folyamatok zajlanak kukkantsunk kicsit a motor háztető alá, lesz rá időnk bőven, ugyanis ezen a ponton nem sokat optimalizáltam,
így sok tartalom többször is letöltésre kerül az egyes virtuális gépeken (javaslatokat várom a hozzászólásban). Íme a vagrant konfiguráció:
<br />
<pre> config.vm.box = "ubuntu/vivid64"
config.vm.provider "virtualbox" do |v|
v.memory = 1024
end
config.vm.box_check_update = false
config.vm.define "slave1" do |slave|
slave.vm.network "private_network", ip: "192.168.50.1"
slave.vm.provision :shell, inline: "hostname slave1 && sh /vagrant/cassandra.sh"
end
config.vm.define "slave2" do |slave|
slave.vm.network "private_network", ip: "192.168.50.2"
slave.vm.provision :shell, inline: "hostname slave2 && sh /vagrant/cassandra.sh"
end
config.vm.define "slave3" do |slave|
slave.vm.network "private_network", ip: "192.168.50.3"
slave.vm.provision :shell, inline: "hostname slave3 && sh /vagrant/cassandra.sh"
end
config.vm.define "master" do |master|
master.vm.network "private_network", ip: "192.168.50.4"
config.vm.provision :shell, inline: "hostname master && sh /vagrant/hadoop.sh"
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "1536"]
end
end
</pre>
Tehát van 4 gépünk egy hálózaton, egy mester és 3 szolga. <b>A host név beállítás sajnos nem perzisztens, de a hibát már csak a következő verzióban javítottam, elnézést.</b> A gépek száma tovább növelhető, az ököl szabály, hogy ami 3 gépen működik, az jó eséllyel működik többön is, ezért én a minimális darabszámot preferáltam. Elég sok leírást olvastam végig a Hadoop fürtök konfigurálásáról, és azonnal meg is jegyezném az egyik legnagyobb problémát az ökoszisztémával kapcsolatban. Olyan ütemben fejlődik a platform, hogy amit ma megírtam, az lehet, hogy holnap már nem is érvényes, vonatkozik ez az architektúrára (természetesen nem messziről szemlélve, az ördög a részletekben lakik), a beállításokra, és a fellépő hibákra is. Számtalan esetben az általam használt 2.6.0-ás verziójú Hadoop komponensben már nem is létezett az a kapcsoló, amivel javasolták az elcsípett kivétel kezelését, és kivétel akadt bőven.
<br />
<br />
Vegyük sorra milyen <a href="https://github.com/sequenceiq/hadoop-docker/compare/2.6.0...mhmxs:2.6.0-static-ip">változtatásokat eszközöltem</a> a beállításokban, de előtte tisztázzunk pár paramétert:
<br />
<ul>
<li>konténerek minimum memória foglalása: 4 Gb memória alatt 256 Mb az ajánlott</li>
<li>konténerek száma: min (2 * processzor magok, 1.8 * merevlemezek száma, Összes memória / konténer minimum memória foglalásával) - min(2, 1.8, 1024 / 256) = 2 egy kis csalással</li>
<li>rendszernek fenntartott memória: 4 Gb esetén 1 Gb, ennél kisebb értékre nincs ajánlás, így én az 512 Mb-ot választottam</li>
<li>összes felhasználható memória: rendelkezésre álló - fenntartott, 1024 - 512 = 512 Mb (gépenként 2 konténer)</li>
</ul>
További részletek és az ajánlott matek <a href="http://docs.hortonworks.com/HDPDocuments/HDP2/HDP-2.0.6.0/bk_installing_manually_book/content/rpm-chap1-11.html">itt</a> található.
<br />
<br />
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-static-ip/Dockerfile">Dockerfile</a>
<br />
<ul>
<li>Telepítettem a Cassandrát</li>
<li>Mivel a Cassandra függősége az Open JDK 8, ezért eredeti Java konfigurációt eltávolítottam</li>
<li>Közös RSA kulcsot generáltam, hogy a konténerek között megoldható legyen a bejelentkezés jelszó nélkül</li>
<li>A Hadoop classpathjára betettem a Cassandrát</li>
<li>Pár további portot kitettem, exposoltam magyarul</li>
<li>Kicsit átrendeztem a parancsok sorrendjét, hogy minél gyorsabban lehessen konfigurációt változtatni, hála a rétegelt fájl-rendszernek a Docker csak a különbségig görgeti vissza a konténert, és onnan folytatja az építési műveletet tovább</li>
</ul>
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-static-ip/core-site.xml">core-site.xml</a>
<br />
<ul>
<li>fs.defaultFS: hdfs://master:9000 - a master gépen fog futni a NameNode, és ezt jó, ha mindenkinek tudja</li>
</ul>
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-static-ip/hdfs-site.xml">hdfs-site.xml</a>
<br />
<ul>
<li>dfs.replication: 2 - a fájlrendszer replikációs faktorát emeltem meg 2-re</li>
<li>dfs.client.use.datanode.hostname: true - a dfs kliensek alap értelmezetten IP alapján kommunikálnak, ha több interfészünk, vagy több hálózati rétegünk van, akkor ez a működés okozhat fejfájást</li>
<li>dfs.datanode.use.datanode.hostname: true - szintén zenész</li>
<li>dfs.namenode.secondary.http-address: master:50090 - a másodlagos NameNode elérhetősége</li>
<li>dfs.namenode.http-address: master:50070 - az elsődleges NameNode elérhetősége</li>
</ul>
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-static-ip/mapred-site.xml">mapred-site.xml</a>
<br />
<ul>
<li>mapreduce.jobtracker.address: master:8021 - szükségünk van egy JobTrackerre</li>
<li>mapreduce.map.memory.mb: 256 - map műveletet végző konténer memória foglalása</li>
<li>mapreduce.reduce.memory.mb: 512 - és a reducet végzőké</li>
<li>yarn.app.mapreduce.am.resource.mb: 512 - az AppMaster által felhasználható memória</li>
<li>yarn.app.mapreduce.am.job.client.port-range: 50200-50201 - alapértelmezetten un. <a href="http://en.wikipedia.org/wiki/Ephemeral_port">ephemeral port</a>ot használ az AppMaster, az egyszerűség (tűzfal, port publikálás, stb.) kedvéért rögzítettem a tartományt</li>
<li>yarn.app.mapreduce.am.command-opts: -Xmx409m - AppMaster processz konfigurációja, <a href="http://stackoverflow.com/questions/30231508/yarn-mapreduce-job-dies-with-strange-message">ez okozott egy kis fejfájást</a>, végül úgy találtam meg a problémát, hogy az eredeti forrásra egyesével rápakoltam a változtatásaimat, jó móka volt</li>
<li>mapreduce.jobhistory.address: master:10020 - elosztott környezetben szükség van JobHistory szerverre</li>
<li>mapreduce.jobhistory.webapp.address: master:19888 - igény esetén webes felületre is</li>
<li>mapreduce.application.classpath: ..., /usr/share/cassandra/*, /usr/share/cassandra/lib/* - az alapértelmezett útvonalakhoz hozzáadtam a Cassandrát</li>
</ul>
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-static-ip/yarn-site.xml">yarn-site.xml</a>
<br />
<ul>
<li>yarn.resourcemanager.hostname: master</li>
<li>yarn.nodemanager.vmem-check-enabled: false - Centos 6 specifikus hiba jött elő, amit a virtuális memória ellenőrzésének kikapcsolásával tudtam megoldani. <a href="http://blog.cloudera.com/blog/2014/04/apache-hadoop-yarn-avoiding-6-time-consuming-gotchas/">Bővebben</a> a hibáról (6. Killing of Tasks Due to Virtual Memory Usage).</li>
<li>yarn.nodemanager.resource.cpu-vcores: 2 - a virtuális processzorok számát csökkentettem, így gépenként csak 2 osztható szét a konténerek között</li>
<li>yarn.nodemanager.resource.memory-mb: 512 - az a memória, amivel a helyi NodeManager rendelkezik</li>
<li>yarn.scheduler.minimum-allocation-mb: 256 - minimális memória foglalás konténerenként</li>
<li>yarn.scheduler.maximum-allocation-mb: 512 - és a maximális párja</li>
<li>yarn.nodemanager.address: ${yarn.nodemanager.hostname}:36123 - a konténer menedzser címe, alapértelmezetten a NodeManager választ portot magának, szintén specifikáltam</li>
<li>yarn.application.classpath: ..., /usr/share/cassandra/*, /usr/share/cassandra/lib/* - a Cassandrát elérhetővé tettem a Yarn számára</li>
</ul>
<a href="https://github.com/mhmxs/hadoop-docker/blob/2.6.0-static-ip/bootstrap.sh">bootstrap.sh</a>
<br />
<pre>echo $SLAVES | sed "s/,/\n/g" > /tmp/slaves
while read line; do printf "\n$line" >> /etc/hosts; done < /tmp/slaves
if [ -z "$(cat /etc/hosts | grep master)" ]; then
printf "\n$MASTER_IP master" >> /etc/hosts
fi
</pre>
<pre>if [ -n "$MASTER" ]; then
echo "Starting master"
rm /tmp/*.pid
echo "master" > $HADOOP_PREFIX/etc/hadoop/masters
echo "master" > $HADOOP_PREFIX/etc/hadoop/slaves
while read line; do echo $line | sed -e "s/.*\s//" >> $HADOOP_PREFIX/etc/hadoop/slaves; done < /tmp/slaves
$HADOOP_PREFIX/sbin/start-dfs.sh
$HADOOP_PREFIX/bin/hdfs dfs -mkdir -p /user/root
$HADOOP_PREFIX/sbin/start-yarn.sh
$HADOOP_PREFIX/sbin/mr-jobhistory-daemon.sh --config $HADOOP_PREFIX/etc/hadoop start historyserver
else
echo "Starting slave"
if [[ $1 == "-bash" ]]; then
echo "To read logs type: tail -f $HADOOP_PREFIX/logs/*.log"
fi
fi
</pre>
Gondoskodtam a megfelelő DNS beállításokról, szétválasztottam a futás jellegét a MASTER környezeti változó alapján, amit a konténer futtatásakor tudunk megadni, valamint indítottam egy JobHistory szervert a masteren. Az <span style="background-color: white; color: #999999; font-size: 11px;">echo "master" > $HADOOP_PREFIX/etc/hadoop/slaves</span> sor gondoskodik arról, hogy a mester is végezzen szolgai munkát, igény szerint ez eltávolítható, és egyúttal némi memória is felszabadítható a mester gépen.<br />
<br />
Miután befejeződött a Vagrant gépek előkészítése, és a konténerek is elkészültek, nincs más hátra, mint elindítani a konténereket, vagy mégsem? Sajnos nem, ugyanis egy kivételbe futunk a job futtatása során, de menjünk egy kicsit bele a részletekbe.<br />
<pre>vagrant ssh slave[1-3]
docker run --name hadoop -h slave[1-3] -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -p 50020:50020 -p 50010:50010 -p 50075:50075 -p 8040:8040 -p 8042:8042 -p 49707:49707 -p 2122:2122 -p 36123:36123 -p 50200-50210:50200-50210 -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
<pre>vagrant ssh master
docker run --name hadoop -h master -e "MASTER=true" -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -p 50020:50020 -p 50090:50090 -p 50070:50070 -p 50010:50010 -p 50075:50075 -p 9000:9000 -p 8021:8021 -p 8030:8030 -p 8031:8031 -p 8032:8032 -p 8033:8033 -p 8040:8040 -p 8042:8042 -p 49707:49707 -p 2122:2122 -p 8088:8088 -p 10020:10020 -p 19888:19888 -p 36123:36123 -p 50200-50210:50200-50210 -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
Bizonyosodjunk meg róla, hogy mind a 4 konténer rendben elindult-e és, hogy a cluster összeállt-e megfelelően:
<br />
<pre>bin/hdfs dfsadmin -report
bin/yarn node -list
</pre>
Tegyünk egy próbát:
<br />
<pre>bin/hdfs dfs -mkdir -p input && bin/hdfs dfs -put $HADOOP_PREFIX/etc/hadoop/* input</pre>
<pre>bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.0.jar wordcount input output
</pre>
<pre>WARN [main] org.apache.hadoop.mapred.YarnChild: Exception running child : java.net.NoRouteToHostException: <b>No Route to Host from master/172.17.0.155 to 172.17.0.159:40896</b> failed on
socket timeout exception: java.net.NoRouteToHostException: No route to host; For more details see: http://wiki.apache.org/hadoop/NoRouteToHost
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at org.apache.hadoop.net.NetUtils.wrapWithMessage(NetUtils.java:791)
at org.apache.hadoop.net.NetUtils.wrapException(NetUtils.java:757)
at org.apache.hadoop.ipc.Client.call(Client.java:1472)
at org.apache.hadoop.ipc.Client.call(Client.java:1399)
at org.apache.hadoop.ipc.WritableRpcEngine$Invoker.invoke(WritableRpcEngine.java:244)
at com.sun.proxy.$Proxy8.getTask(Unknown Source)
at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:132)
Caused by: java.net.NoRouteToHostException: No route to host
at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
at org.apache.hadoop.net.SocketIOWithTimeout.connect(SocketIOWithTimeout.java:206)
at org.apache.hadoop.net.NetUtils.connect(NetUtils.java:530)
at org.apache.hadoop.net.NetUtils.connect(NetUtils.java:494)
at org.apache.hadoop.ipc.Client$Connection.setupConnection(Client.java:607)
at org.apache.hadoop.ipc.Client$Connection.setupIOstreams(Client.java:705)
at org.apache.hadoop.ipc.Client$Connection.access$2800(Client.java:368)
at org.apache.hadoop.ipc.Client.getConnection(Client.java:1521)
at org.apache.hadoop.ipc.Client.call(Client.java:1438)
</pre>
Miközben újra futtattam a jobot megpróbáltam elcsípni a folyamatot, ami az imént a 40896 porton futott, most pedig a 39552-őt használta volna. Elég rövid volt az idő ablak, de én is elég fürge voltam.
<br />
<pre># lsof -i tcp:39552
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 2717 root 235u IPv4 147112 0t0 TCP namenode:60573->172.17.0.15:39552 (SYN_SENT)
java 2733 root 235u IPv4 147111 0t0 TCP namenode:60572->172.17.0.15:39552 (SYN_SENT)
-bash-4.1# ps x | grep 2717
2717 ? Sl 0:01 /usr/lib/jvm/jre-1.8.0-openjdk/bin/java -Djava.net.preferIPv4Stack=true -Dhadoop.metrics.log.level=WARN -Xmx200m -Djava.io.tmpdir=/tmp/hadoop-root/nm-local-dir/usercache/root/appcache/application_1431716284376_0003/container_1431716284376_0003_01_000012/tmp -Dlog4j.configuration=container-log4j.properties -Dyarn.app.container.log.dir=/usr/local/hadoop/logs/userlogs/application_1431716284376_0003/container_1431716284376_0003_01_000012 -Dyarn.app.container.log.filesize=0 -Dhadoop.root.logger=INFO,CLA org.apache.hadoop.mapred.YarnChild 172.17.0.15 39552 attempt_1431716284376_0003_m_000008_1 12</pre>
Elindul a szolgán egy YarnChild processz, ami az első paraméterben megadott IP-n szeretne kommunikálni a második paraméterben megadott ephemeral porton (a <a href="http://grepcode.com/file/repo1.maven.org/maven2/org.apache.hadoop/hadoop-mapreduce-client-app/0.23.1/org/apache/hadoop/mapred/YarnChild.java">forráskód</a>ból derült ki), és a mester nem tud hozzá csatlakozni, hiszen nem a virtuális gép IP címe van megadva, amin a kommunikáció valójában zajlik, és nem is a szolga host neve, hanem a Docker konténer IP-je, amit a mester nem ér el. Kutakodtam a konfigurációk között, próbáltam a forrásból kideríteni, hogy mely beállítással lehetnék hatással erre a működésre, és szomorúan kellett tudomásul vennem, hogy erre nincs kapcsoló vagy nagyon elrejtették.
<br />
<br />
Miután a port publikálási megoldással zsákutcába jutottam az alábbi megoldások jöhettek számításba:
<br />
<ul>
<li>Használom a --net=host Docker kapcsolót, és akkor a konténerek közvetlenül a virtuális gép hálózati csatolójára ülnek - tiszta és száraz érzés</li>
<li>Privát hálózatot hozok létre pl. VPN - sajtreszelővel ...</li>
<li>Konfigurálok egy <a href="https://github.com/weaveworks/weave" target="_blank">Weave</a> hálózatot - na ez már izgalmasan hangzik</li>
</ul>
Az egyszerűség kedvéért elsőként kipróbáltam a --net=host kapcsolót, hogy lássam egyáltalán működik-e az összerakott rendszer.
<br />
<pre>vagrant ssh slave[1-3]
docker run --name hadoop --net=host -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
<pre>vagrant ssh master
docker run --name hadoop --net=host -e "MASTER=true" -e "MASTER_IP=192.168.50.4" -e "SLAVES=192.168.50.1 slave1,192.168.50.2 slave2,192.168.50.3 slave3" -it mhmxs/hadoop-docker:2.6.0 /etc/bootstrap.sh -bash
</pre>
Ezúttal tökéletesen lefutott a job, örülünk, de elégedettek még nem vagyunk. A jelenlegi állapot forrása <a href="https://github.com/mhmxs/hadoop-docker/tree/2.6.0-static-ip">itt</a> érhető el, a Vagrantos környezeté pedig <a href="https://github.com/mhmxs/vagrant-host-hadoop-cassadra-cluster/tree/2.6.0-static-ip">itt</a>.
<br />
<br />
Előzetes a következő rész tartalmából. Hűsünk beállítja a <a href="https://docs.docker.com/swarm/" target="_blank">Swarm</a> clustert, majd némi küzdelem árán lecseréli a kézi DNS konfigurációt automatizált megoldásra. Vajon melyik implementációt választja? Kiderül a <a href="http://jpattern.blogspot.com/2015/05/kontenerezett-hadoop-es-cassandra_25.html">folytatásban</a>...mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-38344199575644141352015-02-17T17:26:00.000+01:002015-02-17T17:26:44.147+01:00JVM futásidejű monitorozásaFigyelem az alábbi bejegyzés nyomokban fizetetlen reklámot és szubjektív véleményt tartalmaz.<br />
<br />
Egy ideje a JVM valós idejű monitorozásának lehetőségeit kutattam, és csak hosszas keresés után találtam meg a projektnek, és a költség tervnek megfelelőt.<br />
Szerencsére megoldás akad bőven a fizetőstől az ingyenesen át a szabadig megoldásokig. A teljesség igénye nélkül szeretnék bemutatni néhányat közülük.<br />
<br />
<ul>
<li><a href="http://www.dynatrace.com/en/index.html" target="_blank">Dynatrace</a> kétségtelen, hogy egyike a legprofibb megoldásoknak. Volt szerencsém egy hosszabb lélegzet vételű prezentációt végigülni, ahol ebből a csodás eszközből kaptunk ízelítőt. Gyakorlatilag a monitorozás, hiba feltárás, és reprodukálás mekkája a Dynatrace. Központilag telepített szerver gyűjti az információkat a különböző ügynököktől (Java, .NET, böngésző), majd ezeket az információkat un. PurePathokba szervezi, ahol nyomon tudjuk követni egy kérés teljes útvonalát az infrastruktúrában. Pontos képet kaphatunk, hogy mely rétegben mennyi időt töltött a kiszolgálás, és a PurePathon belül minden irányban teljes átjárást biztosít a rendszer, ami azt jelenti, hogy pár kattintással el lehet érni a végrehajtó kód sort, a futtatott lekérdezéseket, a felhasználót, és annak a többi kérését, és még sorolhatnám. Profizmusához mérten van az árazása, természetesen létezik ingyenes próba időszak, cserébe viszont képzett kollégák segítenek a rendszert beüzemelni.<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivJ0WtkPuUHYFIhXbvgvjvAnXXLSq0zfIKWryUI7e2WRv_aLnn_vpiKUw86JTq-okTUCXnu-RBjqy5BV7Fqij1VjBYOQfF9EGNnjJpGY5hHQBT1ReOSznW5cno2VTl82Z1ENSzAnpaR9FA/s1600/dynatrace.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivJ0WtkPuUHYFIhXbvgvjvAnXXLSq0zfIKWryUI7e2WRv_aLnn_vpiKUw86JTq-okTUCXnu-RBjqy5BV7Fqij1VjBYOQfF9EGNnjJpGY5hHQBT1ReOSznW5cno2VTl82Z1ENSzAnpaR9FA/s1600/dynatrace.jpg" height="251" width="640" /></a></div>
</li>
</ul>
<ul>
<li>Következő alternatíva a <a href="http://newrelic.com/" target="_blank">New Relic</a>. Mi aktívan használjuk több projekten is, és alapvetően meg vagyunk vele elégedve. Ára sokkal pénztárca barátabb, mint a Dynatracé, de tudása is ezzel arányosan kevesebb. A New Relic képes monitorozni a szervert (mi Linuxot használunk, nincs tapasztalatom egyéb operációs rendszerekkel), az adatbázist, a JVMet, kéréseket, hibákat, majd ezekből tetszetős grafikonokat rajzol. Létezik hozzá mobil alkalmazás és böngésző monitorozó eszköz is. Hátránya, hogy a New Relic szerverei felé jelentenek az ügynökök, így egyfelől van egy minimális késleltetése, másfelől a weboldaluk sebessége is hagy némi kívánnivalót maga után. Kevésbé ár érzékeny projektek esetén kiváló választás lehet.</li>
<li>Az <a href="http://www.appdynamics.com/" target="_blank">AppDynamics</a>ról olvastam még eléggé pozitív cikkeket, sajnos saját tapasztalatom nincs velük kapcsolatban.</li>
<li>Utolsó fizetős megoldás az előzőekhez képest még szerényebb a nyújtott szolgáltatások terén, de olyan kedvező a fizetési modelljük, hogy mindenféleképpen érdemes őket megemlíteni. A <a href="http://sematext.com/" target="_blank">SemaText</a> szinte valósidejű monitorozást végez fáék egyszerűséggel, de a támogatott platformok igen széles palettán mozognak: AWS, Apache, Cassandra, Elasticsearch, HAProxy, HBase, Hadoop-MRv1, Hadoop-YARN, JVM, Kafka, Memcached, MySQL, Nginx, Redis, Sensei, Solr, SolrCloud, Spark, Storm, ZooKeeper. Az ingyenes verzióban 30 percig vissza menőleg örzik meg az adatokat, és még van pár limitáció, de alkalmazásonként választhatunk csomagot, és akármikor felfüggeszthetjük egy alkalmazás/cluster monitorozását (órában van megadva a legkisebb fizetési egység). Amit mindenféleképpen, mint előnyt meg szeretnék említeni, hogy APIjukon keresztül saját metrikékat is viszonylag kényelmesen megjeleníthetünk. Hátránya, hogy a New Relichez hasonlóan az ő szervereik tárolják az adatokat, és az ügynöknek, amit telepíteni kell, rengeteg a csomag-függősége, legalábbis Linuxon.</li>
</ul>
<div>
A nyílt forrású megoldások esetén a tanulási görbével, és a beüzemelés költségével fizetjük meg az árát a monitorozásnak (igaz ezt csak 1x kell). Azt tapasztaltam, hogy képességeikben elmaradnak fizetős társaiktól, de ami sokkal nagyobb probléma (szerintem), hogy nincs próbaidő. Nem tudom egy teszt szerveren kipróbálni, nincs hozzájuk demó felület, amit meg lehetne nyomkodni.</div>
<div>
<ul>
<li>Első delikvens a <a href="https://github.com/javamelody/javamelody/releases" target="_blank">JavaMelody</a>, aminek a telepítése igen egyszerű, a letöltött war fájlt deployoljuk az alkalmazás-szerveren. Hátránya, hogy csak lokális monitorozást végez, ami több mint a semmi, de csak egy hajszállal.</li>
<li>A <a href="http://www.stagemonitor.org/" target="_blank">stagemonitor</a> igen ígéretes projektnek tűnik, kár, hogy csak a JVM helyi megfigyelésére alkalmas, és számunkra csak központosított megoldások jöhetnek számításba. A weboldalt böngészve láthatjuk, hogy igen széles spektrumon követi nyomon az alkalmazás működését, és gyönyörű grafikonokon ábrázolja az adatokat.</li>
<li>Legtöbben az interneten a <a href="http://jamonapi.sourceforge.net/" target="_blank">JAMon</a>t ajánlották, ami egy Java monitorozásra alkalmas API. A dokumentációból első olvasásra kiderült számomra, hogy telepítése nem triviális, és a metrikák pontos megtervezése után az alkalmazásban implementálni is kell azokat. Őszinte leszek nem ugrottam fejest a JAMon világába. Biztos nagyon szép és nagyon jó, de a csapat "produktivitását" nem növeli, az ügyfélnek nem eladható hogy x hétig metrikákat reszelgetünk, meg grafikonokat rajzolgatunk.</li>
<li>Nyílt forrású megoldások közül nekem a <a href="http://www.moskito.org/" target="_blank">MoSKito</a> tűnik a legkiemelkedőbbnek, sajnálom, hogy későn akadtam rá, és addigra már belaktunk egyéb szolgáltatásokat. A MoSKitó kifejezetten Java fürtök valós idejű megfigyelését célozza meg.</li>
</ul>
<div>
Talán ebből a bejegyzésből, és kiderül (legalábbis remélem), hogy nincs szent grál a témában, mert az egyik drága, főleg ha automatikusan skálázódó alkalmazást szeretnénk monitorozni, ahol percek alatt 5-8-ról 30-50-re nőhet a JVMek száma, van amelyik csak lokálisan működik, míg másnak a beüzemelése visz el túlzottan sok erő forrást. Egyesek képesek kontextusban látni az alkalmazást, míg mások csak számokat vizualizálnak. Mindenféleképpen érdemes alaposan körbejárni a témát, és az igényeknek leginkább megfelelőt választani.</div>
</div>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-20844995881301916702015-02-16T10:14:00.003+01:002015-02-16T10:25:45.087+01:00Grails és a LazyInitializationExceptionAmikor valamilyen <a href="http://en.wikipedia.org/wiki/Object-relational_mapping" target="_blank">ORM</a> keretrendszert használunk az alkalmazásunkban, akkor örökösen felmerülő probléma, hogy mely kapcsolatokat töltsük be azonnal, amikor a domain objektumot felszedjük az adatbázisból, és melyeket csak akkor, amikor szükség van rájuk. Grails keretrendszerben a <a href="http://grails.github.io/grails-doc/latest/guide/GORM.html">GORM</a> dübörög (vagy nem), ami pedig a népszerű, vagy mondhatnám sokak által használt, Hibernatere épül. Vegyünk egy egyszerű példát:
<br />
<pre class="brush: java">class Bar {
static hasMany = [foos: Foo]
static mapping = {
foos cascade: 'all-delete-orphan'
}
}
class Foo {
static belongsTo = [bar: Bar]
}
</pre>
<pre class="brush: java">Bar bar = Bar.createCriteria().get {
isNotNull 'id'
maxResults 1
}
bar.discard()
println bar.foos
</pre>
A művelet eredménye egy <a href="http://org.hibernate.lazyinitializationexception/">org.hibernate.LazyInitializationException</a>, hiszen nem töltöttük be a <span style="background-color: yellow;">fookat</span> a domain osztállyal együtt, a discard metódus hatására pedig elveszett a kapcsolat a <a href="https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/Session.html" target="_blank">Hibernate Session</a> és a domain osztályban lévő proxy között. Javísuk a problémát.<br />
<pre class="brush: java">Bar bar = Bar.createCriteria().get {
isNotNull 'id'
fetchMode 'foos', org.hibernate.FetchMode.JOIN
maxResults 1
}
</pre>
Ez a bejegyzés nem jött volna létre, ha csak ennyivel megúsznánk a dolgot. Bár a Hibernate <a href="https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/FetchMode.html#JOIN">dokumentációja</a> szerint a FetchMode.JOIN "Fetch using an outer join", mégis ismét egy LazyInitializationException hibára hivatkozva száll el a kérés mint a győzelmi zászló. A problémát a cascade okozza!? Ugyanis ha eltérünk az alap értelmezett viselkedéstől (<a href="http://en.wikipedia.org/wiki/One-to-many">OTM</a> kapcsolatoknál az alap értelmezett a save-update, tehát a domain osztályt törölve az adatbázisban maradnak a hozzá kapcsolodó entitások), akkor a JOIN hatására nem a domain osztályok kerülnek a domainbe, hanem csak proxyk, tehát akkor szedi fel őket ténylegesen a rendszer, amikor hivatkozunk rájuk. A cascade <a href="http://www.grails.org/doc/latest/ref/Database%20Mapping/cascade.html">dokumentációja</a> semmit nem említ erről az igen kellemetlen melléhatásról. Tudom Grailsbe "nem szokás" discard()olni, én mégis azt javaslom, hogy mielőtt átadjuk a vezérlést a nézetnek, csak a próba kedvéért válasszuk le a domaint a sessionről, és győződjünk meg róla, hogy nem fog n+1 lekérdezést indítani a háttérben, miközben mi meg vagyunk róla győződve, hogy minden rendben.
<br />
<br />
Ahogy említettem FetchMode.JOINnal és nélküle is proxyk jelennek meg a domainbe, egy apró különbség azonban van a két eljárás között. FetchMode.JOIN nélkül az alábbi kódsor hibát eredményez (LazyInitializationException).<br />
<pre class="brush: java">Bar bar = Bar.createCriteria().get {
isNotNull 'id'
maxResults 1
}
bar.discard()
println bar.foos.size()
</pre>
FetchMode.JOIN megadásával pedig nem, amiből arra lehet következtetni, hogy van eltérés a két viselkedés között. Előbbi esetben az egész kapcsolatot egy proxy helyettesíti, utóbbinál pedig a domaineket helyettesíti egy-egy proxy.
<br />
<br />
Lefuttattam a tesztet a Grails legfrissebb verziójával (2.4.4), és minden jel arra utal, hogy már megoldották ezt a problémát. Nem tudom ténylegesen melyik verzióval lett javítva, ezért javaslom mindenkinek, hogy ellenőrizze megfelelően működik-e az alkalmazása, vagy frissítsen a legfrissebb verzióra.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-22736948135711596042014-11-20T23:17:00.000+01:002015-02-16T10:26:00.309+01:00Változékony Grails alkalmazás konfiguráció facepalmAz egyik Grails-es alkalmazásunk furán kezdett viselkedni, ezért nyomozás indult a cégen belül, és hamarosan meg is lett a furcsaság forrása. Az egyik gsp-ben szükség volt egy lista típusú konfiguráció kiíratására, de más sorrendben mint ahogy az rögzítve lett. A fejlesztő kolléga fogta és átrendezte az eredeti konfigurációt, amit a Grails készségesen meg is tesz. Felteszem a kérdést, hogy vajon ez a helyes működés? Egyfelől méltán tükrözi a Groovy és a Grails nyílt filozófiáját, és nem mellesleg hatékonyabbá teszi a fejlesztést is, hiszen futás-időben az egész konfiguráció felépíthető az alkalmazás újraindítása nélkül. Másfelől pedig rés a pajzson, hiszen bármelyik futó szál kénye kedve szerint módosíthatja az alkalmazás működését, ami akár az alkalmazás biztonságát is veszélyeztetheti. Számtalan Grails plugin (pl. Spring Security) tárolja ugyanitt a beállításait, és ezen beállítások kulcsai mind nyilvánosak, tehát bárki számára elérhetőek és módosíthatóak. Ha ez nem volna elegendő, akkor a <a href="https://github.com/groovy/groovy-core/blob/master/src/main/groovy/util/ConfigObject.java">ConfigObject</a> még csak nem is szál-biztos. Amellett, hogy a ConfigObject belső működése nem szál-biztos, a tényleges konfiguráció tárolására <a href="https://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html">LinkedHashMap</a>-et használ, aminek a dokumentációjából idézve:
<br />
<blockquote>
<b>Note that this implementation is not synchronized.</b> If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method.
</blockquote>
Lehet én vagyok maradi, de valahogy ez az egész több sebből vérzik. És, hogy egy kis kód is legyen, az úgy még tudományosabb:
<br />
<pre class="brush: java;">class TestController {
def index() {
def c1 = grailsApplication.config
def c2 = grailsApplication.config
c1.asd = "newconfig"
render c2.asd
}
}
</pre>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-24360788923383007652014-07-30T07:28:00.000+02:002014-09-22T09:53:43.672+02:00Groovy funkcionális eszközökA Groovy sorozatot folytatva (<a href="http://jpattern.blogspot.com/2012/06/baratkozas-groovyval.html">1</a>, <a href="http://jpattern.blogspot.com/2014/05/import-groovytransformimmutable.html">2</a>) ebben a bejegyzésben a nyelv funkcionális aspektusát szeretném bemutatni a teljesség igénye nélkül. Két fontos tulajdonság képezi az alapját a funkcionális programozásnak Groovyban, az egyik, hogy van lehetőség anonim funkciók (<a href="http://groovy.codehaus.org/api/groovy/lang/Closure.html">Closure</a>) írására, a másik pedig, hogy az utolsó kifejezés értéke automatikusan visszatérési érték lesz, ha nincs explicit visszatérési érték meghatározva (üres Closure visszatérési értéke null). Ez a két tulajdonság elengedhetetlen ahhoz, hogy funkcionális szemléletben tudjunk programozni, de pusztán e két dolog használata még nem eredményezi automatikusan a szemlélet-váltást. Vegyük sorra milyen egyéb eszközökkel támogatja a Groovy a munkánkat.
<ul>
<li>
Először nézzük a Closure összefűzést:
<pre class="brush: java;">
def m = { "${it}-" }
def p = { "${it}+" }
def pm = m << p
println pm('') == m(p('')) // true
</pre>
Természetesen a másik irányba is működik a dolog:
<pre class="brush: java;">
def mp = m >> p
println p(m('')) == mp('') // true
</pre>
</li><li>
A Closure.curry() metódus becsomagolja a Closure példányt, és az eredeti Closure paraméterei helyére lehet fix értékeket beállítani. A példa magáért beszél:
<pre class="brush: java;">
def plus = { int f, int s, int t ->
println "$f $s $t"
return f + s + t
}
def fix = plus.curry(0, 0) // további opciók: ncurry() és rcurry()
println fix(5) // 0 0 5
</pre>
</li><li>
Felmerülhet az igény, hogy már meglévő metódusokból csináljuk Closuret. Nem a legelegánsabb megoldás, de mindenféleképpen hasznos ha meglévő eszközeinket szeretnénk "modernizálni":
<pre class="brush: java;">
class o {
void f() {
println 'called'
}
}
def c = o.&f // vagy new o().&f
println c.class // class org.codehaus.groovy.runtime.MethodClosure
</pre>
</li><li>
A funkcionális programozásra igen jellemző a rekurzív végrehajtás, és ezen programozási nyelvek részét képezik a különböző optimalizációs eszközök. Természetesen a Groovyban is van lehetőségünk finomhangolni rekurzióinkat. Az első ilyen eszköz, amit bemutatok a Closure.memoize(), ami nemes egyszerűséggel annyit tesz, hogy a visszaadott csomagoló Closure gyorsítótárazza a végrehajtás eredményeit. Különös tekintettel kell lennünk használatakor a memória-szivárgásokra, mert alapértelmezetten nincs méret határ szabva a gyorsítótárnak:
<pre class="brush: java;">
def a = { print "called" }.memoize() // vagy memoizeBetween(int protectedCacheSize, int maxCacheSize)
a();a() // called
</pre>
<pre class="brush: java;">
def a = { i -> print "called $i " }.memoize() // vagy memoizeAtLeast(int protectedCacheSize) és memoizeAtMost(int maxCacheSize)
a(1);a(2);a(2) // called 1 called 2
</pre>
Meglévő metódusainkat pedig a <a href="http://groovy-lang.org/docs/groovy-2.3.0/html/gapi/groovy/transform/Memoized.html">@Memorized</a> annotációval tudjuk hasonló működésre bírni, mely két opcionális paramétert vár a maxCacheSize és a protectedCacheSize.
</li><li>
A rekurzív hívásoknak van egy igen kártékony mellékhatása a JVMben. Minden egyes hívás rákerül a stackre, ami könnyen <a href="http://docs.oracle.com/javase/8/docs/api/java/lang/StackOverflowError.html">StackOverflowError</a>hoz vezet. Ezt elkerülendő a Closure.trampoline() segítségével referenciát szerezhetünk egy olyan TrampolineClosurera, mely szekvenciálisan hívja az eredeti Closuret egészen addig, míg az TrampolineClosuret ad vissza. Ezzel a technikával mentesíti a stacket, lássuk ezt a gyakorlatban:
<pre class="brush: java;">
def s
s = { l, c = 0 ->
l.size() == 0 ? c : s(l.tail(), ++c)
}.trampoline()
println s(1..10) // 10
</pre>
A Closure.trampoline() metódus mintájára az <a href="http://groovy-lang.org/docs/groovy-2.3.0/html/gapi/groovy/transform/TailRecursive.html">@TailRecursive</a> annotációt használhatjuk, a dokumentációban szereplő megkötésekkel.
</li><li>
A funkcionális nyelvek általában az un. <a href="http://en.wikipedia.org/wiki/Lazy_evaluation"><i>lazy evaluation</i></a> szemléletet követik, ami röviden annyit jelent, hogy csak akkor értékel ki a rendszer valamit, ha arra feltétlenül szükség van. A Groovy is biztosít megoldásokat a paradigma követéséhez.
<pre class="brush: java;">
def l = [].withDefault { 45 }
println l[3] // 45
println l // [null, null, null, 45]
</pre>
Vagy a <a href="http://groovy.codehaus.org/gapi/groovy/lang/Lazy.html">@Lazy</a> annotációval tetszőleges adattagot varázsolhatunk lusta kiértékelésűre. Egy opcionális paraméterével akár <a href="http://jpattern.blogspot.nl/2012/01/weak-es-soft-referenciak-javaban.html">puha referenciá</a>ban is tárolhatjuk az értéket, természetesen az alapértelmezett működés szerint erős referenciát alkalmaz:
<pre class="brush: java;">
class Person {
@Lazy(soft = true) pets = ['Cat', 'Dog', 'Bird']
}
def p = new Person()
println p.dump() // <Person@7b073071 $pets=null>
p.pets
println p.dump() // <Person@18e30556 $pets=java.lang.ref.SoftReference@3f0e6ac>
</pre>
Annyit mindenféleképpen meg kell még jegyeznem, hogy ha a mező statikus, és nem puha referenciában történik a tárolás, akkor a <a href="http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom"><i>Initialization on demand holder</i></a> mintát követi a fordító.
</li>
</ul>
A beépített funkciók után térjünk át a haladó technikákra. <strike>Bár a Groovynak szoros értelemben nem része a <a href="http://gpars.codehaus.org/">GPars</a> keretrendszer</strike>, mégis érdemes kicsit közelebbről megismerkedni vele. A dokumentációból idézve:
<p><i>"GPars is a multi-paradigm concurrency framework, offering several mutually cooperating high-level concurrency abstractions, such as Dataflow operators, Promises, CSP, Actors, Asynchronous Functions, Agents and Parallel Collections."</i></p>
<ul>
<li>
Meglévő Closurejainkat könnyedén aszinkron hívásokká alakíthatjuk a <a href="http://gpars.org/0.12/groovydoc/groovyx/gpars/GParsExecutorsPool.html">GParsExecutorsPool</a> segítségével, ahogy a <a href="http://gpars.codehaus.org/Asynchronous+functions">példa is mutatja</a>.
</li><li>
<a href="http://gpars.codehaus.org/GParsPool">Collectionök párhuzamos feldolgozás</a>ára a <a href="http://gpars.org/0.12/groovydoc/groovyx/gpars/GParsPool.html">GParsPool</a>t tudjuk segítségül hívni. A GParsPool osztály <a href="http://www.drdobbs.com/jvm/parallel-array-operations-in-java-8/240166287">ParallelArray</a> alapon végzi a műveleteket, míg párja a <a href="http://gpars.org/0.12/groovydoc/groovyx/gpars/GParsExecutorsPool.html">GParsExecutorsPool</a> hagyományos thread poolokat használja.
</li><li>
A GPars része egy a Java <a href="http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html">Fork/Join</a> könyvtárára épülő magasabb absztrakciós réteg. Ez a köztes réteg -mely a mindennapi fejlesztést hivatott megkönnyíteni- használata során nem kell szálakkal, poolokkal, és szinkronizációval bajlódnunk. Részletek a <a href="http://gpars.codehaus.org/ForkJoin">dokumentációban</a> találhatók.
</li><li>
A <a href="http://gpars.codehaus.org/Dataflow">Dataflow</> egy alternatív párhuzamos feldolgozási szemlélet</a>. Szépsége az egyszerűségében rejlik, apró párhuzamos feladatokra bonthatjuk az alkalmazásunkat, és amikor az egyik darabka még egy ki nem értékelt adathoz szeretne hozzáférni, akkor blokkolt állapotba kerül amíg egy másik ki nem értékeli azt. Működéséből adódóan nincs verseny helyzet, nincs <a href="http://richardbarabe.wordpress.com/2014/02/21/java-deadlock-livelock-and-lock-starvation-examples/">Dead és Live Lock</a> sem többek között. Megkötés, hogy csak egyszer adhatunk értéket a DataflowVariable életciklusa során.
</li><li>
Az <a href="http://gpars.codehaus.org/Agent">Agent</a>ek szál-biztos, nem blokkoló párhuzamosítást tesznek lehetővé, és ehhez annyit kell tennünk, hogy az osztályunkat a <a href="http://gpars.org/0.12/javadoc/groovyx/gpars/agent/Agent.html">groovyx.gpars.agent.Agent</a>ből származtatjuk. Fontos különbség a Dataflow modellhez képest, hogy az Agent tartalma tetszőlegesen változtatható.
</li><li>
Természetesen elmaradhatatlan kellék a méltán népszerű <a href="http://gpars.org/guide/guide/actors.html">Actor</a> modell. Leegyszerűsítve az Actorok üzeneteket fogadnak, továbbítanak, és válaszolnak azokra. Minden üzenet bekerül egy sorba, majd onnan a feldolgozásra. A megoldás előnye, hogy implicit szál-biztos, nem blokkolt, és nincs szükség szinkronizációra sem a soros feldolgozás miatt. Lényeges tulajdonsága az Actor rendszernek, hogy nem hagyományos szál-kezelést használ, hanem saját maga menedzseli a feladatokat. Létezik állapot-tartó, és állapot-mentes Actor egyaránt.
</li>
</ul>
Ahogy a <a href="http://jpattern.blogspot.com/2012/06/baratkozas-groovyval.html">Barátkozás a Groovyval</a> bejegyzésben is leírtam, a Groovy nem kezdő programozók kezébe való eszköz. Szép és jó ez a sok beépített okosság, de az alapok ismerete nélkül csak még jobban összezavarnak mindent, megnehezítik a hiba feltárást és az elhárítást is. Remélem sikerült kedvet csinálnom a téma mélyebb megismeréséhez, ráadásként pedig egy igazi "ínyencséget" hagytam:
<pre class="brush: java;">
def deliver(String n) {
[from: { String f ->
[to: { String t ->
[via: { String v ->
println "Delivering $n from $f to $t via $v."
}]
}]
}]
}
deliver "Béla" from "Mezőberény" to "Kisfái" via "Traktor" // Delivering Béla from Mezőberény to Kisfái via Traktor.
</pre>mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-65166352703990665282014-05-07T10:58:00.000+02:002014-05-07T10:58:25.063+02:00import groovy.transform.Immutable
A Groovy szemlélet ellentétben a Java-val a nyiltságról szól. Minden alapértelmezetten publikus, nem kell bajlódnunk getter/setter írásával (persze a háttérben történnek dolgok, most a szemléletről beszélek), a metaclass jóvóltából módosítani lehet osztályok működését, és még sorolhatnám. A most bemutatásra kerülő lehetőség mégis sokszor tud hasznos lenni.
<pre class="brush: java">
@groovy.transform.Immutable
class A { Integer a }
new A()
</pre>
A kódot futtatva az alábbi eredményt kapjuk:
<pre>
org.codehouse.groovy.runtime.metaclass.MethodSelectionException: Could not find which method <init>() to invoke from list:
public A#<init>(java.util.HashMap)
public A#<init>(java.lang.Integer)
</pre>
Tehát a fordító nem tudta eldönteni, hogy melyik konstruktorát használja az osztálynak, ami azt jelenti, hogy az ilyen intefésszel ellátott osztályok tagjainak kötelező kezdő értéket adnunk.
<pre class="brush: java">
@groovy.transform.Immutable
class A { Integer a }
new A(0).a = 1
</pre>
Mikor megpróbáljuk módosítani az adattagot, egy kivétellel hálálja meg a JVM.
<pre>
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: a for class: A
</pre>
A történet eddig nem nagy szám, mert az alábbi kódsorok ugyanezt az eredményt adják (kicsit több gépeléssel):
<pre class="brush: java">
class A {
final Integer a
A(a) { this.a = a }
}
new A(0).a = 1
</pre>
De mi a helyzet ezzel:
<pre class="brush: java">
@groovy.transform.Immutable
class A { List a }
A a = new A([])
a.a << 1
</pre>
Ennél az esetnél mutatkozik meg, hogy az annotáció mivel több egy mezei final módosítónál. A listánk bizony egy <a href="http://collection.unmodifiablerandomaccesslist/">Collection.UnmodifiableRandomAccessList</a> lett. Tovább kísérletezve mi történik, ha egy saját osztályt teszünk adattagként A-ba.
<pre class="brush: java">
@groovy.transform.Immutable
class A { B b }
class B { Integer b }
A a = new A(new B())
</pre>
<pre>
java.lang.RuntimeException: @Immutable processor doesn't know how to handle field 'b' of type 'B' while constructing class A.
@Immutable classes only support properties with effectively immutable types including:
- Strings, primitive types, wrapper types, BigInteger and BigDecimal, enums
- other @Immutable classes and known immutables (java.awt.Color, java.net.URI)
- Cloneable classes, collections, maps and arrays, and other classes with special handling (java.util.Date)
Other restrictions apply, please see the groovydoc for @Immutable for further details
</pre>
Amennyiben ellátunk egy osztályt ezzel az annotációval, a Groovy fordító garantálja nekünk, az osztály módosíthatatlan lesz, hiszen kötelezően minden adattagjának szintén módosíthatatlannak kell lennie.
Bővebb információ a <a href="http://groovy.codehaus.org/gapi/groovy/transform/Immutable.html" target="_blank">GroovyDoc</a>-ban, amit fontos kiemelni, hogy @<a href="http://docs.oracle.com/javase/7/docs/api/java/lang/annotation/Target.html" title="Target">Target</a>({ElementType.TYPE}), tehát osztályokra, interfészekre, és enumokra lehet tenni az annotációt, bár utóbbi 2 esetben fordítási hibát fogunk kapni.
<br />
Házi feladat a a <a href="http://groovy.codehaus.org/gapi/groovy/transform/package-summary.html">groovy.transform</a> csomag átböngészése, sok hasznos eszköz lapul még benne.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-77006253278794399132014-04-23T07:52:00.001+02:002014-04-23T11:45:03.484+02:00groovy.lang.Tuple kicsit közelebbrőlBelefutottam egy aranyos Groovy osztályba, a <a href="http://groovy.codehaus.org/api/groovy/lang/Tuple.html" target="_blank">Tuple</a>-be. Ha jobban megvizsgáljuk az öröklési láncot láthatjuk, hogy a Tuple lényegében egy <a href="http://docs.oracle.com/javase/7/docs/api/java/util/AbstractList.html?is-external=true" target="_blank">AbstractList</a> implementáció, és arra való, hogy összetartozó értékeket tároljunk benne. Példányosításkor meghatározzuk a tartalmát, amit a későbbiekben nem lehet módosítani. Kisértetiesen hasonlít a <i>Collections.unmodifiableList()</i>-hez, de a következő példa jól szemlélteti a Tuple eleganciáját:<br />
<pre class="brush: java">Collections.UnmodifiableRandomAccessList foo() { Collections.unmodifiableList([]) }
</pre>
Ezt ne próbáljátok meg lefordítani, nem fog sikerülni. A probléma az, hogy a UnmodifiableRandomAccessList a Collections belső osztája, ha pedig List-re cseréljük a visszatérési érték típusát, akkor a hívó fél részére nem lesz egyértelmű, hogy a lista nem módosítható, vagy ne adj isten egy verzióváltás során lesz UnmodifiableRandomAccessList, és a működő kód megy tönkre (sose láttunk még csak hasonlót sem). Itt jön az elegancia:
<br />
<pre class="brush: java">Tuple foo() { new Tuple() }
</pre>
Ez az egyszerű példa megmutatja mi mindenre alkalmas többek között a Tuple, ha a munkatársainkat szeretnénk kicsit megviccelni (dynamic type rulz)<br />
<pre class="brush: java">def x = (a,b,c) = { x -> new Tuple(x*10,"${x*20}") } << 5
println "$a,$b,$c,$x"
</pre>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-43641838296622505852014-01-18T17:47:00.001+01:002014-01-18T19:02:21.459+01:00Ping-conf Day 2A második nap a <a href="http://www.ping-conf.com/" target="_blank">Ping-conferencián</a> hozta az <a href="http://jpattern.blogspot.com/2014/01/ping-conf-day-1.html" target="_blank">első nap</a> színvonalát. Yevgeniy Brikman izgalmas demóval egybekötött <a href="http://www.slideshare.net/brikis98/composable-and-streamable-play-apps" target="_blank">előadása</a> mindenkit lázba hozott korán reggel. Prezentációját azzal kezdte, hogy elmesélte, hogy másfél éve használják a Play Frameworköt <a href="http://en.wikipedia.org/wiki/LinkedIn" target="_blank">LinkedInnél</a> több mint 60 projekthez nagy sikerrel. Megnevezett két problémás területet tapasztalataik alapján:<br />
<ul>
<li>Nehezen kezelhető komplexitás (szerintem ez nem feltétlen csak Playben jött volna elő az ő esetükben)</li>
<li>Borzalmas teljesítmény</li>
</ul>
<div>
Az első ponttal kapcsolatban megmutatta, hogy a LinkedIn oldalán rengeteg apró kis tartalmi rész van, amik ráadásul eltérnek a különböző felhasználó típusonként (újonc, prémium, etc), ezért nem lehetséges egyetlen kontrollerbe tenni a logikát (hecc kedvéért kipróbálták, és a fordító elszállt a scala fájl méretére hivatkozva). A megoldás, amit használnak, hogy minden egyes kis doboz önállóan is él, tehát van saját kontrollere, nézete, meg minden ami kell. Nagy előnye a módszernek, hogy nagyon könnyen tesztelhető, továbbá nagyon könnyen skálázható vízszintesen, ugyanis az egyes modulok szétoszthatóak több szerver között. A publikus oldalak kontrollereiben pedig egyszerűen legenerálják az egyes részeket, és összefűzik a kiszolgálandó HTMLbe. A technika hátulütője, hogy a statikus erőforrásokat a kontrollerben kell kezelni (szerk,. megj. illetve nem lehet ezen erőforrások tömörítését, egybefűzését valamilyen pluginnel leegyszerűsíteni).</div>
<div>
Ezt a fragmentációs technikát választva az alábbiak igényelnek külön odafigyelést:</div>
<div>
<ul>
<li>Cookiek esetén szükségük van egy metódusra, ami a darabkák headerjeiből összefűzte a Cookie bejegyzéseket</li>
<li>Statikus erőforrások beillesztésére szintén a headerbe kell tenni X.CSS ill. X-JS bejegyzéseket, amit aztán összefűznek, deduplikálják, és a HTML headbe tesznek (erről később, hogy miért pont oda)</li>
<li>Hibakezeléshez a legfelső réteg kiolvassa a darabkák státsz-kódját</li>
</ul>
<div>
Miután végigmentünk a komplexitás témakörén, áttértünk a teljesítmény problémákra. Első lépésben átalakította az alkalmazást, hogy a hagyományos String alapú HTML generálás helyett streamelje a kimenetet. A Scala nyelvben vannak natív eszközök, mindössze egy custom render templatet kellett bekötni a Playbe (az alapértelmezett StringBuildert használ), és az <a href="http://www.playframework.com/documentation/2.0.1/Enumerators" target="_blank">Enumerator</a>, <a href="http://www.playframework.com/documentation/2.1.0/ScalaAsync" target="_blank">Future</a> párossal megoldotta, hogy a szerver az előállított HTMLt azonnal küldte is a kliensnek. Akit érdekel részletesebben a dolog <a href="http://www.playframework.com/documentation/2.0.1/Enumeratees" target="_blank">itt</a> tud utánaolvasni. Ez azért növeli a teljesítményt, mert a HTML headben elhelyezett erőforrások letöltésével nem kell a kliensnek megvárnia, amíg minden kis darabka összeáll. Következő trükk, amivel tovább fokozta a teljesítmény és a felhasználói élményt eléggé leleményes. A darabkák kimenetét a HTML bodyn kívülre, egy script type="text/html-stream" elembe tette, majd JavaScripttel a helyükre illesztette, amikor végzett velük a szerver. A módszer előnye, hogy nincs szükség darabonként egy AJAX kérésre, így tehermentesíthető a szerver, mégis a felhasználó azonnal kap választ a kérésére. Amire érdemes odafigyelni, hogy a hirtelen a helyükre kerülő elemek miatt ugrálhat az oldal, megzavarva ezzel az éppen kattintani vágyó felhasználót, valamint a headerek már a kérés elején kiküldésre kerülnek, ezért azokat a kérés további részében nem lehet változtatni. Nehezebb az oldalt tesztelni, és SEO problémák is felmerülhetnek. A demóalkalmazás egyébként elérhető <a href="https://github.com/brikis98/ping-play" target="_blank">githubon</a>.</div>
</div>
<div>
<br /></div>
<div>
A következő prezentációt James Proper tartotta, aki szintén a teljesítmény-optimalizációt választotta témájául. Az első dolog, amit szép teljesítmény-tesztekkel bemutatott, hogy az aszinkron nem egyenlő a gyorsasággal, sőt! Egy egyszerű példán keresztül bebizonyította, hogy az aszinkron kérés mennyivel tud lassabb lenni a sok context switch miatt, amik elveszik a drága processzoridőt. Folytatásként több módszert is bemutatott, amivel növelhető a teljesítmény, és elkerülhető a rengeteg context switch.</div>
<div>
<ul>
<li>Az első javaslata az volt, hogy használjuk a Scala beépített ExecutionContextjét, ami a Java <a href="http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html" target="_blank">Fork/Join</a> osztályaira épül. <a href="http://www.playframework.com/documentation/2.1.0/ThreadPools" target="_blank">Bővebben</a></li>
<li>Második lehetőségként azt ajánlotta, hogy ne használjuk az első pontban említett contextet, hanem váltsunk ImmediateExecutionContextre</li>
</ul>
Általános jó tanácsként említette, hogy sose használjunk blokkolt erőforrásokat ExecutionContextek használatakor, ha mégis szükségünk van rá, akkor inkább konfiguráljunk menetirányítókat típusonként (na jó ezt senki sem érteni :) "Configure <a href="http://doc.akka.io/docs/akka/snapshot/scala/dispatchers.html" target="_blank">dispatcher</a> per type"). Mivel az Akka tud ilyet, és a Playben van beépített Akka, adja magát, hogy azt érdemes használni.</div>
<div>
Kiemelte azt is, hogy nagyon nagyon nagyon nagy weboldalaknál lehet teljesítmény-növekedést elérni, ha a routingot több különálló fájlba tesszük, és egy custom router megírásával csak a megfelelő fájlt dolgozzuk fel, amikor a kérés beérkezik. A gyorsulás oka, hogy a Play regexpeket illeszt az URI-re, és ha több száz illesztést kell csinálnia, mire a route fájl végén megtalálja a keresett bejegyzést, az felesleges processzor-terhelést jelent. A futtatott benchmarkoknál pár százalékos teljesítmény-növekedés volt mérhető.<br />
<br />
<br />
A teljesítmény optimalizációs témaköröket elhagyva a következő előadást ismét Julien Tournay és Pascal Voitot tartotta, és témája a <a href="https://github.com/scalaz/scalaz-stream" target="_blank">Scalaz-Stream</a> volt. A prezentáció lényege pár mondatban összegfoglalva annyi, hogy miként lehet streamek fogadására lecserélni a jelenleg is működő Iteratee megoldást Playben erre, az egyébként még fejlesztés alatt álló, megoldásra. A prezentációban egyetemi előadásokra emlékeztető részletességgel mesélték el többek között, hogy miért is jó ez az újfajta megközelítés, hogy milyen problémákba ütközhetünk a <a href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" target="_blank">HTTP</a>, és a <a href="http://en.wikipedia.org/wiki/WebSocket" target="_blank">WebSocket</a> eltérősége miatt, stb.<br />
<br />
<br />
Ebédszünet előtt még Adam Evans és Asher Glynn tartott egy (számomra nem annyira érdekes) előadást arról, hogy milyen tapasztalataik voltak, amikor a BBC gyerekeknek szóló PHP-s megoldását lecserélték Play frameworkre. Architekturálisan volt egy PHP frontendjük, ami a BBC Java-s backendjéhez fordult adatokért. Ami miatt a Playre esett a választásuk:<br />
<ul>
<li>Mert Scala, ezt nem is fejtették ki bővebben</li>
<li>Full stack web-framework</li>
<li>Reaktív, könnyű benne non blocking szolgáltatásokat készíteni és hívni</li>
<li><a href="http://www.playframework.com/documentation/2.0.4/JavaTemplates" target="_blank">Typed safe template</a> rendszer eléggé awesome</li>
<li>Sok vállalat használja, és zizeg az egész</li>
</ul>
<div>
Miután kiválasztották a keretrendszert csináltak egy pilot projektet, ami nagyon jól sikerült, a PHP fejlesztőknek könnyű volt átszokni Scalára, és mindenki boldog.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
A sponsor pitch után következő előadás igazán érdekes volt. Grant Klopper a <a href="http://www.theguardian.com/uk" target="_blank">The Guardian</a> hírportál frontendjét kiszolgáló alkalmazásról beszélt, milyen problémákkal kell szembenézniük, milyen megoldásaik vannak, stb. Elmesélte, hogy naponta három Budapestnyi ember látogatja meg a weboldalukat, átlagosan mindegyik megnéz három oldalt. 900 kérést kell kiszolgálniuk minden másodpercben. A rendszer teljesen nyílt forrású, és elérhető a <a href="https://github.com/guardian/frontend" target="_blank">githubon</a>, sőt nem csak elérhető, hanem konkrétan onnan buildelik a live rendszert. A teljes frontend letölthető egyetlen futtatható jar fájlként, és elindítható a java -jar frontend-artifact.jar paranccsal. A rengeteg kérés miatt mindent cachelnek, és a rendszer garantálja, hogy nem töltődik be sohasem ugyanaz a tartalom kétszer, a második kérés mindenféleképpen cache hit lesz. Beszélt továbbá még a deployment folyamatukról is, ami abból áll, hogy elindítanak három új verziót, regisztrálják őket a load balancerbe, majd lekapcsolják a régi hármat. Előadás után külön odamentem hozzá, és rákérdeztem miként kezelik azt a szituációt, amikor az összes node fut, de valami kódváltozás miatt a cachelt elem is megváltozik, és a régi verzióknak még a régit kell kiszolgálniuk, az újaknak pedig már az újakat. Dolgozom elosztott szinkron cachel, és ez nálunk bizony okoz problémát és fejfájást, éppen ezért mi kikapcsoljuk a cachet egészen addig, amíg minden nodeon az új verziójú szoftver nem fut. A válasz egész egyszerűen annyi volt, hogy sehogy. Azt mondta, hogy olyan rövid ideig fordulhat elő ez az eset, és a magas kérésszám miatt a statisztikában meg sem jeleni az a pár hibás kérés.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Utolsó előtti előadást Tobias Neef tartotta a kontrollerek absztraktálásának lehetőségeiről a Playben, hogy a lehető legjobban elkerülhessük a kódismétlést. Pár pontban összeszedve a lényeg:</div>
<div>
<ul>
<li>Készítsünk <a href="http://www.playframework.com/documentation/2.0.4/ScalaActionsComposition" target="_blank">Actionönek az Actionjeinkhez</a></li>
<li>Használjunk <a href="http://www.playframework.com/documentation/2.2.x/ScalaActionsComposition" target="_blank">ActionBuildereket</a></li>
<li>Compozáljuk actionjeinket (szép kifejezés)</li>
<li>Használjuk a <a href="http://www.playframework.com/documentation/2.1.1/ScalaHttpFilters" target="_blank">Filtereket</a></li>
<li>Egyesítsük a hibakezelési és loggolási részeket</li>
</ul>
<div>
<br /></div>
</div>
<div>
A konferencia Johan Andrén <a href="http://www.ustream.tv/recorded/42808260" target="_blank">előadásával</a> zárult, aki a különböző aszinkron lehetőségekről beszélt Scala és Java platformokon. Először a problémára világított rá Johan, miszerint az általunk írt kód a kiszolgálási idő nagyon kicsi részében van végrehajtási fázisban, és az idő nagy részét különböző erőforrásokra való várakozással tölti. Tegyük fel, hogy ha van 20 adatbázis, és 200 kiszolgáló szálunk, akkor a 200 + 1-edik adatbázist nem használó szál nem lesz kiszolgálva. A problémára háromféle megoldást mutatott be.</div>
<div>
Az első, használjuk bátran a Future és Promise osztályokat (részletes információt <a href="http://vazexqi.github.io/JVMFuturesExploration/" target="_blank">itt</a> találsz). Hátrányának azt nevezte meg, hogy amikor több rétegen keresztül dobáljuk a Future osztályokat, a rétegek között állandó jelleggel konvertálni kell azokat. Például egyik szerviz visszatér a userek listájával, de a hívónak JSON listát kell visszaadni, amit az őt hívónak HTML kóddá kell alakítani, stb. stb. Javaslata szerint az alábbi esetekben érdemes ezt a megoldás választanunk:</div>
<div>
<ul>
<li>Amikor más szolgáltatásokkal kommunikálunk</li>
<li>Párhuzamos végrehajtásra, amikor a szálak teljesen elkülöníthetők egymástól</li>
<li>Egyszerű háttérszolgáltatások implementálásakor</li>
</ul>
<div>
Utóbbi esetre nem tenném a nyakam, Tomcat esetén mivel a kiszolgáló szál referenciát őriz a Future objektumra szépen bevárja annak végeredményét, szóval én nem javaslom ezt a kombinációt.</div>
</div>
<div>
Következő lehetőségként az <a href="http://doc.akka.io/docs/akka/snapshot/scala/actors.html" target="_blank">Akka Actors</a>okat vetette fel, segítségével esemény vezérelt programozást tudunk megvalósítani. Az eljárás lényege, hogy van egy bemenő sor, amibe be tudják a kiszolgáló-szálak küldeni a kéréseket, és a rendszer egy szálon szépen sorban végrehajtja azokat. A technika nem sebességéről híres, blokkolja a többi futó szálat, de cserébe jól skálázható. Mikor használjuk:</div>
<div>
<ul>
<li>Amikor állapotokra van szükségünk</li>
<li>Adat streamelés</li>
<li>Esemény vezérelt programozásra</li>
<li>Háttérfolyamatok végzésére</li>
</ul>
<div>
Utolsó lehetősségként a <a href="http://www.playframework.com/documentation/2.0.2/Iteratees" target="_blank">Iteratee</a>-k használatát mutatta be. Sajnos ennek nincs Java-s megfelelője, a Chunks API tud valami hasonlót, de képességei messze elmaradnak a Scala natív megoldásától. A dolog lényege, hogy pici szeletekben adható át a feldolgozásnak az adat (Enumerators), és a rendszer lényegében reagál az adatra, és várja a következő darabkát.</div>
<div>
<ul>
<li>Enumerator[A] -> Iteratee[B, R]</li>
<li>Enumerator[A] -> <a href="http://www.playframework.com/documentation/2.0.1/Enumeratees" target="_blank">Enumeratee</a>[A, B] -> Iteratee[C, R]</li>
</ul>
</div>
<div>
Lényeges külömbség a hagyományos feldolgozással szemben, hogy az első hibás darabka esetén már lehet kezelni a hibát, nem kell a teljes adatfolyamot megvárni.</div>
</div>
<div>
Mikor használjuk:</div>
<div>
<ul>
<li>Streamel adatok esetén</li>
</ul>
<div>
<br /></div>
</div>
<div>
A konferencia végére igencsak megfogyatkozott a létszám, láthatóan mindenki kellőképpen elfáradt. Meg kell hagyni, hogy elég tartalmasak voltak az előadások, szóval le a kalappal a szervezők előtt, nem bízták a véletlenre a mentális zombiságunkat. Gratula ezúton is.</div>
</div>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-60158597363455257022014-01-16T23:19:00.000+01:002014-01-19T08:10:50.221+01:00Ping-conf Day 1Volt szerencsém a napomat a <a href="http://www.ping-conf.com/" target="_blank">Ping-conferencián</a> tölteni. A 2 napos rendezvény fő témája a <a href="http://www.playframework.com/" target="_blank">Play Framework</a>, mely a legelterjedtebb,<strike> hanem az egyetlen</strike> <a href="http://www.scala-lang.org/" target="_blank">Scala</a> nyelvre épülő webes keretrendszer. A rendezvény bár nem volt olcsó, mégis elég nagy tömeget mozgatott meg, hogy őszinte legyek sokkal kevesebb magyar résztvevőre számítottam. A konferencia, mely minden igényt kielégítő módon volt megszervezve, népszerűsége láttán elgondolkoztam, hogy a <a href="http://www.meetup.com/javaforum-hu/" target="_blank">JUM</a>on is több Scalás előadással készülhetnénk, mert az itt látottak alapján komoly érdeklődés van a Scála iránt Magyarországon.<br />
<br />
<br />
Az első előadást Sadek Drobi tartotta, és a funkcionális programozás alapjairól beszélt, betekintést nyújtva példákon keresztül a <i>composability</i> szépségeibe. Röviden: minden funkciónak kötelezően van visszatérési értéke, és ezekkel a funkciókkal könnyedén lehet hívási láncokat alkotni. Az előadás végén elhangzott egy kérdés, miszerint mennyivel jobb megoldás ez, mint a Java 8-cal érkező Lambda kifejezések? A válasz tömör volt és egyszerű, mivel a Java hemzseg a void metódusoktól, igazán sosem lesz benne az az erő, amit a Scala nyújtani képes.<br />
<br />
<br />
A második prezentációt Matt Hamer tartotta, és témának a többszörös JVM architektúrákat választotta. Számomra ez volt az egyik legérdekesebb előadás. Matt elmesélte, hogy az alkalmazás, amit fejlesztenek több részre van felosztva, egy prezentációs rétegre, ahol Play biztosítja a kiszolgálást, és n+1 business rétegre -szintén Play-, ami értelem szerűen backendként funkcionál a prezentációs rétegnek, és egyéb klienseknek. A szeparáció elsődleges okainak a következőket nevezte meg:<br />
<ul>
<li>Könnyebb Load balancing</li>
<li>Izoláció a rétegek között</li>
<li>Különböző gyakorisággal történik a deployment</li>
</ul>
<div>
Miután ismertette az architektúrát, Matt részletesen kifejtette, a két réteg közötti kommunikációs lehetőségeket. Először számba vette a JSON küldözgetést, de ez két JVM között nem a legideálisabb választás, ezért áttért Java közelibb megoldásokra.</div>
<div>
<ul>
<li>java.io.Serialize, mint az köztudott érzékeny a verziózásra, egyáltalán nem rugalmas</li>
<li><a href="https://developers.google.com/protocol-buffers/docs/javatutorial" target="_blank">Protocol Buffer</a>, sokkal jobb választás, de a konfigurációs állományokat állandóan karba kell tartani</li>
<li><a href="http://doc.akka.io/docs/akka/snapshot/java/serialization.html" target="_blank">Akka custom serialization</a>, ez a megoldás bizonyult a legjobbnak, orvosolja a kompatibilitási problémákat, nem kell konfigurációs állományokat napra készen tartani, és nem utolsó sorban a Playben van beépített Akka</li>
</ul>
<div>
<br /></div>
</div>
<div>
Egy rövid szünetet követően a harmadik téma következett "Build reactive apps on the JVM" címmel. Christopher Hunt saját fejlesztésű keretrendszerüket mutatta be. Megmondom őszintén nem tudtam teljesen elképzelni a felépítését, de nagy vonalakban egy Play frameworkre épülő, <a href="http://en.wikipedia.org/wiki/Rhino_(JavaScript_engine)" target="_blank">Rhino</a> enginet használó JavaScript, igen jól olvastad JavaScript webframeworkről volt szó. A rendszerhez már most is elérhető néhány plugin (<a href="http://coffeescript.org/" target="_blank">CoffeeScript</a>, <a href="http://www.jshint.com/" target="_blank">JS Hint</a>, <a href="http://www.lesscss.org/" target="_blank">Less</a>, <a href="http://require.js/">Require.js</a>, <a href="http://en.wikipedia.org/wiki/Jasmine_(JavaScript_framework)" target="_blank">Jasmine</a>). Ez jutott <a href="http://www.youtube.com/watch?feature=player_detailpage&v=iDwY9KGt90c#t=555" target="_blank">eszembe</a>.</div>
<div>
<br /></div>
<div>
A következő, szám szerint a negyedik prezentáció Yann Simon nevéhez fűződik, témája pedig a <a href="https://coderwall.com/p/t_rapw" target="_blank">Cake Patter In Scala</a> gyakorlati bemutatása volt. A példa alkalmazás 3 részre volt tagolva, 1 frontend, és 2 backend modulból állt. Az első verzióban a frontend modulba be voltak égetve a backend modul szervizei, és a hívások. A megoldással a legnagyobb probléma, hogy a teszt-piramis (unit, component, integration) valójában nem is piramis alakú, hiszen kis számú unit teszten kívül lényegében mindent az integration teszt szintjén kell vizsgálni. A probléma felismerését követően elkezdte átalakítani az egyes rétegeket, amíg el nem jutott arra a szintre, hogy mindent a <a href="http://scabl.blogspot.hu/2013/02/cbdi.html" target="_blank">Scala Component based Dependency Injectionnel</a> oldott meg. Én valahol a folyamat közepén vesztettem el a fonalat, mikor elszaporodtak a trait x extends y with z, meg class a extends b self: c kifejezések. Mindentől függetlenül 2 fontos dolgot leszűrtem:</div>
<div>
<ul>
<li>Ez a megoldás annyival jobb más DI megoldásoknál (<a href="http://www.vogella.com/tutorials/SpringDependencyInjection/article.html" target="_blank">Spring</a>, <a href="https://code.google.com/p/google-guice/" target="_blank">Guice</a>), hogy már fordítási időben kiderül ha valami nem stimmel, és nem csak futás időben jönnek elő a problémák</li>
<li>Nem érdemes kimaxolni ezt a megoldást, mert az egyszerű példa esetén is 20 trait "osztályra" volt szükség, inkább az elégséges szinten érdemes megállni</li>
</ul>
<div>
<br /></div>
</div>
<div>
Az ebéd szünetet követően Julien Tournay és Pascal Voitot tartott egy érdekes és egyben vicces szösszenetet "Typesafing your blobs" címmel. Hogy pontosan elénk tárhassák a problémát egészen az ősrobbanásig mentek vissza, majd az utazás a dinoszauruszok korán át egészen napjainkig tartott. Történelmi kalandozásuk során megálltak egy pillanatra a modern korba, amikor az emberek kísérletet tettek egy egységes protokoll kidolgozására, hogy a különböző informatikai rendszerek tudjanak egymással kommunikálni, és megszületett a <a href="http://en.wikipedia.org/wiki/SOAP" target="_blank">SOAP</a>. Folytatásként arról beszéltek, hogy a technológia fejlődésével egyre inkább ki vannak téve a webes szolgáltatások egymásnak és pontosan ezért egyre fontosabb, hogy a külső forrásokból érkező adatokat rugalmasan tudjuk kezelni és ellenőrizni. Példának hozták fel, hogy 2 éve a <a href="http://en.wikipedia.org/wiki/WebSocket" target="_blank">WebSoket</a> még csak Sky-fi volt, ma pedig már szolgáltatások épülnek rá, utalva ezzel, hogy milyen rohamosan fejlődik a világ. A fent említett dolgok vezették rá a srácokat, hogy egy teljesen új, a régivel kompatibilis, de a mai kor elvárásainak megfelelő (vagy inkább jövőbe mutató) validációs API-t fejlesztettek a Playhez. Természetesen teljesen funkcionális szemléletben készült a kód, kihasználja a <a href="http://scalamacros.org/" target="_blank">Scala macro</a>kban rejlő lehetőségeket, és a <a href="http://www.scala-lang.org/old/node/107" target="_blank">case </a><a href="http://www.scala-lang.org/old/node/107" target="_blank">class</a>oknak valamint az <a href="http://daily-scala.blogspot.hu/2010/04/implicit-parameters.html" target="_blank">implicit deklarációnak</a> köszönhetően marshallerek, és konfigurációk nélkül képes egy <a href="http://en.wikipedia.org/wiki/JSON" target="_blank">JSON</a> stinget Scala osztállyá alakítani. Az API másik erőssége, hogy POST-olt adatok ellenőrzésére is alkalmas kvázi módosítás nélkül. A fejlesztés még nagyban folyik, nem production ready a cucc, de akit érdekel <a href="https://github.com/playframework/playframework/pull/1904" target="_blank">itt</a> megtalálja a projektet.</div>
<div>
<br /></div>
<div>
Az utolsó előtti előadást Matthias Nehlsen tartotta, és reaktív alkalmazás-fejlesztésről beszélt Play, <a href="http://scala.js/">Scala.js</a>, és <a href="http://facebook.github.io/react/" target="_blank">ReactJs</a> segítségével. Engem főleg a ReactJs fogott meg, régóta keresek valami hasonló megoldást, de az AngularJs, Ember.js, KnockOut.js eddig nem nagyon jött be. A ReactJs-nek nagyon rövid a tanulási görbéje, és a teljesítménye is kiemelkedő, köszönhető annak, hogy a valós DOM fa mellett van belül egy virtuális DOM, és a DOM manipuláció után csak a diff-et szinkronizálja a valós fával. Érdekes volt látni, hogy milyen egyszerűen kötötte össze a nézeteket renderelő ReactJst a Scalaból generált JavaScript kódot, és a Scala.js-t a Play-el Mathias, lényegében minden Scalában volt megírva, volt pár action binding JavaScriptben, és némi HTML-nek látszó ReactJs template.</div>
<div>
<br /></div>
<div>
Utoljára maradt Nicolas Martignole esettanulmánya, melyben két általa fejlesztett projekt sikertörténetét tárta a nagyérdemű elé. Webes rétegnek Play2-t választotta, adattárolásra és cachelésre pedig <a href="http://redis.io/" target="_blank">Redist</a>. Láthatóan ő boldog volt a megoldással :).</div>
<div>
<br /></div>
<div>
Ennyi történt az első napon, igyekszem holnap is megjelenni/jegyzetelni. <a href="http://jpattern.blogspot.com/2014/01/ping-conf-day-2.html" target="_blank">Folytatás...</a></div>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-27075000453187216292012-12-30T12:50:00.000+01:002013-01-02T14:27:25.639+01:00Reflection - a bajkeverő csodagyerek<a href="http://en.wikipedia.org/wiki/Albert_Hofmann" target="_blank">Albert Hoffmann</a> szavai csengnek a fülemben, amikor a <a href="http://docs.oracle.com/javase/tutorial/reflect/index.html" target="_blank">Reflection</a>-re gondolok: "bajkeverő csodagyerek". Egyfelől milyen hasznos, hogy futásidőben példányosítani lehet osztályokat, hogy feltérképezhetjük őket, hogy módisíthatjuk őket, stb. Aki dolgozott már modern webes keretrendszerrel az tisztában van a Reflection minden előnyével, hisz ez a legegyszerűbb módja feltölteni egy objektumot a kéréssel érkező paramétereknek, vagy egy proxy segítségével megváltoztatni egy adattag viselkedését. Ilyen értelemben elképzelhetetlen lenne a (mai értelemben vett) Java EE terjedése, mert Reflection nélkül még mindig a kőkörban élnénk.<br />
<pre class="brush: java;">RequestParams rp = new RequestParams();
final Field[] fields = rp.getClass().getDeclaredFields();
for (final Field field : fields) {
firstChar = String.valueOf(field.getName().charAt(0));
methodName = "set" + field.getName().replaceFirst(firstChar, firstChar.toUpperCase());
try {
method = rp.getClass().getMethod(methodName, String.class);
method.invoke(rp, request.getParameter(field.getName()));
} catch (final SecurityException e) {
} catch (final NoSuchMethodException e) {
} catch (final IllegalArgumentException e) {
} catch (final IllegalAccessException e) {
} catch (final InvocationTargetException e) {
}
}
</pre>
A példa jól szemlélteti, hogy pofon egyszerű feltölteni a kapott paraméterekkel egy objektumot, újabb paraméterek létrehozásakor nem kell setterekkel bajlódni, ráadásul a kód teljesen hordozható, így bármely osztályra dinamikusan alkalmazható (a példa csak String-eket kezel az egyszerűség kedvéért).<br />
Itt merül fel a kérdés, hogy a Reflection nem egy kétélű fegyver? Mivel mondhatni teljes hozzáférést biztosít objektumjaink belső működéséhez és felépítéséhez, nem lehet ezt rosszra is használni? A válasz egyértelmű IGEN! <a href="http://thedailywtf.com/Articles/Disgruntled-Bomb-Java-Edition.aspx" target="_blank">Daily WTF</a>-en találtam ezt a kedves kis szösszenetet:
<br />
<pre class="brush: java;">class ValueMunger extends Thread {
public void run() {
while(true) {
munge();
try { sleep(1000); } catch (Throwable t) { }
}
}
public void munge() {
try {
Field field = Integer.class.getDeclaredField( "value" );
field.setAccessible( true );
for(int i = -127; i <= 128; i++)
field.setInt(
Integer.valueOf(i),
// either the same (90%), +1 (10%), or 42 (1%)
Math.random() < 0.9 ? i : Math.random() < 0.1 ? 42 : i+1 );
} catch (Throwable t) { ; }
}
}
</pre>
A fenti kódrészlet az Integer wrapper cachet-t zagyválja egy kicsit össze, időnként nem a valós értéket kapjuk vissza, hanem az élet értelmét, ami üzleti kritikus alkalmazásokban igen nagy probléma, nem szeretnénk egy pészmékerben ilyesmi kóddal találkozni, de azt sem szeretnénk, ha a banki alkalmazás viccelné meg a bankszámlánkat időnként. A <a href="http://www.javaspecialists.eu/archive/Issue096.html" target="_blank">final</a> mezőink, a private tagjaink sincsenek biztonságban, ahogy az <a href="http://www.javacodegeeks.com/2012/12/of-hacking-enums-and-modifying-final-static-fields.html" target="_blank">Enum</a>-jaink sem, és ezen a ponton a végtelenségi lehetne sorolni az ártalmas és vicces példákat, ugyanis kijelenthető, hogy (Sun/Oracle) Java verziótól függően szinte mindenhez hozzá lehet férni Reflection API segítségével, nincs menekvés.<br />
Ártalmas kódot hagyhat hátra kilépő munkatárs, tölthetünk le ártatlanul egy külső osztálykönyvtárral, szóval több forrásból is beszerezhetőek, de vajon mit lehet tenni ellenük? Az egyetlen gyógyszer a tudatos és körültekintő felhasználás.<br />
<br />
<ul>
<li>Használjunk statikus kódelemzőt, és szigorúan tartsuk számon azokat a részeket, ahol az alkalmazásunk Reflection API-t használ.</li>
<li>Használjunk nyílt forrású könyvtárakat, és fordítsuk magunk (kódelemzés után).</li>
<li>A letöltött osztálykönyvtárak hitelességét minden lehetséges módon ellenőrizzük, ugyanis koránt sem biztos, hogy senki nem törte fel kedvenc keretrendszerünk weboldalát, és cserélte ott le a letölthető jar-okat saját módosított verziójára (jó lenne a Linux repository-khoz hasonló központosított megoldás).</li>
<li>Csak a készítő által <a href="http://docs.oracle.com/javase/6/docs/api/java/security/Signature.html" target="_blank">aláírt osztályokat</a>, és jar-okat használjunk, ha külső forrásra kell támaszkodnunk.</li>
<li>Minimalizáljuk a külső forrásokat, ne használjunk két külön osztálykönyvtárat közel ugyanarra a feladatra.</li>
</ul>
<div>
Ha van még ötletetek, milyen módszerekkel védhetjük még a hátsónkat, kérlek ne tartsátok vissza magatokat, és írjátok meg, nekem hirtelen ennyi jutott eszembe. A bejegyzésben taglalt téma nem újkeletű, de azt hiszem sosem lehet róla eleget beszélni.</div>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com1tag:blogger.com,1999:blog-7271455030734972271.post-28525004051729904482012-11-04T08:13:00.003+01:002012-11-06T08:23:39.652+01:00Eclipselink beizzítása Jboss 7.1 környezetben<div>
Valaha fejlesztettünk egy portált Liferay platformon. Mivel a fejlesztés elején a gyári Service Buildert <a href="http://jpattern.blogspot.com/2011/12/liferay-service-builder-sotet-oldal.html" target="_blank">elvetettük</a>, teljesen kézenfekvő megoldás volt EJB/JPA párossal implementálni az üzleti és a perzisztens réteget. Első körben Hibernatere esett a választás, mert a Jboss alkalmazásszerver "natívan" támogatta, nem volt más dolgunk, mint deployolni az alkalmazást, és működött. Fejlesztés során nem is akadt semmi problémánk, ám az élesítés előtti utolsó hajrában szembesültünk azzal a ténnyel, hogy a perzisztens réteg teljesítménye a béka feneke alatt van, majd egy kis kutakodás után kikristályosodott, hogy a Hibernate nem bánik túl kedvezően az erőforrásokkal. Nincs mese, másodlagos cachet kell beüzemelni (Ehcachere esett a választás), de sajnos még ezzel sem hozta azt az alkalmazás, amit elvárnánk/megszoktunk. Ekkor jött az ötlet, hogy dobjuk ki a Hibernatet. Eclipselinkkel már volt tapasztalatunk, ráadásul nem is rossz, így megejtettük a váltást. Egyáltalán nem bántuk meg, a teljesítmény az egekbe szökött (pontos számadataink nincsenek, egyszerűen nem bírtuk elérni, hogy az adatbázisszerver CPU 10% fölé menjen, az addigi konstans 80% helyett). Eclipselinkre való átállás nem volt zökkenőmentes, nem pont ugyanúgy értelmezi a JPQL-t mint a Hibernate (egész pontosan az Eclipselink JPQL-t valósít meg, míg a Hibernate a saját HQL-jét használja), ezen felül a Jbossból is ki kellett gyomlálni pár Hibernate jart, úgy, hogy közben a Liferay, ami egyébként Hibernatre épül, még működőképes maradjon.<br />
<br />
Az élesítés óta megjelent a Liferayből 2 újabb verzió, és szerettem volna frissen tartani a rendszert, de természetesen az új Liferayhez új Jboss is dukál. A frissítendő architektúra hibridsége miatt nem volt könnyű szülés a művelet, ennek lépéseit szeretném megosztani.<br />
<br />
Az új Jboss architektúra teljesen modularizált, ezért egy 3rd party lib használata nem annyi, hogy bemásoljuk a megfelelő jar-t a classpathra, hanem modulként definiálnunk kell a rendszerben. Nincs ez másként az Eclipselinkkel sem. Modul létrehozásához annyit kell tennünk, hogy létrehozzuk a modules/org/eclipse/persistence/main könyvtárakat az alkalmazásszerverünkben, és a main könyvtárba bemásoljuk az eclipselink.jart, a konfigurációhoz pedig létre kell hoznunk ugyanitt egy module.xml-t, amiben a modult magát definiáljuk.<br />
<br />
<pre class="brush: xml;"><module name="org.eclipse.persistence" xmlns="urn:jboss:module:1.1">
<resources>
<resource-root path="eclipselink.jar"></resource-root>
<dependencies>
<module name="asm.asm"></module>
<module name="javax.api"></module>
<module name="javax.persistence.api"></module>
<module name="javax.transaction.api"></module>
<module name="javax.validation.api"></module>
<module name="javax.xml.bind.api"></module>
<module name="org.antlr"></module>
<module name="org.apache.ant"></module>
<module name="org.apache.commons.collections"></module>
<module name="org.dom4j"></module>
<module name="org.javassist"></module>
<module name="org.jboss.logging"></module>
</dependencies>
</resources></module>
</pre>
Az előző lépéshez hasonlóan egy Ant, és egy adatbázis modult is létre kell hozni (estemben egy PostgreSQLt).<br />
<pre class="brush: xml;"><module name="org.apache.ant" xmlns="urn:jboss:module:1.1">
<resources>
<resource-root path="ant.jar"></resource-root>
</resources>
</module>
</pre>
<pre class="brush: xml;"><module name="org.postgresql" xmlns="urn:jboss:module:1.1">
<resources>
<resource-root path="postgresql.jar"></resource-root>
</resources>
<dependencies>
<module name="javax.api"></module>
<module name="javax.transaction.api"></module>
<module name="javax.servlet.api" optional="true"></module>
</dependencies>
</module>
</pre>
Ezután nincs más dolgunk az alkalmazásszerveren, mint a standalone/configuration/standalone.xml-t szerkeszteni. Először is definiáljuk a szükséges DataSource-ot.<br />
<pre class="brush: xml;"> <subsystem xmlns="urn:jboss:domain:datasources:1.0">
<datasource enabled="true" jndi-name="java:jboss/datasources/LiferayPool" pool-name="LiferayPool" use-java-context="true">
<connection-url>jdbc:postgresql://[hostname]:[port]/[database]</connection-url>
<driver>postgres</driver>
<pool>
<min-pool-size>14</min-pool-size>
<max-pool-size>20</max-pool-size>
</pool>
<security>
<user-name>postgres</user-name>
</security>
</datasource>
<drivers>
<driver module="org.postgresql" name="postgres">
<xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
</driver>
</drivers>
</subsystem></pre>
Lehet, hogy ez Liferay specifikus, de nálam volt Hibernate konfiguráció is ebben az xmlben, ami miatt timeoutolt a perzisztens réteg inicializálása, így azt egy kecses mozdulattal kitöröltem (egyelőre nem jelentkezett a hiányából fakadó probléma).<br />
<br />
Miután az alkalmzásszerver konfigurációjával végeztünk, már csak az alkalmazásunkat kell egy kicsit reszelgetni. Ahol JNDI név szerint hivatkoztunk EJB Session Beanekre, ott a hivatkozott nevet célszerű átírni. A Jboss az EJB deployment során kiírja, hogy milyen neveken regisztrálta a beaneket, nekem nem mindegyik működött ??, de ez a forma bevált: global/[appname]/[serviceimplclassname].<br />
A persistence.xml-ben is be kell állítanunk pár beállítást.
<br />
<pre class="brush: xml"><provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>java:/jboss/datasources/LiferayPool</jta-data-source>
<properties>
<property name="eclipselink.target-server" value="JBoss"></property>
<property name="jboss.as.jpa.providerModule" value="org.eclipse.persistence"></property>
</properties>
</pre>
<br />
Utolsó lépésként az alkalmazásunkban be kell állítani, hogy milyen Jboss modulok a függőségei, ezt két féle módon is megtehetjük, vagy a META-INF/MANIFEST.MF állományban vesszük fel az alábbi sort:<br />
<pre>Dependencies: org.eclipse.persistence
</pre>
vagy létrehozunk ugyanitt egy jboss-deployment-structure.xml nevű konfigurációs állományt, az alábbi tartalommal:
<br />
<pre class="brush: xml"><jboss-deployment-structure>
<deployment>
<dependencies>
<module name="org.eclipse.persistence"></module>
</dependencies>
</deployment>
</jboss-deployment-structure>
</pre>
Mindenkinek javaslom, hogy tegyen egy próbát az Eclipselinkkel, számos területen jobb, mint a Hibernate, nem csak teljesítményben és szabványkövetésben. Kedvenc tulajdonságom pl, hogy Hibernattel ellentétben a Session bezárása után is eléri még a DataSourcot (limitáltan), így a lustán inicializált relációkat nem kell kézzel betöltögetni még az üzleti rétegben, hanem a helyükre tett proxyn keresztül később is eléri azokat.
</div>
mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com2tag:blogger.com,1999:blog-7271455030734972271.post-62832142422958383262012-06-27T16:11:00.000+02:002012-06-27T18:49:13.411+02:00Barátkozás a GroovyvalRégóta terveztem, hogy megismerkedek a Groovy rejtelmeivel, és most úgy alakult, hogy egy hosszan tartó barátság első napjait élem. A nyelv ismertetést nem is az alapoknál kezdeném, hiszen azok egy Java fejlesztőnek nem szabad, hogy gondot okozzanak, mivel visszafelé teljes a kompatibilitás. Sokkal inkább koncentrálnék a kedvenc programozói eszközökre, amikkel új dimenziókba lehet helyezni az eddig megszokott Java programozást.<br />
<br />
Van két fontos különbség a Java és a Groovy között, amit mindenképpen megosztanék ezen a ponton. <b>Groovyban nincsenek primitív t</b><b>í</b><b>pusok</b>, még ha látszólag úgy is deklarálunk egy változót, az eredmény mindig egy példány lesz a memóriában. A másik, hogy a <b>dupla egyenlőség</b> vizsgálat, Javatól eltérően <b>érték szerint hasonlít össze</b>!<br />
<br />
Minden Java fejlesztő rémálma a NullPointerException, éppen ezért Javaban a műveletek nagy részét megelőzi erre vonatkozó ellenőrzés, ami csak átláthatatlanabbá teszik a kódot. Groovyban ez a teljes vizsgálat elvégezhető egyetlen kérdőjel segítségével:<br />
<br />
<pre class="brush: java;">int foo = bar?.length ? bar.length : -1;
</pre>
Az eredmény, sokkal tömörebb kód, miközben az olvashatóságot sem rontja a szintaxis. A történet egyszerű. A kérdőjel helyére egy null ellenőrzést ékel a fordító.<br />
<br />
Következő hasznos egyszerűsítés amit meg szeretnék említeni az un. Elvis operátort.
<br />
<pre class="brush: java;">int foo = bar ?: -1;
</pre>
Az Elvis operátorral az alapértelmezett értéket lehet meghatározni, amennyiben az eredeti "érték" false vagy null.
<br />
<br />
Soron következő kedvencem a <a href="http://groovy.codehaus.org/Strings+and+GString" target="_blank">GString</a>. Stringek összefűzésének problémájával a legtöbben már egészen biztos találkoztunk. Kis mennyiségű szöveg összefűzésénél még nem is akkora a probléma, mert az egy sorban elvégzett String összefűzés automatikusan egy StringBulder osztályra fordul. Nagyobb mennyiség esetén (hallottam olyan helyről, ahol a mai napig 80 karakter sorhossz) macerássá válik a művelet. A Groovy eszköztárában egy az <a href="http://docs.oracle.com/javaee/6/tutorial/doc/gjddd.html" target="_blank">Expression Language</a>-re kísértetiesen hasonlító megoldást építettek.<br />
<pre class="brush: java;">String foo = "Foo"
String bar = "${foo} Bar"
</pre>
<span style="background-color: white;">Ezt a funkcionalitást kombinálva a több soros stringek deklarációjával, máris kézzelfogható előnyhöz jutunk:</span><br />
<pre class="brush: java;">def sql = """
select * from ${table}
where bar = ${foo}
"""
</pre>
Fontos tudni, hogy a szimpla idézőjelek között létrehozott 'stringek' hagyományos java.lang.String példányok lesznek, a duplával pedig GStringek, ezért ha nem szeretnénk a GString sajátosságait kihasználni, mindig szimpla idézőjellel példányosítsuk stringjeinket.
<br />
<br />
Reguláris kifejezések használatát is lényegesen leegyszerűsítették a Groovys srácok.<br />
<pre class="brush: java;">Pattern pattern = ~/(.*)/
boolean find = 'foo' ==~ pattern
Matcher m = 'foo' =~ pattern
</pre>
<br />
Mint ahogy a bevezetőben említettem, Groovyban nincsenek primitív típusok, most lássuk, hogy ennek miért is van jelentősége. A fordító bizonyos operátorokat automatikusan átfordítja az objektum metódus hívásaira.
<br />
<pre class="brush: java;">a + b // a.plus(b)
a − b // a.minus(b)
a ∗ b // a.multiply(b)
a ∗∗ b // a.power(b)
a / b // a.div(b)
a % b // a.mod(b)
a | b // a.or(b)
a & b // a.and(b)
a ^ b // a.xor(b)
a++ o r ++a // a.next()
a−− o r −−a // a.previous()
a [ b ] // a.getAt(b)
a [ b ] = c // a.putAt(b, c)
a << b // a.leftShift(b)
a >> b // a.rightShift(b)
~a // a.bitwiseNegate()
−a // a.negative()
+a // a.positive()
a <=> b : a.compareTo(b)
</pre>
Ennek előnye egyrészt, hogy megkíméli a programozót rengeted felesleges gépeléstől, másrészt ezt a működést kihasználva saját osztályainkat is fel tudjuk készíteni, hogy értsék a különböző operátorokat. A Groovyban van is erre jó példa, pl. a <a href="http://groovy.codehaus.org/groovy-jdk/java/util/Date.html" target="_blank">Date</a> osztályban.<br />
<pre class="brush: java;">def today = new Date()
def tomorrow = today + 1
def yesterday = today - 1
assert today.plus(1) == tomorrow
assert tomorrow.minus(1) == today
</pre>
Fontos megértenünk 2 dolgot az operátorok átfordítása kapcsán. Az egyik, hogy vannak esetek, amikor a visszatérési objektum tipusa más lesz, mint a operandusé.
<br />
<pre class="brush: java;">StringBuilder sb = 'bar' << 'foo'
</pre>
A másik dolog, a túlcsordulást elhárító típusbővítés, ami azt jelenti például, hogy az 1 + 1.5 az ((BigInteger) 1.5).plus(1) -ra fordul, és az eredmény egy BigDecimal osztályban kerül tárolásra, hiába az Integer állt előbb. A Groovy decimális számok tárolására alapértelmezetten a BigDecimalt használja, elkerülendő a lebegőpontos számok ábrázolásából fakadó hibákat.
<br />
<br />
A következő érdekesség amire szeretném felhívni a figyelmet a Groovy osztálykezelése. A Groovy egy speciális osztályon keresztűl hozzáférést biztosít az osztályokhoz, és lehetőséget ad azok bővítésére.
<br />
<pre class="brush: java;">String.metaClass.prefixFirstLette = { prefix ->
return "${prefix}_${delegate.substring(0, 1)}"
}
println '<span style="background-color: white;">bar'</span><span style="background-color: white;">.prefixFirstLette('foo');</span></pre>
<br />
Az előző példában egy újabb speciális Groovy osztállyal találkozhattunk, a <a href="http://groovy.codehaus.org/api/groovy/lang/Closure.html">Closure</a>-val, mely osztály kiemelten fontos a nyelv szempontjából, és számtalan metódusnak átadható paraméterként.
<br />
<pre class="brush: java;">Closure c = { i ->
return i
}
println c.call(1)
</pre>
A Closure segítségével a Groovy szimulálni tudja a Javaból egyébként igencsak hiányzó névtelen függvények használatát.<br />
<br />
Következő témakör, amelyet fontos kihangsúlyozni a Groovyval kapcsolatban, hogy natív támogatást nyújt listák és mapok kezelésére, ráadásul számos olyan funkcióval egészítették ki ezen osztályokat, amik megkönnyítik a velük végzett műveleteket. Pár példa a teljesség igénye nélkül:
<br />
<pre class="brush: java;">def words = ['ant', 'buffalo', 'cat', 'dinosaur']
assert words.findAll{ w -> w.size() > 4 } == ['buffalo', 'dinosaur']
assert words.collect{ it[0] } == ['a', 'b', 'c', 'd']
def list = [[1,0], [0,1,2]].sort { item -> item.size() }
assert list == [ [1,0], [0,1,2] ]
assert [1, 3, 5] == ['a', 'few', 'words']*.size() //minden elemen végrehajtja a size() metódust
</pre>
A <a href="http://groovy.codehaus.org/groovy-jdk/">GDK</a> plusz extraként kiegészít minden tömböt, kollekciót, és Stringet egy további toList() metódussal.
<br />
<pre class="brush: java;">def greeting = 'Hello Groovy!'
assert greeting[6..11] == 'Groovy'
assert greeting[0,2,4] == 'Hlo'
</pre>
<br />
A következő érdekesség az XML kezelés Groovyban. Okulva a Java hiányosságából, szintén natív támogatás van XML struktúrák kezelésére.
<br />
<pre class="brush: java;">def builder = new groovy.xml.MarkupBuilder()
builder.book {
author 'Bar Foo'
title 'sometitle'
properties {
pages 42
}
}
println builder
</pre>
<pre class="brush: xml;"><book>
<author>Bar Foo</author>
<title>sometitle</title>
<properties>
<pages>42</pages>
</properties>
</book>
</pre>
A Streamek kezelésében is hoz változást a Groovy. Javaval ellentétben nem kell ciklust írnunk a tartalom áttöltéséhez.
<br />
<pre class="brush: java;">def address = 'http://jpattern.blogspot.com/favicon.ico'
def file = new FileOutputStream(address.tokenize("/")[-1])
def out = new BufferedOutputStream(file)
out << new URL(address).openStream()
out.close()</pre>
<br />
Utoljára hagytam a legkevésbé fontos, de talán mégis hasznos újítást az importok területén. Lehetőség van Groovyban importált osztály-t aliasszal megjelelölni.
<br />
<pre class="brush: java;">import org.springframework.context.i18n.LocaleContextHolder as LCH
...
def locale = LCH.getLocale()
</pre>
<br />
A pozitívumok után következzenek a negatívumok, bár személy szerint nem sok ilyet találtam. Az első, hogy a Groovy nem támogatja belső osztályok definiálását, ami szerintem a Java eszköztárának egy fontos kelléke. A Másik, hogy dinamikus típusú nyelv lévén az IDE támogatás meg sem közelíti a Javaét. Bár mindhárom elterjedt IDE (Netbeans, Eclipse, IntelliJ) rendelkezik Groovy támogatással, Javahoz szokott fejlesztőként számtalan kényelmi funkciót kell nélkülözni.
<br />
Véleményem szerint egy elég erőteljes nyelv lett a Groovy, a fejlesztők igyekeztek a Java hiányosságaiból tanulni, miközben megőrizték a Javaban rejlő erőt teljes mértékben. Bár mindenki azt csinál amit akar, én személy szerint alapos Java ismeretek nélkül nem ajánlom a nyelvet kezdőknek, ugyanis ahhoz elég sok dologban tér el a Javatól, hogy rossz szokásokat fejlesszen későbbi Java programozáshoz. Ilyen pl. a dupla egyenlőség vizsgálat, a String automatikus StringBuilderré alakítása bizonyos operátorok használatakor, A streamek kezelése, stb. Remélem további ismerkedésre inspirál mindenkit ez a kis írás, és sokan kiegészítik ezzel a remek eszközzel programozói repertoárjukat.mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com5tag:blogger.com,1999:blog-7271455030734972271.post-47506089587384057902012-03-23T09:22:00.000+01:002012-03-23T09:29:23.193+01:00Könyvajánló: Java fejtörők<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.felsofokon.hu/sites/default/files/imagecache/content_middle/users/jpattern/images/konyvajanlo-java-fejtorok-21195.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://www.felsofokon.hu/sites/default/files/imagecache/content_middle/users/jpattern/images/konyvajanlo-java-fejtorok-21195.jpg" /></a></div>
A <a href="http://konf.fsf.hu/cgis/ossc">Szabad Szoftver Konferencián</a> volt alkalmam megvásárolni Joshua Bloch és Neal Gafter, <a href="http://www.kiskapu.hu/index.php?BODY=BookInfo&OP=details&ID=100386">Java fejtörők</a> című könyvét. Bevallom őszintén elsősorban az akciós ára miatt vásároltam meg, ám azonban azóta az egyik kedvenc olvasmányom lett, mert mindamellett, hogy sokat lehet belőle tanulni, és megmozgatja a fogaskerekeket, még szórakoztat is.<br />
Lássuk először a szerzőket. Joshua Block neve ismerősen csenghet, a Carnegie Mellon University-n doktorált, a Java 5 nyelvi bővítésein dolgozott és a Java Collections Framework tervezését és fejlesztését is irányította többek közt. Dolgozott a Sun Microsystemsnél és jelenleg a Google egyik főmérnöke. Szerzője a díjat nyert <a href="http://kiskapukiado.hu/main.php?SHOW_BODY=books&OP=detailed&PROD_ID=157">Hatékony Java</a> című könyvnek. Ez utóbbi műve szerintem kötelező olvasmány minden Java fejlesztőnek! Neal Gafter a University of Rochesteren szerzett doktorátust, majd az Sun Microsystemsnél irányította a Java fordító fejlesztését rangidős mérnökként. Jelenleg ő is a Googlenél - kinél másnál - dolgozik szoftvermérnökként.<br />
A könyv a Java nyelvben rejlő csapdákkal, buktatókkal, és olyan szélsőséges esetekkel foglalkozik, amelyekbe nap mint nap belefuthatunk munkánk/hobbink során, és kellő ismeret nélkül csak a fejünket vakargathatjuk a nem várt eredmény miatt. A könyv 9 gyakorlati fejezetbe kategorizálja az összesen 95 esetet, és az egyszerűbbtől a bonyolultabb példák felé halad, a leg harcedzettebbeknek is sokszor feladva a leckét. Nem spoilerezek többet, tessék megvenni a könyvet, ha még nincs meg :).<br />
<br />mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com2tag:blogger.com,1999:blog-7271455030734972271.post-68230460595022870062012-01-21T17:29:00.001+01:002012-01-23T13:12:37.560+01:00Weak és Soft referenciák a Javaban<p>Több éves Java tapasztalattal a hátam mögött hallottam először a gyenge (weak) referenciák létezéséről a nyelvben, és úgy gondoltam érdemel pár szót a téma kör. Javaslom, akinek nem világos a JVM szemétgyűjtőjének (továbbiakban GC) működése, ezen a ponton olvassa el Viczián István <a href="http://jtechlog.blogspot.com/2011/12/java-memoriakezeles-szemetgyujto.html">idevágó</a> bejegyzését. Tehát mint tudjuk a GC kidob a memóriából minden olyan objektumot, melyre nem mutat már egyetlen referencia sem. Amikor létrehozunk egy objektumot, Integer foo = new Integer(0), akkor a foo egy strong reference lesz rá, és amíg az objektum "strongly reachable", addig a GC-nek tabu. Mivel ezeket az objektumokat nem tudja a GC felszabadítani, a memória beteltével jön a fránya OutOfMemotyError, és az alkalmazás kilép. Egy darabig persze lehet növelni a memóriát, majd a fizikai határok elérésével lehet elosztani az alkalmazást, de előbb álljunk meg egy szóra! Vajon minden objektumra szükségünk van a memóriában? Kézenfekvő megoldás, hogy bizonyos objektumokat azonnal megszüntessünk, amint nincs rájuk szükség, viszont az objektumok újbóli létrehozása is nagy költség, ebben az esetben pedig értékes processzoridőt fecsérelünk az állandó memóriaallokációra, példányosításra, stb. Arany középútként az 1.2-es, igen az 1.2-es Java verzióban bevezették a weak referenciákat, melynek implementációja a <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/ref/WeakReference.html">WeakReference</a> osztály. A WeakReference referenciát tárol az adott objektumra, annyi különbséggel, hogy ezt az objektumot a GC első futáskor, szó nélkül eltávolítja a memóriából, felszabadítva ezzel a helyet mások számára.</p>
<pre class="brush: java;">
WeakReference<StringBuilder> weakString = new WeakReference<StringBuilder>(new StringBuilder());
int i = 0;
while (weakString != null && weakString.get() != null) {
weakString.get().append(i++);
System.out.println(i);
}
System.out.println("Finish");
</pre>
<p>Kezdetként futási paraméterként állítsuk be a JVM-nek a maximális memóriát mondjuk 2 Mb-ra (-Xmx2m), majd az értékkel játszva láthatjuk, hogy mennyit változik programunk kimenete. Ez a technika kiválóan alkalmas memória gyorsítótárak készítésére, ám amennyiben kulcs-érték-pár alapú gyorsítótárat készítünk a new WeakReference(myMapInstance) helyett használjuk, az erre a célra készített <a href="http://docs.oracle.com/javase/6/docs/api/java/util/WeakHashMap.html">WeakHashMap</a> implementációt. A WeakHashMap a kulcsokat őrzi weak referenciával, és a MapEntry automatikusan eltávolításra kerül, amikor a kulcs már nincs rendszeres használat alatt.</p>
<p>A WeakReference mellett létezik a <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/ref/SoftReference.html">SoftReference</a> osztály is, amely azt garantálja, hogy az OutOfMemoryError előtt felszámolásra kerülnek az objektumok, helyet biztosítva az újonan létrejövőknek.
<pre class="brush: java;">
SoftReference softObj = new SoftReference(new Serializable() {
@Override public void finalize() throws Throwable {
System.out.print("Finalize runned");
super.finalize();
}
});
StringBuilder sb = new StringBuilder("foobar");
while (true) {
sb.append(sb.toString());
}
</pre>
A kimenetben láthatjuk, hogy miközben az alkalmazás akkut memóriahiányban elhalálozott, még utolsó leheletével felszámolta osztályunkat.
<pre>
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
<b>Finalize runned</b> at java.util.Arrays.copyOf(Arrays.java:2882)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390)
at java.lang.StringBuilder.append(StringBuilder.java:119)
at com.blogspot.jpattern.Main.run(Main.java:36)
at com.blogspot.jpattern.Main.main(Main.java:22)
Java Result: 1
</pre>
Természetesen ennél complexebb program esetében az alkalmazás tovább tud dolgozni a felszabadult memóriával.<b>A Java specifikáció szerint semmi garancia nincs a finalize() metódus futására!</b>
</p>
<p>Evezzünk egy kicsit sötétebb vizekre. Ha jól megnézzük a <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/ref/Reference.html">Reference</a> API dokumentációját, láthatjuk, hogy van még egy ismert implementáció, a <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/ref/PhantomReference.html">PhantomReference</a>. Az első különbség társaihoz képest, hogy a bele helyezett objektumra soha nem tudunk referenciát kérni, ugyanis konstans null értékkel válaszol a get() hívására. Másik nagy eltérés lényege tömören, hogy az ilyen objektumokat a GC csak azelőtt rendezi sorba, mielőtt a fizikai memóriából kitakarítaná. Mivel úgy tudom <a href="http://en.wikipedia.org/wiki/Darth_Vader">Darth Vader</a> nagyurat is ennek használata állította át az erő sötét oldalára, én magam nem merészkedtem ennél tovább (esetleg írhatnátok valami konkrét használati esetet).</p>
<p>Előfordulhat, hogy az alkalmazásunkban szükséges tudni, hogy mely objektumokat dobta már ki a GC, és melyeket nem. Ilyen esetben egy <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/ref/ReferenceQueue.html">ReferenceQueue</a> osztályt kell példányosítanunk, és a queue példányt átadni a WeakReference vagy SoftReference konstruktorának. A bejegyzésben említett referenciákkal óvatosan bánjunk, és csak alapos tervezés és tesztelést követően alkalmazzuk éles bevetésben őket.</p>mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com5tag:blogger.com,1999:blog-7271455030734972271.post-81171627023942821632011-12-12T11:31:00.001+01:002011-12-28T15:53:26.706+01:00Liferay Service Builder, a sötét oldalKorábban már volt szó a <a href="http://jpattern.blogspot.com/2010/11/liferay-servide-builder.html">Liferay Service Builder</a>éről, akkor inkább a tudásáról esett szó, most pedig az árny oldalát szeretném taglalni. A Service Builder feladata, hogy egy egységes perzisztens réteget biztosítson anélkül, hogy a különböző WAR-ok eltérő kontextusaival, vagy az eltérő adatbázisok problémájával kellene bajlódnunk. A kezdetben kézenfekvő megoldások mára sajnos nem nyújtják azt a kényelmet, amit egy <a href="http://en.wikipedia.org/wiki/Java_Persistence_API">JPA</a>/<a href="http://en.wikipedia.org/wiki/Java_Transaction_API">JTA</a>-hoz szokott fejlesztő elvár egy efféle megoldástól. Lássuk mik azok a pontok, amiket én személy szerint fájlalok:<br />
<br />
<ul>
<li>Az entitásokat leíró XML-ben csak primitív, String, vagy Date típust jelölhetünk meg az adatok tárolására, viszont osztály szinten a primitív adattagok inicializálódnak, tehát nem tudunk olyan mezőt felvenni, amelynek az értéke üres, vagyis NULL. Képzeljük el a User entitásunkat, egy opcionális életkor mezővel. Mivel az életkor alapértelmezetten 0 értéket vesz fel, nem tudjuk megkülönböztetni a csecsemő felhasználóinkat azoktól, akik nem töltötték ki az életkorukat. A példa légből kapott, de jól szemlélteti a primitív típusok hiányosságát. A Liferay készített egy workaroundot a probléma orvoslására, és használhatunk wrapper osztályokat is a primitívek helyett, viszont ebben az esetben szembesülnünk kell két dologgal, mégpedig, hogy a <a href="http://en.wikipedia.org/wiki/Document_Type_Definition">DTD</a> miatt kedvenc IDE-nk figyelmeztetni fog, hogy nem megfelelő adatot írtunk be, másrészt hivatalosan a Liferay nem támogatja ezt a megoldást.</li>
<li>Rengetegszer futottam bele, hogy a String mezők hossza alapértelmezetten 75 karakter széles. Ezen a ponton lehet vitatkozni, hogy valahol meg kellett húzni a határt az adatbázis méretének és a programozói munkának az optimalizációja közben, de véleményem szerint a 75 karakter a legtöbb esetben nem elég. Lehetett volna egy kicsit a felesleges adatbázis helyfoglalás felé billenteni a mérleget. A programozók feledékenyek, a tesztelők meg hanyagok, és ebből majdnem egyenesen következik, hogy 100 String mezőből legalább 1-nél csak túl későn derül ki, hogy nem elegendő a 75 karakter. Természetesen van megoldás, mégpedig az src/META-INF könyvtárban lévő portlet-model-hints.xml fájlban kell a mezőkre "tippeket" adni a Liferaynek. Persze itt nem csak a mező szélességére adhatunk hasznos tanácsokat, egyéb dolgokat is beállíthatunk, ezeket most nem részletezném.<pre class="brush: xml;"><field name="value" type="String"><hint-collection name="TEXTAREA"></hint-collection></field></pre>
A megoldás árny oldala, hogy EE verzió esetén megcsinálja az adatbázis módosítását a rendszer, azonban az általam próbált CE verziók nem módosították, az adatbázist, és a módosítást végrehajtó SQL-t is magamnak kellett megírnom. Ezen a ponton bukott meg az adatbázis-függetlenség. Nem ennyire elkeserítő a helyzet, mert írhatunk olyan általános SQL-t, amit a Liferay is használ a Service Builder tábláinak legenerálásához, és létezik egy osztály a Liferayben, ami az általános SQL-ből adatbázisnak megfelelő SQL-t generál, amit egy startup hookba ágyazva futtathatunk, de azt hiszem ez túl nagy ár a függetlenségért.
</li>
<li>A tranzakciók kezelése sem túl kifinomult szerintem a Liferayben. A Liferay filozófiája, hogy a tranzakciók kezelését teljesen a <a href="http://en.wikipedia.org/wiki/Spring_AOP#Aspect-oriented_programming_framework">Spring AOP</a>-re bízza, amely a Liferay szívében van konfigurálva. Az alapértelmezett működés, hogy az add*, check*, clear*, delete*, set*, és update* karakter-lánccal kezdődő metódusok egy tranzakciót indítanak. Ettől eltérően csak annyit tehetünk, hogy a service.xml-ben az entitásnál a tx-required mezővel megadhatjuk, hogy ezen felül milyen metódusok indítsanak tranzakciót. Nekem nagyon kényelmes és kézenfekvő, hogy szabadon válogathatok a <a href="http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/Transaction3.html">6 féle tranzakció</a>-kezelés közül, és bármikor eldönthetem, hogy becsatlakozok-e a meglévő tranzakcióba, hogy indítok-e újat, stb. Megint csak egy légből kapott példa, banki tranzakciót indítok a rendszerben, és a folyamat közben szeretném loggolni, hogy pontosan mi történt a tranzakcióval, de sajnos a log szerver timeoutol/betelik a lemez/stb. Ebben az esetben a loggolást végző kódrészlet hibája miatt gördül vissza az egész tranzakció, holott csak egy harmadlagos funkció nem teljesült. Létezik a Liferayben EXT plugin, amivel felül lehet írni a Spring konfigot, de a 7-es verziótól ezek egyáltalán nem lesznek támogatottak, így én sem javaslom senkinek, hogy ezt az utat válassza.</li>
<li>A service réteg deleteAll metódusai a perzisztes rétegen keresztül egyesével törlik az entitásokat, ami egy 10M+ rekordszámú táblánál órákba is telhet. Ezt különösen azért nem értem, mert a Liferay nem kezel entitás-kapcsolatokat, tehát feleslegesnek érzem az adatbázisból egyesével kikérni a rekordokat, és törlést végrehajtani rajtuk. Szerencsére erre is van egy kiskapu, közvetlenül el tudunk kérni egy JDBC kapcsolatot, ahol szabadon garázdálkodhatunk:
<pre class="brush: java;">DataAccess.getConnection.createStatement().execute("delete from MY_Entity");</pre>
Természetesen a kapcsolat élet ciklusáról magunknak kell gondoskodnunk.</li>
<li>Mint fentebb is írtam a Liferay nem igazán kezeli az entitás relációkat. Meg lehet ugyan adni kapcsolatokat, de a OneToOne kapcsolaton kívül, amit kezel a rendszer, minden kapcsolat OneToMany esetén csak egy long típusú mező, ManyToOne esetén pedig egy üres Collection lesz. a OTO kapcsolatot az service.xml-ben a reference tag segítségével adhatjuk meg. Ennek célja egyébként a minél szélesebb adatbázis paletta támogatása, de szerény véleményem szerint ez akkor plusz terhet ró a fejlesztőkre, és annyi hiba lehetőséget, és adatbázis-szemetet eredményez, ami nem biztos, hogy megéri ezt az árat. További hátránya a szemléletnek, hogy így az entitások közötti kapcsolatot View oldalon kell lekezelni, onnan kell az adatbázishoz fordulni minden kapcsolat esetén. A Liferay egyébként MVC patternt követi, de vallja azt a nézetet, hogy lekérdezés esetén lehet közvetlenül a Model-hez fordulni, egyszerűsítve a logikát, így viszont egy standard Liferay portlet plugin jsp-je tele van scriptletekkel, olvashatatlanná téve a kódot. Ízlések és pofonok, én személy szerint szeretem, ha jól el vannak szeparálva a dolgok egymástól, a jsp szerkesztésekor az IDE tud hasznos tanácsokat adni, és nincs tele a Problems fül felesleges figyelmeztetésekkel, ha a Model önállóan végzi a dolgát, és nem a View fejlesztőnek kell gondolkodnia a különböző service hívásokról.</li>
</ul>
<div>
Röviden ennyi jutott most eszembe a Liferay Service Builderének árny oldalairól, remélem ezzel nem vettem el senki kedvét a használattól, nem ez volt a célom. Véleményem szerint ha viszonylag nem túl nagy, hordozható, és/vagy SOAP-os megoldásra van szükség, jó választás lehet, de mielőtt nagy projekteket építenénk erre a komponensre mindenféleképpen végezzünk kutatásokat, kísérletezzünk, hogy kielégíti-e üzleti igényeinket maximálisan.</div>mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com4tag:blogger.com,1999:blog-7271455030734972271.post-17961172213972618452011-10-30T12:23:00.000+01:002011-10-30T12:26:01.363+01:00OWASP AntiSamy Javaban<p>A webes biztonság kérdése egyidős magával az internettel, hiszen a hálózaton elérhető adatokat felhasználók milliói tekintik meg napról-napra. Mivel a weboldalak végeredményben a kliensek gépein futnak, hamar beláthatjuk, hogy a biztonsági kérdések nem elhanyagolhatóak, és mind a klienseknek, mind a weboldalak üzemeltetőinek fontos, hogy a kiszolgált tartalom hiteles és biztonságos legyen. Tipikus támadási forma az un. <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">XSS</a>, amikor pl. egy az oldalba ágyazott külső Javascript próbál szenzitív információkhoz hozzájutni a böngésző valamely biztonsági hibáját kihasználva. Az ilyen és ehhez hasonló problémák megoldására jött létre 2001-ben <a href="https://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project">OWASP Antisamy Project</a>, melynek célja nyílt forrású alternatívát, és szabványt kínálni az alkalmazások megvédésére.</p><p>Mint minden 3rd party fejlesztés, ez is úgy kezdődik, hogy <a href="http://code.google.com/p/owaspantisamy/downloads/list">letöltjük</a> a programkönyvtár lehetőleg legfrissebb verzióját. AntiSamy jelenleg az 1.4.4-es verziónál tart. A dokumentációval ellentétbe a csomag függőségeit nem lehet letölteni, rákeresve a antisamy-required-libs.zip kulcsszavakra találtam egy <a href="http://code.google.com/p/owaspantisamy/downloads/detail?name=antisamy-required-libs-1.2.zip&can=1&q=">oldalt</a>. A készítők szerint deprecated ami a zip-be van, de a próba erejéig megteszi, éles használatra össze kell vadászni a függőségeket. A szoftver konfigurációja eléggé bonyolult, hosszas dokumentáció olvasással és tanulással egész biztos össze lehet dobni egy normális konfig XML-t, de szerencsére több nagyobb felhasználó is rendelkezésre bocsátotta saját összeállítását, így nyugodtan mazsolázgathatunk az eBay, MySpace, vagy akár Slashdot beállításaiból.</p><p>Az általam készített tesztprogram az alábbi (belecsempészve kis Java 7-et):<br />
<br />
<pre class="brush: java;">import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.owasp.validator.html.*;
public class AntiSamyTest {
public static void main(String[] args) {
try {
URL dirtyHtmlUrl = AntiSamyTest.class.getResource("dirtyHtml.html");
Path dirtyHtmlPath = FileSystems.getDefault().getPath(dirtyHtmlUrl.getFile());
List<String> lines = Files.readAllLines(dirtyHtmlPath, Charset.defaultCharset());
URL configXmlUrl = AntiSamyTest.class.getResource("antisamy-ebay-1.4.4.xml");
Policy policy = Policy.getInstance(configXmlUrl.getFile());
AntiSamy as = new AntiSamy();
CleanResults cleanResult = as.scan(concatString(lines), policy);
System.out.println(cleanResult.getCleanHTML());
} catch (IOException | PolicyException | ScanException ex) {
throw new RuntimeException(ex);
}
}
private static String concatString(List<String> input) {
StringBuilder output = new StringBuilder();
for(String line : input) output.append(line);
return output.toString();
}
}</pre>A dirtyHtml.html fájl tartalma szabvány HTML (HTML, HEAD, BODY). A programot futtatva láthatjuk, hogy a body tartalmán kívül minden HTML taget kidobott az AntiSamy. Azért mondom, hogy minden HTML taget, mert ha van pl. title a headbe, a tartalma bizony ottmarad, tehát csak a sallang kerül ki. Szerintem ez utóbbi működés konfigurálható (fixme).</p><p>Az elméleti ismerkedés után ideje valami komolyabb megbizatást adni Samykének. Személy szerint Liferay fejlesztő vagyok, így szinte evidens, hogy erre esett a választásom. A Liferay 6-os verziója óta létezik egy Sanitizers-nek nevezett funkcionalitás, amely, bár még nem teljeskörű, mégis segít az igényes programozónak, a kritikus user inputokat szűrni. A funkcionalitás mint írtam nem teljeskörű, ugyanis egyelőre csak a Blog bejegyzéseket tudjuk kontrollálni out-of-the-box. A dolgunk egyszerű, a portal.properties-ben van egy sanitizer.impl paraméter, amit a portal-ext.properties-ben felül tudunk definiálni. Az alapbeállítás a com.liferay.portal.sanitizer.DummySanitizerImpl osztályra mutat, ami jóformán semmit nem csinál, viszont jó kiindulási pont lehet saját Saniterünk elkészítéséhez. Létezik a Liferaynek beépített osztálya com.liferay.portal.kernel.sanitizer.SanitizerUtil képében, választhatjuk ezt is, de saját megoldást is minden további nélkül.</p>
<p>Miután Blog bejegyzéseinket megvédtük a sokszor óvatlan bloggerektől, sajnos nem dőlhetünk hátra nyugodtan, mivel a Liferayben is, mint minden CMS-ben, nem csak Blogot szerkesztenek a felhasználók, hanem számtalan egyéb módon is lehetőségük van HTML szöveget a rendszerbe juttatni. Mivel "gyári" támogatás még nincs ezen bejegyzésekre, nincs más lehetőségünk, mint hook-ot írni. A Plugin tárolóban van egy "antisamy hook", amely alapján könnyedén megírhatjuk saját kiterjesztésünket. A legjobb módszer un. <a href="http://www.liferay.com/community/wiki/-/wiki/Main/Wrapper+Plugins">Model Wrapper Hook</a> készítése. Hozzunk létre egy hook-ot, majd a liferay-hook.xml fájlba írjuk be a felülírandó szervíz definícióját
<pre class="brush: xml;">
<hook>
<service>
<service-type>com.liferay.portlet.wiki.service.WikiPageLocalService</service-type>
<service-impl>com.test.hooks.SaniterWikiPageLocalService</service-impl>
</service>
</hook>
</pre>
Majd írjuk meg saját osztályjunkat
<pre class="brush: java;">
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.sanitizer.SanitizerUtil;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.service.ServiceContext;
import com.liferay.portlet.wiki.model.WikiPage;
import com.liferay.portlet.wiki.service.WikiPageLocalService;
import com.liferay.portlet.wiki.service.WikiPageLocalServiceWrapper;
public class SaniterWikiPageLocalService extends WikiPageLocalServiceWrapper {
public SaniterWikiPageLocalService(WikiPageLocalService wikiPageLocalService) {
super(wikiPageLocalService);
}
public WikiPage addPage(
long userId, long nodeId, String title, double version,
String content, String summary, boolean minorEdit, String format,
boolean head, String parentTitle, String redirectTitle,
ServiceContext serviceContext)
throws PortalException, SystemException {
String sanitizedContent = SanitizerUtil.sanitize(
serviceContext.getCompanyId(), serviceContext.getScopeGroupId(),
userId, WikiPage.class.getName(), 0, ContentTypes.TEXT_HTML, content);
return super.addPage(userId, nodeId, title, version,
sanitizedContent, summary, minorEdit, format,
head, parentTitle, redirectTitle,
serviceContext);
}
}
</pre>
Bár én a Wiki-t választottam példának, ez alapján bármely más szervízre megírható a szűrés.
</p>mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com0tag:blogger.com,1999:blog-7271455030734972271.post-88375071744272517952011-09-24T15:35:00.000+02:002011-09-24T15:35:39.536+02:00Liferay Portál OSGi bundle támogatással<p>Liferay és OSGi témák már többször is előkerültek, de most egy olyan egyedülálló kombináció bemutatását tűztem ki célul, mely egyesíti a két platform minden előnyét. A bemutatott módszer aktív fejlesztés alatt áll, éles használatát nem ajánlom senkinek. A fejlesztés <a href="http://www.liferay.com/web/raymond.auge/profile">Raymond Augé</a> keze alatt történik, jelenleg a teljes kódbázis branch repóból érhető el. A cikk megírását az érdeklődés ihlette, és a remény, hogy másoknak is megtetszik a kezdeményezés, és programozók tucatjai állnak be Ray mögé, hogy minél előbb core feature legyen a dolog.</p><p>Első lépésként szükségünk lesz a módosított Liferay forrására, amit <a href="https://github.com/rotty3000/liferay-portal/tree/OSGi">itt</a> tudunk beszerezni. A letöltött forrást egyszerűen tömörítsük ki valahová. A fordításhoz egy <a href="http://ant.apache.org/">Apache Ant</a>ot is be kell üzemelnünk, ennek lépéseit nem részletezném. A kibontott Liferay gyökérében találunk egy app.server.properties állományt, melyben tudjuk konfigurálni, hogy milyen konténert szeretnénk használni. Amennyiben valami "különleges" igény nem szól közbe, én az <a href="http://tomcat.apache.org/">Apache Tomcat</a> verziót preferálom, ez egyébként az alapértelmezett. A properties fájlban <a href="http://tomcat.apache.org/download-70.cgi">7.0.21</a>-es verzió van beállítva, letöltés után a Liferay forrásunkkal párhuzamosan lévő bundles könyvtárba csomagoljuk is ki. Vagy az app.server.properties-ben írjuk át az útvonalat, vagy az apache-tomcat-7.0.21 könyvtárat nevezzük át tomcat-7.0.21-re.</p><p>A fordításhoz Antunkat is meg kell kicsit piszkálni. Először is az ecj.jar-t be kell másolni az Ant libjei közé (vagy szimlinkelni, vagy egyéb úton a classpath-ra tenni). Az ecj.jar beszerezhető a Plugin SDK lib könyvtárából. Be kell állítanunk az Ant memóriahasználatát is az alábbi módon.<br />
<pre class="brush: bash;">ANT_OPTS="-Xmx1024m -XX:MaxPermSize=256m"
export ANT_OPTS
</pre>Nincs más hátra, mint elindítani a fordítást a Liferay gyökérkönyvtárában.<br />
<pre class="brush: bash;">ant all
</pre></p><p>A fordítás befejeztével a ../bundles/tomcat-7.0.21/bin/startup.sh parancs futtatásával kelthetjük életre friss Liferayünket. Miután elindult, a Control Panel Server szekcióban találunk egy OSGi Admin menüpontot. Belépve láthatjuk, ha minden jól ment, hogy van egy OSGi System Bundle nevű batyu, ami aktív, tehát az OSGi konténer fut rendben.Az általam próbált verzióba Eclipse Equinox 3.7 volt integrálva, Ray elmondása szerint minden >=4.1-es implementációval változtatás nélkül működik.</p><p>Amit a rendszer tud jelenlegi állás szerint:<br />
<ul><li>Az adminisztrátor portleten keresztül lehetséges OSGi batyuk hozzáadása, eltávolítása, frissítése, elindítása, újraindítása, és leállítása.</li>
<li>Egy saját deploy listenerrel a deploy könyvtárba másolt batyukat a Liferay megpróbálja telepíteni és elindítani.</li>
</ul></p><p>Most, hogy működik a portálunk, nincs más dolgunk, mint nekiállni első OSGi Servletünk megalkotásához. A batyu készítésről <a href="http://jpattern.blogspot.com/2010/07/osgi-szarnyprobalgatas.html">itt</a> írtam részletesen, így az alapokba nem is mennék bele. Hozzunk létre egy OSGi Plugin Projectet. A MANIFEST.MF állomány tartalma legyen a következő:<br />
<pre>Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: OSGiOnLiferay
Bundle-SymbolicName: OSGiOnLiferay;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: javax.servlet;version="2.5.0",
javax.servlet.http;version="2.5.0",
org.eclipse.equinox.http;resolution:=optional,
org.osgi.framework;version="1.6.0"
</pre>Hozzunk létre a gyökér-könyvtárban egy plugin.xml-t, amelybe regisztráljuk a Servletünket.<br />
<pre class="brush: xml;"><plugin>
<extension
point="org.eclipse.equinox.http.registry.servlets"
alias="/osgionliferay"
class="com.blogspot.jpattern.osgionliferay.TestServlet">
</extension>
</plugin>
</pre>Végezetül írjunk egy egyszerű Servletet.<br />
<pre class="brush: java;">public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.print("Java'nother blog");
out.close();
}
}
</pre>A plugin buildelése után egyszerűen másoljuk be a kapott JAR-t a deploy könyvtárba. A Liferay logot nézve az alábbi sor jelöli a sikeres telepítést.<br />
<pre>12:14:29,798 INFO [AutoDeployDir:167] Processing sample.jar</pre>Az admin felületre látogatva remélhetőleg meg is találjuk batyunkat az OSGi Admin felületen, nincs más dolgunk, mint aktiválni.</p><p>És itt jön a szomorú, ám reményteli vég. Mint említettem eléggé fejlesztési fázisban van a projekt, ami sajnálatosan azt is jelenti, hogy ezen a ponton nem tudunk túljutni. Ray a közeljövőben publikál egy birdge Servletet, aminek a segítségével a Liferay a HTTP kéréseket az OSGi HttpService rétegén keresztül bármely, a keretrendszerbe regisztrált Servletnek vagy JSPnek továbbítani tudja. Tervezi a Plugin SDK bővítését is, hogy OSGi batyukat lehessen vele kényelmesen fejleszteni. Véleményem szerint ígéretes a kezdeményezés, hiszen a Liferay egy jól gyúrható platform, az OSGi, pedig égy végtelenül rugalmas keretrendszer.</p>mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com1tag:blogger.com,1999:blog-7271455030734972271.post-2094033271196302292011-08-27T11:24:00.002+02:002011-08-27T11:31:08.791+02:00Validálás, ahogy én csinálom<p>Egy programozó életében rendkívül gyakran előfordul, hogy entitásokat kell validálnia. Lényegében az üzleti logika mindig úgy kezdődik, hogy a bemeneti paramétereket, melyek legtöbbször felhasználói interakció által kerülnek be, validálni kell, hogy a hibás entitások a későbbiekben ne okozzanak hibát a program működésébe. A validálás az a dolog, amit egy kicsit mindenki másként csinál, mindenkinek megvan a saját módszere. Ebben az írásban bemutatnám, én hogyan is szoktam ezt a problémát orvosolni. A bemutatott megoldás nem tökéletes, de remélem gondolat-ébresztőnek megteszi.</p><p>A validálás alapproblémája szerintem a következő: az entitások validitását az üzleti réteg végzi el, amelyhez különböző kliensek kapcsolódnak. A különböző kliensek különböző nyelveken, és módon kívánják megjeleníteni a hibaüzeneteket, tehát az üzleti rétegbe írt return "Hibás a név!" és hasonlók megengedhetetlenek (később úgyis kiderül, hogy többnyelvű lesz a megjelenítés). A következő probléma, amivel szembesülünk, hogy egyszerre több hibát is tudni kell kezelni, hiszen egy bonyolult form kitöltésekor nem szerencsés egyesével untatni a felhasználót a hibák közlésével, meg egyébként sem tudjuk klienseink miként működnek. A nyelvi problémát orvosolni viszonylag egyszerű, vezessünk be jól dokumentált hibakódokat, klienseink, meg feldolgozzák saját belátásuk szerint. Az egyidejű több hibajelzés kérdésköre már egy kicsit rafináltabb dolog. A legegyszerűbb megoldás, egy hibakódokkal feltöltött tömb, vagy Collection visszaadása. Ezzel a megoldással a legnagyobb problémám, hogy rengeteg favágó programozással jár. Ha mindenhová, mind kliensben mind az üzleti rétegben kézzel írogatjuk a hibakódokat, akkor a hibaszázalékunk igencsak megnő, mert óhatatlanul elgépeljük valahol a kódot. Ha bevezetünk konstansokat, vagy Enumokat, akkor legalább a hibákat kiküszöböljük, de a ThisIsMyEntity.ERROR_NAME_REQ gépelésébe fogunk megőszülni.<br />
</p><p>Nekem a következő megoldás vált be: mivel 2db kettő hatványa sosem lesz egy harmadik kettő hatványával egyenlő, a hibakódot adjuk vissza kettő hatványaival. Tehát, ha hiányzik a név adjunk vissza 2-t, amennyiben a lakcím hiányzik adjunk vissza 4-et, ha pedig mindkettő hiányzik adjunk vissza 6-ot. A metódus implementációját személy szerint magába az Entitást megvalósító osztályba szoktam tenni, de ez ízlés dolga. Van aki szerint az Entitásnak csak egy <a href="http://en.wikipedia.org/wiki/Data_transfer_object">DTO</a>-nak kell lennie, és a validitás eldöntése az üzleti logika feladata. Véleményem szerint egy entitás tudja leginkább magáról eldönteni, hogy megfelelőek-e a benne foglalt adatok.<br />
<pre class="brush: java">public int validate() {
int valid = 0;
if (foo == null || foo.isEmpty()) valid |= 1 << 1;
if (bar == null) valid |= 1 << 2;
if (param == null || param.isEmpty()) valid |= 1 << 3;
return valid;
}
</pre>Mivel későbbiekben is bitműveletekkel fogunk operálni, elegánsabbnak tartottam bitműveletekkel megoldani az összeadást, természetesen dolgozhatunk "hagyományos" számokkal is.<br />
</p><p>Kliens oldalon a hibakód lekezelése pofon egyszerű. Kiválasztunk egy tetszőleges 2 hatványt (érdemes a dokumentációban is említettet választani :)), mondjuk a 4-et, és ellenőrizzük, hogy adott bit, 2^2 az 100, tehát a harmadik, egyezik-e a visszakapott érték harmadik bitjével.<br />
<pre class="brush: java;">boolean somethingIsValid = (4 & entity.validate()) == 4;
</pre>Ez a módszer sem tökéletes, hiszen a hibákat csak bővíteni lehet, egyéb változtatás esetén már meglévő klienseink fognak hibásan működni. A teljes példaprogram az alábbi:<br />
<pre class="brush: java;">public class ValidatorTest {
private static final Map<Integer, String> ERRORS = new HashMap<Integer, String>();
static {
ERRORS.put(2, "Foo is required!");
ERRORS.put(4, "Bar is required!");
ERRORS.put(8, "Param is required!");
}
public static void main(String[] args) {
List<Entity> entities = Arrays.asList(new Entity[] {
new Entity(null, null, null),
new Entity("foo", null, null),
new Entity("", null, "param")
});
for(Entity entity : entities) {
System.out.println(entity);
int valid = entity.validate();
for(Integer code : ERRORS.keySet()) {
if ((code & valid) == code) {
System.out.println(ERRORS.get(code));
}
}
}
}
static class Entity {
private String foo;
private Integer bar;
private String param;
public Entity(String foo, Integer bar, String param) {
this.foo = foo;
this.bar = bar;
this.param = param;
}
public int validate() {
int valid = 0;
if (foo == null || foo.isEmpty()) valid |= 1 << 1;
if (bar == null) valid |= 1 << 2;
if (param == null || param.isEmpty()) valid |= 1 << 3;
return valid;
}
}
}
</pre></p><p>Nagyon kíváncsi vagyok a véleményetekre, illetve, hogy nektek milyen módszer vált be ebben a témakörbe.</p>mhmxshttp://www.blogger.com/profile/06849753526791080950noreply@blogger.com3