Spring Boot: 20 секунд на фазу остановки
В настройке
Согласно справочнику Spring Boot 4.1.1, graceful shutdown включён по умолчанию для встроенных Jetty, Reactor Netty и Tomcat, в servlet- и reactive-приложениях. При закрытии контекста уже принятым HTTP-запросам дают время завершиться. Новые запросы эти серверы перестают принимать на сетевом уровне. При постоянных соединениях детали поведения могут отличаться.
Есть ловушка при проверке из IDE: кнопка Stop может оборвать процесс сразу, если среда не отправляет корректный
В настройке
spring.lifecycle.timeout-per-shutdown-phase=20s легко прочитать обещание «приложение завершится за 20 секунд». Но это таймаут отдельной фазы остановки, а не общий лимит завершения процесса. Значение 20 секунд приведено в документации как пример.Согласно справочнику Spring Boot 4.1.1, graceful shutdown включён по умолчанию для встроенных Jetty, Reactor Netty и Tomcat, в servlet- и reactive-приложениях. При закрытии контекста уже принятым HTTP-запросам дают время завершиться. Новые запросы эти серверы перестают принимать на сетевом уровне. При постоянных соединениях детали поведения могут отличаться.
Есть ловушка при проверке из IDE: кнопка Stop может оборвать процесс сразу, если среда не отправляет корректный
SIGTERM. Поэтому сначала проверьте, какой сигнал получает приложение. Иначе принудительное завершение легко принять за ошибку в настройке graceful shutdown.👍2
Bulkhead в Resilience4j: семафор или отдельный пул
Если одна внешняя интеграция заняла все рабочие потоки, остальные запросы тоже могут застрять. Bulkhead позволяет ограничить число одновременных вызовов к выбранной зависимости. Но отдельный пул потоков появляется не в каждой реализации.
У
Такой лимит относится к конкурентным вызовам через конкретный bulkhead. Число запросов в секунду он не задаёт и пропускную способность внешнего сервиса не увеличивает.
Если одна внешняя интеграция заняла все рабочие потоки, остальные запросы тоже могут застрять. Bulkhead позволяет ограничить число одновременных вызовов к выбранной зависимости. Но отдельный пул потоков появляется не в каждой реализации.
SemaphoreBulkhead использует семафор и собственного пула не создаёт. В документации Resilience4j 2.2.0 у maxConcurrentCalls значение по умолчанию 25, у maxWaitDuration — 0. Первый параметр ограничивает одновременные выполнения, второй задаёт, сколько поток может ждать допуска, когда лимит исчерпан.У
FixedThreadPoolBulkhead есть собственный пул и ограниченная очередь. Здесь нужно согласовать размеры пула и queueCapacity. У семафорного варианта размер рабочего пула остаётся вашей заботой.Такой лимит относится к конкурентным вызовам через конкретный bulkhead. Число запросов в секунду он не задаёт и пропускную способность внешнего сервиса не увеличивает.
Spring: событие опубликовано, обработчик не вызван
Событие опубликовано, исключений нет, а метод с
По умолчанию обработчик привязан к
Здесь речь об обычной транзакции, привязанной к потоку. Выбор фазы связывает обработчик с её исходом, но не обеспечивает гарантированную доставку во внешнюю систему.
Transaction-bound Events в Spring
#spring #java
Событие опубликовано, исключений нет, а метод с
@TransactionalEventListener не вызван. Сначала проверьте, была ли в момент публикации активная транзакция.По умолчанию обработчик привязан к
AFTER_COMMIT. При откате он не сработает. Если транзакции вообще нет, Spring пропустит вызов. Атрибут fallbackExecution=true явно разрешает обработку и в этом случае.Здесь речь об обычной транзакции, привязанной к потоку. Выбор фазы связывает обработчик с её исходом, но не обеспечивает гарантированную доставку во внешнюю систему.
Transaction-bound Events в Spring
#spring #java
Приглашаем на Java Jam — бесплатный митап ЮMoney для бэкенд-разработчиков 🔥
✅ 29 октября, четверг, 19:00 МСК — приходите на митап в Санкт-Петербурге или смотрите трансляцию.
Спикеры из ЮMoney и Сбера выступят с докладами:
🟣 Один апгрейд — больше сотни сервисов: как мы обновляем Java-стек в компании
Обсудим, как обновить Gradle, Spring Boot и Java в 120+ компонентах, не останавливая разработку. Расскажем, как выстраивали процессы на протяжении этого времени: от порядка действий и массовых патчей до взаимодействия с командами и решения трудностей, с которыми столкнулись во время последнего обновления.
🟣 Как я автоматизировал свой проект — и всё сломал
Разберём, как выстроить автоматизацию в проекте с помощью ИИ и какие шишки можно набить на пути. Поговорим, как работает длинный контекст в моделях и как организовать работу с учётом этих особенностей.
🟣 Откатить нельзя оставить: мониторинг выкладки релиза на основе Sentry
Ошибки случаются. Узнаем, какие из них появляются или возникают чаще после последнего релиза — и достаточно ли этого, чтобы принять решение об откате.
Зарегистрируйтесь, чтобы принять участие. Все подробности — на сайте митапа Java Jam👈
Спикеры из ЮMoney и Сбера выступят с докладами:
Обсудим, как обновить Gradle, Spring Boot и Java в 120+ компонентах, не останавливая разработку. Расскажем, как выстраивали процессы на протяжении этого времени: от порядка действий и массовых патчей до взаимодействия с командами и решения трудностей, с которыми столкнулись во время последнего обновления.
Разберём, как выстроить автоматизацию в проекте с помощью ИИ и какие шишки можно набить на пути. Поговорим, как работает длинный контекст в моделях и как организовать работу с учётом этих особенностей.
Ошибки случаются. Узнаем, какие из них появляются или возникают чаще после последнего релиза — и достаточно ли этого, чтобы принять решение об откате.
Зарегистрируйтесь, чтобы принять участие. Все подробности — на сайте митапа Java Jam
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Gradle: сгенерированный checksum ещё нужно проверить
Команда
При генерации этой базы Gradle доверяет тому, что уже доступно в репозиториях. Если JAR к этому моменту подменён, в базу попадёт checksum подменённого файла. Поэтому сгенерированные метаданные нужно проверить до принятия в проект.
Checksum подтверждает совпадение с доверенной базой. Он не ищет вредоносный код и сам по себе не устанавливает автора. Changing-зависимости, включая SNAPSHOT, и локальные артефакты этой проверкой не покрываются.
Руководство Gradle 9.7.0
Команда
./gradlew --write-verification-metadata sha256 собирает контрольные суммы зависимостей. Файл gradle/verification-metadata.xml затем служит базой для проверки загружаемых артефактов.При генерации этой базы Gradle доверяет тому, что уже доступно в репозиториях. Если JAR к этому моменту подменён, в базу попадёт checksum подменённого файла. Поэтому сгенерированные метаданные нужно проверить до принятия в проект.
Checksum подтверждает совпадение с доверенной базой. Он не ищет вредоносный код и сам по себе не устанавливает автора. Changing-зависимости, включая SNAPSHOT, и локальные артефакты этой проверкой не покрываются.
Руководство Gradle 9.7.0
Quarkus: три промаха кеша, один вызов метода
Несколько запросов одновременно обращаются к отсутствующему ключу. В Quarkus 3.27 с кешем на Caffeine аннотация
Это работает в пределах одного кеша, одного ключа и одного экземпляра приложения. У объектов, входящих в ключ, должны корректно работать equals() и hashCode(). Разные ключи обрабатываются отдельно. Между экземплярами приложения такой блокировки нет.
При попадании в кеш тело метода не выполняется.
Кеширование в Quarkus 3.27
Несколько запросов одновременно обращаются к отсутствующему ключу. В Quarkus 3.27 с кешем на Caffeine аннотация
@CacheResult обрабатывает такие обращения вместе: первый вызов вычисляет значение, остальные ждут результат.Это работает в пределах одного кеша, одного ключа и одного экземпляра приложения. У объектов, входящих в ключ, должны корректно работать equals() и hashCode(). Разные ключи обрабатываются отдельно. Между экземплярами приложения такой блокировки нет.
lockTimeout задаёт предел ожидания блокировки. Это не таймаут выполнения самого метода. По умолчанию предел ожидания отключён.При попадании в кеш тело метода не выполняется.
Кеширование в Quarkus 3.27
ResourceLock в JUnit: параллельный доступ на чтение
В параллельных тестах JUnit Jupiter
Два теста с
Параллельный запуск нужно включить и задать режим выполнения, например через
Это координация тестов Jupiter, объявивших один ресурс, а не блокировка между процессами ОС.
Parallel Execution и Synchronization
В параллельных тестах JUnit Jupiter
@ResourceLock объявляет общий ресурс по строковому имени и режим доступа к нему.Два теста с
READ для одного ресурса могут выполняться одновременно. READ_WRITE конфликтует с любым другим доступом к тому же ресурсу. Для аннотации на тестовом методе блокировка захватывается до @BeforeEach и освобождается после @AfterEach.Параллельный запуск нужно включить и задать режим выполнения, например через
@Execution(CONCURRENT). Одного флага enabled для параллельного выполнения тестов недостаточно.Это координация тестов Jupiter, объявивших один ресурс, а не блокировка между процессами ОС.
Parallel Execution и Synchronization
NMT в HotSpot: сначала включить, потом искать рост
Native Memory Tracking нужно включить при старте JVM, например флагом
После прогрева сохраните базу командой
NMT учитывает внутренние аллокации HotSpot, но не всю память стороннего native-кода. Рост категории даёт направление для диагностики, а не готовое доказательство утечки.
На карточке — команды из руководства Oracle Java 25.
Native Memory Tracking в HotSpot
#jvm #java
Native Memory Tracking нужно включить при старте JVM, например флагом
-XX:NativeMemoryTracking=summary. Если процесс уже работает без NMT, включить сбор через jcmd не получится.После прогрева сохраните базу командой
jcmd <pid> VM.native_memory baseline. Позже jcmd <pid> VM.native_memory summary.diff покажет изменения по категориям относительно неё.NMT учитывает внутренние аллокации HotSpot, но не всю память стороннего native-кода. Рост категории даёт направление для диагностики, а не готовое доказательство утечки.
На карточке — команды из руководства Oracle Java 25.
Native Memory Tracking в HotSpot
#jvm #java
Micrometer: каждый новый 404 может раздувать метрику
Если в собственной инструментализации записывать сырой URI в тег метрики, запросы к
Micrometer разбирает именно такой пример: если не ограничивать значения URI для 404, кардинальность метрики растёт с каждым новым отсутствующим ресурсом. Вместо произвольного пути можно использовать ограниченное значение вроде
Это задача проектирования собственных тегов. Из примера не следует, что все интеграции Spring Boot по умолчанию записывают сырые 404-пути.
На схеме меняется только тег URI. Значения условные, измерений памяти здесь нет.
Micrometer: Tag Values
Если в собственной инструментализации записывать сырой URI в тег метрики, запросы к
/missing/a, /missing/b и тысячам других несуществующих путей дадут тысячи разных значений тега.Micrometer разбирает именно такой пример: если не ограничивать значения URI для 404, кардинальность метрики растёт с каждым новым отсутствующим ресурсом. Вместо произвольного пути можно использовать ограниченное значение вроде
NOT_FOUND.Это задача проектирования собственных тегов. Из примера не следует, что все интеграции Spring Boot по умолчанию записывают сырые 404-пути.
На схеме меняется только тег URI. Значения условные, измерений памяти здесь нет.
Micrometer: Tag Values
Kafka: heartbeat не заменяет вызовы poll
Consumer может отправлять heartbeat и при этом слишком долго обрабатывать пачку. В Java-клиенте Kafka 4.0 прогресс проверяется отдельно: интервал между вызовами
Если интервал превышен, consumer рискует потерять партиции, а коммит может завершиться
Если обработку вынести в worker, consumer должен продолжать вызывать poll. Auto commit отключают, партиции приостанавливают до конца обработки, а offsets коммитят вручную только после обработки. Сам
KafkaConsumer 4.0: Detecting Consumer Failures
Consumer может отправлять heartbeat и при этом слишком долго обрабатывать пачку. В Java-клиенте Kafka 4.0 прогресс проверяется отдельно: интервал между вызовами
poll ограничивает max.poll.interval.ms.Если интервал превышен, consumer рискует потерять партиции, а коммит может завершиться
CommitFailedException. Размер пачки ограничивает max.poll.records. Увеличение интервала даёт больше времени на обработку, но может замедлить rebalance.Если обработку вынести в worker, consumer должен продолжать вызывать poll. Auto commit отключают, партиции приостанавливают до конца обработки, а offsets коммитят вручную только после обработки. Сам
KafkaConsumer не thread-safe: worker-потоки не должны произвольно вызывать его методы.KafkaConsumer 4.0: Detecting Consumer Failures
StampedLock: читать поля можно до проверки, использовать — после
Оптимистическое чтение в
Если за это время другой поток захватывал write lock, проверка провалится. В примере Oracle чтение повторяют уже под read lock. До проверки поля могут противоречить друг другу. При обходе изменяемого массива или вызове метода по прочитанной ссылке могут понадобиться дополнительные проверки согласованности данных.
Это механизм для коротких участков с известным устройством данных.
StampedLock, Java SE 25
#java
Оптимистическое чтение в
StampedLock обходится без удержания read lock. Поток получает stamp через tryOptimisticRead(), копирует поля в локальные переменные и вызывает validate(stamp). Использовать прочитанные значения можно после успешной проверки.Если за это время другой поток захватывал write lock, проверка провалится. В примере Oracle чтение повторяют уже под read lock. До проверки поля могут противоречить друг другу. При обходе изменяемого массива или вызове метода по прочитанной ссылке могут понадобиться дополнительные проверки согласованности данных.
Это механизм для коротких участков с известным устройством данных.
StampedLock не реентерабелен, а его политика не гарантирует предпочтения читателям или писателям.StampedLock, Java SE 25
#java
🔴 Завтра открытое собеседование с Java-разработчиком
4 ноября (уже завтра!) в 19:00 МСК приходи онлайн и посмотри, как проходит настоящее техническое интервью на Middle Java-разработчика.
Как это будет:
Виктор более 5 лет занимается enterprise-разработкой в финтехе, телекоме и e-commerce. Сейчас он ведущий разработчик банковских сервисов и системы лояльности в Wildberries. Ранее строил highload-микросервисы с нуля.
Участие бесплатное. Эфир проходит в рамках менторской программы ШОРТКАТ для Java-разработчиков, которые хотят вырасти в грейде и зарплате и прокачать технические навыки.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
4 ноября (уже завтра!) в 19:00 МСК приходи онлайн и посмотри, как проходит настоящее техническое интервью на Middle Java-разработчика.
Как это будет:
📂 Виктор Анохин, Java/Kotlin-разработчик Wildberries, ex-Билайн, проведёт собеседование с разработчиком-добровольцем: будет задавать реальные вопросы и давать задачи;
📂 Виктор прокомментирует каждый ответ и объяснит, на что обращает внимание интервьюер;
📂 В конце можно будет задать Виктору любой вопрос.
Виктор более 5 лет занимается enterprise-разработкой в финтехе, телекоме и e-commerce. Сейчас он ведущий разработчик банковских сервисов и системы лояльности в Wildberries. Ранее строил highload-микросервисы с нуля.
Участие бесплатное. Эфир проходит в рамках менторской программы ШОРТКАТ для Java-разработчиков, которые хотят вырасти в грейде и зарплате и прокачать технические навыки.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
ClassValue хранит вычисленные данные для каждого класса
Таблицу методов для диспетчеризации или разобранные метаданные можно кешировать отдельно для каждого класса. Для такой задачи у JDK есть
При гонке вычисление может выполниться несколько раз. С классом свяжется одно значение, его и получат вызывающие потоки. Поэтому
ClassValue, Java SE 25
#java
Таблицу методов для диспетчеризации или разобранные метаданные можно кешировать отдельно для каждого класса. Для такой задачи у JDK есть
ClassValue<T>: get(type) возвращает значение, связанное с конкретным Class, и при необходимости запускает computeValue(type).При гонке вычисление может выполниться несколько раз. С классом свяжется одно значение, его и получат вызывающие потоки. Поэтому
computeValue должен допускать повторный вызов. Если внутри отправлять платёж или регистрировать ресурс с необратимым побочным эффектом, гарантий «ровно один раз» не будет.remove(type) убирает связь и инвалидирует устаревшие вычисления. При следующем обращении значение можно вычислить заново. Здесь удобно хранить именно данные, зависящие от класса, а не произвольный кеш по бизнес-ключам.ClassValue, Java SE 25
#java