Как конвертировать 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 и т.д., может позаботиться о том, чтобы ввести правильный манифест. Если цель вашей банки не запускать ее, но, например, это война за веб-сервлет контейнере, у них есть свои правила для того, как указывать, где живут ваши баны зависимости.