Как конвертировать jar в rsyncable jar?
У меня есть fat/uber JAR, созданный Gradle Shadow. Мне часто нужно посылать жир JAR по сети, и поэтому мне удобно отправлять только дельта файла вместо 40 мегабайт данных. rsync - отличный инструмент для этой цели. Тем не менее, небольшое изменение в моем исходном коде приводит к большому изменению конечного жира JAR, и, следовательно, rsync не помогает, насколько это возможно.
Можно ли конвертировать JAR в JS в rsync-friendly JAR?
Мои идеи решения/обходные пути:
- Положите тяжелый вес на rsync и скажите ему, как он работает со сжатым файлом (я не нашел способа сделать это).
- Преобразование не-rsyncable jar в rsyncable jar
- Скажите Gradle Тень для генерации rsyncable jar (невозможно в данный момент)
Возможно, связанные вопросы:
Ответы
Ответ 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
}
(Используя решение, размещенное здесь fooobar.com/questions/20436/...)
Конечный fatJar намного больше (то есть 56 МБ), чем тот, который для меня создал плагин Shadow (т.е. 35 МБ). Тем не менее, последняя банка кажется rsyncable (когда я делаю крошечные изменения в моем исходном коде, rsync передает только очень небольшое количество данных).
Обратите внимание, что у меня очень ограниченное знание Gradle, так что это всего лишь мое наблюдение, и возможно его дальнейшее улучшение.
Ответ 2
Есть два способа сделать это, оба из которых связаны с отключением сжатия. Gradle сначала выключите его с помощью метода 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
Здесь вы можете найти документы
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/bundling/ZipEntryCompression.html#STORED
Да, вы можете ускорить его примерно на 40% в новом архиве и более чем на 200% в архиве jar, который вы уже использовали rsync'd. Хитрость заключается в том, чтобы не сжимать банку так, чтобы
вы можете воспользоваться алгоритмом chnsing 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
Обратите внимание, что размер несжатой банки составляет около 10 МБ больше.
Затем я rsync'd эти файлы и приурочил их, используя следующие команды. (Обратите внимание, что даже включение сжатия сжатого файла малоэффективно, я объясню позже).
Сжатая банка
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
Uncompressed Jar
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
Мы получили ускорение почти на 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
Uncompressed 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
Таким образом, мы можем ускорить многократное увеличение больших файлов jar с помощью этого метода. Причина этого связана с теорией информации. Когда вы сжимаете данные, он фактически удаляет все, что является общим из данных, т.е. то, что вы оставили, похоже на случайные данные, лучшие компрессоры удаляют большую часть этой информации. Небольшое изменение любого из данных и большинства алгоритмов сжатия оказывает значительное влияние на вывод данных.
Алгоритм Zip эффективно затрудняет для rsync поиск контрольных сумм, которые одинаковы между сервером и клиентом, и это означает, что ему необходимо передать больше данных. Когда вы распаковываете его, вы позволяете rsync делать то, на что это хорошо, отправлять меньше данных для синхронизации двух файлов.
Ответ 3
Насколько мне известно, rsyncable gzip работает путем перепродажи дерева Хаффмана и заполнения до байтов каждые 8192 байта сжатых данных. Это позволяет избежать побочного эффекта на большом расстоянии от сжатия (rsync позаботится о сдвинутых блоках данных, если они по меньшей мере выровнены по байтам)
В этом смысле jar, содержащий небольшие файлы (менее 8192 байта), уже rsyncable, потому что каждый файл сжимается отдельно. В качестве теста вы можете использовать опцию 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 [email protected]
cd ..
rm -rf tempdir
rsync, когда первый файл, содержащийся в jar/zip, удаляется:
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
Итак, есть существенная разница, но не так сильно, как я ожидал.
Также кажется, что изменение режима файла не влияет на передачу (возможно, потому, что он хранится в центральном каталоге?)
Ответ 4
Позвольте сделать один шаг назад; если вы не создаете большие банки, это перестает быть проблемой.
Итак, если вы разворачиваете свои банки с зависимостями отдельно, и вы не помещаете их в одну жирную банку, вы также решили проблему здесь.
Чтобы сделать это, скажем, у вас есть:
- /foo/yourapp.jar
- /foo/lib/guava.jar
- /foo/lib/h2.jar
Затем добавьте в META-INF/MANIFEST.MF
файл yourapp.jar
следующую запись:
Class-Path: lib/guava.jar lib/h2.jar
И теперь вы можете просто запустить java -jar yourapp.jar
, и он будет работать, подбирая зависимости. Теперь вы можете передавать эти файлы по отдельности с помощью rsync; yourapp.jar будет намного меньше, и ваши банки с зависимостями, как правило, не будут изменены, поэтому они не будут занимать много времени, когда rsyncing тоже.
Я знаю, что это напрямую не отвечает на заданный вопрос, но я держал пари в 90% + того времени, когда этот вопрос возникает, а не fatjarring - соответствующий ответ.
NB: Ant, Maven, Guava и т.д., может позаботиться о том, чтобы ввести правильный манифест. Если цель вашей банки не запускать ее, но, например, это война за веб-сервлет контейнере, у них есть свои правила для того, как указывать, где живут ваши баны зависимости.