Groovy Concurrency January 21, 2011 13:49 about 1 year ago
I vores EJB applikation har vi flere processer for hvert klient kald der hverisær tager et sekund at udføre men vi vil gerne have optimeret den samlede procestid. Hvad kan vi gøre?
Denne post handler ikke om ægte samtidighed men mere om at lade flere processer eksekverer på samme tid. Det lyder måske som “same shit”, men det er ikke tilfældet og i denne post vil jeg tillade mig at kalde det for processkalering.
Løsningen fungere som kunstig åndedræt på et noget tilsandet system inficeret med teknisk gæld. Processerne er ikke CPU krævende men venter blot på andre systemer.
Jeg vil fokuser på at isolere enkelte elementer som jobs og eksekvere dem i en parallel proces fremfor en sekventiel række af events. Den samlede eksekverings tid vil blive konstant i forhold til den længst varende proces, plus et overhead. Når alle enkelte processer er meldt færdige join’es de og returneres samlet.
Det kan være en fordel at dele data mellem de eksekverende processer og derved reducere ressource forbrug. At dele data er også en ulempe da mange programmeringssprog ikke håndter sideefekter særligt godt. Løsningen har at gøre med låsning, isolation og idempotente data. Men det er en anden historie.
Når man eksekvere processer sekventielt stopper man hvis en proces fejler. Det kan man ikke i et parallelt univers men må vente på at alle processer er eksekveret og derefter checke hvorvidt en af dem er fejlet. Og dog, måske er det god information at fejle. I dette tilfælde er fordelen ved proces skalering er muligheden for at skalerer lineært.
Indtil videre er der ikke noget her som ekspert udvikling uden videre vil give sig i kast med. Men der var en betydningsfuld information tilstede, nemlig at dette software skal eksekveres i en j2e kontekst og i følge specifikationen skal containeren have 100 procent kontrol over alle tråde. At have kontrol betyder at alle skabte EJB beans har metoder der kan kaldes af containeren når den skal restarte, slukke eller andet. Containeren garantere kontrol
Når man skaber egne tråde har de ikke disse, hook metoder som containeren kan invokere for at sende signaler om adfærd og kan derfor ikke kontrollers. Man kunne tænke sig at at en tråd ville blive skabt med det formål at holde en ekstern resource. Når containeren bliver beordret til en action kalder den videre til de instanser den har skabt mens andre ukendte instanser ikke har en correlation til containeren og kan medføre at den “hænger” og spærret for normale container aktioner.
J2e specifikationen har et threading dogma. Der findes ikke en metode til at fork’e processer for at udføre processkalering. En klient forspørgsel til containeren vil blive udført sekventielt fra ende til anden.
Den almindelige accepterede måde at udføre processkalering i j2e regi og samtidige overholde specifikationen består af en JMS kø, en producere og mange consumers. Men jeg vil naturligvis bruge den beskidte løsning og lave mine egne tråde fra udfra en container administreret hovedtråd.
En relativ advarsel: Vi befinder os på kanten af container specifikationen og forskellige vendors har fortolket dette emne mere eller mindre seriøs og mit råd er at benytte evolutionary prototyping for at skabe konkrete beviser for de kritiske step undervejs fremfor at gætte på en container reaktion.
Kode eksemplet herunder er eksekveret med succes på Tomcat 6+ og Bea 10+. Selve koden er lavet i groovy for at udnytte groovy’s fantastiske evne til at forme sig. Til at skabe tråde bruges Java’s nye concurrent klasser. Bemærk at førende eksperter rekommendere at, ikke container baserede tråde altid skal skabes i en thredgroup der hæftes på den container-styrede hovedtråd. Det skulle give containeren bedst muligt “change” for at kunne deallokere de enkelte tråde fordi containeren skulle have adgang til threadgroups.
import java.util.concurrent.*
NORMAL = 1000; LONG = 2000; LIMIT = 500
def MAX = 100
def LOW = (MAX * 0.15) // < 5%
def HIGH = (MAX * 0.95) // > 95%
def numbers = []; (1..3).collect{ numbers << Math.random() * MAX }
/* en række tal mellem 1 og 100 hvoraf de fem procent laveste og 5 procent højeste vil fejle. */
def task = { n ->
t = {
if (n < LOW) { throw new RuntimeException("TIMEOUT LOW")}
if (n > HIGH) { throw new RuntimeException("TIMEOUT HI")}
if (n > LOW && n < HIGH) {sleep(LONG)}
sleep(NORMAL); (n+(n*0.25)); n; } // job in a closure
t as Callable // return block as a callable
}
/* Læg mærke til at der faktisk er to kode blokke: den yderste returnere en groovy closure eller
function pointer mens den inderste blok af kode returnere kodeblokken selv plus en adderet
affærd i form af et Callable interface der specificere metoden call() hvilket betyder at denne
kodeblok kan eksekveres som en selvstændigt tråd. Dette svare til en java klasse der
implementere callable interface og den tilhørende metode call(). */
/* her konverteres mellem en liste af tal til en liste af callable objekts (wrapping by task) */
def workers = []; numbers.each { n -> workers.add(task(n)) }
/* Java’s concurrent executors med en fixed antal tråde */
def executor = Executors.newFixedThreadPool(workers.size());
/* invokeall alle jobs med en max eksekverings tid */
def futures = executor.invokeAll(workers, LIMIT, TimeUnit.SECONDS);
/* en liste af futures objects med resultater, eksekvering slut eller maxtime udløbet */
/* besøg alle */
for (Future future : futures) {
try {
if (future.isCancelled()) { // cancelled pga. tidsoverløb
println "Cancelled"
} else {
println future.get() // vellykket job
}
} catch(ExecutionException e) { // exception pga. fejlet job
println "--> "+e.getMessage()
}
}
Som ved alle processer er det vigtigt med en stop betingelse. Hvis en proces ikke bliver færdig eller ikke slipper en ressource korrekt vil det ofte kunne lade gøre at parse alle tests men alligevel ikke kunne køre applikationen i produktion.
Lige meget om du laver en tråd i en container eller i en standalone applikation skal den løbe ud af ud af scope eller blive piggybacked. Proces kontrol opnås ved negativ feedback.
By Frank Vilhelmsen - 2 tags: java groovy - Add comment