Java в облаках

Будущее наступает. Нам уже очень сложно вообразить, как люди жили без постоянного доступа в интернет, как встречались, не имея телефонов, как ходили в фотосалоны, а потом хранили редкие фото всю жизнь, да даже как каждый день ходить на работу в офис — многие уже забыли. Думаю, очень скоро в прошлое отойдет и разворачивание приложений на физических серверах. Все уйдут в облака.

По дороге с облаками. И Java

По дороге с облаками. И Java

Беда в том, что наша любимая Java не то чтобы сильно приспособлена для размещения в облаке. Благодаря политике «пишем однажды — работает везде» у нас есть сложный многоступенчатый процесс подготовки сервиса, занимающий много памяти и процессорного времени, за что приходится очень серьезно платить облачному сервису.

Благо, как я уже писал, будущее наступило, и у нас есть некоторые технологии, делающие жизнь сервиса в облаке дешевле и приятнее. Сегодня рассмотрим две из них: технологию Native Image с GraalVM и технологию CraC.

Меня зовут Султанов, и я тимлид (тяжелый вздох). Стараюсь делать приложения быстрыми. Иногда даже получается. А еще у меня есть канал, где можно обсудить эту и другие статьи. Подписывайтесь, там интересно.

Зачем всё это изобрели

Можно ли разместить в облаке обычное Java-приложение на какой-то стоковой JDK? Конечно. Для этого нужно всего лишь вложить размещаемый сервис в Docker-контейнер, завести под Kubernetes, и все прекрасно заработает. Ну то есть как прекрасно…

Этапы прекрасной работы стоковой JVM

Этапы прекрасной работы стоковой JVM

Стоковая JDK любит JIT-компиляцию, а значит тратит много CPU (за который нужно платить облачному провайдеру), к тому же хранит эвристики и статистику в памяти, скомпилированный код в памяти, и начинает компиляцию не сразу, а только после сеансов интерпретации, и только когда накопит достаточно статистики. Этапов JIT-компиляции несколько, classloader тоже тащит всё в память, и за этим всем нужно еще и прибраться GarbageCollector«у.

Вобщем, не так уж всё и прекрасно. Если нам нужно развернуть один экземпляр низконагруженного сервиса — вроде ничего страшного и особо дорогого, но если нам необходимо постоянно разворачивать и тушить сервисы, балансируя нагрузку — уже дороговато. Если же наш сервис должен сразу давать быстрый отклик, и это критично для бизнеса, то нас ждет полный провал.

Решений на самом деле было изобретено довольно много, сегодня мы посмотрим на два из них. И первым выступит

GraalVM с технологией Native Image

Согласно информации на официальном сайте, технология мало того что имеет высочайшую производительность, так еще и полиглот (то есть может работать с несколькими языками). Попробуем проникнуть в суть применяемого решения.

Как это работает

Основной фишкой технологии Native Image является АОТ — Ahead of Time compilation. Означает это примерно следующее — вместо долгого процесса сборки приложения на дефолтной JDK GraalVM формирует тот самый Native Image, то есть образ приложения, уже готовый для контейнеризации.

В отличие от режима JIT, в котором компиляция и выполнение происходят одновременно, в режиме AOT компилятор выполняет все операции во время сборки, перед выполнением. Основная идея состоит в том, чтобы перенести всю «тяжелую работу» — дорогостоящие вычисления — на этап создания образа, чтобы это можно было сделать один раз, а затем во время выполнения сгенерированные исполняемые файлы запускаются быстро и готовы с самого начала, потому что все заранее вычисляется и предварительно скомпилировано.

Работа GraalVM

Работа GraalVM

Утилита GraalVM «native-image» принимает байт-код Java в качестве входных данных и выводит собственный исполняемый файл. Это происходит в три этапа:

  • Анализ точек входа (обычно это main метод). GraalVM Native Image определяет, какие классы, методы и поля Java достижимы во время выполнения, и только они будут включены в собственный исполняемый файл. Анализ итеративно обрабатывает все транзитивно достижимые пути кода до тех пор, пока не будет достигнута фиксированная точка и анализ не закончится. Это касается не только кода приложения, но также библиотек и классов JDK — всего, что необходимо для упаковки приложения в самостоятельный двоичный файл.

  • Инициализация во время сборки. GraalVM Native Image по умолчанию инициализирует класс во время выполнения, чтобы обеспечить корректное поведение. Но если класс не связан внешними зависимостями и не обращается к внешним ресурсам, он будет инициализирован во время сборки. Это делает ненужной инициализацию и проверки во время выполнения и повышает производительность.

  • Создание моментальных слепков heap. Все, что инициализировано, сохраняется в слепок heap, и потом просто грузится в память в момент старта приложения, делая ненужным разогрев.

Перечисленные меры по оптимизации старта приложения действительно приводят к хорошим результатам. Приложение стартует на порядок быстрее.

НО!

Есть в этом рациональное зерНО

Есть в этом рациональное зерНО

Чудес не бывает, и любая технология, имеющая прекрасные достоинства, несет с собой отражение этих достоинств — мерзкие недостатки. И у GraalVM они тоже есть.

Представленная технология полностью меняет подход к разработке приложений. Раньше было как? Написал, отладил, и забыл. Всю дальнейшую работу можно было доверить долгому, но очень стабильному и многократно опробованному процессу компиляции, иногда переставляя флаги в JVM.

Теперь же нужно заранее думать, а что будет при создании образа? Как поведет себя приложение при АОТ? Может ли умная машина вырезать из образа что-то нужное?

Эту проблему вобщем-то признают и сами авторы технологии Native image. Например, чтобы в вашем приложении заработала рефлексия, нужно поддерживать создаваемый вручную (что неприятно) или автоматически (что опасно) конфигурационный файл, разрешающий рефлексию некоторым частям программы. На самом деле выглядит, как костыль.

Так же GraalVM использует не самый продвинутый сборщик мусора (G1), имеет слабо отлаживаемый код, IDE не подсвечивает ошибки, связанные с Native Image, технология Linux-специфична. Со временем это конечно исправят, но мы-то живем и разрабатываем приложения в настоящий момент. Сейчас совершенно непонятно, с какими сложностями придется столкнуться на проде, используя Native image. Но, как говорится, кто не рискует…

CRaC — Coordinated Restore at Checkpoint

Одним из конкурентов Native image в деле ускорения и удешевления старта приложений в облаке является технология CRaC. Вобщем-то вся суть изложена в названии технологии. CRaC — это про создание образов приложения и восстановление из них.

Как это работает?

В ядре линукса (да-да, CRaC тоже Linux-специфичен) очень давно существует другая технология, а именно CRIU — Coordinated Restore in Userspace. Именно её использует внутри себя CRaC. CRUI позволяет заморозить любой процесс в виде набора некоторых файлов, и увидеть полное содержимое памяти, все состояния потоков, которые работали в этот момент, то есть попросту сохраняет слепок состояния процесса. И этот слепок (в виде набора файлов) можно где-то сохранить и потом запустить, через некоторое время или даже на другой физической машине, и он как будто бы ничего не заметит, и начнет работать так же, как он работал до этого, с того же места.

Собственно, CRaC это и есть адаптация технологии CRIU для Java. Вроде просто, и должно прекрасно работать. Как показывают замеры, время старта из образа на два порядка меньше времени традиционного старта приложения на стоковой JVM.

НО!

Как всегда, существуют проблемы. Наше приложение не работает само по себе, у него всегда есть связи, которые тоже сохраняются в слепке приложения. Чтобы подобного не происходило, нужна некая процедура, называемая координацией. На самом примитивном уровне это делается так:

Дополнительный код для использования CRaC

Дополнительный код для использования CRaC

То есть мы должны вручную обработать закрытие всех ресурсов перед созданием образа, а потом открытие этих ресурсов снова. Если этого не сделать, то система просто откажется создавать образ.

Следствием создания образа системы являются проблемы с безопасностью. Сам образ никому нельзя показывать, так как там вся подноготная. Компрометация образа будет катастрофой для безопасности.

Так же проблемой будет являться размер образа, который по сути будет аналогичен размеру разогретого JVM-приложения.

Заключение

На текущий момент обе технологии демонстрируют потенциал, но вместе с тем и проблемы, которые принято называть детскими. Разработчики их решают, но когда решат, и когда появятся достаточные гарантии, чтобы использовать Native image и CRaC на проде, и не поседеть, пока неясно.

Безумству храбрых поём мы песню!

Безумству храбрых поём мы песню!

Habrahabr.ru прочитано 2474 раза