A következő címkéjű bejegyzések mutatása: Async. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: Async. Összes bejegyzés megjelenítése

2016. október 19., szerda

Go 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.

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 előadását 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.

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ó cikket. 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 sync, ami szinkronizációs problémákra nyújt megoldást, illetve a sync/atomic, 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.

Kitaláltam egy egyszerű kis feladatot, amin keresztűl bemutatok pár fontos eszközt működés közben.
  • Legyen egy szál típus, ami 1000 véletlen számot generál, és a legnagyobbat elküldi feldolgozásra.
  • 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.
  • 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.
  • 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.
  • Mérjük a végrehajtás idejét a generálás kezdetétől.
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.

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 sync.RWMutex-et választottam.

A következő kódblockban implementálásra kerül a statisztikázó egység.

Majd megírjuk a feldolgozó egységet.

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.

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 sync.WaitGroup-ot.

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 sync.Once-ot választottam, ami garantálja ezt a működést.

Nincs más dolgunk mint megvárni és kiírni az eredményt.

A teljes forráskód elérhető itt.

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.
go build -race
Ez után futtatva valami hasonló kimenetet kapunk:
==================
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)
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.

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 Concurrency is not parallelism és a Go concurrency patterns előadásokat.

2014. július 30., szerda

Groovy funkcionális eszközök

A Groovy sorozatot folytatva (1, 2) 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 (Closure) í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.
  • Először nézzük a Closure összefűzést:
    def m = { "${it}-" }
    def p = { "${it}+" }
    def pm = m << p
    
    println pm('') == m(p('')) // true
    
    Természetesen a másik irányba is működik a dolog:
    def mp = m >> p
    println p(m('')) == mp('') // true
    
  • 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:
    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
    
  • 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":
    class o {
        void f() {
            println 'called'
        }
    }
    
    def c = o.&f // vagy new o().&f
    
    println c.class // class org.codehaus.groovy.runtime.MethodClosure
    
  • 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:
    def a = { print "called" }.memoize() // vagy memoizeBetween(int protectedCacheSize, int maxCacheSize)
    a();a() // called
    
    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
    
    Meglévő metódusainkat pedig a @Memorized 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.
  • 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 StackOverflowErrorhoz 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:
    def s
    s = { l, c = 0 ->
        l.size() == 0 ? c : s(l.tail(), ++c)
    }.trampoline()
    
    println s(1..10) // 10
    
    A Closure.trampoline() metódus mintájára az @TailRecursive annotációt használhatjuk, a dokumentációban szereplő megkötésekkel.
  • A funkcionális nyelvek általában az un. lazy evaluation 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.
    def l = [].withDefault { 45 }
    println l[3] // 45
    println l // [null, null, null, 45]
    
    Vagy a @Lazy annotációval tetszőleges adattagot varázsolhatunk lusta kiértékelésűre. Egy opcionális paraméterével akár puha referenciában is tárolhatjuk az értéket, természetesen az alapértelmezett működés szerint erős referenciát alkalmaz:
    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>
    
    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 Initialization on demand holder mintát követi a fordító.
A beépített funkciók után térjünk át a haladó technikákra. Bár a Groovynak szoros értelemben nem része a GPars keretrendszer, mégis érdemes kicsit közelebbről megismerkedni vele. A dokumentációból idézve:

"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."

  • Meglévő Closurejainkat könnyedén aszinkron hívásokká alakíthatjuk a GParsExecutorsPool segítségével, ahogy a példa is mutatja.
  • Collectionök párhuzamos feldolgozására a GParsPoolt tudjuk segítségül hívni. A GParsPool osztály ParallelArray alapon végzi a műveleteket, míg párja a GParsExecutorsPool hagyományos thread poolokat használja.
  • A GPars része egy a Java Fork/Join 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 dokumentációban találhatók.
  • A Dataflow egy alternatív párhuzamos feldolgozási szemlélet. 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 Dead és Live Lock sem többek között. Megkötés, hogy csak egyszer adhatunk értéket a DataflowVariable életciklusa során.
  • Az Agentek szál-biztos, nem blokkoló párhuzamosítást tesznek lehetővé, és ehhez annyit kell tennünk, hogy az osztályunkat a groovyx.gpars.agent.Agentből származtatjuk. Fontos különbség a Dataflow modellhez képest, hogy az Agent tartalma tetszőlegesen változtatható.
  • Természetesen elmaradhatatlan kellék a méltán népszerű Actor 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.
Ahogy a Barátkozás a Groovyval 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:
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.

2014. január 18., szombat

Ping-conf Day 2

A második nap a Ping-conferencián hozta az első nap színvonalát. Yevgeniy Brikman izgalmas demóval egybekötött előadása 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 LinkedInnél több mint 60 projekthez nagy sikerrel. Megnevezett két problémás területet tapasztalataik alapján:
  • Nehezen kezelhető komplexitás (szerintem ez nem feltétlen csak Playben jött volna elő az ő esetükben)
  • Borzalmas teljesítmény
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).
Ezt a fragmentációs technikát választva az alábbiak igényelnek külön odafigyelést:
  • Cookiek esetén szükségük van egy metódusra, ami a darabkák headerjeiből összefűzte a Cookie bejegyzéseket
  • 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)
  • Hibakezeléshez a legfelső réteg kiolvassa a darabkák státsz-kódját
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 Enumerator, Future párossal megoldotta, hogy a szerver az előállított HTMLt azonnal küldte is a kliensnek. Akit érdekel részletesebben a dolog itt 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ő githubon.

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.
  • Az első javaslata az volt, hogy használjuk a Scala beépített ExecutionContextjét, ami a Java Fork/Join osztályaira épül. Bővebben
  • Második lehetőségként azt ajánlotta, hogy ne használjuk az első pontban említett contextet, hanem váltsunk ImmediateExecutionContextre
Á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 dispatcher per type"). Mivel az Akka tud ilyet, és a Playben van beépített Akka, adja magát, hogy azt érdemes használni.
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ő.


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 Scalaz-Stream 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 HTTP, és a WebSocket eltérősége miatt, stb.


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:
  • Mert Scala, ezt nem is fejtették ki bővebben
  • Full stack web-framework
  • Reaktív, könnyű benne non blocking szolgáltatásokat készíteni és hívni
  • Typed safe template rendszer eléggé awesome
  • Sok vállalat használja, és zizeg az egész
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.


A sponsor pitch után következő előadás igazán érdekes volt. Grant Klopper a The Guardian 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 githubon, 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.


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:

A konferencia Johan Andrén előadásával 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.
Az első, használjuk bátran a Future és Promise osztályokat (részletes információt itt 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:
  • Amikor más szolgáltatásokkal kommunikálunk
  • Párhuzamos végrehajtásra, amikor a szálak teljesen elkülöníthetők egymástól
  • Egyszerű háttérszolgáltatások implementálásakor
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.
Következő lehetőségként az Akka Actorsokat 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:
  • Amikor állapotokra van szükségünk
  • Adat streamelés
  • Esemény vezérelt programozásra
  • Háttérfolyamatok végzésére
Utolsó lehetősségként a Iteratee-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.
  • Enumerator[A] -> Iteratee[B, R]
  • Enumerator[A] -> Enumeratee[A, B] -> Iteratee[C, R]
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.
Mikor használjuk:
  • Streamel adatok esetén

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.