Navigation
Blog FIDA
Conoscenza - Storie di successo - Whitepaper
newspaper Panoramica chevron_right Blog
Leuchtende Cloud-Illustration über digitale Datenströme in Blau- und Violetttönen.
SerPhoto
Blog

Veloce, snello, capace di adattarsi al cloud: Creazione nativa Java con Quarkus

Quarkus è oggi uno dei framework Java più popolari per i moderni ambienti cloud e container. Colpisce per le sue API snelle, la forte estensibilità e l'eccellente documentazione. Particolarmente interessante è la funzione di compilazione nativa, che può essere utilizzata per tradurre le applicazioni in binari compatti e ad alte prestazioni, ideali per l'uso nel cloud. In questo articolo forniamo una panoramica compatta: Spieghiamo le tecnologie sottostanti, mostriamo come integrare la compilazione nativa in un'applicazione Quarkus esistente, quali sono gli aspetti da tenere d'occhio e infine forniamo le metriche che dimostrano il potenziale di questa funzione.

Che cos'è una compilazione nativa e che cosa ci aspettiamo da essa?

Per compilazione nativa si intende un profilo di compilazione che dà luogo a un eseguibile nativo che può essere eseguito senza la Java Virtual Machine (JVM) e quindi non richiede l'installazione separata del JDK. A tale scopo, il bytecode JVM esistente viene tradotto direttamente in codice macchina utilizzando un'applicazione speciale chiamata Mandrel.

L'idea di far eseguire il codice direttamente dal sistema operativo invece di farlo compilare ogni volta dalla JVM non è del tutto nuova e viene già utilizzata dalla JVM stessa tramite la compilazione just-in-time (JIT). Non ci si può aspettare un'accelerazione delle singole routine. Tuttavia, speriamo di ottenere dei vantaggi, soprattutto in termini di avvio dell'applicazione e di consumo di memoria, due aspetti particolarmente importanti nel settore del cloud. Naturalmente, questo risultato potrebbe essere ottenuto anche utilizzando C++ o moderni linguaggi di basso livello come Rust o Go, ma da un lato i framework e le librerie dell'ambiente Java sono molto più maturi, dall'altro possiamo attingere all'ampio bacino di sviluppatori Java e dobbiamo formare meno persone.

Mandrel o GraalVM?

Nel contesto delle build native, il nome "GraalVM" è spesso usato in modo intercambiabile con "Mandrel". In realtà, si tratta di due distribuzioni dello stesso software: GraalVM utilizza come base il JDK di Oracle, mentre Mandrel si basa sull'OpenJDK. Nel seguito ci riferiremo sempre a Mandrel, poiché questa variante viene fornita, tra l'altro, con le immagini del container Quarkus. In generale, comunque, i due pacchetti sono intercambiabili, proprio come le versioni del JDK.

Come creare una build nativa?

Gli eseguibili nativi sono di solito eseguibili Linux e, poiché non è prevista la compilazione incrociata, gli utenti Linux sono avvantaggiati. Tuttavia, anche Windows è ben supportato grazie al Windows Subsystem for Linux (WSL). Tuttavia, la compilazione sul sistema host non è molto utile, poiché sono necessari molti pacchetti aggiuntivi che ingombrano il sistema, sempre che possano essere installati. Alcune configurazioni WSL in particolare, ad esempio se sono controllate da Rancher o Docker Desktop, sono progettate per funzionalità minime e non sono adatte a questo processo. È quindi meglio utilizzare la modalità container. Tuttavia, occorre fare attenzione, poiché il nome del flag necessario dipende dal fatto che il demone Docker sia indirizzato localmente o in remoto. Quest'ultimo caso si verifica in particolare con gli host Windows, poiché Docker viene eseguito in WSL o in una macchina virtuale ed è quindi considerato remoto. Con la nostra versione di Maven (3.9.6), abbiamo anche riscontrato il problema che il parametro flag -D utilizzato nella documentazione di Quarkus non andava d'accordo con il nome del flag, motivo per cui raccomandiamo di usare invece la versione lunga --define:

java code

Qual è il problema?

Si potrebbe pensare che non ci sia alcuna differenza, dato che i container dovrebbero incapsulare l'intero processo di compilazione. Ma in qualche modo il nostro codice sorgente (o il file JAR costruito nella fase intermedia) deve raggiungere il compilatore, ed è proprio qui che sta il problema: i container locali possono semplicemente includerlo tramite un volume, mentre i container remoti ricevonoi dati tramite docker cp. Tuttavia, quest'ultimo crea ulteriori livelli di versioning, che dovrebbero essere evitati per i container locali.

Immagini Docker

Native Build è anche in grado di produrre immagini Docker oltre all'eseguibile puro. È sufficiente impostare il flag quarkus.container-image.build su true. Se il progetto è stato creato con lo strumento Quarkus CLI, riceverete anche dei file Docker già pronti. Questi possono essere usati direttamente per la costruzione, eseguendo docker build -f src/main/docker/Dockerfile.native, che consente un migliore controllo sul nome dell'immagine.

E per quanto riguarda Windows?

In generale, è possibile creare eseguibili nativi di Windows, i cosiddetti file PE. Purtroppo, ci sono diversi ostacoli sulla nostra strada. Innanzitutto, è necessario Visual Studio Community 2022 con il carico di lavoro del C++, poiché i semplici strumenti di compilazione non includono un compilatore C++. Quindi è necessario eseguire il processo di compilazione in una console di ambiente fornita da VSC o attivarlotramite una chiamata batch nel file %MANDREL_HOME%/bin/native-image.cmd :

chiama "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat".

Infine, nella maggior parte dei casi, l'antivirus verrà messo a dura prova. Non gli piace che gli eseguibili vengano copiati nella cartella temporanea dell'utente ed eseguiti durante la compilazione:

Dal nostro punto di vista, questo sforzo non vale la pena, poiché Windows non ha alcuna rilevanza come piattaforma di destinazione. Linux è utilizzato soprattutto nel settore del cloud.

Cosa bisogna considerare in termini di codice?

In generale, se è possibile creare un file JAR autonomo che funzioni su un'installazione JDK vergine, è possibile trasformarlo in un eseguibile nativo. Gli artefatti che producono puro bytecode JVM sono impacchettati direttamente nel file JAR finale di Quarkus e, poiché questo viene utilizzato per la compilazione nativa, tutto ciò che è importante è già presente. Tuttavia, ci sono alcune piccole insidie.

SSL

La crittografia è diventata parte integrante del web moderno. Nessun sito web oggi può fare a meno dell'SSL, per non parlare delle API. Questo rende ancora più frustrante il fatto che l'SSL non sia attivato di default. È necessario inserire la seguente riga in applications.properties perché funzioni:

quarkus.ssl.native=true

In realtà esistono tutta una serie di estensioni di Quarkus che impostano questo flag, ma non bisogna farci affidamento.

Riflessione

A differenza della JVM, la riflessione generale non è più possibile con la compilazione nativa, poiché Mandrel rimuove i percorsi di codice inutilizzati. Purtroppo, la riflessione è molto utilizzata, ad esempio per la serializzazione e per gli ORM come Hibernate. Fortunatamente, Quarkus offre un modo per registrare le classi per la riflessione, l'annotazione @RegisterForReflection, opportunamente denominata. Questa può essere applicata alle classi corrispondenti o utilizzando il parametro targets, particolarmente utile per le classi provenienti da artefatti esterni:

reflections

Iniezione di dipendenza

L'iniezione di dipendenze non causa problemi finché avviene nel codice principale, ma se i servizi devono essere caricati da un artefatto, devono essere registrati in applications.properties:

quarkus.index-dependency.kafka-quickstart-models.group-id=en.fida

quarkus.index-dependency.kafka-quickstart-models.artifact-id=kafka-quickstart-models

Questo problema si verifica anche con le build JVM e non è un problema specifico del nativo, ma in tutti i casi produce messaggi di errore opachi che potrebbero essere erroneamente attribuiti alla build nativa.

AWS Lambda

È necessario inserire un artefatto aggiuntivo nel pom.xml per AWS Lambda:

Questo artefatto offre plugin aggiuntivi che si agganciano al processo di compilazione e rendono il risultato più gradevole per AWS Lambda. Invece di un puro eseguibile, viene creato un function.zip, che può essere caricato direttamente nel pannello di controllo AWS. È necessario creare anche una classe handler:

TestLambda.java

È inoltre necessario memorizzare il gestore in application.properties:

application.properties

quarkus.lambda.handler=test

Queste modifiche non sono specifiche della build nativa e sono necessarie anche per i lambda basati su JVM.

Cosa bisogna considerare durante l'esecuzione?

Sebbene l'eseguibile possa essere eseguito su qualsiasi Linux moderno, l'incapsulamento tramite Docker è migliore nella maggior parte dei casi. Anche il deployment è un gioco da ragazzi grazie all'immagine costruita sopra. Per AWS Lambda sono necessarie ancora alcune impostazioni. In primo luogo, il runtime deve essere cambiato in "Amazon Linux 2023", poiché si tratta di un normale eseguibile Linux e non è necessaria alcuna JVM. In secondo luogo, la variabile d'ambiente DISABLESIGNALHANDLERS deve essere impostata su true per eliminare alcune incompatibilità tra Quarkus e l'ambiente AWS Lambda Custom Runtime. Il file function.zip può quindi essere semplicemente caricato per la distribuzione.

E i vantaggi?

Come caso di prova, abbiamo creato una semplice applicazione che genera e ordina n UUID casuali ed è controllata tramite un semplice endpoint REST o un Lambda.

Il primo grande punto di forza delle build native diventa evidente fin dall'inizio: il tempo di avvio ridotto. La compilazione nativa della variante Lambda si avvia costantemente in un quarto del tempo richiesto dalla compilazione JVM. Con la variante REST, la divergenza è ancora maggiore a causa della JVM meno ottimizzata rispetto ad AWS.

Tempo di avvio

Riposo

Startzeit

AWS Lambda

Init

Ci sono anche vantaggi in termini di tempi di esecuzione, almeno finché l'applicazione è ancora "fredda". Una volta riscaldata, la JVM può tenere il passo con le build native grazie alla compilazione JIT.

Tempi di esecuzione

Riposo

Erzeugung
Sortierung

AWS Lambda

Erzeugung
Sortierung

Veniamo ora al principale svantaggio delle build native: il tempo di compilazione. A causa del funzionamento di Mandrel, il codice sorgente viene compilato due volte: prima in bytecode JVM, che viene poi compilato in bytecode nativo nel secondo passaggio. Questo allunga i tempi di compilazione a diversi minuti anche per i nostri semplici esempi. Fortunatamente, questa fase deve essere eseguita solo una volta per ogni distribuzione, il che è gestibile nella maggior parte delle situazioni.

Non mancano delle misure?

Anche il lettore più distratto dovrebbe aver notato che non tutte le misurazioni sono state effettuate per ogni caso di test. Ciò si spiega semplicemente con la grande somiglianza dei casi: i servizi testati localmente si comportano come un Lambda caldo dopo l'avvio, sia in termini di consumo di memoria che di tempo di esecuzione. La distribuzione su Kubernetes, ad esempio, non cambierebbe la situazione, poiché i servizi una volta distribuiti al di fuori dell'ambiente cloud non vengono spenti. Questo accade con i fornitori di cloud commerciali, poiché molti più utenti condividono l'hardware effettivamente esistente.

D'altra parte, i casi di test Lambda non differiscono in complessità dai casi locali durante la creazione, quindi non ci si deve aspettare sorprese nel tempo di creazione.

Conclusione

L'uso di Quarkus per le build native di Java offre notevoli vantaggi, soprattutto nei moderni ambienti cloud e container. L'esecuzione compatta e ad alte prestazioni delle applicazioni senza JVM aumenta l'efficienza, soprattutto in termini di tempi di avvio e consumo di memoria. Nonostante i tempi di creazione più lunghi e alcune sfide tecniche, i vantaggi superano gli svantaggi per molti casi d'uso, soprattutto nel cloud. Volete saperne di più sulle possibilità e i vantaggi delle build Java Native con Quarkus? Contattate FIDA per rendere i vostri progetti più efficienti e a prova di futuro.

Informazioni sull'autore

Benjamin Kleiner ist Backend-Entwickler bei der FIDA und sorgt dafür, dass im Hintergrund alles reibungslos funktioniert. Mit Expertise in Java, OpenAPI, SQL, Kafka und AWS Lambda schafft er das stabile Rückgrat moderner Webanwendungen. Seine Leidenschaft für Accessibility und gutes UX treibt ihn an, benutzerfreundliche und leistungsfähige Lösungen zu entwickeln.

Articoli correlati

Symbolbild für KI im Einsatz im HR-Management. Virtueller Bildschirm mit Symbolen aus dem Personalbereich, wie einem Lebenslauf. Eine reale Person bedient diesen Bildschirm mit einem Stift.
CV con un semplice clic - La nostra AI in uso presso un'agenzia di reclutamento

La sfida: un processo manuale e dispendioso in termini di tempo per la creazione di CV per un'importante agenzia di reclutamento. La soluzione: l'uso della nostra soluzione AI personalizzata GPT4YOU.

Per saperne di più
Team an einem Schfreibtisch mit Arbeitsmaterialien
Blog
Che cos'è la scrittura UX e come migliora l'esperienza dell'utente?

Il design è molto più che colore, forma e layout. Il contenuto è altrettanto importante, perché il design e la funzione dei testi sul web influenzano in modo significativo l'esperienza dell'utente. Il linguaggio è un elemento di design altrettanto importante: guida, indirizza, informa e, in ultima analisi, decide se le persone agiscono o abbandonano un prodotto digitale.

Per saperne di più
Kleine Spielzeugroboter vor einer Tafel
Blog
Che cos'è Machine Learning (l'apprendimento automatico) e come può essere utilizzato?

Forse conoscete la sensazione: aprite Netflix e trovate subito una serie che si adatta perfettamente ai vostri gusti. Oppure il vostro smartphone organizza le vostre foto in modo così intelligente da permettervi di trovare determinati momenti con un solo tocco. Forse starete pensando: "È davvero comodo". Ed è proprio questo il senso dell'apprendimento automatico (Machine Learning).

Per saperne di più