2016-04-04 39 views
14

Ho un JAR grasso/uber generato dal plug-in Gradle Shadow. Spesso ho bisogno di inviare il grasso JAR su rete e quindi, per me è conveniente inviare solo delta del file invece di 40 MB di dati. rsync è un ottimo strumento per questo scopo. Tuttavia, un piccolo cambiamento nel mio codice sorgente porta a un grande cambiamento nel JAR finale di grasso e di conseguenza rsync non sta aiutando il più possibile.Come convertire jar in jar rsyncable?

Posso convertire il JAR grasso in JAR rsync-friendly?

Le mie idee di una soluzione/soluzioni:

  • mettere il peso pesante su rsync e dire in qualche modo che funziona con un file compresso (non ho trovato alcun modo per farlo).
  • Convertire vaso non rsyncable a rsyncable vaso
  • Dillo Gradle Ombra di generare vaso rsyncable (not possible at the moment)

domande Forse Related:

+0

Commento in caso qualcuno risponda.Devo saperlo anche io. –

+0

È un'opzione per inviare il JAR decompresso con rsync e comprimerlo nuovamente sul dispositivo remoto? In questo modo rsync dovrebbe essere in grado di avere poco traffico. –

+0

Beh, è ​​un'opzione. Preferirei preparare tutto sulla macchina sorgente però. Penso che questa soluzione richiederebbe anche molte operazioni inutili sul disco di I/O. –

risposta

1

ho sostituito il mio codice di configurazione originale in build.gradle:

shadowJar { 
    zip64 true 
    entryCompression = org.gradle.api.tasks.bundling.ZipEntryCompression.STORED 
    exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA' 
    manifest { 
     attributes 'Main-Class': 'com.my.project.Main' 
    } 
} 

con

jar { 
    manifest { 
     attributes(
       'Main-Class': 'com.my.project.Main', 
     ) 
    } 
} 

task fatJar(type: Jar) { 
    manifest.from jar.manifest 
    classifier = 'all' 
    from { 
     configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } 
    } { 
     exclude "META-INF/*.SF" 
     exclude "META-INF/*.DSA" 
     exclude "META-INF/*.RSA" 
    } 
    with jar 
} 

(Utilizzando la soluzione postato qui https://stackoverflow.com/a/31426413/99256)

La fatJar finale è molto più grande (vale a dire 56 MB) di quello che il plugin Shadow ha prodotto per me (es. 35 MB). Tuttavia, il vaso finale sembra essere rsyncable (quando faccio un piccolo cambiamento nel mio codice sorgente, rsync trasferisce solo una piccola quantità di dati).

Si noti che ho una conoscenza molto limitata di Gradle quindi questa è solo la mia osservazione e potrebbe essere possibile migliorarla ulteriormente.

+0

Ha disattivato la compressione che è ciò che ho fatto nella mia risposta. su questo ... org.gradle.api.tasks.bundling.ZipEntryCompression.STORED qui https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/bundling/ZipEntryCompression.html#STORED – Harry

+0

@ Harry Se integri la mia risposta (una soluzione per gradle) alla tua risposta (per renderla una risposta completa dal mio punto di vista), ti ricompenserò volentieri la taglia in quanto mi piace la tua risposta in generale. –

+0

L'ho appena aggiornato. Questo aiuta. Se vuoi altre modifiche alla risposta fammelo sapere. Come nota a margine se sei interessato alla compressione, dai uno sguardo al premio hutter http://prize.hutter1.net/. Ho perso me stesso per diversi mesi scherzando con la compressione quando l'ho trovato. – Harry

2

Per quanto ne so, gzip rsyncable funziona resettando l'albero di Huffman e riempendo i limiti dei byte ogni 8192 byte di dati compressi. Ciò evita l'effetto collaterale a lungo raggio sulla compressione (rsync si occupa dei blocchi di dati spostati se sono almeno allineati)

In questo senso, un jar contenente piccoli file (meno di 8192 byte) è già sincronizzabile, poiché ciascuno il file viene compresso separatamente. Come test potresti usare l'opzione -0 di jar (senza compressione) per controllare se aiuta rsync, ma penso che non lo farà.

Per migliorare la rsyncability è necessario (almeno):

  • Assicurarsi che i file sono memorizzati nello stesso ordine.
  • Assicurarsi che anche i metadati associati ai file non modificati siano immutati, poiché ogni file ha un'intestazione di file locale. Ad esempio, l'ultima modifica è problematica per i file .class.
    Non sono sicuro per jar, ma zip consente campi aggiuntivi, alcuni dei quali potrebbero impedire corrispondenze rsync, ad es. l'ultimo tempo di accesso per l'estensione unix.

Edit: ho fatto alcuni test con i seguenti comandi:

FILENAME=SomeJar.jar 

rm -rf tempdir 
mkdir tempdir 

unzip ${FILENAME} -d tempdir/ 

cd tempdir 

# set the timestamp to 2000-01-01 00:00 
find . -print0 | xargs --null touch -t 200001010000 

# normalize file mode bits, maybe not necessary 
chmod -R u=rwX,go=rX . 

# sort and zip files, without extra 
find . -type f -print | sort | zip ../${FILENAME}_normalized -X [email protected] 

cd .. 
rm -rf tempdir 

statistiche rsync quando il primo file contenuto nel vaso/zip viene rimosso:

total: matches=1973 hash_hits=13362 false_alarms=0 data=357859 
sent 365,918 bytes received 12,919 bytes 252,558.00 bytes/sec 
total size is 4,572,187 speedup is 12.07 

quando il primo file viene rimosso e ogni timestamp viene modificato:

total: matches=334 hash_hits=124326 false_alarms=4 data=3858763 
sent 3,861,473 bytes received 12,919 bytes 7,748,784.00 bytes/sec 
total size is 4,572,187 speedup is 1.18 

Quindi c'è una differenza significativa, ma non tanto quanto mi aspettassi.

Sembra anche che cambiare la modalità di file non influisce il transfert

+0

Grazie. Ho una conoscenza di base di come rsyncable Sfortunatamente, non sto rispondendo alla mia domanda perché non dici come posso fare quello che proponi. Apprezzo il tuo input –

+0

La soluzione più semplice che riesco a pensare è decomprimere il barattolo, cambiare i timestamp e il repack è ordinato, dipende da quale sistema operativo si usa, ad esempio per Linux, sarebbe basato su 'unzip',' touch' e 'zip'. Non è difficile, ma trovo un po 'strano che non ci sia uno strumento costruito che già fa che – bwt

+0

@btw: sto usando Linux, puoi per favore mostrare un esempio funzionante del tuo approccio? –

1

Facciamo un passo indietro (forse perché è archiviato nella directory centrale?); se non si creano vasetti grandi, questo cessa di essere un problema.

Quindi, se si distribuiscono i barattoli di dipendenza separatamente e non li si inserisce in un singolo contenitore di grassi, è stato risolto anche il problema.

Per fare questo, diciamo che avete:

  • /foo/yourapp.jar
  • /foo/lib/guava.jar
  • /foo/lib/h2.jar

Poi, mettere nel file META-INF/MANIFEST.MF del yourapp.jar la seguente voce:

Class-Path: lib/guava.jar lib/h2.jar 

E ora puoi semplicemente eseguire java -jar yourapp.jar e funzionerà, recuperando le dipendenze. Ora puoi trasferire questi file individualmente con rsync; yourapp.jar sarà molto più piccolo, e i tuoi jar di dipendenza di solito non sono stati modificati, quindi quelli non impiegheranno molto tempo durante la rsyncing di entrambi.

Sono consapevole che questo non risponde direttamente alla domanda effettiva, ma scommetto nel 90% + dei tempi in cui viene visualizzata questa domanda, non fatjarring è la risposta appropriata.

NB: Ant, Maven, Guava, ecc. Possono occuparsi di inserire la corretta voce manifest in. Se l'intento del jar non è quello di eseguirlo, ma, ad esempio, è una guerra per un contenitore di web servlet , quelli hanno le loro regole su come specificare dove vivono i tuoi vasi di dipendenza.

3

Ci sono due modi per fare ciò entrambi comportano la disattivazione della compressione. Gradle prima poi spegnerlo con il metodo del vaso ...

È possibile farlo usando Gradle (questa risposta in realtà è venuto dalla OP)

shadowJar { 
    zip64 true 
    entryCompression = org.gradle.api.tasks.bundling.ZipEntryCompression.STORED 
    exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA' 
    manifest { 
     attributes 'Main-Class': 'com.my.project.Main' 
    } 
} 

con

jar { 
    manifest { 
     attributes(
       'Main-Class': 'com.my.project.Main', 
     ) 
    } 
} 

task fatJar(type: Jar) { 
    manifest.from jar.manifest 
    classifier = 'all' 
    from { 
     configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } 
    } { 
     exclude "META-INF/*.SF" 
     exclude "META-INF/*.DSA" 
     exclude "META-INF/*.RSA" 
    } 
    with jar 
} 

La cosa fondamentale qui è che la compressione è stato spento cioè

org.gradle.api.tasks.bundling.ZipEntryCompression.STORED 

è possibile trovare la documentazione qui

https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/bundling/ZipEntryCompression.html#STORED

Sì, è possibile accelerarlo di circa il 40% su un nuovo archivio e di oltre il 200% su un vaso archiviare hai già rsync'd. Il trucco è quello di non comprimere il barattolo in modo che tu possa sfruttare l'algoritmo di chunking di rsyncs.

ho usato i seguenti comandi per comprimere una directory con un sacco di file di classe ...

jar cf0 uncompressed.jar . 
jar cf compressed.jar . 

Questo ha creato i seguenti due vasi ...

-rw-r--r-- 1 rsync jar 28331212 Apr 13 14:11 ./compressed.jar 
-rw-r--r-- 1 rsync jar 38746054 Apr 13 14:10 ./uncompressed.jar 

Si noti che la dimensione del vaso non compresso è di circa 10 MB più grande.

Quindi ho eseguito il rsync di questi file e li ho cronometrati utilizzando i seguenti comandi. (Nota, anche l'attivazione della compressione per il file compresso ha avuto scarso effetto, spiegherò più avanti).

Jar compressa

time rsync -av -e ssh compressed.jar [email protected]:/tmp/ 

building file list ... done 
compressed.jar 

sent 28334806 bytes received 42 bytes 2982615.58 bytes/sec 
total size is 28331212 speedup is 1.00 

real 0m9.208s 
user 0m0.248s 
sys 0m0.483s 

Jar non compresso

time rsync -avz -e ssh uncompressed.jar [email protected]:/tmp/ 

building file list ... done 
uncompressed.jar 

sent 11751973 bytes received 42 bytes 2136730.00 bytes/sec 
total size is 38746054 speedup is 3.30 

real 0m5.145s 
user 0m1.444s 
sys 0m0.219s 

Abbiamo acquisito un aumento di velocità di quasi il 50%. Questo almeno accelera il rsync e otteniamo una buona spinta ma per quanto riguarda i successivi rsyncs in cui una piccola modifica è stata effettuata .

ho rimosso un file di classe dalla directory che è stato di 170 byte di dimensione ricreato i vasi falciare sono queste dimensioni ..

-rw-r--r-- 1 rsycn jar 28330943 Apr 13 14:30 compressed.jar 
-rw-r--r-- 1 rsync jar 38745784 Apr 13 14:30 uncompressed.jar 

Ora i tempi sono molto diversi.

Jar compressa

building file list ... done 
compressed.jar 

sent 12166657 bytes received 31998 bytes 2217937.27 bytes/sec 
total size is 28330943 speedup is 2.32 

real 0m5.435s 
user 0m0.378s 
sys 0m0.335s 

Jar non compresso

building file list ... done 
uncompressed.jar 

sent 220163 bytes received 43624 bytes 175858.00 bytes/sec 
total size is 38745784 speedup is 146.88 

real 0m1.533s 
user 0m0.363s 
sys 0m0.047s 

in modo che possiamo accelerare rsyncing grande vaso file molto con questo metodo. La ragione di ciò è legata alla teoria dell'informazione. Quando comprimi i dati, in pratica rimuove tutto ciò che è comune ai dati, ovvero ciò che ti rimane assomiglia molto a dati casuali, i migliori compressori rimuovono più di queste informazioni. Una piccola modifica a qualsiasi dato e la maggior parte degli algoritmi di compressione hanno un effetto drammatico sull'output dei dati.

L'algoritmo Zip rende effettivamente più difficile per rsync trovare checksum uguali tra server e client e ciò significa che è necessario trasferire più dati. Quando lo decidi, stai facendo sì che rsync faccia ciò che è buono, invia meno dati per sincronizzare i due file.