我有Gradle Shadow插件生成的胖/超级JAR。我经常需要通过网络发送胖JAR,因此,方便我只发送文件的delta而不是cca 40 MB的数据。 rsync是一个很好的工具。但是,我的源代码中的一个小变化导致最终胖JAR发生了很大的变化,因此rsync没有尽可能多的帮助。
我可以将胖JAR转换为rsync友好的JAR吗?
我对解决方案/解决方法的想法:
可能相关的问题:
答案 0 :(得分:3)
有两种方法可以做到这一点,这两种方法都涉及关闭压缩。首先使用jar方法将其关闭...
您可以使用gradle执行此操作(此答案实际上来自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'
}
}
与
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
}
关键是压缩已经关闭,即
org.gradle.api.tasks.bundling.ZipEntryCompression.STORED
您可以在这里找到文档
是的,您可以在新存档上加快约40%,在已经rsync的jar存档上加速200%以上。诀窍是不要压缩罐子 你可以利用rsyncs分块算法。
我使用以下命令来压缩包含大量类文件的目录...
jar cf0 uncompressed.jar .
jar cf compressed.jar .
这创造了以下两个罐子......
-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
请注意,未压缩Jar的大小约为10MB。
然后我对这些文件进行rsync并使用以下命令对它们进行计时。 (注意,即使打开压缩文件的压缩效果也不大,我稍后会解释)。
压缩罐
time rsync -av -e ssh compressed.jar jar@rsync-server.org:/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
time rsync -avz -e ssh uncompressed.jar jar@rsync-server.org:/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
我们已经获得了近50%的加速。这至少加速了rsync和 我们得到了很好的推动但是后续的rsyncs会有什么变化 已经完成了。
我从重新创建的170字节大小的目录中删除了一个类文件 罐子里的割子就是这么大..
-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
现在时间非常不同。
压缩罐
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
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
因此,我们可以使用此方法加速rsyncing大型jar文件。其原因与信息理论有关。当您压缩数据时,它实际上会删除数据中常见的所有内容,即您留下的内容与随机数据非常相似,最佳压缩器会删除更多此类信息。对任何数据和大多数压缩算法的微小改动都会对数据输出产生巨大影响。
Zip算法实际上使rsync更难以找到服务器和客户端之间相同的校验和,这意味着它需要传输更多数据。当你解压缩它时,你让rsync做它擅长的事情,发送更少的数据来同步这两个文件。
答案 1 :(得分:2)
据我所知,rsyncable gzip的工作原理是将每个8192字节的压缩数据重置Huffman树并填充到字节边界。这避免了对压缩的长程副作用(rsync处理移位的数据块,如果它们至少是字节对齐的)
从这个意义上讲,包含小文件(小于8192字节)的jar已经是可同步的,因为每个文件都是单独压缩的。作为测试,您可以使用jar的-0
选项(无压缩)来检查它是否有助于rsync,但我认为它不会。
为了改善你需要的rsyncability(至少):
.class
文件的最后修改时间是有问题的
我不确定jar,但zip允许额外的字段,其中一些可能会阻止rsync匹配,例如unix扩展的最后访问时间。编辑:我使用以下命令进行了一些测试:
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 -@
cd ..
rm -rf tempdir
删除jar / zip中包含的第一个文件时的rsync stats:
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
删除第一个文件并修改每个时间戳时:
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
因此存在显着差异,但不如我预期的那么多。
似乎更改文件模式不会影响transfert(可能因为它存储在中央目录中?)
答案 2 :(得分:1)
让我们退后一步;如果你不创造大罐子,这不再是一个问题。
所以,如果你单独部署你的依赖关系罐,并且你没有把它们装进一个胖罐子里,那你也解决了这个问题。
要做到这一点,让我们说你有:
然后,将以下条目的META-INF/MANIFEST.MF
yourapp.jar
文件放入:
Class-Path: lib/guava.jar lib/h2.jar
现在你可以运行java -jar yourapp.jar
并且它可以正常工作,从而获得依赖关系。您现在可以使用rsync单独传输这些文件; yourapp.jar会小得多,你的依赖关系罐子通常也不会改变,所以那些在rsyncing时也不会花费太多时间。
我知道这并没有直接回答实际提出的问题,但我打赌这个问题出现的时间是90%以上,而不是讨厌是适当的答案。
注意:Ant,Maven,Guava等可以处理正确的清单条目。如果你的jar的意图不是运行它,但是,例如,它是一个网络的战争servlet容器,它们有自己的规则来指定你的依赖项所在的位置。
答案 3 :(得分:1)
我替换了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'
}
}
与
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
}
(使用此处发布的解决方案https://stackoverflow.com/a/31426413/99256)
最终的fatJar比Shadow插件为我生成的(即35 MB)要大得多(即56 MB)。但是,最终的jar似乎是可以同步的(当我在源代码中进行微小的更改时,rsync只传输非常少量的数据)。
请注意,我对Gradle的知识非常有限,所以这只是我的观察,有可能进一步改进。