Ни для кого не секрет, что приложения на Spring могут задумываться на старте. Особенно это заметно с развитием проекта: новый сервис стартует быстро и радует отзывчивостью, потом начинает обрастать функционалом, появляются всё новые и новые зависимости, а итоговый дистрибутив распухает на десятки мегабайт. И вот, для того чтобы просто запустить этот сервис локально, приходится ждать полминуты, минуту, две… В такие моменты ожидания у разработчика могут возникнуть вопросы: почему же так долго? что там такого происходит под капотом? может, не нужно было добавлять ту библиотеку?
Всем привет, меня зовут Алексей Лапин, я ведущий разработчик в Luxoft. В статье расскажу про инструмент в виде веб-приложения для анализа фазы старта сервисов на Spring Boot, использующий данные actuator startup endpoint. Это может помочь ответить на вопросы выше.
Небольшое введение
Я делал это приложение для себя, чтобы разобраться в новом модуле Spring, который раньше не видел, и потренироваться во фронтенде. В сети есть разные решения, но они не работали или давно не обновлялись, а мне хотелось создать актуальный вспомогательный инструмент для функциональности Spring Boot.
Spring Boot Startup Endpoint
Начиная с версии 2.4 в Spring Boot появилась реализация ApplicationStartup, записывающая события, произошедшие во время старта сервиса, и actuator endpoint, возвращающий список этих событий.
Ответ данного endpoint (/actuator/startup) выглядит следующим образом:
{
"springBootVersion": "2.5.3",
"timeline": {
"startTime": "2021-09-06T13:38:05.049490700Z",
"events": [
{
"endTime": "2021-09-06T13:38:05.159435400Z",
"duration": "PT0.0898001S",
"startTime": "2021-09-06T13:38:05.069635300Z",
"startupStep": {
"name": "spring.boot.application.starting",
"id": 0,
"tags": [
{
"key": "mainApplicationClass",
"value": "com.github.al.realworld.App"
}
],
"parentId": null
}
},
...
{
"endTime": "2021-09-06T13:38:06.420231Z",
"duration": "PT0.0060049S",
"startTime": "2021-09-06T13:38:06.414226100Z",
"startupStep": {
"name": "spring.beans.instantiate",
"id": 7,
"tags": [
{
"key": "beanName",
"value": "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"
}
],
"parentId": 6
}
},
...
]
….}
}
Подробное описание всех полей сообщения можно найти в документации Spring Boot Actuator, но все названия и так говорят сами за себя. У события есть id и parentId, что позволяет создать древовидное представление. Также есть поле duration, показывающее время, затраченное на событие + сумму времён всех дочерних событий. Поле tags содержит список атрибутов события, например имя или класс создаваемого bean.
Для того чтобы активировать сбор данных о событиях загрузки, необходимо передать экземпляр класса BufferingApplicationStartup в метод setApplicationStartup у SpringApplication. В данном случае используется конструктор, принимающий количество событий для записи. Все события сверх этого предела будут проигнорированы и не попадут в выдачу startup endpoint.
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(App.class);
application.setApplicationStartup(new BufferingApplicationStartup(1000));
application.run(args);
}
}
По умолчанию данный endpoint имеет путь /actuator/startup и поддерживает методы GET для получения событий и POST для получения событий и очистки буфера, так что последующие обращения в этот endpoint вернут пустой список событий.
Таким образом информацию, выдаваемую startup endpoint, будем считать источником данных для анализа. Веб-приложение-анализатор представляет из себя SPA (Single Page Application) без бэкенда. В него нужно загрузить события, произошедшие во время запуска сервиса, а оно построит их визуализацию. Загруженные данные никуда не передаются и нигде не сохраняются.
Для реализации был выбран язык Typescript, так как он кажется более привлекательным вариантом для Java-разработчика по сравнению с Javascript из-за привычной строгой типизации и множества ООП возможностей. Мне показалось, что было очень легко переключиться с Java на Typescript и быстро написать работающий код. В качестве фреймворка для построения UI был выбран VueJS 3. Я не имею ничего против React, Angular и других представителей фронтенд-ландшафта, однако на данный момент VueJS показался хорошей опцией ввиду низкого порога вхождения и отличного набора инструментов из коробки. Следующим шагом была выбрана библиотека компонентов. От неё требовалась совместимость с VueJS 3 и наличие компонентов для работы с таблицами. Рассматривались Element Plus, Ionic Vue, NaiveUI, но из-за наличия кастомизируемых компонентов для работы с таблицами была использована библиотека PrimeVue.
Приложение имеет строку навигации с элементами Analyzer (это главный экран приложения), Usage (инструкцией по использованию) и ссылкой на GitHub-репозиторий проекта.
На главной странице приложения отображается форма с тремя способами ввода данных для анализа. Первый способ позволяет указать ссылку на развёрнутый сервис Spring Boot. В этом случае будет сделан http-запрос в указанный endpoint и данные автоматически загрузятся для визуализации. Данный способ применим для случаев, когда сервис доступен из интернета или поднят локально. Также для загрузки по url может потребоваться дополнительная конфигурация сервиса в части CORS-заголовков и Spring Security. Второй и третий способы — это загрузка json-файла или напрямую его контента.
Развёрнутое приложение находится по адресу https://alexey-lapin.github.io/spring-boot-startup-analyzer
В качестве демонстрации работы анализатора используется мой сервис Spring Boot, развёрнутый на Heroku. Этот сервис реализует бэкенд проекта RealWorld (https://github.com/gothinkster/realworld). Нужный endpoint можно найти по адресу https://realworld-backend-spring.herokuapp.com/actuator/startup. Сервис сконфигурирован таким образом, чтобы отправлять корректные CORS-заголовки на GET-запросы от анализатора.
После загрузки событий с помощью одного из способов данные визуализируются в виде древовидной структуры. При этом все строки, имеющие дочерние элементы, скрыты. Для навигации по этому дереву можно использовать иконки > слева от идентификатора записи, либо сразу раскрывать или скрывать все записи, используя кнопки Expand All / Collapse All.
В случае если событий много, может потребоваться некоторое время на отрисовку раскрытия всех записей.
В табличном виде сразу отображаются все события. При этом все колонки, кроме Tags, сортируются.
CI + хостинг
На одном из предыдущих проектов я был вовлечён в глобальную DevOps-трансформацию организации и решал задачи по автоматизации процессов релизного цикла и выстраиванию CI/CD-пайплайнов. Это был интересный опыт, который сейчас помогает разрешить вопросы, касающиеся написания исходного кода продукта. В данном случае, как и для большинства моих OSS-проектов, в качестве git-хостинга используется GitHub, предоставляющий множество полезных инструментов для CI, хранения артефактов, документации, project management, хостинга статических сайтов и множество других. Для нужд анализатора были использованы Actions и Pages.
GitHub Actions настроен на автоматическую сборку проекта по созданию pull request, commit в master и push тэга. При этом по push тэга также произойдёт развёртывание собранного проекта на GitHub Pages, а также сборка Docker-образа и отправка его на DockerHub.
В дополнение к общедоступному инстансу анализатора на GitHub Pages (https://alexey-lapin.github.io/spring-boot-startup-analyzer) есть возможность использования Docker образа на основе Nginx (https://hub.docker.com/r/lexlapin/spring-boot-startup-analyzer). Это может быть полезно, например, для тех случаев, когда сервисы Spring Boot находятся во внутренней сети организации, из которой нет доступа в интернет, но есть Docker и есть возможность загрузить образ.
Для запуска контейнера нужно выполнить следующую команду:
docker run -d --name sbsa -p 8080:80 lexlapin/spring-boot-startup-analyzer
Если есть необходимость обращаться к этому контейнеру через reverse proxy, то для этого предусмотрена возможность передачи пути через переменную среды (UI_PUBLIC_PATH):
docker run -d --name sbsa -p 8080:80 -e UI_PUBLIC_PATH=/some-path lexlapin/spring-boot-startup-analyzer
Что улучшить
В дальнейшем планируется доработка экрана с результатами анализа. Было бы полезно добавить вкладку со сводкой по типам событий их количеству и общему затраченному времени, например количество и общее время создания бинов. Также по кратким сводным таблицам можно будет построить диаграммы, к тому же PrimeVue предоставляет такую возможность через библиотеку ChartJS. В древовидном и табличном виде можно сделать цветовое кодирование для выделения длительных событий. Дополнительно стоит добавить фильтрацию событий, например по типам.
Заключение
Предлагаемый анализатор позволяет в удобной форме визуализировать данные, полученные от actuator startup endpoint, детально оценить время, затраченное на различные типы событий, происходящих во время старта сервиса, и в целом эффективнее обработать startup-информацию. Приложение имеет публичный инстанс на GitHub Actions, а также доступно в виде Docker-образа. Данное приложение было успешно применено на одном из проектов Luxoft для разбора загрузки замедлившихся сервисов и помогло обнаружить несколько классов с неоптимальной логикой в конструкторах.
VovkaSOL
Интересная штука, спасибо. Ещё бы пример как по этой информации ускорять его. Допустим context.refresh ускорить)