Zpět na domů

Zpoždění retry Spring Kafka: jak opravit

Článek rozebírá příčinu zpoždění v retry-tópicech Spring Kafka s @RetryableTopic: přetížení výchozího TaskScheduler. Diagnostika prostřednictvím metrik a threaddump, oprava — zvětšení poolu na 10. Doporučení k monitorování.

Oprava zpoždění v Spring Kafka retry tópicech
Advertisement 728x90

Neblokující opakování v Spring Kafka: Diagnostika zpoždění a řešení

Při implementaci asynchronního opakování pro Kafka s využitím @RetryableTopic v Spring Boot 3.2 a Spring Kafka 3.1 se při zátěži objevují zpoždění na retry-tópicích. Hlavní tópic je zpracováván rovnoměrně, ale retry-0 hromadí zprávy po dobu 10–12 minut, poté se částečně vyprázdní a znovu stagnuje. To vede k trhavému zpracování.

Metriky ukazují vzácné špičky fetch-požadavků namísto rovnoměrného pollování. Consumer periodicky dotazuje broker pro heartbeat, ale skutečné čtení probíhá sporadicky s vysokou latencí.

Klíčové metriky pro analýzu

Pro diagnostiku shromážděte následující metriky Spring Kafka:

Google AdInline article slot
  • kafka_consumer_time_between_poll_avg / max — intervaly mezi pollováním.
  • kafka_consumer_last_poll_seconds_ago — čas od posledního pollování.
  • kafka_consumer_poll_idle_ratio_avg — podíl nečinného pollování.
  • kafka_consumer_fetch_manager_fetch_rate / fetch_latency_avg — rychlost a zpoždění fetchování.
  • spring_kafka_listener_seconds_count / sum — čas zpracování listeneru.

Grafy fetch-rate a volání listenerů potvrzují: zpracování se aktivuje trhavě, poll idle je vysoký.

Diagnostika pomocí threaddump

Threaddump odhalil ~180 vláken ve stavu TIMED_WAITING na ScheduledThreadPoolExecutor$DelayedWorkQueue.take. To ukazuje na přetížení scheduleru, který se používá pro odložené obnovení partií.

Mechanismus pozastavení v RetryableTopic

@RetryableTopic používá ContainerPartitionPausingBackOffManager: při prvním zpracování retry-zprávy zkontroluje backoff, pozastaví partii na now() + delay a naplánuje obnovení přes ListenerContainerPauseService.

Google AdInline article slot

Kód pozastavení:

private final TaskScheduler scheduler;

public void pausePartition(MessageListenerContainer messageListenerContainer, 
                           TopicPartition partition,
                           Duration pauseDuration) {
    Instant resumeAt = Instant.now().plusMillis(pauseDuration.toMillis());
    messageListenerContainer.pausePartition(partition);
    this.scheduler.schedule(() -> {
        messageListenerContainer.resumePartition(partition);
    }, resumeAt);
}

Při vysoké zátěži se fronta úloh scheduleru přeplní. Ve výchozím nastavení je spring.task.scheduling.pool.size=1, což vytváří úzké hrdlo: úlohy na obnovení se hromadí, fetchování se nespouští včas.

Řešení: Zvětšení fondu scheduleru

Nastavte spring.task.scheduling.pool.size=10 v application.properties.

Google AdInline article slot

Výsledky:

  • Zpoždění retry-0 se stabilizovalo na přijatelné úrovni (neklesá na 0 kvůli backoffu).
  • Poll-intervaly se zkrátily, fetch-rate vzrostl.
  • Volání listenerů se stala rovnoměrnými.

Graf zpoždění po opravě: drží se vodorovně bez růstu. Fetch latence se snížila, pollování je častější.

Co je důležité

  • Omezený scheduler: výchozí fond s 1 vláknem nezvládá hromadná pozastavení/obnovení při zátěži na retry.
  • Metriky primárně: sledujte fetch-rate, poll-idle, listener-time před threaddumpem.
  • Transakce sekundárně: ack-mode=record, max.poll.records=1 neovlivňují zpoždění.
  • Concurrency OK: Spring Kafka správně škáluje podle partií, i s násobením tópiců.
  • Alternativa: pro highload zvažte vlastní retry s kontrolou časového razítka ve smyčce místo pozastavení.

Doporučení pro monitoring

Přidejte dashboardy:

  • Commit/rollback rate pro exactly-once.
  • Velikost fronty scheduleru (vlastní metrika).
  • Události pozastavení/obnovení partií.
  • Rozdíl mezi backoff časovým razítkem a aktuálním časem.

Testujte pod zátěží před nasazením do produkce. Zdrojové kódy Spring Kafka poskytují úplný obraz mechaniky.

— Editorial Team

Advertisement 728x90

Číst dál