ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 89
Abonament PDF

Java Fast & Light

Daniel Jecan
Technology Researcher @ Jpard Solutions
PROGRAMARE

Cu toate că multă lume s-a așteptat ca Java și JVM (Java Virtual Machine) să devină din ce în ce mai puțin populare, acestea se află încă în top (Java nu s-a aflat niciodată mai jos de locul 2 în cadrul indexului TIOBE), dar utilizarea acestora în abordările Serverless (fără server) și microservicii este în declin din cauza consumului mare de memorie și a vitezei lente de boot. Java ca limbaj de programare are o istorie destul de lungă, de mai mult de douăzeci de ani, o perioadă în care lumea software a trecut prin schimbări importante. Marea promisiune Java a fost de la bun început că ne permite să scriem cod o singură dată și să îl rulăm oriunde (platforme și sisteme de operare multiple). Containerele au apărut oferind posibilitatea de a împacheta aplicațiile, librăriile și sistemul de operare într-un singur container ce poate rula oriunde, făcând portabilitatea JVM mai puțin relevantă. Tendința actuală este de a avea aplicații rapide, reactive, cu latență scăzută ce sunt disponibile mereu. Containerele și instrumentele de orchestrare a containerelor, precum Kubernetes oferă aceste lucruri independent de limbajul de programare.

Java Fast & Light. Quarkus?

Despre Quarkus

Site-ul oficial definește Quarkus drept un framework Java cu suport nativ de Kubernetes ce este destinat cu precădere pentru JVM-uri precum OpenJDK HotSpot și GraalVM. Mai mult, este un framework full-stack ce conține librării consacrate, oferind și posibilitatea ca frameworkul să fie extins cu librării terțe. Modelul de programare Quarkus se bazează pe standarde consacrate ce pot fi standarde oficiale sau frameworkuri lider pe domenii specifice (mai multe detalii se găsesc în secțiunea dedicată avantajelor). Quarkus este open source, disponibil cu Apache Software License 2.0 sau cu orice altă licență compatibilă, fiind susținut de Red Hat, companie specializată în tehnologii open source pentru domeniul enterprise și renumită pentru optimizări JVM (e.g. primul server de aplicație care a rulat în cloud pe Red Hat OpenShift, pe un plug computer și pe Raspberry Pi). 

Istoria Quarkus

Istoria începe cu achiziția JBoss de către Red Hat în 2006. Aceasta a avut ca obiectiv accelerarea trecerii spre arhitectura orientată pe servicii (SOA), permițând aplicațiilor disponibile pe web să ruleze pe o platformă cu costuri reduse, open source. O altă etapă importantă a fost lansarea JBoss AS7 în 2011 ce a reprezentat un salt major care a avut în vedere refacerea arhitecturii frameworkului. Următorul pas a fost redenumirea JBoss AS în Wildfly (2014) ca rezultat al votului public al membrilor comunității JBoss. În 2015, Wildfly Swarm a fost introdus pentru a dezasambla WildFly AS și a reasambla suficient din acesta pentru a crea un executabil JAR self-contained, adică un fat JAR cu toate dependențele împachetate într-un fișier JAR. Acest lucru a inclus o versiune minimalistă a WildFly împreună cu dependențele obligatorii și codul aplicației. În iunie 2018, WildFly Swarm a trecut printr-un proces de rebranding, Red Hat considerând că numele nu mai avea sens. Termenul swarm avea conotații multiple, iar trecerea la o nouă arhitectură nu justifica menținerea numelui WildFly. Totuși, WildFly Swarm și Thorntail erau, de fapt, același proiect. Finalmente, după anunțul privitor la Thorntail 4.x, a existat un proiect intern la Red Hat ce a privit în mod holistic utilizarea Java în Kubernetes în contextul reducerii majore a consumului de resurse și a îmbunătățirii performanței. A fost ulterior lansat public drept proiectul Quarkus, care, dincolo de îmbunătățirea performanței și reducerea consumului de resurse, promitea o experiență de dezvoltare deosebită utilizând SmallRye pentru a implementa specificațiile Eclipse MicroProfile. 

Skinny vs Thin vs Hollow vs Fat

Există o serie de tehnici de împachetare a microserviciilor în Java, de la împachetarea tuturor serviciilor într-un singur pachet la asamblarea lor în doar o singură bucată customizată de cod. Să vedem ce presupune fiecare tehnică de împachetare.

Skinny

Conține doar codul specific implementat și nimic altceva. Sunt îndepărtate toate dependențele lăsându-se doar bucata de cod customizat. 

Thin

Se pornește de la codul customizat al unei aplicații și se adaugă dependențele directe ale aplicației. Pentru o aplicație Java EE, aceasta ar include conținutul web și logica de business alături de dependențele terțe, dar fără Java EE runtime. Aplicația nu ar putea rula de una singură.

Hollow

Este opusul lui Thin. Conține doar ce e necesar pentru ca aplicația să ruleze, dar nu conține aplicația în sine. De fapt, este un server de aplicație care poate rula aplicația, dar comparativ cu serverul tradițional, această pre-împachetare include doar dependențele necesare pentru a rula o aplicație specifică și nu mai mult de atât. 

Fat

Aici regăsim pachetul integral: codul aplicației, dependențele directe ale aplicației, dar și tot ce e necesar ca aplicația să ruleze de una singură. 

Maven și Spring Boot au făcut această tehnică de împachetare populară, ceea ce include tot ce este necesar pentru a rula toată aplicația pe un mediu standard Java Runtime (i.e. puteți rula aplicația utilizând java -jar myapp.jar). Elementele extra la runtime incluse în fat jar (și mărimea fișierului său) depind de framework și de funcționalitățile runtime pe care le utilizează aplicația.

De ce am nevoie de aceste lucruri?

Amprenta unei aplicații a devenit un factor important datorită apariției puterii computaționale de închiriat, popularității proceselor DevOps și a containerelor Linux. Lansarea pe mai multe medii de mai multe ori pe zi este un bun scenariu, unde un impact semnificativ se poate obține minimizând dimensiunea aplicațiilor. Codul customizat nu trebuie minimizat, dar reducerea numărului de repetări în care o aplicație și dependențele sale sunt transportate în rețea împreună cu reducerea efectivă a dimensiunii pachetului pot optimiza integrarea continuă, permițând economisirea de timp pentru stocare, transport și procesare a fiecărei noi versiuni de aplicație.  

Ce ar trebui să folosesc?

Totul depinde foarte mult de context. Fat JAR-urile sunt atractive datorită portabilității lor, ușurinței de execuție în IDE-uri și trăsăturii lor de tot-ce-îți-trebuie-este-JRE. Cu cât se separă mai ușor nivelurile (în skinny/thin), cu atât se economisește mai mult în ceea ce privește network/disk/CPU pe viitor. Mai trebuie menționat beneficiul de a putea face patch nivelurilor independent unele de altele, de exemplu, a distribui rapid soluții pentru repararea vulnerabilităților de securitate către toate aplicațiile ce rulează. Așadar, este o decizie ce variază de la caz la caz, luând în calcul beneficiile și compromisurile înainte de luarea unei decizii, conștienți fiind că nu există un panaceu.

Analiza comparativă a pachetelor

GraalVM și Substrate VM

GraalVM a fost creată drept o mașină virtuală universală ce poate rula aplicații scrise într-o mare varietate de limbaje precum JavaScript, Python, Ruby, R și limbaje bazate pe JVM precum Java, Scala sau Kotlin. Ceea ce este special la GraalVM este că permite compilarea programelor în prealabil (ahead-of-time - AOT) într-un executabil nativ. În acest fel codul Java ar putea fi compilat direct în cod ce poate fi citit de mașină. Fișierul binar rezultat nu rulează pe Java HotSpot VM, ci folosește componente necesare precum gestionarea memoriei și programarea de threaduri de pe o implementare diferită a unei mașini virtuale, numită Substrate VM scrisă în Java și compilată într-un fișier executabil nativ.

Când măsurăm memoria în Java, RSS (Resident Set Size) este un indice de performanță (KPI) important luând în calcul dimensiunea heap și non-heap, ce se adaugă la totalul memoriei consumate. Durata cold boot up (boot + primii timpi de răspuns) reprezintă date metrice importante când vorbim de containere rapide cu viață scurtă.

Stackul tradițional, nativ în cloud, din imagine, este un fat jar ce conține logica customizată de business, dependențele directe ale aplicației și mediul runtime Java EE care are o amprentă evidentă. O îmbunătățire semnificativă se poate observa când se folosește Quarkus + Open JDK la aproape jumătate de RSS având un timp de bootare de la zero de patru ori mai bun. Abordarea implică reducerea dinamicii (încărcarea claselor la runtime, reflecție, proxies etc.) unui runtime de tip enterprise, ceea ce înseamnă 90% din ecosistemul Java. Aplicațiile Spring boot bazate pe containere au un nivel suplimentar de abstractizare, ceea ce este complet inutil într-un mediu bazat pe containere. Aveți o aplicație Java ce rulează pe JVM într-un container ce nu se va schimba. În ziua de azi, livrabilul nu mai este aplicația, ci containerul, deci pachetul container și nu WAR-uri, de exemplu. Tot efortul de rulare a unei aplicații în JVM într-un container este inutil, deci AOT are sens dacă aplicațiile sunt împachetate în containere. Vestea bună este că majoritatea acestor lucruri se pot face la build time. Aici este locul unde Quarkus este puternic, oferind un ecosistem ce pune la dispoziție suport pentru AOT la build time, ceea ce face ca GraalVM să poată fi folosit de programatorii Java. În final, îmbunătățirea RSS și a timpilor de boot de la zero când folosim Quarkus + GraalVM reprezintă aspectul cel mai impresionant. Astfel, se profită de avantajele abordării complete a Quarkus pentru generarea executabilului specific (nativ) unui sistem de operare din codul Java ce este împachetat ca o imagine container

Procesul de build pentru Quarkus este descris mai jos prin pașii necesari scrierii aplicației pentru a se produce o imagine container.

Avantaje majore

Containerul pe primul loc - o experiență ideală pentru Kubernetes

Deoarece inițializarea unui container se desfășoară pe durata a câteva zeci de milisecunde, aceasta permite scalarea automată a microserviciilor în containere. Mai mult, execuția pe loc pentru Kubernetes, precum și FaaS (Function-as-a-service) devin posibile. Densitatea contează și ea pentru aplicațiile native în cloud. Aderarea la 12-factor app, Microservices și Kubernetes conduce la multe JVM-uri per aplicație. Deși se câștigă la elasticitate și robustețe, costul memoriei de bază utilizate per serviciu începe să se adune. Mai mult, o bucată din acel cost nu este strict necesar. Executabilele compilate static beneficiază de optimizări precum eliminarea codului inutil, unde doar elementele de framework (inclusiv JDK în sine) ce sunt folosite efectiv de serviciu sunt incluse în imaginea rezultată. Făcând aplicația accesibilă nativ, Quarkus poate fi folosit pentru a împacheta dens multe instanțe de servicii pe o gazdă (host), fără a compromite securitatea. Mai mult, un alt avantaj al folosirii imaginilor native este amprenta redusă la nivel de aplicație și imagine container

Satisfacția reală a programatorului

Nivelul de motivație și satisfacție al programatorului când se dezvoltă o aplicație este important, având un impact direct asupra performanței și a calității codului produs. În primul rând, Quarkus oferă o abordare unitară a configurării cu toate proprietățile configurării într-un singur fișier, astfel încât nu mai trebuie consultate mai multe fișiere de configurare pentru a face o schimbare. Totuși, marele avantaj pentru programatori este o reîncărcare live foarte rapidă, ceea ce elimină timpii de așteptare la reîncărcarea aplicației. Acesta este un factor foarte important în determinarea nivelului de satisfacție pentru un programator, deoarece îl menține eficient și concentrat pe ceea ce face. Ca în multe alte domenii, regula 80/20 se aplică și aici, eficiența crescută oferind cod simplificat pentru 80% din cazurile frecvente și flexibilitate pentru restul de 20%. Nu în ultimul rând, generarea executabilului nativ se face fără efort.

Platforme bine integrate ce oferă suport atât pentru stilul de programare imperativ, cât și pentru cel reactiv

Majoritatea programatorilor Java sunt familiarizați cu modelul programării imperative și ar prefera să folosească aceste cunoștințe când adoptă o nouă platformă. În același timp, programatorii adoptă din ce în ce mai mult o abordare cloud nativă, bazată pe evenimente, asincronă, și un model reactiv pentru a răspunde cerințelor de business pentru a construi aplicații responsive cu procesare paralelă. Quarkus este gândit să unifice cele două modele pe aceeași platformă, oferind flexibilitatea de a alege un model sau altul, în funcție de caz.

Fundamentat pe cele mai bune standarde și OSS

Quarkus aduce un framework full-stack, ce este ușor de utilizat, gestionând librării consacrate, legate la standarde acceptate - acestea includ Eclipse MicroProfile, JPA/Hibernate, JAX-RS/RESTEasy, Eclipse Vert.x, Netty, lista devenind din ce în ce mai mare în fiecare zi. Frameworkul poate fi extins folosind frameworkul de extensii încorporat în platformă, reducând complexitatea de a face frameworkurile terțe să ruleze pe Quarkus și să compileze rezultând un fișier binar nativ GraalVM.

Provocări și limitări

Quarkus vine din direcția în care runtime-ul de Enterprise Java conține mai mult decât este necesar pentru un container. Odată obținută imaginea container, funcționalitatea nu ar trebui să se modifice și în mod tipic, odată pornită aplicația într-un container, aceasta nu se mai modifică. Inversarea controlului (Inversion of Control - IoC) se află în centrul Enterprise Java. Programatorii specifică în mod declarativ ce ar trebui să se întâmple, iar implementarea are grijă de metodele efective de invocare. Deși acest lucru implică efort la runtime, conduce la un model de programare foarte productiv. Dacă aplicațiile nu trebuie să se modifice la runtime, cea mai mare parte a dinamicii s-ar putea rezolva la build time, ceea ce presupune că ceea ce rezultă la nivel de cod ar fi invocări directe, specifice. Totuși nu același efect a fost obținut și în trecut utilizând frameworkuri enterprise ce nu ofereau suport pentru IoC, fiind necesare invocările directe pentru a putea utiliza funcționalitatea? Răspunsul pentru programator este nu. Aceeași abordare declarativă se folosește în gestionarea acelorași adnotări, dar procesul de build se rezumă la a scoate magia părții dinamice. Acest lucru este însă și una dintre limitări.  

Timpul îndelungat pentru build, care, în funcție de specificațiile mașinii, poate dura mai mult de 30 de secunde, este unul din dezavantajele unui build nativ. Acesta poate fi chiar mai îndelungat decât ceea ce este obișnuit pentru universul Java. Prin urmare, acest lucru presupune că buildul nativ nu trebuie executat la fiecare modificare de cod. Dacă îl rulăm doar ca parte a livrării continue, trebuie luat în considerare un timp mai lung de execuție pentru întregul proces. Mergând mai departe, testarea completă a fiecărui build înainte de a ajunge în mediul de producție presupune că timpii de testare end-to-end devin mai mari. Totuși, Quarkus permite și deploymentul thin al artefactelor, ceea ce e posibil din moment ce frameworkul separă librăriile de codul customizat. Precum în cazul aplicațiilor Java Enterprise, acest lucru permite să se folosească beneficiile Docker prin optimizarea copy-on-write pentru nivelurile de imagine ale sistemelor de fișiere. Durata unui build și transmiterea imaginii containerului afectează timpul total de execuție. În acest caz, deploymentul artefactelor thin oferă cea mai bună experiență posibilă, deoarece ceea ce contează este cât de repede se face re-build și se retransmit nivelurile care se schimbă. Timpii de execuție pentru pipeline vs. timpii de start-up ai unui container reprezintă un compromis de luat în calcul pentru fiecare proiect. Deploymenturile blue-green reflectă o abordare ce ar trebui luată în calcul pentru scenarii, dincolo de scalarea la zero când se încearcă evitarea downtime pentru utilizatori. Cu o astfel de abordare, timpii de start-up din producție nu mai sunt vitali, deoarece un deployment incremental permite evitarea downtime prin folosirea versiunii vechi până când versiunea nouă este gata. Dacă livrarea de versiuni noi în producție este mai relevantă decât scalarea la zero, atunci abordarea thin deployment s-ar putea să fie mai potrivită. 

Quarkus nu oferă încă suport integral pentru setul complet de standarde EE (e.g. EJBs). Aceasta este una dintre limitări, dar o bună parte din funcționalitate este fie acoperită, fie înlocuită de ceva similar. Tranzacțiile sunt posibile, de exemplu, iar funcționalitatea de planificare este posibilă prin adnotarea \@Scheduled oferită chiar de Quarkus. Echipa Red Hat a încercat să acopere cea mai mare parte a funcționalităților de care au nevoie majoritatea proiectelor și continuă dezvoltarea frameworkului. Este impresionant cât de matur și complex a devenit deja acest framework

O altă limitare este declararea în cadrul pluginului Maven care nu ar trebuie folosită pentru a face build la tot. Proiectele ar trebui să folosească variantele implicite ale Maven, lăsând tot ceea ce este deasupra în seama infrastructurii de integrare continuă. Quarkus permite debarasarea de majoritatea definițiilor din fișierul pom.xml, astfel încât definirea și buildul imaginii native să se întâmple în cadrul integrării continue. Mai mult, CLI-ul (Command Line Interface) nativ la care se face reclamă și care va fi introdus în curând pare promițător.

Concluzii

Quarkus permite realizarea de scenarii ce nu au fost posibile înainte (e.g. Java Enterprise pentru cloud nativ și Serverless) în special cele legate de timpul necesar inițializării unei aplicații. Pentru abordările de tip scalare la zero, această tehnologie merită luată în calcul, deoarece se bazează pe tehnologii consacrate pe care le duce mai departe și pe care le împachetează într-un framework unitar. Astfel, programatorii pot folosi foarte ușor standardele enterprise cu care sunt deja familiarizați. Combinarea acestui aspect cu o implementare foarte optimizată promite o rețetă de succes. 

Dacă această abordare îi face pe oameni să se oprească și să regândească tehnologia pe care o folosesc, atunci Quarkus este benefic întregului ecosistem Java. Deși nu este perfect din punct de vedere al suportului pentru toate standardele Java EE, trebuie menționat faptul că echipa din spatele acestui framework se mișcă repede. Așadar, ne putem aștepta ca multe frameworkuri să fie suportate, astfel încât acestea să fie compilate nativ împreună cu aplicațiile customizate la cheie. 

Resurse:

Sponsori

  • comply advantage
  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects