Kafka Streams: обработка потоков

Урок про то, как считать прямо над потоком — фильтровать, агрегировать, джойнить — не разворачивая отдельный кластер вычислений.

Kafka Streams — клиентская библиотека для обработки потоков: читает из топиков, преобразует данные (map/filter/агрегации/джойны/окна) и пишет результат обратно в топики, работая как обычное приложение.

Зачем это нужно

Брокер только хранит и доставляет; вычисления над потоком — отдельная задача. Kafka Streams встраивает её прямо в ваше приложение: не нужен отдельный движок вроде Flink или Spark, достаточно подключить библиотеку. Вы пишете обычный сервис, который масштабируется и отказоустойчив за счёт самой Kafka.

KStream против KTable

KStreamKTable
Этопоток событий (каждая запись — факт)таблица состояний (ключ -> текущее значение)
Новая запись по ключуещё одно событиеобновление значения ключа
Аналогияжурнал транзакцийрезультат компакции лога

Двойственность «поток-таблица» — сердце Streams: поток фактов можно свернуть в таблицу состояний (агрегация), а таблицу — развернуть обратно в поток изменений.

Операции

  clicks (KStream)
    .filter(событие -> событие.valid)
    .groupByKey()
    .windowedBy(5 минут)
    .count()            -> KTable: кликов по ключу за окно
    .toStream()
    .to("clicks-per-5min")

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

Джойны и stateful-обработка

Streams умеет джойнить потоки между собой и поток с таблицей: обогатить событие заказа данными о пользователе (orders.join(users)). Такие операции stateful — им нужно помнить состояние (накопленные счётчики, последние значения ключей). Streams хранит это состояние в локальном встроенном хранилище (RocksDB) и одновременно зеркалит его в compacted-топик Kafka.

Как работает под капотом

Параллелизм Streams наследуется от партиций входных топиков: задачи Streams привязаны к партициям, поэтому запустить можно столько экземпляров приложения, сколько партиций. Локальное состояние (RocksDB на диске инстанса) дублируется в changelog-топик с компакцией — это делает stateful-обработку отказоустойчивой: упал инстанс, его партиции и состояние подхватит другой, восстановив RocksDB-хранилище из changelog. По сути Streams строит exactly-once конвейер поверх транзакций Kafka из предыдущего раздела.

Частые ошибки

  • Агрегировать без окон. «Сумма за всё время» по бесконечному потоку растёт неограниченно; задавайте окна.
  • Джойнить потоки с разным ключом партиционирования. Для джойна данные должны быть co-partitioned (одинаковый ключ и число партиций).
  • Игнорировать changelog-топик. Без него локальное состояние не восстановить после падения инстанса.

Итоги

  • Kafka Streams считает над потоком прямо в приложении: filter, агрегации, джойны, окна.
  • KStream — поток фактов, KTable — таблица состояний; они взаимно преобразуемы.
  • Stateful-состояние живёт в RocksDB и зеркалится в changelog-топик ради отказоустойчивости.
Проверьте себя
1. Чем KTable отличается от KStream?
AKTable быстрее
BKStream — поток событий-фактов, KTable — таблица «ключ -> текущее значение»
CKTable не хранится в Kafka
DЭто синонимы
2. Зачем при агрегации потока нужны окна?
AДля красоты
BПоток бесконечен; окно («за 5 минут») даёт конкретный, завершённый ответ
CОкна ускоряют запись
DБез окон Kafka падает
3. Как Kafka Streams восстанавливает состояние после падения инстанса?
AПересчитывает с нуля каждый раз
BИз compacted changelog-топика, в который зеркалится локальное RocksDB-хранилище
CСостояние теряется навсегда
DИз ZooKeeper