Domanda

Durante la distribuzione di una webapp Java di grandi dimensioni (> 100 MB .war) attualmente sto usando il seguente processo di distribuzione:

  • Il file .war dell'applicazione viene espanso localmente sulla macchina di sviluppo.
  • L'applicazione espansa è rsync: ed dalla macchina di sviluppo all'ambiente live.
  • Il server app nell'ambiente live viene riavviato dopo rsync. Questo passaggio non è strettamente necessario, ma ho scoperto che il riavvio del server delle applicazioni durante la distribuzione evita " java.lang.OutOfMemoryError: PermGen space " a causa del frequente caricamento di classe.

Aspetti positivi di questo approccio:

  • rsync riduce al minimo la quantità di dati inviati dalla macchina di sviluppo all'ambiente live. Il caricamento dell'intero file .war richiede più di dieci minuti, mentre un rsync richiede un paio di secondi.

Aspetti negativi di questo approccio:

  • Mentre rsync è in esecuzione, il contesto dell'applicazione viene riavviato poiché i file vengono aggiornati. Idealmente, il riavvio dovrebbe avvenire dopo il completamento di rsync, non quando è ancora in esecuzione.
  • Il riavvio dell'app server provoca circa due minuti di inattività.

Vorrei trovare un processo di distribuzione con le seguenti proprietà:

  • Tempo di inattività minimo durante il processo di distribuzione.
  • Tempo minimo impiegato per il caricamento dei dati.
  • Se il processo di distribuzione è specifico del server delle app, il server delle app deve essere open-source.

Domanda:

  • Dati i requisiti indicati, qual è il processo di distribuzione ottimale?
È stato utile?

Soluzione

È stato notato che rsync non funziona bene quando si inviano modifiche a un file WAR. Il motivo è che i file WAR sono essenzialmente file ZIP e, per impostazione predefinita, vengono creati con file membri compressi. Piccole modifiche ai file dei membri (prima della compressione) comportano differenze su larga scala nel file ZIP, rendendo inefficace l'algoritmo di trasferimento delta di rsync.

Una possibile soluzione è utilizzare jar -0 ... per creare il file WAR originale. L'opzione -0 indica al comando jar di non comprimere i file dei membri durante la creazione del file WAR. Quindi, quando rsync confronta le versioni vecchie e nuove del file WAR, l'algoritmo di trasferimento delta dovrebbe essere in grado di creare differenze diff. Quindi organizzare che rsync invii i diff (o file originali) in forma compressa; per esempio. usa rsync -z ... o un flusso / trasporto di dati compressi sotto.

EDIT: a seconda di come è strutturato il file WAR, potrebbe anche essere necessario utilizzare jar -0 ... per creare file JAR componenti. Ciò si applicherebbe ai file JAR che sono spesso soggetti a modifiche (o che sono semplicemente ricostruiti), piuttosto che a file JAR di terze parti stabili.

In teoria, questa procedura dovrebbe fornire un miglioramento significativo rispetto all'invio di normali file WAR. In pratica non l'ho provato, quindi non posso promettere che funzionerà.

Il rovescio della medaglia è che il file WAR distribuito sarà significativamente più grande. Ciò potrebbe comportare tempi di avvio webapp più lunghi, anche se sospetto che l'effetto sarebbe marginale.


Un approccio completamente diverso sarebbe quello di esaminare il file WAR per vedere se è possibile identificare i JAR della libreria che probabilmente non cambieranno (quasi). Estrarre questi JAR dal file WAR e distribuirli separatamente nella directory common / lib del server Tomcat; per esempio. utilizzando rsync .

Altri suggerimenti

Aggiornamento:

Da quando questa risposta è stata scritta per la prima volta, è emerso un modo migliore per distribuire file di guerra su Tomcat senza tempi di inattività. Nelle versioni recenti di Tomcat è possibile includere i numeri di versione nei nomi dei file di guerra. Ad esempio, è possibile distribuire contemporaneamente i file ROOT ## 001.war e ROOT ## 002.war nello stesso contesto. Tutto ciò che segue ## viene interpretato come un numero di versione da Tomcat e non fa parte del percorso di contesto. Tomcat manterrà tutte le versioni della tua app in esecuzione e fornirà nuove richieste e sessioni alla versione più recente che è completamente attiva, completando con grazia vecchie richieste e sessioni sulla versione con cui hanno iniziato. La specifica dei numeri di versione può essere effettuata anche tramite Tomcat Manager e persino le attività di catalina. Maggiori informazioni qui .

Risposta originale:

Rsync tende a essere inefficace sui file compressi poiché l'algoritmo delta-transfer cerca le modifiche nei file e una piccola modifica in un file non compresso può alterare drasticamente la versione compressa risultante. Per questo motivo, potrebbe essere logico risincronizzare un file di guerra non compresso anziché una versione compressa, se la larghezza di banda di rete si rivela un collo di bottiglia.

Cosa c'è di sbagliato nell'utilizzo dell'applicazione Tomcat Manager per eseguire le tue distribuzioni? Se non si desidera caricare l'intero file di guerra direttamente sull'app Tomcat Manager da una posizione remota, è possibile risincronizzarlo (non compresso per i motivi sopra menzionati) in una posizione segnaposto sulla scatola di produzione, reimballarlo in una guerra e quindi consegnalo al gestore localmente. Esiste una bella attività formica fornita con Tomcat che consente di eseguire lo script delle distribuzioni utilizzando l'app Tomcat Manager.

C'è un altro difetto nel tuo approccio che non hai menzionato: mentre l'applicazione è parzialmente distribuita (durante un'operazione rsync), l'applicazione potrebbe trovarsi in uno stato incoerente in cui le interfacce modificate potrebbero non essere sincronizzate, nuovo / le dipendenze aggiornate potrebbero non essere disponibili, ecc. Inoltre, a seconda della durata del lavoro rsync, l'applicazione potrebbe effettivamente riavviarsi più volte. Sei consapevole che puoi e dovresti disattivare il comportamento di ascolto di file modificati e riavvio in Tomcat? In realtà non è raccomandato per i sistemi di produzione. Puoi sempre eseguire un riavvio manuale o tramite script della form tramite l'applicazione Tomcat Manager.

La tua applicazione non sarà disponibile per gli utenti durante il riavvio, ovviamente. Ma se sei così preoccupato per la disponibilità, hai sicuramente server web ridondanti dietro un bilanciamento del carico. Quando si distribuisce un file di guerra aggiornato, è possibile che temporaneamente il bilanciamento del carico invii tutte le richieste ad altri server Web fino al termine della distribuzione. Sciacquare e ripetere per gli altri server Web.

In qualsiasi ambiente in cui i tempi di inattività sono considerati, si sta sicuramente eseguendo una sorta di cluster di server per aumentare l'affidabilità tramite ridondanza. Prenderei un host dal cluster, lo aggiornerei e poi lo restituirei al cluster. Se si dispone di un aggiornamento che non può essere eseguito in un ambiente misto (ad esempio, è necessario modificare lo schema incompatibile sul database), è necessario rimuovere l'intero sito, almeno per un momento. Il trucco è di avviare i processi di sostituzione prima di far cadere gli originali.

Utilizzo di tomcat come esempio: è possibile utilizzare CATALINA_BASE per definire una directory in cui verranno trovate tutte le directory di lavoro di Tomcat, separate dal codice eseguibile. Ogni volta che distribuisco software, eseguo la distribuzione in una nuova directory di base in modo da poter avere un nuovo codice residente sul disco accanto al vecchio codice. Posso quindi avviare un'altra istanza di Tomcat che punta alla nuova directory di base, avviare tutto e avviare, quindi scambiare il vecchio processo (numero di porta) con quello nuovo nel bilanciamento del carico.

Se sono preoccupato di preservare i dati della sessione attraverso lo switch, posso configurare il mio sistema in modo tale che ogni host abbia un partner sul quale replica i dati della sessione. Posso eliminare uno di questi host, aggiornarlo, ripristinarlo in modo che raccolga i dati della sessione e quindi scambiare i due host. Se ho più coppie nel cluster, posso eliminare la metà di tutte le coppie, quindi fare un interruttore di massa o posso farle una coppia alla volta, a seconda dei requisiti del rilascio, dei requisiti dell'azienda, ecc. Personalmente, tuttavia, preferisco consentire agli utenti finali di subire la perdita occasionale di una sessione attiva piuttosto che affrontare il tentativo di aggiornare con sessioni intatte.

È tutto un compromesso tra l'infrastruttura IT, la complessità del processo di rilascio e lo sforzo degli sviluppatori. Se il tuo cluster è abbastanza grande e il tuo desiderio abbastanza forte, è abbastanza facile progettare un sistema che può essere sostituito senza tempi di inattività per la maggior parte degli aggiornamenti. Le grandi modifiche dello schema spesso costringono i tempi di inattività effettivi, poiché il software aggiornato di solito non è in grado di adattarsi al vecchio schema e probabilmente non è possibile cavarsela copiando i dati in una nuova istanza db, facendo l'aggiornamento dello schema e quindi passando i server al nuovo db, poiché avrai perso tutti i dati scritti sul vecchio dopo che il nuovo db è stato clonato da esso. Naturalmente, se si dispone di risorse, è possibile incaricare gli sviluppatori di modificare la nuova app in modo da utilizzare i nomi di nuove tabelle per tutte le tabelle aggiornate e è possibile attivare i trigger sul db live che aggiornerà correttamente le nuove tabelle con i dati come è scritto nelle vecchie tabelle dalla versione precedente (o forse usa le viste per emulare uno schema dall'altro). Visualizza i nuovi server delle app e scambiali nel cluster. Ci sono molti giochi a cui puoi giocare per ridurre al minimo i tempi di fermo se hai le risorse di sviluppo per costruirli.

Forse il meccanismo più utile per ridurre i tempi di inattività durante gli aggiornamenti del software è assicurarsi che l'app possa funzionare in modalità di sola lettura. Ciò fornirà alcune funzionalità necessarie ai tuoi utenti ma ti consentirà di apportare modifiche a livello di sistema che richiedono modifiche al database e simili. Posizionare l'app in modalità di sola lettura, quindi clonare i dati, aggiornare lo schema, visualizzare i nuovi server delle app rispetto al nuovo db, quindi cambiare il bilanciamento del carico per utilizzare i nuovi server delle app. L'unico tempo di inattività è il tempo richiesto per passare alla modalità di sola lettura e il tempo necessario per modificare la configurazione del bilanciamento del carico (la maggior parte dei quali può gestirlo senza alcun tempo di inattività).

Il mio consiglio è di usare rsync con versioni esplose ma di distribuire un file di guerra.

  1. Crea una cartella temporanea nell'ambiente live in cui avrai esploso la versione di webapp.
  2. Rsync versioni esplose.
  3. Dopo aver completato con successo rsync, creare un file di guerra in una cartella temporanea nella macchina dell'ambiente live.
  4. Sostituisci la vecchia guerra nella directory di distribuzione del server con una nuova dalla cartella temporanea.

La sostituzione della vecchia guerra con una nuova è raccomandata nel contenitore JBoss (che si basa su Tomcat) perché è un'operazione atomica e veloce ed è sicuro che quando lo schieratore inizierà l'intera applicazione sarà nello stato distribuito.

Non è possibile creare una copia locale dell'applicazione Web corrente sul server Web, eseguire la sincronizzazione su quella directory e quindi forse anche utilizzando i collegamenti simbolici, in un "andare", indicare Tomcat a una nuova distribuzione senza tempi di inattività?

Il tuo approccio per risincronizzare la guerra estratta è abbastanza buono, anche il riavvio poiché credo che un server di produzione non dovrebbe avere la distribuzione a caldo abilitata. Quindi, l'unico aspetto negativo è il downtime quando è necessario riavviare il server, giusto?

Suppongo che tutto lo stato della tua applicazione sia conservato nel database, quindi non hai problemi con alcuni utenti che lavorano su un'istanza del server delle app mentre altri utenti si trovano su un'altra istanza del server delle app. In tal caso,

Esegui due server di app : avvia il secondo server di app (che è in ascolto su altre porte TCP) e distribuisci l'applicazione lì. Dopo la distribuzione, aggiorna la configurazione di Apache httpd (mod_jk o mod_proxy) in modo che punti al secondo server delle app. Riavviare con grazia il processo httpd di Apache. In questo modo non avrai tempi di inattività e i nuovi utenti e le richieste verranno automaticamente reindirizzati al nuovo server delle app.

Se è possibile utilizzare il clustering del server delle app e il supporto della replica delle sessioni, sarà uniforme anche per gli utenti che sono attualmente connessi, poiché il secondo server delle app si risincronizzerà non appena si avvia. Quindi, quando non ci sono accessi al primo server, spegnilo.

Questo dipende dall'architettura dell'applicazione.

Una delle mie applicazioni si trova dietro un proxy di bilanciamento del carico, dove eseguo una distribuzione scaglionata, eliminando efficacemente i tempi di inattività.

Se i file statici sono una parte importante del tuo grande WAR (100Mo è piuttosto grande), metterli al di fuori del WAR e distribuirli su un server Web (ad esempio Apache) davanti al tuo server delle applicazioni potrebbe accelerare le cose. Inoltre, Apache di solito fa un lavoro migliore nel servire file statici rispetto a un motore servlet (anche se la maggior parte di loro ha fatto progressi significativi in ??quell'area).

Quindi, invece di produrre una grossa GUERRA grassa, mettila nella dieta e produci:

  • un grosso ZIP con file statici per Apache
  • una GUERRA meno grassa per il motore servlet.

Opzionalmente, andare oltre nel processo di assottigliamento di WAR: se possibile, distribuire Grails e altri JAR che non cambiano frequentemente (che è probabilmente il caso della maggior parte di essi) a livello del server delle applicazioni.

Se riesci a produrre una WAR più leggera, non mi preoccuperei di riorganizzare le directory piuttosto che gli archivi.

Punti di forza di questo approccio:

  1. I file statici possono essere hot " distribuiti " su Apache (ad esempio utilizzare un collegamento simbolico che punta sulla directory corrente, decomprimere i nuovi file, aggiornare il collegamento simbolico e voil & # 224;).
  2. La GUERRA sarà più sottile e ci vorrà meno tempo per dispiegarla.

Debolezza di questo approccio:

  1. C'è un altro server (il web server), quindi questo aggiunge (un po ') più complessità.
  2. Dovrai cambiare gli script di build (non un grosso IMO).
  3. Dovrai cambiare la logica rsync.

Non sono sicuro che questo risponda alla tua domanda, ma condividerò semplicemente il processo di distribuzione che utilizzo o incontro nei pochi progetti che ho fatto.

Simile a te, non ricordo di aver mai effettuato una riassegnazione o un aggiornamento completo della guerra. Il più delle volte, i miei aggiornamenti sono limitati a pochi file jsp, forse una libreria, alcuni file di classe. Sono in grado di gestire e determinare quali sono gli artefatti interessati e, di solito, abbiamo impacchettato questi aggiornamenti in un file zip, insieme a uno script di aggiornamento. Eseguirò lo script di aggiornamento. Lo script procede come segue:

  • Effettua il backup dei file che verranno sovrascritti, magari in una cartella con la data e l'ora di oggi.
  • Decomprimi i miei file
  • Arresta il server delle applicazioni
  • Sposta i file sopra
  • Avvia il server delle applicazioni

Se i tempi di inattività sono un problema, e in genere lo sono, i miei progetti sono in genere HA, anche se non condividono lo stato ma utilizzano un router che fornisce il routing di sessione appiccicoso.

Un'altra cosa che sono curioso sarebbe, perché la necessità di risincronizzare? Dovresti essere in grado di sapere quali sono le modifiche richieste, determinandole nel tuo ambiente di gestione temporanea / sviluppo, non eseguendo controlli delta con live. Nella maggior parte dei casi, dovresti ottimizzare il tuo rsync per ignorare comunque i file, come alcuni file di proprietà che definiscono le risorse utilizzate da un server di produzione, come la connessione al database, il server smtp, ecc.

Spero che sia utile.

A che cosa è impostato PermSpace? Mi aspetterei di vedere crescere anche questo, ma dovrebbe scendere dopo la raccolta delle vecchie classi? (o ClassLoader si siede ancora?)

Pensando ad alta voce, potresti risincronizzare in una directory separata con nome versione o data. Se il contenitore supporta collegamenti simbolici, è possibile SIGSTOP il processo di root, passare alla radice del filesystem del contesto tramite collegamento simbolico e quindi SIGCONT?

Per quanto riguarda il riavvio del contesto iniziale. Tutti i contenitori hanno opzioni di configurazione per disabilitare la ridistribuzione automatica su file di classe o modifiche alle risorse statiche. Probabilmente non è possibile disabilitare le ridisposizioni automatiche sulle modifiche di web.xml, quindi questo file è l'ultimo da aggiornare. Quindi, se disabiliti per ridistribuire automaticamente e aggiornare web.xml come ultimo vedrai il contesto riavviare dopo l'intero aggiornamento.

Carichiamo la nuova versione della webapp in una directory separata, quindi spostiamo per scambiarla con quella in esecuzione, oppure utilizziamo i collegamenti simbolici. Ad esempio, abbiamo un link simbolico nella directory di tomcat webapps denominato "myapp", che punta all'attuale webapp denominata "myapp-1.23". Carichiamo la nuova webapp su "myapp-1.24". Quando tutto è pronto, arrestare il server, rimuovere il collegamento simbolico e crearne uno nuovo che punta alla nuova versione, quindi riavviare il server.

Disabilitiamo il ricaricamento automatico sui server di produzione per le prestazioni, ma nonostante ciò, la modifica dei file all'interno della webapp in modo non atomico può causare problemi, poiché i file statici o persino le pagine JSP potrebbero cambiare in modo da causare collegamenti interrotti o peggio.

In pratica, le webapp si trovano effettivamente su un dispositivo di archiviazione condiviso, quindi i server cluster, con bilanciamento del carico e di failover hanno tutti lo stesso codice disponibile.

Lo svantaggio principale della tua situazione è che il caricamento richiederà più tempo, poiché il tuo metodo consente a rsync di trasferire solo file modificati o aggiunti. Potresti prima copiare la vecchia cartella webapp in quella nuova e sincronizzarla con essa, se fa una differenza significativa e se è davvero un problema.

Tomcat 7 ha una bella funzione chiamata " distribuzione parallela " progettato per questo caso d'uso.

L'essenza è che si espanda il .war in una directory, direttamente in webapps / o con collegamento simbolico. Le versioni successive dell'applicazione si trovano nelle directory denominate app ## version , ad esempio myapp ## 001 e myapp ## 002 . Tomcat gestirà le sessioni esistenti andando alla versione precedente e le nuove sessioni andando alla nuova versione.

Il problema è che devi essere molto attento alle perdite di PermGen. Ciò è particolarmente vero con Grails che utilizza molto PermGen. VisualVM è tuo amico.

Usa solo 2 o più server Tomcat con un proxy su di esso. Tale proxy può essere di apache / nignix / haproxy.

Ora in ciascuno dei server proxy è presente " in " e "fuori" l'URL con le porte è configurato.

Prima copia la tua guerra nel Tomcat senza interrompere il servizio. Una volta schierata la guerra, questa viene automaticamente aperta dal motore Tomcat.

Nota controllo incrociato unpackWARs = " true " e autoDeploy = " true " nel nodo " Host " all'interno server.xml

Sembra così

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true"
        xmlValidation="false" xmlNamespaceAware="false">

Ora vedi i registri di Tomcat. Se non ci sono errori significa che è andato a buon fine.

Ora tocca tutte le API per i test

Ora vieni sul tuo server proxy.

Cambia semplicemente la mappatura dell'URL di sfondo con il nome della nuova guerra. Poiché la registrazione con i server proxy come apache / nignix / haProxy ha richiesto molto meno tempo, si avvertiranno tempi di inattività minimi

Refer - https://developers.google.com/speed/pagespeed/module / domains per mappare gli URL

Stai usando Resin, Resin ha integrato il supporto per il versioning delle app web.

http://www.caucho.com/resin-4.0/ admin / deploy.xtp # VersioningandGracefulUpgrades

Aggiornamento: il processo di watchdog può aiutare anche con problemi di permgenspace.

Non è una "best practice" ma qualcosa a cui ho appena pensato.

Che ne dici di distribuire la webapp attraverso un DVCS come git?

In questo modo puoi far capire a git quali file trasferire sul server. Hai anche un buon modo per tirartene indietro se si rivela essere rotto, fai solo un ripristino!

Ho scritto uno script bash che accetta alcuni parametri e risincronizza il file tra i server. Accelera molto il trasferimento rsync per archivi più grandi:

https://gist.github.com/3985742

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top