Текст
                    
Apache Kafka in Action FROM BASICS TO PRODUCTION A NATOLY ZELENIN, A LEXANDER K ROPP FOREWORD BY A DAM BELLEMARE
Apache Kafka в действии О Т БАЗОВЫХ КОНЦЕПЦИЙ ДО ПРОДАКШЕНА А НАТОЛИЙ ЗЕЛЕНИН, А ЛЕКСАНДР К РОПП ПРЕДИСЛОВИЕ А ДАМА БЕЛЬМАРА 2026
ББК 32.973.233 УДК 004.042 З-48 Зеленин Анатолий, Кропп Александр З-48 Apache Kafka в действии. От базовых концепций до продакшена. — СПб.: Питер, 2026. — 384 с.: ил. — (Серия «Библиотека программиста»). ISBN 978-5-4461-4491-4 Apache Kafka — это эталонная платформа для аналитики в реальном времени, передачи событий и потоковой обработки. Выступая в качестве центрального узла для распределенных данных, Kafka обес­ печивает беспрепятственный обмен между продюсерами и потребителями по модели «издатель — под­ писчик». Kafka легко обрабатывает миллионы событий в секунду, а его надежная архитектура гарантирует высокую отказоустойчивость и масштабируемость. «Apache Kafka в действии» — это практическое руководство для ИТ-специалистов, которые внедряют Kafka в приложения и инфраструктуры, обрабатывающие огромные объемы данных. Издание охваты­ вает все ключевые темы — от основ Kafka до продвинутых операций, а теория дополнена наглядными иллюстрациями и примерами из ежедневной практики. Читатели научатся настраивать кластеры Kafka, производить и потреблять сообщения, обрабатывать потоковые данные в реальном времени, а также ин­ тегрировать Kafka в корпоративные системы. В книге доступно изложены принципы создания надежных приложений Kafka и преимущества распределенной архитектуры для масштабируемости и устойчивости. 16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.) ББК 32.973.233 УДК 004.042 Права на издание получены по соглашению с Manning Publications. Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав. Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги. В книге возможны упоминания организаций, деятельность которых запрещена на территории Российской Федерации, таких как Meta Platforms Inc., Facebook, Instagram и др. Издательство не несет ответственности за доступность материалов, ссылки на которые вы можете найти в этой книге. На момент подготовки книги к изданию все ссылки на интернет-ресурсы были действующими. ISBN 978-1633437593 англ. ISBN 978-5-4461-4491-4 Authorized translation of the English edition © 2025 Manning Publications. This translation is published and sold by permission of Manning Publications, the owner of all rights to publish and sell the same. © Перевод на русский язык ООО «Прогресс книга», 2026 © Издание на русском языке, оформление ООО «Прогресс книга», 2026 © Серия «Библиотека программиста», 2026
Краткое содержание Часть I Начало работы Глава 1. Введение в Apache Kafka.............................................................................................24 Глава 2. Первые шаги в работе с Kafka....................................................................................34 Часть II Основные концепции Глава 3. Топики и сообщения в Kafka.....................................................................................44 Глава 4. Kafka как распределенный лог..................................................................................59 Глава 5. Надежность......................................................................................................................84 Глава 6. Производительность.................................................................................................. 107 Часть III Внутренние механизмы Kafka Глава 7. Управление кластером.............................................................................................. 128 Глава 8. Создание и сохранение сообщений...................................................................... 139 Глава 9. Потребление сообщений.......................................................................................... 160 Глава 10. Очистка сообщений................................................................................................. 177 Часть IV Kafka в корпоративной среде Глава 11. Интеграция внешних систем с помощью Kafka Connect............................ 193 Глава 12. Потоковая обработка............................................................................................... 223 Глава 13. Управление................................................................................................................. 254 Глава 14. Эталонная архитектура Kafka.............................................................................. 279 Глава 15. Мониторинг и оповещения в Kafka.................................................................... 294 Глава 16. Управление аварийными ситуациями.............................................................. 316 Глава 17. Сравнение Kafka с другими технологиями..................................................... 331 Глава 18. Корпоративные архитектуры............................................................................... 347 Приложения Приложение А. Настройка тестового окружения Kafka............................................... 368 Приложение Б. Настройка мониторинга............................................................................ 374
Оглавление Предисловие.....................................................................................................................................14 Введение.............................................................................................................................................16 О книге.........................................................................................................................................17 Для кого эта книга...........................................................................................................17 Структура издания.........................................................................................................17 О коде...........................................................................................................................................18 Благодарности..................................................................................................................................19 От издательства.........................................................................................................................20 О научном редакторе русскоязычного издания...................................................20 Об авторах.........................................................................................................................................21 Иллюстрация на обложке............................................................................................................22 Часть I Начало работы Глава 1. Введение в Apache Kafka...........................................................................................24 1.1. Что такое Apache Kafka и как он решает наши проблемы..................................25 1.2. Kafka в корпоративных экосистемах.........................................................................26 1.3. Архитектура Kafka............................................................................................................29 1.4. Запуск и использование Kafka.....................................................................................31 1.5. Траектория обучения......................................................................................................32 Резюме..........................................................................................................................................33 Глава 2. Первые шаги в работе с Kafka.................................................................................34 2.1. Пример использования...................................................................................................34 2.2. Создание сообщений.......................................................................................................35 2.3. Потребление сообщений................................................................................................36 2.4. Параллельное потребление и генерация сообщений...........................................37 2.5. Графические интерфейсы пользователя в Kafka...................................................40 Резюме..........................................................................................................................................42
   Оглавление 7 Часть II Основные концепции Глава 3. Топики и сообщения в Kafka....................................................................................44 3.1. Топики..................................................................................................................................44 3.1.1. Просмотр топиков................................................................................................45 3.1.2. Создание, настройка и удаление топиков....................................................48 3.2. Сообщения..........................................................................................................................51 3.2.1. Типы сообщений...................................................................................................52 3.2.2. Форматы данных..................................................................................................54 3.2.3. Структура сообщений.........................................................................................55 Резюме..........................................................................................................................................57 Глава 4. Kafka как распределенный лог................................................................................59 4.1. Логи.......................................................................................................................................59 4.1.1. Что такое лог..........................................................................................................60 4.1.2. Основные свойства лога....................................................................................61 4.1.3. Kafka как лог..........................................................................................................62 4.2. Kafka как распределенная система.............................................................................64 4.2.1. Партиционирование и ключи..........................................................................65 4.2.2. Группы потребителей.........................................................................................70 4.2.3. Репликация.............................................................................................................74 4.3. Компоненты Kafka............................................................................................................77 4.3.1. Координационный кластер...............................................................................78 4.3.2. Брокер......................................................................................................................79 4.3.3. Клиенты...................................................................................................................79 4.4. Kafka в корпоративной среде........................................................................................80 Резюме..........................................................................................................................................82 Глава 5. Надежность.....................................................................................................................84 5.1. Подтверждения..................................................................................................................85 5.1.1. Стратегии подтверждения в Kafka.................................................................86 5.1.2. Подтверждения и синхронизированные реплики....................................87 5.1.3. Гарантии доставки сообщений в Kafka.........................................................91 5.2. Транзакции.........................................................................................................................94 5.2.1. Транзакции в базе данных.................................................................................95 5.2.2. Транзакции в Kafka.............................................................................................96 5.2.3. Транзакции и потребители...............................................................................97 5.3. Репликация и принцип «лидер — фолловеры»......................................................99 Резюме....................................................................................................................................... 105
   8 Оглавление Глава 6. Производительность................................................................................................. 107 6.1. Настройка топиков для повышения производительности............................. 109 6.1.1. Масштабирование и балансировка нагрузки.......................................... 109 6.1.2. Определение необходимого количества партиций............................... 110 6.1.3. Изменение количества партиций................................................................ 112 6.2. Производительность продюсера............................................................................... 115 6.2.1. Конфигурация продюсера.............................................................................. 115 6.2.2. Тестирование производительности продюсера...................................... 118 6.3. Конфигурация и оптимизация брокеров............................................................... 120 6.3.1. Оптимизация брокеров................................................................................... 121 6.3.2. Определение количества брокеров и их размера................................... 122 6.4. Производительность потребителей......................................................................... 123 6.4.1. Конфигурация потребителей........................................................................ 123 6.4.2. Тестирование производительности потребителей................................ 123 Резюме....................................................................................................................................... 125 Часть III Внутренние механизмы Kafka Глава 7. Управление кластером............................................................................................. 128 7.1. Управление кластером с помощью Apache Kafka Raft..................................... 129 7.2. Управление кластером с помощью ZooKeeper.................................................... 131 7.3. Миграция с ZooKeeper на KRaft............................................................................... 133 7.4. Подключение к кластеру Kafka................................................................................. 134 Резюме....................................................................................................................................... 137 Глава 8. Создание и сохранение сообщений.................................................................... 139 8.1. Продюсер.......................................................................................................................... 140 8.1.1. Генерация сообщений...................................................................................... 140 8.1.2. Процесс производства сообщений.............................................................. 142 8.1.3. Продюсер и подтверждения.......................................................................... 142 8.2. Брокер................................................................................................................................ 144 8.2.1. Прием и сохранение сообщений.................................................................. 144 8.2.2. Брокеры и подтверждения............................................................................. 145 8.3. Структуры данных и файлов..................................................................................... 146 8.3.1. Метаданные, контрольные точки и топики............................................. 146 8.3.2. Каталог партиций.............................................................................................. 147 8.3.3. Данные логов и индексы................................................................................. 150 8.3.4. Сегменты.............................................................................................................. 152 8.3.5. Удаленные топики............................................................................................ 153 8.4. Репликация...................................................................................................................... 154 8.4.1. Синхронизированные реплики.................................................................... 154
   Оглавление 9 8.4.2. High Watermark................................................................................................. 155 8.4.3. Влияние задержек при репликации............................................................ 157 Резюме....................................................................................................................................... 158 Глава 9. Потребление сообщений......................................................................................... 160 9.1. Получение сообщений................................................................................................. 161 9.1.1. Запросы fetch...................................................................................................... 161 9.1.2. Получение данных из ближайшей реплики............................................. 162 9.2. Обработка fetch-запросов от потребителей брокером...................................... 162 9.3. Смещения и потребитель............................................................................................ 164 9.3.1. Управление смещениями............................................................................... 164 9.3.2. Смещения в Kafka............................................................................................. 165 9.4. Группы потребителей Kafka....................................................................................... 168 9.4.1. Управление группами потребителей.......................................................... 168 9.4.2. Распределение партиций между потребителями................................... 171 9.4.3. Статическое членство...................................................................................... 174 Резюме....................................................................................................................................... 175 Глава 10. Очистка сообщений................................................................................................ 177 10.1. Зачем удалять сообщения........................................................................................ 177 10.2. Методы очистки в Kafka........................................................................................... 178 10.3. Удаление логов по возрасту и размеру................................................................ 179 10.3.1. Когда происходит очистка логов............................................................... 180 10.3.2. Хранение смещений....................................................................................... 182 10.4. Сжатие логов................................................................................................................. 182 10.4.1. Когда происходит очистка логов при использовании сжатия............................................................................................................................... 184 10.4.2. Как работает очиститель логов.................................................................. 187 10.4.3. Tombstone.......................................................................................................... 189 Резюме....................................................................................................................................... 189 Часть IV Kafka в корпоративной среде Глава 11. Интеграция внешних систем с помощью Kafka Connect......................... 193 11.1. Что такое Kafka Connect........................................................................................... 194 11.2. Кластер Kafka Connect: распределенный режим.............................................. 196 11.2.1. Настройка кластера Kafka Connect.......................................................... 196 11.2.2. Создание коннектора..................................................................................... 197 11.2.3. Тестирование коннектора............................................................................ 198 11.3. Масштабируемость и отказоустойчивость Kafka Connect........................... 199 11.4. Конфигурация исполнителей................................................................................. 200
   10 Оглавление 11.5. REST API Kafka Connect.......................................................................................... 203 11.5.1. Состояние кластера Kafka Connect........................................................... 204 11.5.2. Создание, изменение и удаление коннекторов..................................... 206 11.6. Конфигурация коннектора...................................................................................... 208 11.6.1. Общая конфигурация коннектора............................................................ 208 11.6.2. Обработка ошибок в Kafka Connect......................................................... 209 11.7. Преобразования отдельных сообщений.............................................................. 211 11.8. Пример Kafka Connect: коннектор-источник JDBC....................................... 213 11.8.1. Подготовка коннектора-источника JDBC............................................. 213 11.8.2. Настройка коннектора-источника JDBC............................................... 214 11.8.3. Источник-коннектор JDBC......................................................................... 215 11.9. Пример Kafka Connect: коннектор отслеживания изменений данных.... 217 11.9.1. Подготовка коннектора Debezium для PostgreSQL............................ 217 11.9.2. Настройка коннектора Debezium для PostgreSQL.............................. 218 11.9.3. Тестирование коннектора Debezium для PostgreSQL....................... 219 Резюме....................................................................................................................................... 221 Глава 12. Потоковая обработка............................................................................................. 223 12.1. Обзор потоковой обработки.................................................................................... 224 12.1.1. Библиотеки для потоковой обработки данных.................................... 225 12.1.2. Обработка данных.......................................................................................... 226 12.2. Потоковые обработчики........................................................................................... 227 12.2.1. Типы обработчиков........................................................................................ 228 12.2.2. Топологии обработчиков............................................................................. 231 12.3. Потоковая обработка с использованием SQL................................................... 232 12.4. Состояния потоков..................................................................................................... 234 12.4.1. Потоки и таблицы........................................................................................... 235 12.4.2. Агрегации........................................................................................................... 237 12.4.3. Соединения потоков...................................................................................... 238 12.4.4. Пример использования: уведомления..................................................... 241 12.5. Потоковая обработка и время................................................................................. 244 12.5.1. Время относительно....................................................................................... 244 12.5.2. Временные окна............................................................................................... 245 12.5.3. Пример использования: обнаружение мошенничества..................... 247 12.6. Масштабирование Kafka Streams........................................................................... 248 Резюме....................................................................................................................................... 251 Глава 13. Управление................................................................................................................. 254 13.1. Управление схемами.................................................................................................. 255 13.1.1. Зачем нужны схемы....................................................................................... 256 13.1.2. Уровни совместимости................................................................................. 257
   Оглавление 11 13.1.3. Реестры схем..................................................................................................... 261 13.1.4. Avro...................................................................................................................... 263 13.2. Безопасность................................................................................................................. 264 13.2.1. Шифрование на транспортном уровне.................................................... 265 13.2.2. Аутентификация............................................................................................. 267 13.2.3. Авторизация...................................................................................................... 268 13.2.4. Шифрование при хранении........................................................................ 270 13.2.5. Сквозное шифрование.................................................................................. 271 13.2.6. Безопасность в ZooKeeper............................................................................ 271 13.2.7. Защита незащищенного кластера Kafka................................................. 272 13.3. Квоты в Kafka: защита кластера от перегрузки................................................. 273 Резюме....................................................................................................................................... 277 Глава 14. Эталонная архитектура Kafka............................................................................ 279 14.1. Полезные компоненты и инструменты................................................................ 280 14.1.1. kcat....................................................................................................................... 281 14.1.2. Графические интерфейсы пользователя................................................. 282 14.1.3. Управление ресурсами Kafka...................................................................... 283 14.1.4. Cruise Control for Apache Kafka.................................................................. 283 14.2. Окружения развертывания...................................................................................... 285 14.2.1. Kafka на собственном оборудовании компании................................... 285 14.2.2. Kafka в виртуализированных окружениях............................................ 286 14.2.3. Kafka в Kubernetes: Strimzi.......................................................................... 287 14.2.4. Запуск Kafka в публичном облаке............................................................ 288 14.3. Требования к оборудованию................................................................................... 289 14.3.1. Брокеры.............................................................................................................. 290 14.3.2. Координационный кластер.......................................................................... 292 Резюме....................................................................................................................................... 292 Глава 15. Мониторинг и оповещения в Kafka.................................................................. 294 15.1. Метрики инфраструктуры....................................................................................... 295 15.2. Метрики брокеров....................................................................................................... 296 15.2.1. Метрики сервера Kafka................................................................................. 296 15.2.2. Метрики лога Kafka........................................................................................ 298 15.2.3. Метрики сети Kafka....................................................................................... 298 15.2.4. Метрики контроллера Kafka....................................................................... 299 15.3. Метрики клиента......................................................................................................... 301 15.3.1. Общие метрики клиентов............................................................................ 301 15.3.2. Метрики продюсера....................................................................................... 303 15.3.3. Метрики потребителей................................................................................. 304 15.3.4. Метрики Kafka Connect и Kafka Streams................................................ 307
   12 Оглавление 15.4. Оповещения.................................................................................................................. 309 15.4.1. Настройка оповещений на основе метрик............................................. 309 15.4.2. Решение проблем с помощью оповещений............................................ 310 15.5. Окружения развертывания Kafka и сложности их мониторинга............... 311 15.5.1. Kafka на собственном оборудовании компании................................... 311 15.5.2. Kafka на виртуальных машинах................................................................. 311 15.5.3. Kafka в публичном облаке............................................................................ 312 15.5.4. Kafka в Kubernetes.......................................................................................... 312 15.5.5. Kafka как управляемый сервис.................................................................. 313 15.5.6. Вопросы безопасности в различных средах........................................... 313 Резюме....................................................................................................................................... 313 Глава 16. Управление аварийными ситуациями.............................................................. 316 16.1. Источники и виды сбоев........................................................................................... 317 16.1.1. Сбои в сети........................................................................................................ 317 16.1.2. Сбои вычислительных ресурсов................................................................ 318 16.1.3. Сбои хранилища.............................................................................................. 320 16.1.4. Сбои в центре обработки данных.............................................................. 320 16.2. Резервное копирование в Kafka.............................................................................. 323 16.3. Зеркалирование кластеров Kafka с помощью MirrorMaker......................... 324 16.3.1. Активный/пассивный кластер................................................................... 325 16.3.2. Активный/активный кластер..................................................................... 326 16.3.3. Топология «звезда»........................................................................................ 327 Резюме....................................................................................................................................... 329 Глава 17. Сравнение Kafka с другими технологиями.................................................... 331 17.1. Данные внутри и снаружи........................................................................................ 332 17.2. Kafka и классические системы обмена сообщениями.................................... 333 17.2.1. Универсальность Kafka................................................................................. 334 17.2.2. Операционная сложность в классических системах обмена сообщениями................................................................................................................. 335 17.2.3. Управление классическими системами обмена сообщениями....... 336 17.3. REST и Kafka................................................................................................................ 338 17.3.1. Проблемы синхронного взаимодействия............................................... 338 17.3.2. Альтернативные стратегии взаимодействия......................................... 339 17.4. Реляционные базы данных и Kafka....................................................................... 340 17.4.1. Сильные и слабые стороны реляционных баз данных...................... 340 17.4.2. Комбинация Kafka и реляционных баз данных в современных архитектурах.................................................................................... 341 17.5. Kafka как ядро платформы потоковой обработки данных............................ 343 Резюме....................................................................................................................................... 345
   Оглавление 13 Глава 18. Корпоративные архитектуры............................................................................. 347 18.1. Kafka как основа Data Mesh..................................................................................... 348 18.1.1. Сложности традиционного управления данными.............................. 348 18.1.2. Принципы Data Mesh.................................................................................... 348 18.1.3. Data Mesh и традиционные подходы....................................................... 349 18.1.4. Роль Kafka в Data Mesh................................................................................ 351 18.2. Освобождение данных из основных систем с помощью Kafka................... 353 18.3. Kafka для больших данных...................................................................................... 355 18.4. Kafka для промышленного Интернета вещей................................................... 356 18.4.1. Применение Kafka в промышленном Интернете вещей................... 356 18.4.2. Хранение данных............................................................................................ 357 18.4.3. Интеграция данных и управление доступом......................................... 358 18.4.4. Когда использовать несколько кластеров Kafka.................................. 359 18.5. С чем не стоит путать Kafka..................................................................................... 360 18.5.1. Kafka — это не реляционная база данных............................................... 360 18.5.2. Kafka — это не синхронный интерфейс связи....................................... 361 18.5.3. Kafka — это не платформа для обмена файлами.................................. 362 18.5.4. Kafka для небольших приложений — под вопросом........................... 363 18.5.5. Kafka не замена хорошей архитектуре..................................................... 364 Резюме....................................................................................................................................... 365 Приложения Приложение А. Настройка тестового окружения Kafka............................................ 368 A.1. Операционные системы.............................................................................................. 369 A.2. Скачивание Kafka......................................................................................................... 369 A.3. Конфигурация Kafka................................................................................................... 370 A.4. Подготовка каталогов данных.................................................................................. 370 A.5. Запуск Kafka................................................................................................................... 371 A.6. Остановка Kafka............................................................................................................ 372 Приложение Б. Настройка мониторинга........................................................................... 374 Б.1. Prometheus....................................................................................................................... 374 Б.2. Экспортер для Prometheus......................................................................................... 376 Б.3. Prometheus Alertmanager............................................................................................ 379 Б.4. Grafana.............................................................................................................................. 380
Предисловие Я начал работать с Apache Kafka в начале 2015 года с версии 0.8.2. Периодически я вношу свой вклад в развитие платформы и активно наблюдаю за этим процессом. За прошедшие годы я не раз видел, как организации внедряют Apache Kafka и добиваются успеха. Спустя десятилетие Kafka заметно отличается от своей первоначальной версии. В сообществе специалистов, использующих его на практике, придумывали и реализовывали всевозможные варианты развертывания. Опыт реального применения привел к появлению практических возможностей, которые стали неотъемлемой частью ядра проекта Kafka. В этой книге Анатолий Зеленин и Александр Кропп делятся своим многолетним практическим опытом работы с Kafka. Анатолий, преподаватель и основатель DataFlow Academy, обучает людей по всему миру не только эффективному использованию Kafka, но и интеграции вспомогательных компонентов: Kafka Connect, MirrorMaker, реестров схем, систем мониторинга и других. Алекс — опытный практик. Работая как консультант, он помогает компаниям внедрять облачные платформы для потоковой обработки данных, оснащенные современными инструментами. Он специализируется на Kubernetes и модели «инфраструктура как код» (Infrastructure as Code, IaC). Вместе Анатолий и Алекс создали превосходное руководство по Apache Kafka. Их первую книгу, выпущенную в 2021 году, вы (как и я) могли пропустить — если только не говорите по-немецки. К счастью, авторы решили выпустить обновленное издание, в котором учитывается множество изменений, произошедших в экосистеме Apache Kafka за последние годы. Одно из главных достоинств этой книги — она подходит для знакомства с Kafka. В первых главах вы узнаете о потоковой обработке событий и ее важности в современном мире. Имея возможность реагировать на изменения в бизнес-процессах практически мгновенно, вы получаете немало преимуществ, а также доступ
   Предисловие 15 к эффективным архитектурным паттернам и множеству новых функций. Если вы никогда не работали с Apache Kafka — эта книга для вас. В то же время книга будет полезна и экспертам. Анатолий и Алекс рассматривают в ней многие сложные вопросы управления и эксплуатации множества кластеров, распределенных по всему миру. Репликация данных, аварийное восстановление, превентивный мониторинг — лишь часть тем, которые поднимают авторы. Наконец, Анатолий и Алекс делятся полезными советами по оптимизации кластеров и приложений, снижению затрат и поддержанию работоспособности потоков событий. Уверен, это издание станет вашей настольной книгой. Адам Беллемар, автор книги Building Event-Driven Microservices1 1 Беллемар А. Создание событийно-управляемых микросервисов. Масштабирование использования организационных данных. — СПб., 2022.
Введение Мы работаем с Apache Kafka уже много лет — Анатолий как преподаватель и практик, а Алекс как консультант по Kafka. Когда немецкое издательство Hanser предложило нам написать книгу на эту тему, мы согласились, решив, что это будет легко. Мы и не подозревали, что работа над первым изданием — на немецком языке — займет почти два года интенсивных исследований архитектурных паттернов, технических деталей и писательских приемов. Отклик читателей превзошел наши ожидания. Мы написали книгу впервые, поэтому нас поразили фантастические отзывы, и мы были рады продуктивному общению с нашей аудиторией. Однако нас постоянно спрашивали, почему мы написали книгу на немецком языке и когда она выйдет на английском. Кроме того, нам казалось, что в книге чего-то недостает. Нам не хватило времени по­ дробно описать ключевую тему: как успешно интегрировать Kafka в организации. Вопросы о роли Kafka в организации, архитектурных адаптациях и стратегиях внедрения остались без ответа. Когда издательство Manning обратилось к нам с предложением о сотрудни­ честве по книге «Apache Kafka в действии», мы увидели идеальную возможность. Мы предложили перевести и дополнить наше немецкое издание, вместо того чтобы работать с уже существующей книгой, которую Manning опубликовало ранее. Так мы получили шанс не только обновить материал в соответствии с текущим состоянием Kafka и добавить недостающую информацию, но и сделать книгу доступной по всему миру. И вновь мы недооценили масштаб проекта! Мы приложили множество усилий, и теперь в вашем распоряжении полностью переработанная и дополненная версия книги «Apache Kafka в действии». Мы надеемся, что она окажется полезной для ваших проектов в сфере обработки данных в реальном времени. Если встретите нас на конференциях или онлайн — не стесняйтесь поздороваться и связаться с нами! Мы всегда рады обмениваться идеями с сообществом Kafka.
   Введение 17 О книге Мы написали эту книгу, чтобы поделиться практическим опытом и помочь вам познакомиться с Kafka. За годы обучения специалистов и внедрения Kafka в организациях мы поняли, что работает, а что — нет. Основное внимание в книге уделяется приобретению вами практических навыков — от базовых действий до управления производственными системами, — но в ней также представлены теоретические основы, знать которые необходимо для успешной работы с Kafka. Считайте эту книгу руководством, которого так не хватало нам самим, когда мы впервые начали работать с Kafka. В книге вы найдете наглядные схемы, практические советы и простые примеры кода, которые можно сразу применить на практике. Для кого эта книга Мы написали эту книгу для ИТ-специалистов, которые только знакомятся с Kafka или уже какое-то время работают с этой платформой. Будет хорошо, если вы имеете общее предоставление о современных ИТ-архитектурах и распределенных системах, но предварительный опыт работы с Kafka не требуется. На каком бы этапе профессионального пути вы ни находились, мы поможем вам сделать следующий шаг. Разработчики, системные администраторы, архитекторы и технические руководители, которые хотят улучшить свою инфраструктуру данных, найдут в книге практические рекомендации. Даже эксперты по Kafka откроют для себя что-то новое и неожиданное, исследуя сложные паттерны, организационные задачи и малоизвестные функции. Структура издания Книга состоит из четырех частей и 18 глав. Каждая глава логически продолжает предыдущую, что помогает читателю сформировать целостное представление о Kafka — от базовых принципов до внедрения в корпоративных средах. Часть I «Начало работы» (главы 1–2) — вводная. Вы узнаете, что такое платформа Apache Kafka, как она устроена и какую роль играет в современных архитектурах данных. На практических примерах вы научитесь выполнять базовые операции, такие как работа с топиками, продюсерами и потребителями. Часть II «Основные концепции» (главы 3–6) посвящена ключевым компонентам Kafka. Мы подробно рассмотрим топики и сообщения, проанализируем роль Kafka как распределенного лога и выясним, как система обеспечивает надежность, используя репликацию и транзакции. В завершение мы проведем углубленный анализ оптимизации производительности, охватывающий такие важные вопросы, как стратегии партиционирования и настройка конфигураций.
   18 Введение В части III «Внутренние механизмы Kafka» (главы 7–10) раскрываются принципы работы Kafka. Вы изучите тонкости управления кластером, узнаете, как сообщения создаются и сохраняются, каковы механизмы их потребления, а также освоите стратегии очистки данных. В этой части описываются технические подробности, которые нужно знать для устранения проблем и оптимизации развертывания Kafka. В части IV «Kafka в корпоративной среде» (главы 11–18) мы перейдем от тео­ рии к практической реализации. Мы рассмотрим ключевые темы, такие как интеграция систем с использованием Kafka Connect, потоковая обработка, управление данными и эталонные архитектуры. Вы получите практические навыки мониторинга, выполнения аварийного восстановления, а также научитесь выбирать нужный вам вариант технологии. Часть завершается рекомендациями о том, как избежать типичных ошибок и успешно реализовать Kafka в современных архитектурах. Книгу лучше читать по порядку, но опытные специалисты могут обращаться к главам выборочно. Если вы уже знакомы с основами Kafka, то можете бегло просмотреть часть I, но рекомендуем изучить также часть II, поскольку в ней содержатся идеи, которые могут оказаться полезными даже для опытных пользователей. Технические руководители, отвечающие за архитектурные решения, могут прочесть части I, II и IV, при необходимости обращаясь к некоторым техническим подробностям, описанным в части III. В книге вы найдете множество практических примеров, демонстрирующих, как рассматриваемые концепции можно применять в реальных сценариях. Если вы только начинаете работать с Kafka или ищете руководство по настройке, то в приложении A вы найдете подробные инструкции по созданию тестового окружения, а в приложении Б — рекомендации по настройке мониторинга. Изучать справочные материалы в приложениях можно по мере необходимости. О коде В книге содержится множество примеров исходного кода как в листингах, так и среди обычного текста. В обоих случаях он будет отформатирован моноширинным шрифтом. Во многих случаях мы переформатировали исходный код: добавили разрывы строк и переделали отступы, чтобы он уместился на странице. В редких случаях даже этого оказалось недостаточно, и листинги содержат маркеры разрыва строки (➥). Многие листинги снабжены аннотациями, в которых описаны важные концепции. Исполняемые фрагменты кода доступны в онлайн-версии книги на платформе liveBook по адресу https://livebook.manning.com/book/apachekafka-in-action.
Благодарности В первую очередь мы благодарим наши семьи и друзей за то, что неизменно поддерживали нас, пока мы работали над этой книгой. Ваша помощь была бесценной. Мы глубоко признательны сообществу Kafka — ваши совместные усилия превратили Kafka в выдающийся проект, о котором стоит писать книги. Особого признания заслуживают участники наших тренингов и клиенты. Благодаря опыту работу с вами и, конечно, финансовой поддержке мы смогли написать эту книгу. Мы выражаем благодарность всем, кто поддержал выпуск первого, немецкого издания в издательстве Hanser, и особенно признательны Сильвии Хассельбах, чье терпение и готовность помочь позволили нам успешно довести этот проект до конца. Кроме того, мы благодарны команде издательства Manning, особенно нашему редактору Коннору О’Брайену, комментарии, замечания и предложения которого значительно улучшили книгу. Благодарим Пурушотхама Чикканаяканалли Кришнеговду, опытного ИТ-специалиста, который уже более 20 лет работает с такими технологиями, как Apache Kafka, Java, Spring и микросервисы, и выступил научным редактором и корректором этой книги. Благодаря его тщательным проверкам и вниманию к деталям текст стал более точным и понятным. Мы хотели бы искренне поблагодарить Адама Беллемара за превосходное предисловие к этой книге. Выражаем отдельную благодарность редакторской и производственной командам издательства Manning, которые помогли опубликовать эту книгу. Их внимание к деталям и терпеливое отношение к нашим вопросам позволили нам внести множество улучшений в это издание. Наконец, мы благодарим всех рецензентов как первого издания на немецком языке, так и этой книги. Ваш усердный труд и ценные замечания сделали эту
   20 Благодарности книгу лучше: Инга Блюндель, Вальтер Форкель, Даниэла Гризингер, Тобиас Хеллер, Винсент Лацко, Андрей Олунчек, Элин Риксман, Томас Треппер, Дэвид Вебер. Спасибо рецензентам из Manning, среди которых Афшин Пайдар, А. Дж. Бхандал, Эл Пезевски, Алексей Артемов, Алиреза Агамохаммади, Анандаганеш Балакришнан, Андре Шефер, Андрес Сакко, Энтони Нанда, Асиф Икбал, Бассам Исмаил, Кристиан Тоудаль, Дэвид Глойн-Кокс, Габор Ласло Хайба, Ганеш Сваминатхан, Гатикрушна Саху, Джильберто Таккари, Константин Глумов, Джереми Чен, Джим Уитфилд, Жорже Бо, Джозеф Паход, Джастин Рейзер, Кристина Касаничова, Лакшминараянан А. С, Маддула Арати, Мануэл Ребелло де Андраде, Марк Дешам, Маттиас Дж. Сакс, Максим Волгин, Майкл Хайль, Михаил Малев, Милорад Имбра, Онофрей Джордж, Петер Сабо, Радждип Гурмит, Рамбабу Поса, Ричард Майнзен, Рональд Харинг, Шарат Чандра Парашара, Симон Верховен, Симоне Сгуазза, Шрихари Шридхаран, Стив Гудман, Сумит Пал, Тоби Лазар, Венката Янамадала, Виктория Долженко, Винисиус Венц, Вивек Лакханпал, Уильям Жамир Силва, Уильям Уолш, Йогеш Шетти, Зородзайи Мукуйя. От издательства Мы выражаем огромную благодарность клубу рецензентов ИТ-литературы ReadIT Club за помощь в работе над русскоязычным изданием книги и их вклад в повышение качества переводной литературы. Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com (издательство «Питер», компьютерная редакция). Мы будем рады узнать ваше мнение! На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах. О научном редакторе русскоязычного издания Дмитрий Богатырь — руководитель группы логирования в одном из крупнейших маркетплейсов. Более восьми лет внедряет DevOps-практики и строит высоконагруженные системы. Участвовал в создании инфраструктуры с нуля и принятии ключевых архитектурных решений. Проектирует и развивает отказоустойчивые высоконагруженные кластеры Kafka, а также занимается потоковой обработкой данных в масштабах крупного e-commerce.
Об авторах Александр Кропп работает с Apache Kafka и Kubernetes, активно участвуя в проектировании облачных платформ и создании эффективных систем мониторинга. Опираясь на свой многолетний опыт в качестве архитектора и преподавателя в сфере коммуникационных сетей и облачных вычислений, он творчески подходит к решению сложных задач. Анатолий Зеленин — известный эксперт и преподаватель по Apache Kafka. Он славится своими интерактивными мастер-классами, которые привлекают клиентов из различных отраслей по всей Европе, особенно из банковского и промышленного секторов. Помимо работы ИТ-консультантом и преподавателем, Анатолий увлекается путешествиями, исследуя разные уголки нашей планеты. Вместе Александр и Анатолий работают в DataFlow Academy — ведущей европейской компании в области обучения и консалтинга. Она специализируется на технологиях обработки данных в реальном времени, делая упор на практических и интерактивных обучающих программах.
Иллюстрация на обложке Иллюстрация называется La Nocellara и взята из книги Франческо де Буркарда Uses and costumes of Naples and surroundings described and painted, опубликованной в 1853 году и посвященной традициям Неаполитанского королевства XIX века. В те времена по одежде можно было легко определить род занятий, социальный статус или место проживания. Издательство Manning отдает дань изобретательности и инициативности ИТ-индустрии, вдохновляясь при оформлении обложек многовековым разнообразием региональных культурных традиций.
Часть I Начало работы Apache Kafka — фундаментальная технология в динамичной сфере обработки данных и интеграции систем. Но с чего начать и как использовать весь потенциал платформы в своих приложениях и рабочих процессах? В первой части мы рассмотрим основные элементы, необходимые для запуска и работы Kafka. Начиная с введения в Kafka, мы исследуем его ключевые концепции, компоненты и место в современных корпоративных экосистемах. Мы обсудим, что такое Kafka, где он применяется и как поддерживает работу архитектур, ориентированных на данные. В главе 1 мы рассмотрим архитектуру Kafka и взаимодействие ее компонентов — продюсеров, потребителей, топиков и брокеров, — позволяющее обеспечить масштабируемый и отказоустойчивый обмен сообщениями. В главе 2 мы поговорим об основных операциях Kafka: создании топиков, генерации и потреблении сообщений, использовании инструментов командной строки. В этой части вы сможете на практике поработать с базовой конфигурацией Kafka и подготовитесь к изучению более сложных тем, изложенных в последующих главах.
1 Введение в Apache Kafka В этой главе 3 Общее описание и варианты применения Apache Kafka. 3 Место Kafka в корпоративной экосистеме. 3 Архитектура Kafka. 3 Запуск и использование Kafka. Современные корпоративные приложения часто состоят из независимых компонентов и сервисов, взаимодействующих путем обмена сообщениями. Чем больше серверов, дата-центров, облачных платформ и географических регионов включает в себя система, тем сложнее управлять потоками сообщений. Дополнительные сложности возникают из-за требований к надежности, отказоустойчивости и работе почти в реальном времени. Apache Kafka — высокопроизводительная распределенная платформа обмена сообщениями, способная эффективно обрабатывать огромные потоки данных в системах с высокими требованиями. Apache Kafka — это также распределенный программный брокер сообщений с открытым исходным кодом, разрабатываемый в рамках фонда Apache на языках Java и Scala. В этой главе мы поговорим о базовых компонентах Apache Kafka и их назначении, а также рассмотрим возможные сферы применения и, что немаловажно, место Kafka во всех экосистемах предприятия. Наконец, мы обсудим требования к использованию Kafka: аппаратное обеспечение, инструменты и языки программирования.
   Глава 1. Введение в Apache Kafka 25 1.1. Что такое Apache Kafka и как он решает наши проблемы Мы живем здесь и сейчас. Заказывая товары на маркетплейсе, мы ожидаем, что заказ будет подтвержден сразу же, а доставку можно будет отслеживать в реальном времени. Если с картой проводится подозрительная операция, то мы хотим узнать об этом сейчас, а не завтра. Как потребители мы привыкли к тому, что организации обрабатывают данные и реагируют на них мгновенно. Эти ожидания в сочетании с бурным ростом объемов данных — начиная от потоков информации, поступающей с датчиков Интернета вещей, и заканчивая взаимодействием потребителей с компанией по цифровым каналам — серьезно усложняют работу ИТ-команд. Традиционные архитектуры, ориентированные на пакетную обработку данных в конце дня, не вписываются в новую реальность, где счет идет на секунды. Крупным организациям с давно не модернизируемыми системами особенно важно перейти на работу с потоками данных в реальном времени, сохраняя надежность системы. При разделении монолитных приложений на распределенные сервисы сложность системы резко возрастает. Команды развивают сервисы независимо друг от друга, но при этом должны поддерживать надежный поток данных по всей организации. Как сохранить стабильность системы при взаимодействии десятков сервисов? Как гарантировать, что обновления не нарушат работу других компонентов? И как обрабатывать огромные потоки данных, циркулирующих между системами, не создавая при этом запутанную сеть из точечных интеграций? Для решения этих проблем Kafka предлагает архитектурный шаблон, выступая в роли центральной нервной системы для данных. Не случайно, по данным сообщества Kafka, более 80 % компаний из списка Fortune 100 используют Kafka для этих целей. Он стал основой современных распределенных архитектур, помогая обеспечивать независимое развитие сервисов и предотвращать системные сбои с помощью асинхронной коммуникации и перехода с пакетной обработки на потоковую. Kafka — это надежная платформа с открытым исходным кодом для распределенной потоковой обработки, кардинально меняющая подход к управлению потоками данных. По сути, Kafka выступает как постоянный распределенный лог для всех событий в организации. Представьте центральную магистраль, которая надежно хранит и в реальном времени обрабатывает всю значимую информацию — от действий клиентов до изменений состояния систем. Такой подход дает несколько важных преимуществ. Сохранение потоков данных позволяет не только обрабатывать их в реальном времени, но и воспроизводить. Восстановив работу после сбоя, системапотребитель может начать с того места, на котором остановилась. Новый
   26 Часть I. Начало работы сервис, требующий исторических данных, обработает прошлые события так же легко, как текущие. Благодаря своей распределенной архитектуре Kafka горизонтально масштабируется для работы с огромными объемами данных, не снижая отказоустойчивость. Один кластер Kafka обрабатывает миллионы событий в секунду, гарантируя, что данные не будут потеряны даже в случае сбоя отдельных частей системы. Благодаря этим возможностям Kafka стал незаменимым инструментом во многих отраслях. Финансовые учреждения используют его для обработки миллионов транзакций в реальном времени и синхронизации всех систем — от обнаружения мошенничества до уведомлений клиентов. Производители направляют через Kafka потоки данных с датчиков на тысячах устройств Интернета вещей, чтобы организовывать прогнозное обслуживание и мониторинг в реальном времени. В розничной торговле Kafka применяется для управления запасами и обработки заказов. Kafka реализует эти возможности через модель «публикация — подписка» (publisher-subscriber; pub/sub; издатель — подписчик) или, как принято ее называть в сообществе Kafka, модель «продюсер — потребитель». Продюсеры отправляют сообщения в определенные топики, а потребители обрабатывают их по мере необходимости. Но от традиционных систем обмена сообщениями Kafka отличается возможностью постоянного хранения данных (персистентностью) — данные, записанные в Kafka, можно считывать многократно в течение нескольких часов, дней или даже месяцев. Благодаря персистентности, распределенной архитектуре и дополнительным инструментам (Kafka Connect, Kafka Streams) Kafka из высокопроизводительного брокера сообщений для LinkedIn превратился в нечто гораздо более мощное: центральную нервную систему для корпоративных данных. Эта книга предназначена для ИТ-специалистов, которые хотят получить более глубокое представление о Kafka и способах его интеграции в существующие инфраструктуры. Она подойдет архитекторам, системным администраторам, разработчикам и инженерам данных. Это не пособие разработчика с примерами на Java. Здесь вы найдете практические рекомендации и советы по эффективному применению Kafka в своих проектах. Наша цель — дать вам знания и навыки по использованию Kafka для потоковой передачи данных и их обработки в реальном времени в различных ИТ-средах. 1.2. Kafka в корпоративных экосистемах Что мы имеем в виду, называя Kafka «центральной нервной системой для данных»? Идея заключается в том, что каждое событие (рис. 1.1) — значимое происшествие или инцидент, генерирующий данные и несущий важную информацию, — сохраняется в Kafka. В контексте корпоративных систем событие
   Глава 1. Введение в Apache Kafka 27 представляет действия, изменения или транзакции в разных частях организации: взаимодействия с пользователем, обновления систем, финансовые операции и любые другие бизнес-процессы. Рис. 1.1. Kafka выступает в роли центральной нервной системы для данных в компании. Каждое событие, происходящее на предприятии, сохраняется в Kafka. Другие сервисы могут реагировать на эти события асинхронно и обрабатывать их по своему усмотрению В событийно-ориентированной архитектуре и в Kafka событием называется структурированный набор данных, содержащий подробную информацию о конкретном происшествии. Такие события обычно генерируются различными компонентами или приложениями предприятия, а затем публикуются в топик Kafka — структурную единицу организации потоков данных. Другие системы или приложения могут подписываться на эти топики, чтобы получать и обрабатывать события. При таком подходе различные части корпоративной экосистемы обмениваются данными в реальном времени. Во многих компаниях растет разрыв между традиционными (legacy) системами, на которых работают текущие бизнес-процессы и модели, и новыми системами, построенными по современным принципам. В новых системах инновационные сервисы и решения разрабатываются с учетом современных методологий. Хотя интегрировать старые и новые системы можно и без Kafka (используя адаптеры или антикоррупционные слои1), он играет ключевую роль в их взаимодей1 Антикоррупционный слой (anti-corruption layer, ACL) — это архитектурный шаблон проектирования в предметно-ориентированном проектировании (DDD), используемый для интеграции двух несовместимых систем, например новой микросервисной архитектуры и старой (legacy) системы. Он выступает в роли посредника, преобразующего данные и интерфейсы, чтобы внешняя, часто «грязная» модель данных не нарушала целостность новой, «чистой» доменной модели. — Примеч. науч. ред.
   28 Часть I. Начало работы ствии. Kafka обеспечивает бесшовный обмен сообщениями между новейшими динамичными сервисами и традиционными системами, не требуя значительных изменений последних. Традиционные системы часто не отвечают требованиям современных внутренних и внешних клиентов. Они обрабатывают данные большими пакетами с заданными интервалами, тогда как в современном динамичном мире мгновенный доступ к информации стал необходимостью. Например, актуальный остаток на банковском счете должен отображаться сразу — не через день и уж тем более не через неделю. Мы хотим в реальном времени отслеживать посылки и моментально узнавать об изменении их статуса. Современные автомобили, к примеру, генерируют огромные объемы данных, которые передаются на анализ в корпоративные центры и часто используются для работы над беспилотными автомобилями. Kafka помогает компаниям перейти от пакетной обработки в более традиционных системах к работе с потоками данных в реальном времени, позволяя мгновенно обновлять информацию, чтобы соответствовать ожиданиям клиентов и отраслевым трендам. Хотя изначально Kafka разрабатывался для обработки в реальном времени, в определенных случаях его можно использовать и для обработки пакетов, скажем, раз в день. Подходы к разработке программного обеспечения тоже меняются. Мы больше не пытаемся вместить весь функционал в монолитные приложения, соединенные друг с другом, а разделяем большие сервисы на микросервисы. В этой архитектуре приложения состоят из небольших, независимо развертываемых сервисов, взаимодействующих с помощью четко определенных API. Команды разных сервисов меньше зависят друг от друга, приложения проще масштабировать, а разработка и развертывания программного обеспечения становятся более гибкими. Преимущества микросервисов полноценно реализуются при асинхронном обмене данными. Даже если один микросервис находится на обслуживании, остальные продолжают работу. Кроме того, микросервисам требуются методы коммуникации, позволяющие форматам данных в одном сервисе развиваться независимо от других сервисов. Kafka играет ключевую роль в этом контексте, предоставляя надежную платформу для асинхронной коммуникации и потоковой передачи данных и позволяя микросервисам функционировать независимо друг от друга, без помех обмениваясь информацией в слабосвязанном режиме. Еще один тренд, обусловленный виртуализацией и распространением облачных архитектур, — возможность обходиться без специализированного оборудования. В отличие от других систем обмена сообщениями Kafka не требует выделенных устройств. Он работает на стандартном оборудовании, без специальных функций отказоусточивости, и корректно обрабатывает сбои подсистем, так что, даже если в центре обработки данных возникли проблемы, сообщения будут доставляться стабильно. Как же Kafka обеспечивает такую надежность и производительность? Как применять его в различных сценариях и что при этом учитывать? Ответы на эти и многие другие вопросы вы найдете в этой книге.
   Глава 1. Введение в Apache Kafka 29 1.3. Архитектура Kafka Архитектура Kafka — не просто базовый фреймворк, а тщательно продуманная система для бесперебойной передачи, хранения и обработки данных. Рассмотрим ключевые компоненты Kafka (рис. 1.2), чтобы понять, почему он стал главным инструментом для распределенных приложений с потоковой обработкой данных. Рис. 1.2. Компоненты и поток данных в Kafka Разработчикам и просто любопытствующим будет интересно увидеть, как орга­ низованы компоненты архитектуры Kafka и как этот инструмент определяет будущее потоковой передачи данных. 1. Сообщения (message) несут полезную нагрузку, передаются в виде массивов байтов и «за кадром» перед отправкой обычно группируются в пакеты. 2. Продюсеры (producer) отправляют сообщения лидеру партиции и выбирают партицию сами с помощью партиционера. 3. Топики (topic) объединяют сообщения по бизнес-темам аналогично таблицам в базе данных.
   30 Часть I. Начало работы 4. Партиции (partition) лежат в основе производительности Kafka. Деление топиков на партиции обеспечивает параллелизацию и масштабирование процессов, а партиции реплицируются на нескольких брокерах в целях отказоустойчивости и высокой доступности. 5. Потребители (consumer) получают и обрабатывают сообщения от Kafka, считывая данные из разных партиций и топиков. 6. Группы потребителей обрабатывают сообщения параллельно, позволяя масштабировать их потребление, поскольку партиции и сообщения распределяются между потребителями. При сбое одного потребителя остальные в группе берут на себя его задачи, гарантируя отказоустойчивость. 7. Брокеры (broker) — это серверы Kafka. Они равномерно распределяют реплики и задачи между собой, чтобы повысить производительность, и обеспечивают надежность: при отказе одного брокера его функции перехватывает другой. 8. Лидеры (leader) — это брокеры, ответственные за операции чтения и записи в партиции. Распределяются между всеми брокерами максимально равномерно. 9. Фолловеры (follower) — это брокеры, на которые копируются партиции с лидера для повышения отказоустойчивости. 10. Координационный кластер (Kafka Raft (KRaft), ранее — кворум, или кластер, ZooKeeper) используется Kafka для координации собственной работы. Рассмотрим взаимодействие компонентов Kafka на примере. Представьте банковское приложение, обрабатывающее денежные переводы. Это продюсер, который генерирует сообщения о каждом событии перевода. В сообщении указаны счет отправителя, счет получателя, сумма и время операции. Это сообщение не будет направлено потребителю напрямую — сначала оно попадет в топик Kafka с именем bank-transfers. Топик здесь можно сравнить с категорией, по которой объединяются сообщения. Этот топик состоит из нескольких партиций, и каждая из них обрабатывает часть поступающих сообщений, таким образом обеспечивая параллельную обработку. В нашем случае партиции могут управлять денежными переводами разных типов. Кластер Kafka, состоящий из брокеров, обеспечивает функционирование всей системы. Брокеры — это серверы, которые хранят данные и управляют ими. Вместе они составляют отказоустойчивый и масштабируемый кластер Kafka. Каждый брокер управляет одной или несколькими партициями каждого топика, обеспечивая эффективное распределение данных. В традиционных конфигурациях Kafka для координации брокеров и управления метаданными использовался ZooKeeper, который играл ключевую роль
   Глава 1. Введение в Apache Kafka 31 в поддержании распределенной работы Kafka. В более новых версиях Kafka обходится без ZooKeeper — теперь для внутренней координации применяется протокол KRaft (упоминавшийся выше), так что Kafka управляет координацией напрямую, без внешней системы. ПРИМЕЧАНИЕ Подробности перехода с ZooKeeper на KRaft будут описаны в следующих главах. Пока достаточно знать, что он был нужен для повышения масштабируемости и упрощения архитектуры. Теперь рассмотрим происходящее с точки зрения потребителей. Это приложения или сервисы, которые читают сообщения из топиков Kafka. Для эффективного чтения потребители объединяются в группы. Каждая группа состоит из нескольких потребителей, а каждая партиция доступна для чтения только одному потребителю в группе. Параллельная обработка сообщений выполняется быстрее. У каждой партиции есть лидер и несколько фолловеров. Лидер отвечает за операции чтения и записи, а фолловеры реплицируют данные, чтобы обеспечить отказоустойчивость. При отказе лидера его заменяет фолловер, так что поток данных не прерывается. Итак, сообщения о банковских переводах в Kafka эффективно обрабатываются группами потребителей, благодаря чему на счете сразу отображается корректный остаток. Кластер Kafka с его распределенной архитектурой и механизмами отказоустойчивости формирует основу для этого надежного и масштабируемого потока данных. Kafka упорядочивает потоки событий, управляя лидерами и фолловерами, распределяя сообщения по партициям и координируя работу брокеров. 1.4. Запуск и использование Kafka Для эффективного запуска Kafka необходимо несколько компонентов и условий. В первую очередь требуется надежный и правильно настроенный набор серверов для размещения кластера Kafka. Каждый сервер в кластере выступает в роли брокера и участвует в распределенной обработке потоков данных. Кроме того, нужна адекватная сетевая инфраструктура с низкой задержкой и высокой пропускной способностью, которая сможет обеспечить бесперебойное взаимодействие между брокерами Kafka. Пользователи должны четко понимать требования к данным и проектировать топики и партиции так, чтобы данные распределялись и упорядочивались эффективно. Продюсеры и потребители нужно написать таким образом, чтобы они эффективно взаимодействовали с Kafka, обеспечивая отправку, прием и обработку данных в реальном времени. Наконец, требуется комплексная стратегия мониторинга и управления, позволяющая отслеживать производительность Kafka и оперативно устранять неполадки. Для успешной
   32 Часть I. Начало работы работы нужны правильно настроенная инфраструктура, надежная сеть и хорошее понимание архитектуры Kafka и связанных компонентов. Самостоятельно управлять Kafka можно, но сложно. В облаке многие вендоры предлагают Kafka в виде управляемого решения. Например, Amazon Managed Streaming for Apache Kafka (Amazon MSK) или Azure HDInsight. Кроме того, существуют специализированные вендоры, такие как Confluent и Aiven. Можно также использовать сервисы с поддержкой Kafka, наподобие Redpanda или WarpStream, которые обещают дополнительные функции или больше производительности за меньшие деньги. Эти предложения выглядят интересно, но у нас недостаточно опыта работы с ними, чтобы можно было что-то посоветовать. Нужно понимать, что инфраструктура Kafka — только часть успешной потоковой архитектуры, поэтому, даже если в компании используются управляемые сервисы, в ней должны быть свои специалисты. СОВЕТ У нас был успешный опыт работы с Confluent и Aiven, так что мы можем их рекомендовать. Помимо упомянутых инфраструктурных компонентов, потребуются определенные языки программирования и инструменты. Сам Kafka реализован на Scala и Java, поэтому на серверах, на которых размещаются брокеры Kafka, должна быть установлена среда выполнения Java (Java Runtime Environment, JRE). Для взаимодействия с Kafka и разработки приложений-продюсеров и приложенийпотребителей, как правило, применяются Java, Scala, Python и другие языки, поддерживаемые клиентами Kafka. В целях упрощения интеграции Kafka предлагает официальные клиентские библиотеки для различных языков. 1.5. Траектория обучения В последующих главах мы последовательно рассмотрим Kafka, плавно переходя от базовых концепций к внутренним нюансам работы. В книге много схем и рисунков, наглядно иллюстрирующих ключевые принципы, архитектуру и процессы Kafka. Они помогут вам лучше понять распределенную архитектуру, а также устройство и взаимодействие топиков, партиций и брокеров. Обучение строится на практическом подходе — мы используем простые, но эффективные примеры, позволяющие сразу приступить к работе с Kafka. Прикладные сценарии и пошаговые инструкции помогут вам научиться применять полученные знания для создания и запуска приложений Kafka. Все примеры — от создания и потребления сообщений до настройки и управления кластерами Kafka — продуманы так, чтобы вы могли экспериментировать с ними и оттачивать навыки. С помощью наглядных иллюстраций и практических примеров мы разъясним все сложности Kafka и поможем применить полученный опыт в вашей работе.
   Глава 1. Введение в Apache Kafka 33 Резюме Kafka — это мощная распределенная платформа для потоковой обработки данных, работающая по модели «публикация — подписка» и обеспечивающая беспрепятственный поток данных между продюсерами и потребителями. Kafka широко применяется в различных отраслях и превосходно справляется с задачами анализа данных в реальном времени, генерации событий (event sourcing), агрегации логов и потоковой обработки, помогая организациям принимать решения на основе актуальных данных. Архитектура Kafka ориентирована на отказоустойчивость, масштабируемость и долговечность, гарантируя надежную передачу и хранение данных даже при системных сбоях. В финансах, розничной торговле, телекоммуникациях и других сферах Kafka используется для обнаружения мошенничества в реальном времени, обработки транзакций, управления запасами, обработки заказов, мониторинга сетей и обработки масштабных потоков данных. Помимо основной системы обмена сообщениями, в экосистему Kafka входят Kafka Connect (коннекторы для внешних систем) и Kafka Streams (библиотека для разработки потоковых приложений), повышающие общую полезность платформы. Kafka может служить центральным узлом для интеграции разнородных систем. Продюсеры отправляют сообщения в Kafka для распределения. Потребители получают сообщения из Kafka и обрабатывают их. Топики организуют сообщения по каналам, или категориям. Партиции разделяют топики, обеспечивая параллельную обработку и масштабирование процессов. Брокеры — это серверы Kafka, управляющие хранением, распределением и извлечением данных. KRaft или ZooKeeper координирует задачи в кластере Kafka. Kafka обеспечивает устойчивость данных, используя репликацию. Kafka масштабируется горизонтально путем добавления брокеров в кластер. Kafka работает на стандартном оборудовании. Kafka реализован на Java и Scala, но предлагает клиенты и для других языков (например, Python).
2 Первые шаги в работе с Kafka В этой главе 3 Знакомство с примером использования. 3 Создание топика с помощью команды kafka-topics.sh. 3 Генерация сообщений с использованием команды kafka-console-producer.sh. 3 Получение сообщений с помощью команды kafka-console-consumer.sh. В этой главе мы рассмотрим базовый функционал Apache Kafka. Для начала создадим основной топик, а затем сохраним в него созданные сообщения. Наконец, мы извлечем эти сообщения, чтобы завершить цикл и получить представление о базовых операциях Kafka. В приложении А приводятся инструкции по созданию кластера Kafka с тремя брокерами. 2.1. Пример использования В книге показывать эффективность применения Kafka и его возможности мы будем на едином примере. Для этой роли мы выбрали интернет-магазин (рис. 2.1). Создание самой платформы магазина мы затрагивать не будем, нам просто нужно вписать в контекст ключевые концепции и методы Kafka. Мы рассмотрим различные функции: управление пользователями и товарами, обработку
   Глава 2. Первые шаги в работе с Kafka 35 заказов и платежную интеграцию, и вы увидите, как Kafka улучшает потоковую передачу данных, обработку в реальном времени и масштабируемость системы в этих областях. Рис. 2.1. Общая схема ИТ-инфраструктуры для интернет-магазина 2.2. Создание сообщений Давайте подробно рассмотрим нашу систему управления товарами. Мы хотим сохранять все изменения цен в Kafka, создав для этого выделенный топик. Такая фиксация пригодится нам для ведения истории изменений цен, мониторинга в реальном времени, аналитики и принятия решений на основе актуальных и исторических данных о ценах. Для начала создадим специализированный топик в Kafka. Топики аналогичны таблицам в базах данных — в них тоже хранятся тематические наборы данных. Соблюдая общепринятые соглашения об именовании (использование строчных букв и разделение компонентов точками для наглядности и единообразия), мы присваиваем топику имя products.prices.changelog. Создаем топик: $ kafka-topics.sh \ --create \ --topic products.prices.changelog \ --partitions 1 \ --replication-factor 1 \ --bootstrap-server localhost:9092 Created topic products.prices.changelog. Управлять топиками можно с помощью команды kafka-topics.sh. Этот скрипт, как и многие другие в этой книге, поставляется с Kafka. Здесь мы создаем топик products.prices.changelog (--topic products.prices.changelog), используя
   36 Часть I. Начало работы аргумент --create. Мы начинаем с одной партиции (--partitions 1) и не реплицируем данные (--replication-factor 1), чтобы пока ничего не усложнять. Наконец, мы указываем кластер Kafka, к которому будем подключаться. У нас это локальный кластер, по умолчанию прослушивающий порт 9092 (--bootstrapserver localhost:9092). Команда подтверждает успешное создание топика. Если на данном этапе возникают ошибки, то, возможно, Kafka не запущен или топик с таким именем уже есть. ПРИМЕЧАНИЕ Скрипты Confluent Kafka не имеют расширения .sh. Для пользователей Windows доступны версии скриптов с расширением .bat, но мы настоятельно рекомендуем использовать Windows Subsystem for Linux (WSL). Итак, теперь нам есть где хранить изменения цен. Как только цена на товар меняется, в этот топик отправляется новое сообщение. Для упражнений мы будем использовать консольную утилиту kafka-console-producer.sh. Продюсер подключается к Kafka, берет данные из командной строки и отправляет их как сообщения в топик (указываемый параметром --topic). Запишем сообщение coffee pads 10 в топик products.prices.changelog: $ echo "coffee pads 10" | kafka-console-producer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 ПРИМЕЧАНИЕ Консольный продюсер Kafka не выводит подтверждение после успешной отправки сообщения. Поскольку мы передаем вывод команды echo напрямую в продюсер, то интерактивный режим ввода отсутствует и обратная связь не отображается. ПРИМЕЧАНИЕ Любители кофе удивятся, почему мы выбрали coffee pad вместо более привычного coffee pod. Это не одно и то же, но если в итоге мы все равно получаем чашку кофе, то какая разница? 2.3. Потребление сообщений Аналитический компонент должен считать данные, чтобы в реальном времени оценивать, как изменения цен влияют на заказы. Благодаря этому организация сможет оперативно корректировать ценовую стратегию и управление запасами. Для получения отправленного сообщения мы запускаем kafka-console-consumer.sh, входящий в консольную утилиту Kafka: $ kafka-console-consumer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 # Для отмены нажмите Ctrl+C Processed a total of 0 messages
   Глава 2. Первые шаги в работе с Kafka 37 При запуске kafka-console-consumer.sh выполняется до тех пор, пока мы явно его не отменим (например, нажав Ctrl+C). Для потребителя consumer мы снова указываем нужный топик (--topic products.prices.changelog). К нашему удивлению, никакого сообщения мы не видим. Дело в том, что по умолчанию kafka-console-consumer.sh начинает читать с конца топика и выводит только новые сообщения. Для вывода уже считанных данных нужно добавить флаг --from-beginning: $ kafka-console-consumer.sh \ --topic products.prices.changelog \ --from-beginning \ --bootstrap-server localhost:9092 coffee pads 10 # Для отмены нажмите Ctrl+C Processed a total of 1 messages На этот раз отображается сообщение coffee pads 10! Что случилось? С по­ мощью команды kafka-topics.sh мы создали топик products.prices.changelog в Kafka, а затем, используя команду kafka-console-producer.sh , сгенерировали сообщение coffee pads 10. Далее мы считали сообщение, выполнив команду kafka-console-consumer.sh. Этот поток данных показан на рис. 2.2. При отсутствии дополнительных аргументов kafka-console-consumer.sh начинает чтение с конца. Для считывания всех сообщений нужно указать флаг --from-beginning. Рис. 2.2. Мы генерируем данные с помощью команды kafka-console-producer.sh, сохраняем сообщение в топик products.prices.changelog, а затем считываем его, используя команду kafka-console-consumer.sh 2.4. Параллельное потребление и генерация сообщений В отличие от многих других систем обмена сообщениями в Kafka сообщения можно перечитывать, а значит, несколько независимых потребителей (представляющих отдельные системы) могут подключаться к одному топику и считывать данные параллельно. Допустим, у нас есть система анализа и система управления запасами, которые потребляют сообщения с изменениями цен (рис. 2.3). Иногда для ретроспективного анализа нам требуются исторические данные о ценах.
   38 Часть I. Начало работы В таких случаях мы можем запускать потребитель несколько раз, каждый раз получая одни и те же данные для дальнейшей оценки. Рис. 2.3. Изменения цен на товары доступны нескольким системам Но теперь мы хотим, чтобы текущая цена в системе управления запасами обновлялась немедленно при появлении новых данных. Для этого запускаем kafka-console-consumer.sh в окне терминала, имитируя систему управления запасами. Как только новые данные поступают, потребитель получает их из Kafka и отображает в командной строке: # Напоминаем: нажмите Ctrl+C, чтобы остановить работу потребителя $ kafka-console-consumer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 Сымитируем изменение цены, запустив kafka-console-producer.sh. Команда будет выполняться, пока мы не нажмем Ctrl+D, чтобы отправить продюсеру сигнал EOF (end of file — «конец файла»): # Нажмите Ctrl+D, чтобы остановить работу продюсера $ kafka-console-producer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 Команда kafka-console-producer.sh отправляет в Kafka по одному сообщению на каждую введенную строку. Это значит, что теперь мы можем вводить сообщения в терминал продюсера: # > > > Окно продюсера coffee pads 11 coffee pads 12 coffee pads 10
   Глава 2. Первые шаги в работе с Kafka 39 В окне потребителя почти сразу отобразится: # Окно coffee coffee coffee потребителя pads 11 pads 12 pads 10 Теперь мы еще хотим получать эти сообщения в инструменте анализа продаж, который независимо запускает свой потребитель. Запустим kafka-consoleconsumer.sh в другом окне терминала. Должны отобразиться все данные с начала. Здесь мы дважды видим coffee pads 10, поскольку это первое сообщение, которое мы отправили в начале главы: # Окно потребителя 2 $ kafka-console-consumer.sh \ --topic products.prices.changelog \ --from-beginning \ --bootstrap-server localhost:9092 coffee pads 10 coffee pads 11 coffee pads 12 coffee pads 10 Мы видим, что после записи данные доступны для параллельного чтения несколькими потребителями, никак не связанными между собой. По умолчанию Kafka хранит данные семь дней, но мы можем запретить удаление данных, чтобы их позже мог считать потребитель, которому они нужны. Рассмотрим сценарий, в котором за изменения цен отвечают несколько отделов, занимающихся разными категориями товаров. Kafka эффективно обрабатывает такую вариативность и одновременно принимает данные от множества продюсеров, каждый из которых представляет свою категорию товаров (рис. 2.4). Запустим еще один продюсер в отдельном окне терминала и посмотрим, как это работает: # Окно продюсера 2 $ kafka-console-producer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 > pillow 30 > blanket 40 Мы видим все сообщения от всех продюсеров на всех потребителях в порядке генерации сообщений: # Окно потребителя 1 [...] pillow 30 blanket 40 # Окно потребителя 2 [...] pillow 30 blanket 40
   40 Часть I. Начало работы Рис. 2.4. Несколько продюсеров из разных отделов параллельно записывают сообщения в топик products.prices.changelog Мы рассмотрели первый сценарий, в рамках которого данные записывались в Kafka, а затем считывались из него. Сначала мы с помощью консольной утилиты kafka-topics.sh создали топик products.prices.changelog, чтобы сохранять в него все изменения цен на товары. Затем, используя команду kafka-consoleproducer.sh, мы записали в этот топик данные об изменении цены. Далее мы прочли и вывели эти данные с помощью команды kafka-console-consumer.sh. Затем мы попробовали генерировать сообщения, параллельно используя несколько продюсеров, и считывать их несколькими потребителями. Добавив флаг --from-beginning в kafka-console-consumer.sh, мы считали прошлые сообщения. В ходе этого процесса мы освоили базовые операции записи данных в Kafka и чтения их из него, используя консольные утилиты. Теперь можно закрыть все открытые терминалы. Продюсеры закрываются нажатием Ctrl+D, а потребители — Ctrl+C. Этот пример не должен создавать впечатление, что Kafka применяется только в таких простых сценариях. На самом деле эта платформа используется для обработки значительных объемов данных (порядка десятков гигабайт в секунду). 2.5. Графические интерфейсы пользователя в Kafka Графические интерфейсы заметно упрощают администрирование и мониторинг кластеров Kafka. Например, проект Kafbat UI (https://github.com/kafbat/kafka-ui; открытый исходный код) (рис. 2.5) или Kadeck (www.kadeck.com; по лицензии) предоставляют интуитивно понятные способы работы с Kafka, позволяя визуализировать потоки данных, управлять топиками, партициями и группами потребителей, а также выполнять административные задачи. Kafbat UI, в частности, предлагает полный обзор кластеров Kafka, упрощая процесс отслеживания
   Глава 2. Первые шаги в работе с Kafka 41 потока сообщений, проверки журналов аудита и настройки системных параметров. Благодаря этому разработчики и администраторы получают удобный инструмент для эффективной работы с экосистемами Kafka. Рис. 2.5. На этом снимке экрана Kafbat UI отображаются сообщения из конкретного топика и представлен обзор генерируемых и потребляемых сообщений. В этом интерфейсе пользователи могут анализировать отдельные сообщения, в том числе их ключ, значение и временну́́ю метку, что упрощает мониторинг и устранение неполадок с потоками данных в реальном времени ПРЕДУПРЕЖДЕНИЕ Мы настоятельно не рекомендуем использовать графические интерфейсы для записи данных или изменения конфигураций в продакшене. Генерация сообщений в графическом интерфейсе нарушит согласованность, обработку ошибок и масштабирование. Kafka предназначен для обмена данными между системами, а не для взаимодействия с человеком. Генерировать или потреблять данные в продакшене могут только администраторы, и исключительно в критических ситуациях. Итак, в этой главе мы обсудили основы Kafka и создали практический сценарий. В следующей главе мы углубимся в архитектуру Kafka. Мы разберем структуру сообщений Kafka и способы их организации в топики. В этом контексте мы также поговорим о масштабируемости и надежности Kafka. Кроме того, мы подробнее рассмотрим продюсеры, потребители и сам кластер Kafka.
   42 Часть I. Начало работы Резюме Kafka предлагает множество полезных скриптов для управления топиками, а также генерации и потребления сообщений. Создавать топики Kafka можно с помощью команды kafka-topics.sh. Для генерации сообщений используйте команду kafka-console-producer.sh. Команда kafka-console-consumer.sh считывает данные из топика. Топики можно заново считывать с начала. Несколько потребителей могут независимо и одновременно считывать данные из одного топика. Несколько продюсеров могут параллельно записывать данные в один топик. Графические интерфейсы Kafka позволяют пользователям просматривать сообщения в топиках в реальном времени, отображая такие детали, как ключ, значение и временна́я метка. Эти интерфейсы упрощают мониторинг и диагностику потоков данных в Kafka.
Часть II Основные концепции В этой части мы подробно рассмотрим ключевые концепции Apache Kafka, уделив особое внимание тому, как архитектура и основные функции обеспечивают эффективную и масштабируемую потоковую передачу данных. Мы детально обсудим топики, сообщения, распределенные логи, надежность и оптимизацию производительности в Kafka. Глава 3 посвящена топикам и сообщениям Kafka, их структуре, потокам и форматам данных. В главе 4 Kafka рассматривается как распределенный лог с механизмами партиционирования и репликации на примере реальных приложений. В главе 5 обсуждается надежность Kafka; особенное внимание уделяется настройкам подтверждения, сохраняемости данных, отказоустойчивости и транзакционным возможностям. В главе 6 рассказывается об оптимизации производительности: о стратегиях партиционирования, настройках продюсеров и потребителей, а также о конфигурациях брокеров.
3 Топики и сообщения в Kafka В этой главе 3 Работа с топиками Kafka. 3 Как топики организуют поток данных в Kafka. 3 Сообщения как базовые единицы данных в Kafka. В этой главе мы рассмотрим главные элементы Apache Kafka: топики и сообщения. Топики — это каналы, по которым организуются и распределяются данные, а сообщения представляют собой отдельные единицы данных, передаваемые через эти каналы. Мы обсудим различные типы сообщений, их структуры и форматы данных, а также разберем, как эти элементы способствуют эффективной потоковой передаче и обработке данных. К концу главы вы получите полное представление о том, как управлять топиками Kafka и из чего состоят сообщения. 3.1. Топики Топики Kafka — это вариант организации потока данных аналогично таблицам в базе данных. Они помогают упорядоченно управлять различными типами и объемами информации. Сложные данные могут распределяться между несколькими топиками, поэтому важно эффективно управлять ими, чтобы обес­ печить их бесперебойную потоковую передачу и обработку.
   Глава 3. Топики и сообщения в Kafka 45 3.1.1. Просмотр топиков Создатель топика Kafka редко является его единственным пользователем, и другим участникам полезно понимать общую конфигурацию топика, чтобы эффективнее взаимодействовать с ним и соблюдать согласованные ключевые параметры, управляющие поведением топика. Рассмотрим, например, топик products.prices.changelog из предыдущей главы: $ kafka-topics.sh \ --describe \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 Topic: products.prices.changelog TopicId: GGYA9u_aRPSd0JRaGn2eBA PartitionCount: 1 ReplicationFactor: 1 Configs: Topic: products.prices.changelog Partition: 0 Leader: 3 Replicas: 3 Isr: 3 Elr: LastKnownElr: Для управления топиками в Kafka есть команда kafka-topics.sh . Аргумент --describe позволяет детально рассмотреть топик. Аргументы --topic и --bootstrap-server уже известны нам по предыдущей главе — с их помощью мы выбираем топик products.prices.changelog (--topic products.prices.chan­ gelog) в локальном кластере Kafka (--bootstrap-server localhost:9092). Рассмотрим, что мы получили. В первой строке представлена общая информация о топике products.prices.changelog. Поле TopicId содержит уникальный идентификатор, который Kafka автоматически создает для каждого топика. PartitionCount указывает количество партиций в топике и соответствует значению, установленному при создании топика (--partitions 1). Партиции в Kafka служат для распараллеливания обработки данных и повышения масштабируемости и пропускной способности системы, поскольку несколько продюсеров могут записывать данные в разные партиции одновременно, а потребители — также одновременно считывать из них данные. ReplicationFactor определяет степень избыточности для хранения сообщений. Значение 1 указывает на то, что сообщения не реплицируются, поэтому отказоустойчивость не обеспечивается. О партициях и репликации мы поговорим в следующих главах. Дополнительные настройки конфигурации топика находятся в разделе Configs. В нашем примере это поле пустое, поскольку мы не вносили изменений в стандартную конфигурацию. Более подробно настройки Configs мы рассмотрим далее в книге. Последняя строка содержит информацию о единственной партиции и ее репликах. Здесь только одна партиция, но если бы их было больше, то вывод содержал бы по одной строке для каждой. Чтобы понять, что означают остальные числа, нужно чуть больше узнать об архитектуре Kafka. Мы уже не раз упоминали кластер Kafka, но что это такое? На рис. 3.1 изображены компоненты Kafka.
   46 Часть II. Основные концепции Рис. 3.1. Типичное окружение Kafka состоит из самого кластера Kafka, а также продюсеров и потребителей, которые записывают данные в Kafka и читают их из него. Кроме того, здесь есть координационный кластер на основе Apache Kafka Raft (KRaft) О продюсерах и потребителях мы говорили в предыдущей главе. Брокеры отвечают за обработку и хранение сообщений в кластере Kafka. Координационный кластер управляет брокерами, но этот момент мы пока что проигнорируем. Остальные значения в последней строке (Topic: products.prices.changelog Partition: 0 Leader: 3 Replicas: 3 Isr: 3, Elr:, astKnownElr:) относятся к идентификаторам одного из трех брокеров. Значение Replicas указывает, на каких брокерах реплицируется партиция. В данном случае она доступна только на брокере с идентификатором 3. Аббревиатура ISR расшифровывается как in-sync replicas и переводится как «синхронизированные реплики». Это брокеры с актуальными данными для этой партиции. Поле Leader указывает брокер, ответственный за партицию. Для коэффициента ReplicationFactor мы установили 1, то есть партиция не реплицируется и записи содержат только идентификатор одного из трех брокеров. При создании нового топика Kafka старается равномерно распределить партиции и реплики между всеми брокерами для балансировки нагрузки. Пока мы создали только один топик, брокер выбирается произвольно. В этом примере выбран брокер с ID 3.
   Глава 3. Топики и сообщения в Kafka 47 ELR расшифровывается как eligible leader replicas — «кандидаты на роль лидера». Это список брокеров из ISR, которые могут стать лидерами партиции в случае отказа текущего лидера. Поле LastKnownElr содержит список ранее подходящих кандидатов, которые завершили работу некорректно. Концепции ISR и ELR мы подробнее рассмотрим в главах 5 и 8. ПРИМЕЧАНИЕ В дальнейшем мы для краткости будем опускать некоторые поля, выводимые командой --describe, если они не связаны с обсуждаемой темой. Пока что мы не получили ответа на важный вопрос: как определить, какие топики есть в кластере Kafka? В нашем примере ответ относительно прост — мы создали только топик products.prices.changelog. На практике кластер Kafka может содержать множество топиков, созданных разными пользователями. Вывести их можно с помощью команды kafka-topics.sh, которая отображает все топики в кластере Kafka: $ kafka-topics.sh \ --list \ --bootstrap-server localhost:9092 __consumer_offsets products.prices.changelog Добавим аргумент --list и снова укажем брокер Kafka (--bootstrap-server localhost:9092 ). Не имеет значения, какой именно брокер управляет топиком. Таким образом, мы могли бы обратиться к двум другим брокерам (--bootstrap-server localhost:9093 и --bootstrap-server localhost:9094 соответственно). Мы видим, что топик __consumer_offsets отличается. Этот топик был автоматически создан Kafka, о чем свидетельствует двойное подчеркивание в начале имени. Он хранит текущую позицию чтения для каждого потребителя — смещение (offset), которое выступает для потребителей чем-то вроде книжной закладки. Фактически мы уже применяли смещения, когда работали с первым примером: # Окно потребителя 2 $ kafka-console-consumer.sh \ --topic products.prices.changelog \ --from-beginning \ --bootstrap-server localhost:9092 coffee pads 10 coffee pads 11 coffee pads 12 coffee pads 10 По умолчанию kafka-console-consumer.sh начинает чтение с конца топика, то есть обрабатывает только новые сообщения. Флаг --from-beginning указывает, что чтение нужно начинать с самого раннего смещения, чтобы охватить предыдущие сообщения.
   48 Часть II. Основные концепции ПРЕДУПРЕЖДЕНИЕ Технически вы можете вручную создавать топики с двойным подчеркиванием в имени, но мы настоятельно не рекомендуем делать это. Kafka резервирует имена с двойным подчеркиванием для внутренних системных топиков и специальных целей. Использование подобного формата для пользовательских топиков может вызвать конфликты с внутренними топиками Kafka, проблемы совместимости в будущем и путаницу при администрировании. 3.1.2. Создание, настройка и удаление топиков Итак, мы рассмотрели топики и их свойства, а теперь перейдем к их созданию и настройке. Чтобы начать заново, удалим топик products.prices.changelog, используя команду kafka-topics.sh с аргументом --delete: $ kafka-topics.sh \ --delete \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 Если мы выведем топики в нашем кластере, то заметим, что топика products.pri­ ces.changelog больше нет: $ kafka-topics.sh \ --list \ --bootstrap-server localhost:9092 __consumer_offsets ПРИМЕЧАНИЕ Смещения для удаленного топика в топике __consumer_offsets были удалены после удаления самого топика products.prices.changelog. В кластере остался только один топик — __consumer_offsets, и мы заново создадим удаленный products.prices.changelog: $ kafka-topics.sh \ --create \ --topic products.prices.changelog \ --replication-factor 2 \ --partitions 2 \ --bootstrap-server localhost:9092 Created topic procuts.prices.changelog. Мы используем уже знакомую команду kafka-topics.sh с аргументом --create. Обратите внимание на ReplicationFactor и количество партиций. Здесь мы указали значение 2 вместо 1 для коэффициента репликации (чтобы повысить надежность) и для количества партиций (чтобы поддержать параллельную обработку). О надежности и производительности мы поговорим позже. А пока рассмотрим детали нового топика products.prices.changelog с помощью аргумента --describe:
   Глава 3. Топики и сообщения в Kafka 49 $ kafka-topics.sh \ --describe \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 Topic: products.prices.changelog TopicId: 2VvA24HxRwqhF-znSY1tAQ PartitionCount: 2 ReplicationFactor: 2 Configs: Topic: products.prices.changelog Partition: 0 Leader: 3 Replicas: 3,2 Isr: 3,2 Topic: products.prices.changelog Partition: 1 Leader: 1 Replicas: 1,3 Isr: 1,3 Как и следовало ожидать, для параметров PartitionCount и ReplicationFactor установлено значение 2, и появился новый TopicId. Обратите внимание на следующие две строки: каждая соответствует партиции, которые нумеруются с нуля. Партиция 0 находится на брокерах с идентификаторами 3 и 2 (Replicas: 3,2), причем брокер 3 играет роль лидера (Leader: 3) и, следовательно, несет основную ответственность за партицию 0. Партиция 1 расположена на брокере 3, а также на брокере 1 (Replicas: 1,3), однако на этот раз роль лидера принадлежит брокеру 1 (Leader: 1) (рис. 3.2). Рис. 3.2. Реплики и партиции топика product.prices.changelog распределяются между брокерами. Брокер 3 — лидер для партиции 0, а брокер 1 — лидер для партиции 1 Отдельные партиции и реплики размещаются не случайно — Kafka стремится равномерно распределить нагрузку между всеми брокерами. Мы можем удалять и заново создавать топик сколько угодно раз, но один и тот же брокер никогда не будет лидером для обеих партиций одновременно. Зато брокер-лидер для партиции может измениться. Аналогично распределяются и реплики между брокерами: если брокеров три, то все четыре реплики не окажутся только на двух. Меняться может лишь конкретное распределение между брокерами. Что будет, если позже мы захотим изменить количество партиций и реплик, чтобы повысить производительность или надежность? Каждый раз удалять
   50 Часть II. Основные концепции и создавать топик заново неудобно, а иногда и невозможно, ведь все данные будут утеряны. К счастью, Kafka знает, что делать в таких случаях. Увеличим количество партиций до трех: $ kafka-topics.sh \ --alter \ --topic products.prices.changelog \ --partitions 3 \ --bootstrap-server localhost:9092 Для настройки топика добавим в команду kafka-topics.sh аргумент --alter. Выбираем топик products.prices.changelog (--topic products.prices.chan­ gelog) и задаем количество партиций 3 (--partition 3). После выполнения команды посмотрим, что изменилось: $ kafka-topics.sh \ --describe \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 Topic: products.prices.changelog PartitionCount: 3 ReplicationFactor: 2 Configs: Topic: products.prices.changelog Partition: 0 Leader: 3 Replicas: 3,2 Isr: 3,2 Topic: products.prices.changelog Partition: 1 Leader: 1 Replicas: 1,3 Isr: 1,3 Topic: products.prices.changelog Partition: 2 Leader: 2 Replicas: 2,3 Isr: 2,3 Как видите, количество партиций PartitionCount теперь действительно равно 3. В конце вывода появилась новая строка с информацией о только что созданной партиции 2. Аргумент --alter не влияет на выбор лидеров и реплик для уже существующих партиций. В нашем примере брокер 2 был выбран лидером для нового раздела, а брокер 3 стал репликой (рис. 3.3). Рис. 3.3. Реплики и партиции топика product.prices.changelog после увеличения количества партиций до трех. Брокер 2 теперь лидер для дополнительной партиции 2
   Глава 3. Топики и сообщения в Kafka 51 У нас есть топик __consumer_offsets с 50 партициями, поэтому один брокер был назначен лидером для 17 партиций, а другим двум брокерам досталось по 16 партиций. Из-за этого распределения первые две партиции топика products.pri­ ces.changelog были назначены брокерам с меньшим количеством партиций. Однако любой из брокеров мог быть выбран лидером для партиции 3. Если бы мы увеличили количество партиций до пяти (добавив три новых), то каждый брокер стал бы лидером для одной дополнительной партиции, что привело бы к более сбалансированному распределению. ПРЕДУПРЕЖДЕНИЕ Уменьшить количество партиций в Kafka невозможно, так как это приведет к потере данных. Невозможность перемещения данных между парти­ циями — ключевой принцип Kafka, обеспечивающий сохранение порядка сообщений внутри партиций. Более подробно об этом мы поговорим в главе 6. Изменить коэффициент репликации можно с помощью команды kafkatopics.sh с аргументом --replica-assignment или команды kafka-reassignpartitons.sh. Не забывайте, что коэффициент репликации не может превышать количество брокеров. ПРИМЕЧАНИЕ Если требуется перераспределить партиции только для одного топика, то достаточно выполнить команду kafka-topics.sh. Но для более сложных сценариев, особенно при одновременной работе с несколькими топиками, рекомендуем использовать инструмент kafka-reassign-partitions.sh. Он дает больше возможностей, позволяя выполнять сложное перераспределение партиций и даже автоматически балансировать их распределение для нескольких топиков параллельно. 3.2. Сообщения В предыдущем разделе мы обсуждали топики. Но зачем они нужны? В первой главе мы отправляли строки в Kafka и получали их из него. Kafka не разбирается в типах данных и работает исключительно с байтовыми массивами — последовательностями байтов, базовых единиц хранения данных в компьютерах. Поскольку Kafka оперирует байтовыми массивами, то обрабатывает сообщения любого формата или структуры, поддерживая работу с разнородными данными. Единственное важное правило — Kafka оптимизирован для обработки большого количества небольших сообщений. Максимальный размер сообщения — 1 Мбайт. Это значение изменить можно, но не стоит, иначе снизится производительность и потребуется слишком много ресурсов. Что подразумевается под большим количеством небольших сообщений? Такие компании, как LinkedIn, в 2019 году обрабатывали с помощью Kafka около 7 триллионов (не миллиардов) сообщений в день. При этом у LinkedIn на тот момент был не один кластер Kafka, а около сотни. Даже в меньших масштабах Kafka позволяет обрабатывать сообщения
   52 Часть II. Основные концепции очень быстро. Главное — соблюдать ограничение в 1 Мбайт. Например, передавать с помощью Kafka большие файлы будет нецелесообразно. Для таких сценариев лучше найти другие решения. 3.2.1. Типы сообщений Какие данные обычно записывают в Kafka? Это во многом зависит от конкретного сценария, но, по нашему опыту, чаще встречаются четыре типа сообщений: состояния; изменения; события; команды. На практике большинство систем используют комбинацию этих типов сообщений. Например, ERP-система может генерировать событие об изменении цены на товар по акции. Отдельный сервис, отслеживающий акции, может затем отправить команду сервису уведомлений, чтобы тот проинформировал покупателей о промоакции. Сервис уведомлений работает независимо и может использоваться другими сервисами для рассылки уведомлений. Далее мы кратко рассмотрим каждый из этих типов сообщений. Состояния Сообщение описывает текущее состояние субъекта или объекта. Оно содержит полную информацию об объекте. Интересующий нас вопрос зависит от того, что именно нам важно знать: например, как выглядит наблюдаемый объект в текущий момент или за некоторый период времени. Кроме того, если требуется только последнее состояние объекта, можно использовать сжатие логов (log compaction) в совокупности с ключами, чтобы сократить объем хранимых данных. Об этом мы поговорим в главе 10. Представим, что хотим хранить текущие товарные запасы вместе с ценой в топике products.changelog: # Одно {"id": {"id": {"id": сообщение на 123, "name": 234, "name": 345, "name": строку "coffee pad", "price": "10", "stock": 101010} "cola", "price": "2", "stock": 52} "energy drink", "price": "3", "stock": 42} Изменения Если изменение незначительное, то перезаписывать все состояние не обязательно. В таких случаях удобно помещать в сообщение только изменение состояния. В нашей системе управления запасами может использоваться топик pro­ ducts.stocks.changes, в котором собираются изменения остатков товаров — при покупке товаров клиентами или при поступлении поставок на склад:
   Глава 3. Топики и сообщения в Kafka # Одно {"id": {"id": {"id": {"id": 53 сообщение на строку 123, "stock": 1000} 234, "price": 2} 345, "stock": 60, "price": 3} 234, "name": "cola zero"} Мы значительно сократим объем данных, если не будем снова и снова отправлять значения, оставшиеся прежними. Кроме того, при записи разницы проще определить, что изменилось с момента последнего сообщения. События События описывают произошедшее, добавляя к сообщению контекст. Например, можно описать поступление новой партии товара или факт покупки товара клиентом. Логи относятся к особому типу событий. Kafka широко используется для сбора логов, предоставляя надежное и масштабируемое решение для агрегации и обработки данных логов из разных источников. Сам Kafka обладает многими характеристиками логов, о чем мы подробно поговорим в следующей главе. Состояния и изменения часто могут послужить исходными данными, на основе которых генерируются события в другом топике. Если вернуться к нашим первым действиям по освоению Kafka, то products.prices.changelog можно рассматривать как топик событий, куда можно было записывать следующие сообщения: # Одно {"id": {"id": {"id": {"id": сообщение на строку 123, "event": "vat_adjustment", "payload": {"price": 2.19}} 234, "event": "data_correction", "payload": {"name": "Coke Zero"}} 345, "event": "storehouse_delivery", "payload": {"stock": 100"}} 234, "event": "promotion_start", "payload": {"price": 1.99}} Глядя на эти сообщения, мы уже начинаем понимать, с какими событиями они связаны. Здесь мы ищем ответ на вопрос: «Что только что произошло?» ПРИМЕЧАНИЕ Записи Kafka всегда содержат временну́ю метку. Команды Иногда мы хотим дать указание другой системе. В отличие от событий, которые описывают уже свершившиеся факты, команды запрашивают выполнение действия в будущем. Например, сообщение может содержать инструкции для сервиса уведомлений, согласно которым он должен информировать клиента о промоакции: # Одно сообщение на строку {"command": "notify_customer", "customer_id": 123456, "message": "The promotion for Coke Zero started. It's now 1.99"} {"command": "notify_customer", "customer_id": 123457, "message": "The promotion for Coke Zero started. It's now 1.99"} {"command": "notify_customer", "customer_id": 123458, "message": "The promotion for Coke Zero started. It's now 1.99"}
   54 Часть II. Основные концепции Такой подход увеличивает связанность, поскольку команду должна выполнить другая система. Отправляя сообщение о событии, отправитель не заботится о том, кто будет его получателем и что произойдет дальше, а команды требуют ответа или действия от системы-получателя. 3.2.2. Форматы данных С типами сообщений мы определились, а теперь рассмотрим форматы данных. Kafka обрабатывает данные исключительно как байтовые массивы и не интерпретирует их содержимое, то есть у него нет встроенных функций для манипуляций с данными. Такое архитектурное решение — залог высокой производительности Kafka. Один из самых популярных форматов в экосистеме Kafka — JSON, поскольку он понятен как людям, так и компьютерам. Но, как и в случае с XML, объем данных может значительно увеличиваться, и даже сжатие сообщений не всегда эффективно решает эту проблему. Если размер данных критичен, то можно использовать бинарные форматы, например Google Protocol Buffers (Protobuf; https:// developers.google.com/protocol-buffers) или Apache Avro (http://avro.apache.org/), довольно популярные в сообществе Kafka. По нашему опыту, почти каждую партицию хоть раз за время ее существования читает человек, а с бинарными форматами это сложнее. Главное — выбрать единый формат. Не рекомендуется хранить в одном топике данные разных форматов — негативные последствия будут особенно заметны по мере увеличения количества топиков в кластере Kafka и старения кластера. В этом контексте особенно важно документировать форматы данных. При использовании бинарных форматов для интерпретации требуется схема. По сути, она выступает в роли плана или набора правил, определяющих структуру и формат данных. Это относится не только к бинарным форматам, но и к JSON (с использованием схемы JSON (http://json-schema.org/)) и XML (с XSD (www.w3schools.com/ xml/schema_intro.asp) или Document Type Definition (DTD; www.w3schools.com/ xml/xml_dtd_intro.asp)). Главное преимущество заключается в применении схем, которые обеспечивают единообразие между топиками и упрощают интерпретацию данных в рамках заданного формата. Более подробно управление схемами и сами схемы мы рассмотрим позже. ПРИМЕЧАНИЕ Вы могли заметить, что формат сообщений в нашем примере с изменениями выглядит иначе. На первый взгляд тут есть противоречие, но это различие абсолютно допустимо, так как схема может содержать необязательные поля. В зависимости от сценария необязательные поля могут добавляться или опускаться, как показано в примере.
   Глава 3. Топики и сообщения в Kafka 55 3.2.3. Структура сообщений При детальном рассмотрении сообщений в Kafka становится очевидно, что они состоят из нескольких компонентов: ключа (не обязательно), значения, пользовательских заголовков (не обязательно) и временно́й метки (рис. 3.4). Каждый компонент выполняет свою функцию в процессе обмена данными и их обработки в экосистеме Kafka. Если явно не указывать ключи в командах kafka-consoleproducer.sh, то сообщения будут создаваться без ключей. ПРЕДУПРЕЖДЕНИЕ Заголовки здесь аналогичны HTTP-заголовкам — они предназначены для технических метаданных, например для передачи идентификаторов трассировки. Никогда не передавайте в заголовках бизнес-данные. Можно и вовсе обходиться без заголовков. Рис. 3.4. Сообщение (запись) в Kafka содержит временну́ю метку, значение, ключ (не обязательно) и пользовательские заголовки с метаданными (не обязательно) Ключ в Kafka — необязательный атрибут сообщения, позволяющий объединять сообщения в группы. Он влияет на распределение данных между партициями: сообщения с одинаковым ключом обычно связаны друг с другом (подробнее об этом поговорим позже). Значение содержит основную полезную нагрузку сообщения — собственно данные, которые будут обрабатываться потребителями. Топики не создаются под отдельные ключи — при создании топика, предназначенного для сообщений с ключами, не нужно указывать дополнительные параметры. Пример ключа — название товара. Создадим топик products.prices.change­ log.keys: $ kafka-topics.sh \ --create \ --topic products.prices.changelog.keys \ --replication-factor 2 \ --partitions 2 \ --bootstrap-server localhost:9092 Created products.prices.changelog.keys.
   56 Часть II. Основные концепции Запустим kafka-console-producer.sh, чтобы создавать сообщения с ключами: # Ранее мы создали топик products.prices.changelog.keys $ kafka-console-producer.sh \ --topic products.prices.changelog.keys \ --property parse.key=true \ --property key.separator=: \ --bootstrap-server localhost:9092 Помимо обычных характеристик, таких как топик и кластер Kafka, мы передаем еще два параметра: --property parse.key=true активизирует парсинг ключей в kafka-console-producer.sh, а --property key.separator=: задает двоеточие (:) в качестве разделителя ключей и значений. Создадим несколько сообщений: # > > > > Окно продюсера: coffee pads:10 cola:2 coffee pads:11 coffee pads:12 Если мы сейчас опять запустим kafka-console-consumer.sh, то увидим только цены, поскольку по умолчанию потребитель не выводит ключи: $ kafka-console-consumer.sh \ --from-beginning \ --topic products.prices.changelog.keys \ --bootstrap-server localhost:9092 10 2 11 12 Processed a total of 4 messages Нажмем Ctrl+C для отмены. Чтобы выводились еще и ключи, нужно явно активизировать эту опцию, добавив в команду --property print.key=true: $ kafka-console-consumer.sh \ --from-beginning \ --topic products.prices.changelog.keys \ --property print.key=true \ --property key.separator=":" \ --bootstrap-server localhost:9092 coffee pads:10 cola:2 coffee pads:11 coffee pads:12 Processed a total of 4 messages Теперь потребитель показывает сообщения с ключами. Зачем нам ключи? В базах данных, хранящих пары «ключ — значение», это очевидно: данные сохраняются с конкретным ключом, по которому мы можем к ним обращаться. А зачем они в Kafka? Прежде всего, ключ необязателен, и, как видно из вывода команды kafka -console-consumer.sh, старые сообщения с тем же ключом не удаляются.
   Глава 3. Топики и сообщения в Kafka 57 В следующей главе мы рассмотрим Kafka как распределенный лог и выясним, что обычно топики состоят из нескольких партиций. Ключи помогают гарантировать, что сообщения будут записываться в одну и ту же партицию, так как по ним Kafka определяет, в какую партицию отправляется сообщение. Мы подробнее рассмотрим эту концепцию в одной из следующих глав. Kafka гарантирует порядок сообщений только в пределах одной партиции, так что если бы мы создали топик products.prices.changelog с несколькими партициями, то невозможно было бы гарантировать, что сообщение coffee pads 10 будет прочитано до сообщения coffee pads 11. Это привело бы к путанице с ценами в магазине и — что хуже — к потере согласованности между системами из-за разного порядка чтения сообщений. Если бы мы создали топик products.prices.changelog.keys с несколькими партициями, то ключи гарантировали бы корректную последовательность сообщений для товара coffee pads, но не порядок чтения для разных товаров: cola:2 coffee pads:10 coffee pads:11 coffee pads:12 В большинстве случаев мы не возражаем против того, чтобы сообщения с разными ключами считывались вразнобой — гораздо важнее гарантировать порядок сообщений с одним ключом. Если ключ не указан, то сообщения распределяются между партициями по кругу (round-robin). ПРИМЕЧАНИЕ Свойства для работы с ключами задаются на стороне клиента, а не брокера или топика. Резюме Kafka организует данные в топики. Топики можно распределять между партициями, чтобы повысить производительность. Топики можно реплицировать между брокерами, чтобы повысить надежность. Топики можно создавать, просматривать, изменять и удалять с помощью скрипта kafka-topics.sh. Количество партиций в топике нельзя уменьшить. Партиции можно переназначать между брокерами, чтобы перераспределить нагрузку. Сложные переназначения партиций можно выполнять с помощью скрипта kafka-reassign-partitions.sh. Kafka оптимизирован для обмена множеством (триллионами) небольших сообщений размером не более 1 Мбайт.
   58 Часть II. Основные концепции Сообщения Kafka можно разделить на четыре типа: состояния, изменения, события и команды. Состояния содержат полную информацию об объекте. Изменения содержат только разницу и позволяют сократить объем данных, но само по себе изменение мало о чем говорит — ему требуется контекст или полное состояние. События добавляют контекст к сообщению и описывают произошедшее в сфере бизнеса. Команды велят другим системам что-то сделать. Форматы данных и схемы играют в Kafka важную роль по обеспечению согласованности. Сообщения в Kafka состоят из технических метаданных, таких как временна́я метка, необязательные пользовательские заголовки (метаданные), необязательный ключ и значение, которое является основным содержимым. Сообщения с одинаковым ключом отправляются в одну и ту же партицию, поэтому порядок таких сообщений гарантируется для одного продюсера. Ключи также можно использовать для сжатия логов, чтобы удалять устаревшие данные.
4 Kafka как распределенный лог В этой главе 3 Свойства и сценарии применения логов. 3 Kafka как лог. 3 Kafka как распределенная система: партиции и репликация. 3 Компоненты кластера Kafka. 3 Kafka в корпоративной среде. В этой главе мы рассмотрим концепцию логов и принципы использования логов в Kafka. Мы разберем архитектуру Kafka как распределенной системы и углубимся в его ключевые компоненты. В завершение мы обсудим практическое применение Kafka в корпоративной среде, уделив особое внимание его преимуществам для управления данными предприятия. 4.1. Логи Kafka удобно представлять в виде лога. Хотя термин «лог» может показаться непривычным в этом контексте, все же мы постоянно сталкиваемся с логами в работе.
   60 Часть II. Основные концепции 4.1.1. Что такое лог Операционные системы генерируют системные логи. Если мы отвечаем за эти системы, то регулярно заглядываем в логи, чтобы проверить состояние систем и найти причины ошибок. Существуют специальные системы мониторинга логов, которые активизируют оповещения при определенных событиях и (в идеале редко!) даже будят нас среди ночи, чтобы мы могли устранить неполадки. Приложения и сервисы тоже создают логи событий, позволяющие отслеживать их работу: выявлять ошибки или подтверждать стабильность функционирования. Логи систем и приложений применяются для анализа проблем и мониторинга состояния. В этом контексте логи полезны и необходимы, но не составляют главную функцию самих систем. С системами баз данных дело обстоит иначе. Журнал фиксации изменений скрыт глубоко внутри системы, но это один из ключевых компонентов. Базы сохраняют все изменения данных в этом журнале. Если данные добавляются, изменяются или удаляются, то база сначала записывает это изменение в журнал фиксации и только потом — в соответствующие таблицы. При сбое базы данных журнал фиксации гарантирует возможность восстановления до последнего согласованного состояния без потери данных. Этот принцип применим и к репликации, например, в кластерных конфигурациях баз данных. Часто для такой репликации журнал фиксации изменений синхронизируется между узлами кластера, а каждый сервер независимо от других строит таблицы на основе данных из реплицированного журнала. Все типы логов отвечают на вопрос о том, что произошло. Логи систем и приложений дают ответ операторам систем, а журналы фиксации изменений в базах данных отвечают на этот вопрос для других серверов в кластере или даже для самой системы при сбоях или ошибках. Возвращаясь к примеру из главы 1, можно заметить сходство с логами. Вот, например, содержимое топика products.prices.changelog: coffee coffee coffee coffee pads pads pads pads 10 11 12 10 Причина этого сходства проста: логи — центральная структура данных в Kafka. В отличие от баз данных в Kafka лог не скрывается, а становится основным элементом системы. Наши данные хранятся в логах Kafka, и мы можем использовать здесь шаблоны и принципы, применимые к логам систем, приложений и баз данных.
   Глава 4. Kafka как распределенный лог 61 4.1.2. Основные свойства лога Логи используются не только в сфере информационных технологий. Простейший пример — дневник. Если мы начнем с первой страницы и будем ручкой (не карандашом) писать по странице каждый день, не оставляя пустого места между записями, то увидим характерные черты лога. Порядок и сортировка. Сообщения в логе отсортированы по времени. Самые старые сообщения находятся в его начале, самые новые — в конце. Как и в дневнике: если не оставлять пустых страниц, то запись на странице 10 будет старше записи на странице 11. Направление записи и чтения. Новые записи всегда добавляются в конец лога. При чтении мы обычно начинаем с определенной страницы, сначала просматривая старые записи, затем новые. Конечно, можно вернуться к более ранним записям, но естественное направление чтения — от старых к новым. Неизменяемость. После добавления записи ее нельзя легко изменить или удалить. В дневнике, конечно, мы можем делать пометки на страницах, зачеркивать слова или вырывать листы, но такие изменения обычно заметны. Еще одно интересное свойство логов — они наглядно показывают, как изменялась со временем описываемая ими часть мира, и мы можем выяснить, как эта часть выглядела в определенный момент времени. Изучая причины ошибок, мы часто обращаемся к логам системы или приложения. Если прочитать записи в логе в хронологическом порядке, то можно проследить, какие изменения происходили со временем, и найти подсказку о том, что пошло не так. То же самое применимо к базам данных. Чтобы определить, как выглядела таблица в конкретный момент времени, мы начинаем с первой записи журнала фиксаций изменений, последовательно продвигаясь вперед, запись за записью, наблюдая за изменениями таблицы и понимая, почему в ней отображаются именно эти данные. Кроме того, логи позволяют путешествовать в прошлое. Если нам нужно восстановить состояние базы данных на позавчерашний день, то мы можем пройтись по логу от начала до позавчера. На основе этого знания можно дать более формальное определение лога. Лог — это список событий, для которого определены две операции (см. схему на рис. 4.1): операция записи — мы добавляем новые записи в список; операция чтения — мы начинаем с определенной записи и последовательно читаем от старых записей к новым, обычно до конца лога.
   62 Часть II. Основные концепции Обычно логи слишком длинные, чтобы их можно было прочитать целиком за один раз. Логи приложений могут достигать размера в несколько гигабайт. Как запомнить, какие записи мы уже прочитали, а какие еще нет? Записи пронумерованы. Первая запись получает номер 0, вторая — 1 и т. д. В Kafka эта позиция называется смещением — мы упоминали его в предыдущей Рис. 4.1. Лог представляет собой последовательный список, где главе, когда хотели прочитать топик с начаэлементы добавляются в конец, ла. Смещение выполняет две функции: по- а чтение происходит с определенной казывает, где находится сообщение в логе, позиции (смещения). Например, мы а также указывает на запись, которую мы читаем, начиная со смещения 0, затем прочтем следующей. Берясь за книгу, мы решаем читать со смещения 4 и т. д. открываем ее на первой странице (смещение 0), прочитываем несколько следующих и запоминаем номер страницы, с которой начнем в следующий раз. В Kafka смещения служат чем-то вроде закладок. ПРИМЕЧАНИЕ Брокеры Kafka автоматически присваивают смещение записи в момент ее сохранения в топик, обеспечивая точное позиционирование внутри лога. Системы, читающие логи, могут хранить эти смещения в своей памяти (ОЗУ), но это не очень надежный подход: при сбое системы ей, возможно, придется начать чтение с начала. Kafka запоминает эти смещения за нас, сохраняя их внутри топика __consumer_offsets, который вы уже видели в предыдущей главе при просмотре всех топиков в кластере. Это означает, что Kafka не только отслеживает логи, но и предоставляет потребителям средства для управления смещениями и своим прогрессом чтения. 4.1.3. Kafka как лог Поскольку логи в ИТ-системах постоянно пополняются новыми записями, то у них обычно нет конца. В Kafka мы отмечаем смещение, с которого хотим начать чтение, и постоянно обращаемся к кластеру, чтобы узнать, нет ли новых записей. Если они есть, то мы получаем их и отмечаем следующее смещение для чтения. Если новых записей нет, то мы не получаем данные и не изменяем текущее смещение. Стоит отметить, что смещение описывает только позицию сообщения, но не его содержимое. Это последовательная нумерация без дополнительного смысла, как номера страниц в книге. Это важно понимать, поскольку в логах смещение — единственный способ обращения к данным. Мы не можем обращаться к конкретным элементам в логе.
   Глава 4. Kafka как распределенный лог 63 Это обстоятельство существенно влияет на архитектуру сервисов, использующих Kafka. Он не подходит для ответа на произвольные запросы данных. Не следует использовать его и для доступа к данным по определенному ключу, как в хранилищах пар «ключ — значение». Любая из этих операций потребует поиска по всем данным в логе. Логи или Kafka не предназначены для замены баз данных, кэшей и аналитических инструментов, зато Kafka помогает наладить эффективный обмен данными между системами в рамках предприятия (рис. 4.2). Рис. 4.2. Лог идеально подходит для обмена данными между системами. Обычно мы не работаем с данными в логе напрямую, а храним их в формате, оптимальном для конкретного сценария. Например, реляционные базы данных позволяют выполнять сложные запросы, хранилища пар «ключ — значение» в памяти (например, Redis) обеспечивают быстрый доступ к подготовленным данным, а поисковые системы реализуют функции поиска по данным в логе Вместо нецелевого использования Kafka в качестве центральной базы данных для сервисов мы применяем его как центральный хаб данных, при этом выбирая в каждом сервисе наиболее подходящую технологию хранения данных в зависимости от конкретных сценариев использования. Например, если мы хотим создать поисковый сервис для товаров, то записываем данные из Kafka в поисковую систему наподобие Elasticsearch. Если нужен специальный анализ данных, то можно использовать реляционную базу данных, например PostgreSQL, и записывать данные из Kafka в нее. В этой главе мы говорим о Kafka как о логе. Мы знаем, что лог — это список, в конец которого последовательно записываются данные, от старых записей к новым. После записи данных в лог они не изменяются и не удаляются. Одна из причин широкого использования логов Kafka в качестве распределенной системы — предельная простота и очевидность этой структуры данных. В отличие от баз, которые хранят текущее состояние системы, логи обычно содержат полную историю событий с самого начала. Несмотря на искушение, не стоит рассматривать логи как универсальное решение и использовать Kafka в роли базы данных. В последующих главах мы рассмотрим Kafka как ядро платформы потоковой обработки данных и изучим оптимальные способы взаимодействия Kafka с другими компонентами ИТ-инфраструктуры.
   64 Часть II. Основные концепции 4.2. Kafka как распределенная система Представляя Kafka в виде лога, мы много узнаем о его работе, но этого недостаточно для полноценного понимания. Лучше рассматривать Kafka как распределенный лог. Одиночный лог не обеспечит требуемую скорость, масштабируемость и отказоустойчивость, поэтому важно научиться эффективно распределять логи между несколькими серверами. Серверы массового производства, возможно, недостаточно надежны, зато дешевы. Поэтому мы не покупаем для Kafka дорогостоящее специализированное оборудование (например, мейнфреймы и т. д.), а развертываем его на стандартных системах и компенсируем повышенную уязвимость к сбоям за счет увеличения количества компьютеров. Мы не пытаемся избегать ошибок любой ценой, а учимся принимать их и проектировать системы с учетом их возможности. На практике этот подход прекрасно работает. При использовании одиночной системы уровень надежности обычно нас устраивает, но аппаратные сбои в центрах обработки данных остаются распространенной проблемой. Например, компания Backblaze ведет статистику надежности своих жестких дисков (www.backblaze.com/b2/hard-drive-test-data.html), которая показывает, что годовая вероятность отказа используемых накопителей составляет около 1 %. Компьютерные системы можно масштабировать вертикально или горизонтально (рис. 4.3). Рис. 4.3. При вертикальном масштабировании мы добавляем ресурсы отдельным экземплярам. Горизонтальное масштабирование подразумевает добавление новых экземпляров в систему При вертикальном масштабировании мы увеличиваем мощность отдельных экземпляров. Вместо сервера баз данных на 8 ядер и 16 Гбайт ОЗУ мы устанавливаем сервер с 64 ядрами и 256 Гбайт ОЗУ, надеясь добиться повышения производительности. Это простой способ улучшить показатели системы, и он хорошо работает некоторое время. К сожалению, надежность при этом не возрастает. В большинстве случаев замена сервера на более мощный не приводит к существенному снижению вероятности отказа. При горизонтальном масштабировании вместо более крупных и мощных серверов мы покупаем несколько обычных. Кажется, это идеальный выход, ведь
   Глава 4. Kafka как распределенный лог 65 увеличивать масштаб можно почти до бесконечности: как только возникает дефицит мощностей, мы просто добавляем еще один сервер. Мы ожидаем, что благодаря параллелизации производительность существенно возрастет, а надежность и сохранность данных повысятся за счет дополнительной избыточности. К сожалению, не все так просто. Распределение системы между несколькими серверами — задача не из легких. Для координации работы требуется программное обеспечение. Какой сервер должен обрабатывать конкретный запрос? Что будет, если сервер выйдет из строя? Как определить, что он перестал работать или, что еще хуже, отвечает на запросы некорректно? Как заменить вышедшие из строя серверы? Это не теоретические проблемы — разработчикам и операторам распределенных систем приходится всерьез задумываться о них, что подтверждается регулярными сбоями даже у крупных ИТ-корпораций, таких как Google, Amazon, Netflix и др. Работать с распределенными системами бывает сложнее, особенно если вы привыкли к вертикальному масштабированию. Здесь требуется иное мышление. Мы не стараемся обеспечить максимальную надежность систем, а принимаем возможность ошибок и, более того, учитываем ее в повседневной работе, учась справляться с ними. Кроме того, мы начинаем задумываться о влиянии сбоев еще на этапе проектирования системы. Очень важный момент — проектировать и разрабатывать распределенные системы максимально упрощенно. Kafka — сложная система, но его базовые концепции и технологии максимально просты. Например, сам по себе лог — это одна из простейших структур для хранения больших объемов данных. Лог легко реплицировать (копировать сообщение за сообщением) и разделять (вести несколько логов вместо одного). 4.2.1. Партиционирование и ключи Изначально Kafka проектировался для обработки огромных объемов данных на очень высокой скорости. Кроме того, во многих сценариях критически важно обеспечить сохранность и целостность данных и избежать их потери и повре­ ждения. Поэтому можно настроить Kafka так, чтобы сообщения записывались надежно и сохранялся их порядок следования. В оставшейся части главы вы увидите, как Kafka достигает такой высокой производительности и надежности. Вы уже знаете, что распределение системы по нескольким серверам позволяет повысить производительность, но это слишком поверхностный взгляд. В большинстве случаев изначально скорость обработки сообщений при горизонтальном масштабировании даже падает. Это похоже на многоядерные процессоры: если задачу нельзя распараллелить, то наличие дополнительных ядер не принесет пользы. Поэтому в контексте горизонтального масштабирования следует говорить не только о повышении производительности, но и о самой возможности параллелизации. Она позволяет обрабатывать больше сообщений за единицу
   66 Часть II. Основные концепции времени, чем это возможно на одном сервере, но для этого сам процесс обработки сообщений должен поддерживать параллелизацию. Самый простой способ распределения данных между подсистемами — разделение на партиции (в системах баз данных это называют сегментированием). Представим, что мы создаем глобальную систему заказа такси. Мы могли бы хранить все данные о поездках в центральной базе, поскольку так было бы удобнее анализировать общие закономерности перемещений, оптимизировать операции и улучшать сервис. Но бизнес будет расти, и объем данных заметно превысит возможности одного сервера, сервис будет работать медленно, клиенты уйдут к конкурентам. При детальном анализе выясняется, что не обязательно централизованно хранить все данные о поездках по всем городам — достаточно вместе хранить данные одного города, а порядок данных из разных городов не имеет значения. Такой подход упрощает горизонтальное масштабирование: можно начать с одного сервера и по мере увеличения клиентской базы добавлять новые серверы, перенося на них данные отдельных городов. Мы можем поступить аналогично в нашем примере с интернет-магазином из предыдущих глав. Не требуется хранить все данные о товарах в едином логе — достаточно сохранять в нем данные для каждого товара. Если порядок данных между разными товарами не соблюдается, это некритично. Гораздо важнее упорядочить данные по одному товару (рис. 4.4). Рис. 4.4. Лог А содержит все данные о кофейных капсулах, а лог Б — все данные о коле В Kafka используется аналогичный подход: мы не записываем все данные топика в единый лог, а разделяем топик на партиции, указывая их количество при его создании. Создадим топик с несколькими партициями, используя уже знакомую нам команду kafka-topics.sh, но количество партиций увеличим до двух (--partitions): $ kafka-topics.sh \ --create \ --topic products.prices.changelog.multi-partitions \ --partitions 2 \ --replication-factor 1 \ --bootstrap-server localhost:9092 Created topic products.prices.changelog.multi-partitions.
   Глава 4. Kafka как распределенный лог 67 Отправим в этот топик данные, используя команду kafka-console-producer.sh: $ kafka-console-producer.sh \ --topic products.prices.changelog.multi-partitions \ --bootstrap-server localhost:9092 > coffee pads 10 > cola 2 > energy drink 3 > coffee pads 11 > coffee pads 12 > coffee pads 10 # Для отмены нажмите Ctrl+D С помощью команды kafka-console-consumer.sh можно снова считать эти данные: $ kafka-console-consumer.sh \ --topic products.prices.changelog.multi-partitions \ --from-beginning \ --bootstrap-server localhost:9092 cola 2 coffee pads 11 coffee pads 10 energy drink 3 coffee pads 10 coffee pads 12 # Для отмены нажмите Ctrl+C Processed a total of 6 messages Мы видим, что порядок сообщений вдруг нарушился! В некоторых случаях это не страшно, но в нашем примере это приведет к тому, что в магазине будут отображаться неправильные цены и, что еще хуже, нарушится согласованность между разными системами, которые зависят от порядка считывания сообщений. Соблюдать порядок важно в большинстве сценариев, так как несогласованная и некорректная обработка негативно отразится на надежности и точности систем, передающих и обрабатывающих потоки данных через Kafka. Дело в том, что команда kafka-console-producer.sh автоматически распределяет данные между партициями, а Kafka гарантирует порядок сообщений только в одной партиции. ПРИМЕЧАНИЕ Возможно, нужно будет остановить работу продюсера и перезапустить его, чтобы гарантировать отправку сообщений в другие партиции, особенно учитывая, как мало у нас сообщений. Кроме того, для полноценного наблюдения проблемы с порядком сообщений может понадобиться обратиться к ним несколько раз. Гарантировать порядок сообщений можно только в том случае, если они записываются в одну партицию. Для этого необходимо назначить им одинаковый ключ. Ключи являются необязательными и представляют собой простые
   68 Часть II. Основные концепции байтовые массивы аналогично значениям. При указании ключей библиотека Kafka в продюсере определяет целевую партицию на основе хеш-значения ключа: partition_number = hash(key) % number_of_partitions; Для этого библиотека Kafka сначала вычисляет хеш-значение ключа, а затем берет остаток от деления значения на количество партиций. Если оно меняется в процессе работы, то порядок сообщений в Kafka не гарантируется. ПРЕДУПРЕЖДЕНИЕ По умолчанию библиотека librdkafka и клиентская библиотека Java распределяют сообщения по партициям с помощью разных алгоритмов хеширования. В средах, где разные системы используют разные языки программирования или клиентские библиотеки (например, бэкенд на Java и приложения на Python с librdkafka), это может привести к несогласованному распределению сообщений, особенно при использовании библиотек потоковой обработки вроде Kafka Streams. Чтобы обеспечить правильную работу, нужно установить для партиционера значение murmur2_random в продюсерах librdkafka. В результате клиенты Java и librdkafka будут использовать одинаковую стратегию партиционирования для равномерного распределения сообщений между партициями. Если порядок сообщений неважен, то ключ можно опустить. В этом случае продюсеры распределяют сообщения по партициям, используя метод циклического перебора. Первое сообщение отправляется в партицию 0, второе — в партицию 1 и т. д. В примере без ключей видно, что сообщения группировались следующим образом: сначала обрабатывались все четные сообщения, затем все нечетные (рис. 4.5). Однако случайным образом могло получиться и наоборот. Рис. 4.5. Нечетные сообщения отправлялись в партицию 0, четные — в партицию 1
   Глава 4. Kafka как распределенный лог 69 Начиная с версии 2.4, продюсер Kafka использует усовершенствованный метод циклического перебора для сокращения сетевых запросов и увеличения пропускной способности. Kafka не выбирает новую партицию после каждого сообщения, а собирает сообщения в уже существующие батчи и меняет партицию только после отправки всего батча. При таком подходе сетевых запросов становится меньше, батчи заполняются лучше, а сжатие, если используется, работает эффективнее. Итак, продюсеры распределяют сообщения по различным партициям. В идеале партиции равномерно распределены между всеми брокерами, а значит, нагрузка сбалансирована. Указать ключ можно с помощью свойств --parse-keys и key.separator в команде kafka-console-producer.sh. Как и раньше, создадим новый топик с двумя партициями и отправим сообщения с ключами в виде названий товаров: # Сначала создайте топик products.prices.changelog.multi-partitions-keys $ kafka-console-producer.sh \ --topic products.prices.changelog.multi-partitions-keys \ --property parse.key=true \ --property key.separator=":" \ --bootstrap-server localhost:9092 > coffee pads:10 > cola:2 > cola:1 > energy drink:3 > coffee pads:11 > coffee pads:12 > energy drink:4 > coffee pads:10 # Для отмены нажмите Ctrl+D В потребителе включим свойство print.key для вывода ключей: $ kafka-console-consumer.sh \ --from-beginning \ --topic products.prices.changelog.multi-partitions-keys \ --property print.key=true \ --property key.separator=":" \ --bootstrap-server localhost:9092 coffee pads:10 cola:2 cola:1 coffee pads:11 coffee pads:12 coffee pads:10 energy drink:3 energy drink:4 # Для отмены нажмите Ctrl+C Processed a total of 8 messages
   70 Часть II. Основные концепции Здесь видно, что данные расположены не в порядке записи, но сообщения с одинаковым ключом сохраняют корректную последовательность, даже если между ними вклиниваются другие товары (рис. 4.6). Рис. 4.6. Сообщения с одинаковым ключом направляются в одну партицию ПРЕДУПРЕЖДЕНИЕ Неравномерное распределение ключей в Kafka приводит к дисбалансу в распределении данных между партициями. Ключи определяют размещение в партициях, поэтому из-за диспропорции некоторые партиции будут перегружены. В результате будет нарушен баланс нагрузки и упадет общая производительность кластера. Чтобы оптимизировать нагрузку и использование ресурсов в Kafka, необходимо тщательно продумывать распределение ключей. Обычно проблема распределения ключей не возникает, если количество уникальных ключей значительно превышает количество партиций. Например, если ключом является идентификатор клиента, а таких клиентов тысячи, то распределение между партициями будет достаточно сбалансированным. Однако требуется осторожность, если небольшое количество ключей генерирует значительный объем трафика. В таких случаях могут возникнуть перекосы и узкие места. Напри­мер, социальные сети должны обрабатывать нагрузку от топпользователей, генерирующих более 90 % трафика, иначе, чем трафик обычных пользователей, чтобы сохранить эффективность и быстрый отклик системы. 4.2.2. Группы потребителей Теперь мы можем распределить партиции по брокерам, тем самым балансируя нагрузку между брокерами. Продюсеры сами определяют, какие данные в какие партиции попадают. Если ключи не указаны, то продюсеры распределяют данные по партициям по принципу циклического перебора: сначала в партицию 0, затем
   Глава 4. Kafka как распределенный лог 71 в партицию 1 и т. д. Если ключи указаны, то сообщения с одинаковым ключом попадают в одну и ту же партицию. Обычно такой подход обеспечивает высокую производительность, но если данные из всех партиций читает только один потребитель, то он может не успевать их обрабатывать. Kafka не рассылает данные по потребителям, а потребители забирают их сами, поэтому корректно написанный потребитель не перегрузится — просто будет обрабатывать данные медленно. Пока мы еще не знаем, как масштабировать потребитель, так что он становится узким местом, а система не может полноценно использовать преимущества Kafka (рис. 4.7). Рис. 4.7. Если у нас только один потребитель, читающий данные из всех партиций, то он может не успевать их обрабатывать В большинстве случаев к одним и тем же данным обращаются потребители разных типов. В нашем интернет-магазине, например, аналитический сервис может считывать данные из топика products.prices.changelog, чтобы обновлять цены в базе данных. Для этого сервиса вполне достаточно одного потребителя, так как задача простая и требует мало ресурсов. У нас может быть еще один сервис, который берет данные из того же топика products.prices.changelog, чтобы выполнить научный анализ на основе данных из разных топиков и источников. В этом случае одного потребителя уже недостаточно — обработка данных займет слишком много времени. Для такого сценария требуются два условия. Во-первых, у разных сервисов должна быть возможность совершенно независимо друг от друга читать данные из партиций. Например, научный аналитический сервис должен иметь доступ к данным, даже если сервис метрик их уже считал. К счастью, в Kafka это легко реализуется, поскольку с помощью смещений потребители сами определяют, какие данные читать.
   72 Часть II. Основные концепции Во-вторых, нам нужен способ горизонтально масштабировать сервисы, чтобы данные партиций распределялись между экземплярами сервиса и считывались не как-нибудь, а еще и в правильном порядке. Для этого каждая партиция доступна для чтения только одним экземпляром сервиса одновременно. Партиция не может обрабатываться двумя экземплярами одного сервиса, поскольку это приведет к нарушению порядка данных. Группа потребителей отвечает обоим условиям. Потребители образуют группу, если совпадают их group.id. Потребители внутри группы распределяют нагрузку максимально равномерно между участниками и записывают свои смещения в Kafka. Это означает, что если остановить и перезапустить потребитель в группе, то он продолжит обработку с нужного места благодаря сохраненному смещению. Это показано на рис. 4.8. Рис. 4.8. Группы потребителей позволяют разделить обработку нескольких партиций между разными экземплярами одного сервиса. Часто данные из топика считываются несколькими группами потребителей одновременно. Группы потребителей изолированы друг от друга и не влияют на обработку данных другими группами Возьмем топик products.prices.changelog.multi-partitions-keys из предыдущего подраздела: $ kafka-console-consumer.sh \ --from-beginning \ --topic products.prices.changelog.multi-partitions-keys \ --property print.key=true \ --property key.separator=":" \ --group products \ --bootstrap-server localhost:9092
   Глава 4. Kafka как распределенный лог 73 coffee pads:10 energy drink:3 coffee pads:11 coffee pads:12 energy drink:4 coffee pads:10 cola:2 Cola:1 # Для отмены нажмите Ctrl+C Processed a total of 8 messages Единственное, что мы здесь изменили, — указали группу потребителей (--group products). При повторном запуске команды мы не увидим никаких сообщений, а при нажатии Ctrl+C получим уведомление, что обработано 0 сообщений: Processed a total of 0 messages Чтобы увидеть, как потребители в группе распределяют нагрузку между собой, запустим несколько потребителей одновременно с одинаковым идентификатором группы. Для этого можно выполнить команду, которую мы использовали ранее, в двух терминалах и расположить оба окна рядом, чтобы наблюдать вывод одновременно. Дополнительно запустим продюсер, который сгенерирует данные: $ kafka-console-producer.sh \ --topic products.prices.changelog.multi-partitions-keys \ --property parse.key=true \ --property key.separator=: \ --bootstrap-server localhost:9092 > energy drink:2 > energy drink:3 > cola:2 > cola:5 > energy drink:1 > cola:2 # Для отмены нажмите Ctrl+D Мы видим картину разделения между потребителями (табл. 4.1). Таблица 4.1. Окна потребителей Потребитель 1 Потребитель 2 cola:2 energy drink:2 cola:5 energy drink:3 cola:2 energy drink:1 Это значит, что потребители в группе успешно распределили работу между собой. Потребитель 1 отвечал за партицию с сообщениями для cola, а потребитель 2
   74 Часть II. Основные концепции взял на себя вторую партицию. Партиция доступна только одному потребителю из группы, но он при этом может читать несколько партиций. У нас в топике всего две партиции, поэтому нет смысла создавать больше двух потребителей в группе. Мы еще вернемся к группам потребителей в главе 6, посвященной производительности. ПРИМЕЧАНИЕ Если бы мы указали coffee pads вместо energy drink, то все сообщения попали бы к одному потребителю, так как оказались в одной партиции. 4.2.3. Репликация Нам уже известно, что партиционирование помогает надежно распределять данные между несколькими системами, а теперь мы хотим повысить надежность нашей системы. По своему опыту мы знаем, что компьютерные системы выходят из строя без видимых причин, жесткие диски внезапно ломаются, кто-то удаляет не ту папку и т. д. Потеря данных происходит по разным причинам, а самый надежный способ ее предотвратить — репликация. В небольших масштабах на личном компьютере мы (хотелось бы верить) настраиваем резервные копии, чтобы не потерять все данные при сбое системы. Резервное копирование применяется и в корпоративной среде. Недостаток такого подхода — длительное восстановление. Резервные копии не гарантируют бесперебойную работу системы, просто в случае катастрофы мы не потеряем данные. Поэтому резервное копирование называется холодной репликацией. Данные реплицируются, но доступ к ним иногда занимает много времени. В Kafka нет встроенной поддержки холодной репликации. Если нам нужны резервные копии, то мы должны создать их самостоятельно. Зато Kafka применяет стратегию теплой репликации: данные сохраняются не на одном сервере, а на нескольких. Если один брокер Kafka выходит из строя, то другой почти мгновенно берет на себя его функции и как ни в чем не бывало продолжает обрабатывать запросы от продюсеров и потребителей. В Kafka репликация настраивается на уровне топика, причем коэффициент репликации задается независимо. Обычно это делается при создании топика. Создадим топик products.prices.replication с тремя партициями и коэффициентом репликации 3: $ kafka-topics.sh \ --create \ --topic products.prices.replication \ --partitions 3 \ --replication-factor 3 \ --bootstrap-server localhost:9092 Created topic products.prices.changelog.replication.
   Глава 4. Kafka как распределенный лог 75 Логично предположить, что коэффициент репликации не может превышать количество доступных брокеров. В тестовом окружении, описанном в приложении A, используются три брокера Kafka. При попытке создания топика с четырьмя репликами система возвращает сообщение об ошибке: Error while executing topic command : Unable to replicate the partition 4 time(s): The target replication factor of 4 cannot be reached because only 3 broker(s) are registered. […] ERROR org.apache.kafka.common.errors.InvalidReplicationFactorException: Unable to replicate the partition 4 time(s): The target replication factor of 4 cannot be reached because only 3 broker(s) are registered. (org.apache.kafka.tools.TopicCommand) Посмотрим, что случилось после создания топика, выполнив команду kafkatopics.sh с флагом --describe: $ kafka-topics.sh \ --describe \ У нас три партиции и три реплики, --topic products.prices.replication \ как задумывалось --bootstrap-server localhost:9092 Topic: products.prices.replication PartitionCount: 3 ReplicationFactor: 3 Configs: Topic: products.prices.replication Partition: 0 Leader: 2 Replicas: 2,1,3 Isr: 2,1,3 Elr: Topic: products.prices.replication Partition: 1 Leader: 3 Replicas: 3,2,1 Isr: 3,2,1 Elr: Topic: products.prices.replication Partition: 2 Leader: 1 Replicas: 1,3,2 Isr: 1,3,2 Elr: Подробная информация обо всех трех партициях Мы видим, что для партиции 0 лидером является брокер 2. Что это значит? Стратегия репликации Kafka следует принципу «один лидер — несколько фолловеров», то есть у каждой партиции есть один брокер-лидер. В нашем примере брокер 2 выступает лидером для партиции 0 топика products.prices. replication. В зависимости от коэффициента репликации брокеров-фолловеров может быть больше. Все брокеры, на которых хранится данная партиция, называются репликами.То есть при коэффициенте репликации 1 обычно имеется одна реплика (один лидер) и ни одного фолловера. При коэффициенте репликации 3, как в нашем примере, в нормальном состоянии будет три реплики (один лидер и два фолловера). Зная это, можно понять остальной вывод команды kafka-topics.sh. Replicas: 1,3,2 означает, что реплики расположены на брокерах 1, 3 и 2. По умолчанию лидером партиции становится брокер, идентификатор которого указан первым в списке реплик. Из вывода видно, что это правило соблюдается для остальных двух партиций: брокер 3 первым указан для партиции 1 и является ее лидером, а лидер партиции 2 — брокер 1.
   76 Часть II. Основные концепции Наконец, строка Isr: 2,1,3 относится к in-sync-репликам, то есть к синхронизированным репликам, которые находятся в актуальном состоянии. Если список ISR не совпадает со списком реплик, это указывает на проблему, например на выход брокера из строя. Подробнее об ISR мы поговорим в одной из следующих глав. Как мы уже сказали, в Kafka у партиции всегда есть один лидер, через который проходят все операции чтения и записи. Фолловеры существуют исключительно для того, чтобы как можно быстрее реплицировать данные с лидера и обеспечить доступность в случае его сбоя (рис. 4.9). Рис. 4.9. Потребители и продюсеры взаимодействуют только с лидером (за редкими исключениями). Фолловеры непрерывно реплицируют новые сообщения от лидера. Если лидер выходит из строя, то его заменяет один из фолловеров При сбое лидера его роль переходит к одному из синхронизированных фолловеров (ISR) или хотя бы к кандидату на роль лидера (ELR). Продюсеры и потребители автоматически переключаются на нового лидера. Главное преимущество логов в Kafka — относительно простая и надежная реализация репликации. Фолловеры Kafka аналогично потребителям запрашивают новые сообщения у лидера, начиная с определенного смещения, и записывают эти данные в свой локальный лог. Благодаря этому обеспечивается высокая производительность при относительно простом коде и сохраняется порядок сообщений. Но гарантировать порядок обработки сообщений будет сложнее. Kafka достигает этого путем комбинирования методов: лидер всегда остается авторитетным источником данных для каждой партиции, а фолловеры поддерживают синхронизацию, непрерывно запрашивая данные у лидера. Этот подход обеспечивает строгую согласованность репликации: все потребители получают сообщения в правильном порядке независимо от того, с какой реплики считывают данные. Возникает вопрос: если лидер один, то можно ли равномерно распределить нагрузку между брокерами? Нет, если в кластере Kafka только одна партиция.
   Глава 4. Kafka как распределенный лог 77 Но обычно их множество. Один брокер не является лидером для всех партиций — у каждой он свой. При создании партиций Kafka стремится распределить роль лидера максимально сбалансированно между брокерами. Равномерное распределение нагрузки требует особого внимания в продакшене, так как это ключевой фактор производительности Kafka. 4.3. Компоненты Kafka В принципе, кластер Kafka состоит всего из трех компонентов: координационного кластера, брокеров и клиентов. Координационный кластер и брокеры мы уже несколько раз упоминали, но что насчет клиентов и какую роль играют потребители и продюсеры? Ответ прост: под клиентами подразумеваются все приложения, подключающиеся к кластеру Kafka, в том числе уже известные нам потребители и продюсеры. Типичный кластер Kafka обычно состоит из координационного кластера и трех дополнительных брокеров (рис. 4.10). Причину такой структуры мы подробнее объясним чуть позже. Рис. 4.10. Типичное окружение Kafka состоит из самого кластера Kafka, а также клиентов, которые записывают и читают данные из Kafka. До появления координационного кластера на основе Kafka Raft (KRaft) его роль выполнял кворум ZooKeeper. При отсутствии ZooKeeper брокеры могут сами брать на себя задачи координационного кластера или передавать их отдельному координационному кластеру
   78 Часть II. Основные концепции 4.3.1. Координационный кластер Распределенной системе требуется определенный уровень оркестрации. В Kafka эту задачу выполняет координационный кластер (рис. 4.11). Он постоянно отслеживает текущее состояние кластера, а также доступность и работоспособность всех брокеров. В рамках этой задачи он управляет списком всех активных брокеров и отвечает за добавление в кластер новых брокеров и удаление из кластера имеющихся. Рис. 4.11. Kafka использует координационный кластер на основе KRaft или ZooKeeper. В обоих случаях для достижения консенсуса требуется нечетное количество узлов (обычно три или пять) и протокол для достижения консенсуса Координационный кластер хранит метаданные о топиках и управляет доступом к топикам. Для управления ими он также хранит назначение партиций брокерам и помнит лидера каждой партиции. С помощью координационного кластера один из брокеров назначается контроллером, который отвечает за управление состоянием отдельных партиций и реплик. Например, он назначает брокеры партициям и определяет лидеров партиции. Если один брокер выходит из строя, то контроллер автоматически переназначает партиции и выбирает новых лидеров, чтобы поддержать доступность и согласованность. До появления KRaft роль координационного кластера выполнял кворум ZooKeeper. Он не является частью Kafka, а представляет собой независимый проект Apache Software Foundation. Это распределенный сервис координации, который помогает управлять конфигурацией, именованием и синхронизацией в распределенных системах. ZooKeeper предоставляет централизованный реестр для хранения метаданных и предлагает примитивы, такие как распределенные блокировки, необходимые распределенным приложениям. В Kafka ZooKeeper отвечал за метаданные кластера, управлял выбором лидеров, отслеживал назначения партиций и хранил списки контроля доступа (access control list, ACL). Однако ZooKeeper не был создан специально для Kafka, что приводило к ряду проблем. Как отдельная система, он требовал дополнительных навыков и затрат на обслуживание. Общая модель данных, хотя и давала много возможностей, не была оптимизирована для конкретных потребностей Kafka, поэтому при масштабировании возникали узкие места производительности. Эти ограничения привели к разработке Kafka Raft (KRaft), который берет на себя координацию внутри Kafka. KRaft имеет несколько преимуществ: упрощает архитектуру за счет устранения зависимости от стороннего ZooKeeper, улучшает
   Глава 4. Kafka как распределенный лог 79 производительность благодаря более специализированной системе управления метаданными и снижает операционную сложность благодаря наличию единой системы. Кроме того, KRaft вводит более эффективный протокол для обработки обновлений метаданных и выбора лидеров, что особенно полезно для крупных кластеров с частыми изменениями топологии. Начиная с Kafka 3.3, KRaft стал доступен для продакшена и теперь считается рекомендуемым механизмом координации. Для небольших кластеров ключевое преимущество KRaft заключается в возможности работы внутри самого кластера Kafka — часть брокеров может выполнять как стандартные операции Kafka, так и функции координации. Однако в крупных промышленных окружениях со множеством брокеров по-прежнему рекомендуется настраивать выделенный координационный кластер, чтобы обеспечить максимальную производительность и четкое разделение ответственности. Для надежной координации (при использовании KRaft или ZooKeeper) координационный кластер должен состоять из нечетного количества узлов, чтобы можно было достигать консенсуса большинством голосов. Если узла три, то кластер продолжает работу при отказе одного из них. При пяти узлах система сохраняет кворум даже при выходе из строя двух узлов. СОВЕТ Для небольших кластеров Kafka рекомендуется развертывать координационный кластер из трех брокеров KRaft или узлов ZooKeeper. В крупных кластерах можно запустить пять экземпляров, чтобы повысить отказоустойчивость. 4.3.2. Брокер Брокеры формируют собственно кластер Kafka и отвечают за прием, хранение и отправку сообщений и данных. Обычно кластер Kafka состоит из нескольких брокеров, поскольку без этого невозможна репликация данных — одна из ключевых концепций Kafka. Если кластер состоит из двух брокеров, то репликация уже возможна, но очень скоро возникнут проблемы. Например, при обслуживании кластера мы отключаем один брокер, а второй (и последний) выходит из строя, что приводит к потере данных. Поэтому брокеров должно быть не меньше трех. При увеличении нагрузки можно добавлять брокеры, чтобы повысить производительность. 4.3.3. Клиенты В Kafka клиентом считается любое внешнее приложение, подключающееся к кластеру. Строго говоря, клиенты нельзя назвать компонентами Kafka. Скрипты kafka-console-consumer.sh и kafka-console-producer.sh, знакомые нам по предыдущим главам, и есть клиенты Kafka. Это приложения на Java, обращающиеся к Kafka API. Клиенты на других языках обычно используют библиотеку
   80 Часть II. Основные концепции librdkafka (https://github.com/edenhill/librdkafka/). Подробнее о работе клиентов мы поговорим в части III. Простейшие типы клиентов — продюсеры и потребители. Продюсеры производят и записывают данные в Kafka, а потребители их потребляют, что очевидно из названий. Для обработки потоков данных Kafka предоставляет библиотеку Kafka Streams (https://kafka.apache.org/documentation/streams/), позволяющую фильтровать, преобразовывать и объединять данные. Она одновременно выступает в роли потребителя и продюсера. Фреймворк Kafka Connect (https://kafka.apache.org/ documentation/#connect) используется для интеграции Kafka с внешними системами. Например, с его помощью можно наполнять целые базы данными из кластера Kafka. Подробнее о работе с внешними системами, Kafka Streams и Kafka Connect мы поговорим в части IV. СОВЕТ Использование Kafka Connect вместо пользовательского кода потребителя или продюсера дает следующие преимущества: масштабируемость, бесшовную интеграцию с различными источниками данных, использование встроенных коннекторов, модульность и упрощенную обработку ошибок. Мы получаем стандартизированный, поддерживаемый и масштабируемый подход к интеграции данных. 4.4. Kafka в корпоративной среде Мы рассмотрели Kafka как распределенный лог. На практике Kafka представляет собой распределенную систему с несколькими брокерами, координационным кластером и, конечно, всеми продюсерами и потребителями, которые обмениваются данными через Kafka, стремясь решить бизнес-задачи. Сама по себе эта архитектура очень удобна для бизнеса, но часто централизованного хранилища и механизма маршрутизации данных недостаточно. Kafka как центр платформы потоковой обработки поможет использовать ценность бизнес-данных и принимать обдуманные решения почти в реальном времени. Правда, один он с этим не справится (рис. 4.12). Кластер Kafka не существует изолированно — он должен быть тесно связан с имеющимися системами предприятия, такими как многочисленные системы баз данных, хранящие основные данные, решения для обмена сообщениями, соединяющие проприетарные сервисы и сторонних поставщиков, устаревшие приложения, которые уже никто не решается модифицировать, и т. д. Разумеется, для подключения этих систем к Kafka можно написать отдельные потребители и продюсеры. К счастью, Kafka предоставляет Kafka Connect — инструмент и фреймворк для эффективного и надежного подключения сторонних систем. Kafka Connect поставляется напрямую с Apache Kafka и распространяется под той же лицензией Apache 2.0. В корпоративной среде могут оставаться мейнфреймы, для которых нет библиотек Kafka, и писать пользовательские
   Глава 4. Kafka как распределенный лог 81 коннекторы для Kafka Connect было бы крайне затруднительно. Для подключения таких систем можно, например, использовать один из доступных на рынке REST-прокси. Это позволяет системам, взаимодействующим через HTTP/REST, работать с Kafka. Рис. 4.12. Одним Kafka не обойтись. Экосистема Kafka предлагает множество компонентов для интеграции Kafka в корпоративную инфраструктуру и построения платформы потоковой обработки данных ПРИМЕЧАНИЕ Confluent предлагает REST Proxy — проприетарный REST-прокси для своей платформы Confluent Enterprise. В качестве альтернативы существует открытая версия REST Proxy от Aiven: https://github.com/aiven/karapace. Мы можем подключить к Kafka множество систем, которые не только обмениваются данными почти в реальном времени, но еще и оценивают или обрабатывают их. Поскольку данные в Kafka поступают потоком, то для этой цели нужна система потоковой обработки. Существует множество конкурирующих фреймворков для реализации потоковой обработки с Kafka: Kafka Streams, Apache Flink, Akka от Scala и др. С помощью библиотек потоковой обработки можно относительно легко реализовать масштабируемые и надежные операции даже на больших наборах данных, такие как фильтрация, преобразование отдельных наборов данных и объединение разных потоков. Некоторые решения на рынке позволяют выполнять потоковую обработку даже без навыков программирования.
   82 Часть II. Основные концепции Чем больше данных хранится в Kafka и чем дольше мы будем их хранить, тем важнее обеспечить согласованный формат. Если с кластером Kafka работает несколько команд, то управлять схемами данных особенно важно, при этом в Apache Kafka нет встроенных возможностей управления схемами, поскольку Kafka не обращает внимания на формат данных. Существует несколько подходов к управлению схемами: от хранения определений в Git-репозитории до специализированных компонентов, таких как Schema Registry от Confluent или реестр схем в Karapace. ПРИМЕЧАНИЕ Schema Registry от Confluent входит в состав платформы Confluent Enterprise. Karapace, включающий в себя ранее упомянутый REST Proxy и Schema Registry, доступен на GitHub: https://github.com/aiven/karapace. Если мы храним в Kafka очень важные данные, то, возможно, стоит зеркалировать их в дополнительные центры обработки данных. С момента выпуска Kafka MirrorMaker 2 у нас появился инструмент, значительно упрощающий использование Kafka в распределенных ЦОД. В некоторых случаях можно рассматривать подход с резервным копированием и восстановлением, хотя он оказывается более сложным и ресурсоемким для крупных кластеров Kafka, чем на первый взгляд. Кроме того, если мы не просто экспериментируем с Kafka, изучая новую технологию, то потребуются дополнительные компоненты. Платформу потоковой обработки необходимо тщательно отслеживать с помощью инструментов мониторинга. В идеале настройку и конфигурацию Kafka следует выполнять не вручную, а с использованием средств автоматизации. Отдельное внимание, особенно в крупных компаниях, необходимо уделять правилам соответствия и их применению. В книге мы не будем подробно рассматривать Kafka как платформу потоковой обработки данных, тем более что эта область динамично развивается. Некоторые из этих вопросов более подробно мы обсудим в главе 18, но лучше проконсультироваться с экспертами по Kafka, которые в курсе текущего положения дел. Резюме Лог — это последовательный список, в котором элементы добавляются в конец, а чтение происходит с определенной позиции. Kafka представляет собой распределенный лог, в котором данные топика распространяются по нескольким партициям на нескольких брокерах. Смещения определяют позицию сообщения внутри партиции. Kafka используется для обмена данными между системами. Он не заменяет базы данных, хранилища пар «ключ — значение» или поисковые движки. Партиции используются для горизонтального масштабирования топиков и обеспечения параллельной обработки.
   Глава 4. Kafka как распределенный лог 83 Продюсеры выбирают партицию для записи, используя партиционер. Сообщения с одинаковыми ключами попадают в одну и ту же партицию. Группы потребителей применяются для масштабирования потребителей и распределения нагрузки: одна партиция всегда обрабатывается одним потребителем внутри группы. Репликация обеспечивает надежность за счет дублирования партиций на нескольких брокерах внутри кластера Kafka. У каждой партиции есть один лидер, отвечающий за ее координацию. Kafka состоит из координационного кластера, брокеров и клиентов. Координационный кластер отвечает за оркестрацию кластера Kafka, то есть за управление брокерами. Брокеры формируют собственно кластер Kafka и отвечают за прием, хранение и предоставление сообщений. Клиенты производят и потребляют сообщения, подключаясь к брокерам. Существуют различные фреймворки и инструменты для простой интеграции Kafka в существующую корпоративную инфраструктуру.
5 Надежность В этой главе 3 Настройки подтверждений в Kafka. 3 Доступность данных и отказоустойчивость в Kafka. 3 Гарантии доставки сообщений в Kafka. 3 Транзакционные возможности Kafka. 3 Принцип «лидер — фолловер» в Kafka. Теперь вы знаете, как выглядят топики и сообщения в Kafka и в чем он похож на лог. В этой главе мы исследуем некоторые ключевые элементы работы Kafka. Прежде чем переходить к обсуждению того, как повлиять на производительность кластера Kafka (этой теме посвящена глава 6), мы рассмотрим параметры, которые можно менять для повышения надежности. Тему надежности можно условно разделить на три категории. Первая — сохранность данных (data durability), то есть как Kafka обеспечивает корректное и постоянное хранение данных. Вторая категория — доступность (availability), которая гарантирует продюсерам возможность записи данных, а потребителям — постоянный доступ к записанным данным. Сохранность данных и доступность неплохо обеспечиваются благодаря репликации. Проблемы возникают при рассмотрении согласованности (consistency) — третьей категории в концепции надежности Kafka. Согласованность имеет различные
   Глава 5. Надежность 85 проявления, которые мы детально рассмотрим далее в этой главе, но ее цель — гарантировать, что данные в кластере сохраняются точно в соответствии с ожиданиями. Обеспечить согласованность в распределенной системе не такто просто. Для гарантии доставки отправленных сообщений Kafka использует подтверждения (acknowledgments, ACKs), идемпотентные записи и транзакции, позволяющие продюсерам отправлять сообщения атомарно и обеспечивать целостность данных при множественных записях. Прежде чем углубиться в тему подтверждений, транзакций и репликации в Kafka, кратко рассмотрим потенциальные проблемы, связанные с сохранностью, доступностью и согласованностью. Представим кластер Kafka из трех брокеров, как в предыдущих примерах. Пока все работает исправно, проблем с сохранностью, доступностью и согласованностью данных не возникает. Но что будет, если один из брокеров внезапно выйдет из строя? Сможем ли мы считывать данные из топиков? Как это повлияет на согласованность данных? Сможем ли мы и дальше производить и записывать данные? Что произойдет, если сломанный брокер внезапно снова начнет работать или откажет еще один? Поддерживается ли гарантия постоянного хранения данных? Можем ли мы и дальше считывать данные, или это вызовет проблемы согласованности? Как видите, иногда приходится расставлять приоритеты между отдельными свойствами. Что для нас важнее — непрерывно записывать и считывать данные даже при серьезных сбоях или гарантировать правильность данных? На все эти вопросы мы ответим в следующих разделах. 5.1. Подтверждения Подтверждения успешной передачи данных используются многими сетевыми протоколами, такими как TCP или IEEE 802.11 (Wi-Fi), но механизмы на нижних уровнях коммуникации не всегда способны гарантировать успешную передачу данных. Например, если линия данных сильно перегружена или полностью нарушена, то даже TCP не может дать гарантий и прерывает процесс после нескольких неудачных попыток. Даже если передача прошла успешно, это не гарантирует, что приложение обработало ее корректно, поэтому многие приложения, в том числе Kafka, используют собственные подтверждения. В Kafka подтверждения используют продюсеры, а потребители применяют для гарантии чтения другой метод, который мы подробно рассмотрим в одной из следующих глав. В Kafka подтверждения не только гарантируют, что брокер получил данные от продюсера, но и играют свою роль в стратегии репликации. Подтверждения в Kafka никак не зависят от конфигурации топика и настраиваются в продюсере, но конфигурация топика в сочетании с подтверждениями существенно влияет на надежность и производительность.
   86 Часть II. Основные концепции 5.1.1. Стратегии подтверждения в Kafka Прежде чем говорить о последствиях использования подтверждений в Kafka, рассмотрим различные способы и доступные варианты настройки подтвер­ ждений. Она контролируется непосредственно продюсером. Есть три варианта настройки (рис. 5.1). Мы обсудим их в порядке убывания с точки зрения общей надежности системы. Рис. 5.1. Конфигурации подтверждений. Если установлено acks=all (или acks=–1), то продюсер не получает обратную связь, пока все синхронизированные реплики не реплицируют сообщение. Если установлено acks=1, то продюсер получает ответ, как только лидер получит сообщение. Если установлено acks=0, то продюсер отправляет сообщения и вообще не ждет никаких подтверждений Продюсер kafka-console-producer.sh использует для этой цели аргумент --producer-property, с помощью которого можно задать такие свойства, как подтверждения (ACK): $ kafka-console-producer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 \ --producer-property acks=all В первом случае зададим для стратегии подтверждения значение all. Это означает, что после успешного получения сообщения брокер, или лидер партиции, не отправит подтверждение продюсеру до тех пор, пока сообщение не будет успешно распространено на все синхронизированные реплики (ISR). Реплики считаются синхронизированными, если получали актуальные данные за последние 30 секунд. ПРИМЕЧАНИЕ Значение acks=–1 эквивалентно acks=all. Кроме того, можно установить acks=1: $ kafka-console-producer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 \ --producer-property acks=1
   Глава 5. Надежность 87 Эту настройку можно наглядно сравнить с TCP. Получив сообщение, лидер отправляет подтверждение продюсеру. В отличие от конфигурации acks=all продюсер получает подтверждение быстрее, так что задержка немного сокращается. Но если лидер выходит из строя сразу после отправки подтверждения и до распространения сообщений по фолловерам, то следует ожидать потери данных. Кроме того, сообщение может записаться в топик дважды, если не принять дополнительные меры предосторожности. К этому мы вернемся чуть позже. Наконец, рассмотрим вариант acks=0: $ kafka-console-producer.sh \ --topic products.prices.changelog \ --bootstrap-server localhost:9092 \ --producer-property acks=0 В этом случае брокер не отправляет подтверждение продюсеру. Такая конфигурация аналогична протоколу UDP. Продюсер отправляет данные, но ему все равно, дошли они или нет. Он просто отправляет их один раз, и Kafka не гарантирует, что они будут сохранены. Это не означает, что сообщения не доходят, тем более что Kafka по-прежнему использует TCP на транспортном уровне. И все же эта стратегия подтверждения не отличается надежностью и допустима только в случаях, когда потеря данных некритична. Зато такая конфигурация обеспечивает более высокую производительность по сравнению с двумя другими стратегиями. Представим сценарий, в котором датчик каждую миллисекунду измеряет температуру и отправляет данные в кластер Kafka. Если речь идет не о контроле температуры фармацевтической продукции, то потеря некоторых значений, вероятно, некритична, так как важны текущие показания, а повторная отправка старых данных добавит лишней нагрузки на сеть и брокеры. В этой ситуации значение acks=0 обеспечивает наилучшую производительность, если потеря данных допустима, а значение acks=all — наилучшую согласованность. ПРИМЕЧАНИЕ С версии Kafka 3.0 установлено значение по умолчанию acks=all. В предыдущих версиях — acks=1. 5.1.2. Подтверждения и синхронизированные реплики Свойство конфигурации топика min.insync.replicas действует совместно с настройкой продюсера acks=all. Мы знаем, что при acks=all лидер не отправляет подтверждение продюсеру до тех пор, пока все синхронизированные реплики (ISR) не сохранят данные. Задавая min.insync.replicas, мы указываем, что хотя бы некоторое количество реплик должно быть синхронизировано, прежде чем можно будет отправить подтверждение в конфигурации acks=all. Если доступно или синхронизировано недостаточно реплик, то продюсер получает соответствующее сообщение об ошибке и повторяет попытку.
   88 Часть II. Основные концепции Ради эксперимента создадим новый топик products.prices.changelog.min-isr-2 и попытаемся записать в него данные: $ kafka-topics.sh \ --create \ --topic products.prices.changelog.min-isr-2 \ --replication-factor 3 \ --partitions 3 \ --config min.insync.replicas=2 \ --bootstrap-server localhost:9092 С помощью аргумента --config мы точнее настраиваем топик. Например, задаем минимальное количество ISR, равное двум: min.insync.replicas=2. Посмотрим, что за топик у нас получился: $ kafka-topics.sh \ --describe \ --topic products.prices.changelog.min-isr-2 \ --bootstrap-server localhost:9092 Topic: products.prices.changelog.min-isr-2 PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=2 Topic: products.prices.changelog.min-isr-2 Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 Topic: products.prices.changelog.min-isr-2 Partition: 1 Leader: 2 Replicas: 2,3,1 Isr: 2,1,3 Topic: products.prices.changelog.min-isr-2 Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 1,2,3 Как и ожидалось, у нас три партиции, каждая с тремя синхронизированными репликами. Пока наши три брокера доступны, мы не заметим разницы в стратегиях подтверждения, поэтому отключаем два брокера. ПРИМЕЧАНИЕ Если брокер с ID 2 является активным контроллером, то кластер перестанет отвечать, так как оставшийся контроллер не сможет избрать новый активный контроллер. Эту проблему можно легко решить, временно включив один из отключенных брокеров и снова остановив его через некоторое время. В приложении A вы найдете скрипт kafka-broker-stop.sh, который мы используем на данном этапе для остановки брокеров с ID 2 и ID 3. Посмотрим, что стало с нашим топиком: $ kafka-broker-stop.sh 3 $ kafka-broker-stop.sh 2 $ kafka-topics.sh \ --describe \ --topic products.prices.changelog.min-isr-2 \ --bootstrap-server localhost:9092 Topic: products.prices.changelog.min-isr-2 PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=2 Topic: products.prices.changelog.min-isr-2 Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1
   Глава 5. Надежность 89 Topic: products.prices.changelog.min-isr-2 Partition: 1 Leader: 1 Replicas: 2,3,1 Isr: 1 Topic: products.prices.changelog.min-isr-2 Partition: 2 Leader: 1 Replicas: 3,1,2 Isr: 1 Как видите, брокер 1 взял на себя задачи брокера 3, став лидером партиции 2, и брокера 2, став лидером партиции 1. Брокеры 2 и 3 пропали из списка ISR. Отправим в топик сообщения с настройкой acks=0. Запустим скрипт kafkaconsole-consumer.sh в другом терминале, чтобы прочитать сообщение напрямую: $ kafka-console-consumer.sh \ --topic products.prices.changelog.min-isr-2 \ --from-beginning \ --bootstrap-server localhost:9092 Затем запустим kafka-console-producer.sh: $ kafka-console-producer.sh \ --topic products.prices.changelog.min-isr-2 \ --bootstrap-server localhost:9092 \ --producer-property acks=0 > cola 0 Проблем с недоступными брокерами или отправкой сообщений нет, но никаких сообщений в kafka-console-consumer.sh мы не видим. Поскольку мы указали acks=0, то никто не гарантирует, что сообщение сохранится, так как для потреб­ ления сообщений требуется наличие хотя бы минимального количества ISR. То же правило действует при указании значений acks=1: $ kafka-console-producer.sh \ --topic products.prices.changelog.min-isr-2 \ --bootstrap-server localhost:9092 \ --producer-property acks=1 > cola 1 Хотя наше сообщение не отображается в окне потребителя, мы можем быть уверены, что оно было успешно сохранено на лидере партиции, поскольку мы указали acks=1. Посмотрим, что будет, если мы попробуем создать сообщение при acks=all: $ kafka-console-producer.sh \ --topic products.prices.changelog.min-isr-2 \ --bootstrap-server localhost:9092 \ --producer-property acks=all > cola all > […] WARN [Producer clientId=console-producer] Got error produce response with correlation id 10 on topic-partition products.prices.changelog.min-isr-2-2, retrying (2 attempts left). Error: NOT_ENOUGH_REPLICAS (org.apache.kafka.clients.producer.internals.Sender) […] WARN [Producer clientId=console-producer] Got error produce response with correlation id 11 on topic-partition products.prices.changelog.min-isr-2-2, retrying (1 attempts left).
   90 Часть II. Основные концепции Error: NOT_ENOUGH_REPLICAS (org.apache.kafka.clients.producer.internals.Sender) […] WARN [Producer clientId=console-producer] Got error produce response with correlation id 12 on topic-partition products.prices.changelog.min-isr-2-2, retrying (0 attempts left). Error: NOT_ENOUGH_REPLICAS (org.apache.kafka.clients.producer.internals.Sender) […] ERROR Error when sending message to topic products.prices.changelog.minisr-2 with key: null, value: 8 bytes with error: (org.apache.kafka.clients. producer.internals.ErrorLoggingCallback) org.apache.kafka.common.errors. NotEnoughReplicasException: Messages are rejected since there are fewer in-sync replicas than required. Сначала мы получаем предупреждения о недостаточном количестве реплик (NOT_ENOUGH_REPLICAS), и продюсер пытается повторно отправить сообщение. После нескольких неудачных попыток возникает ошибка — сообщение отклонено из-за недостаточного количества ISR, поскольку только одна реплика синхронизирована, а требуется хотя бы две. Если бы мы остановили только один брокер, то смогли бы производить и потреблять сообщения со значением acks=all. Запустим брокеры снова: $ ~/kafka/bin/kafka-server-start.sh -daemon ~/kafka/config/kafka2.properties $ ~/kafka/bin/kafka-server-start.sh -daemon ~/kafka/config/kafka3.properties Наконец мы сможем получить сообщения cola 0 и cola 1, отправленные при acks=0 и acks=1 соответственно, поскольку перезапущенные брокеры нагнали лидера и восстановили синхронизацию. ПРИМЕЧАНИЕ В предыдущих версиях Kafka для потребления сообщения не требовалось соблюдать условие по минимальному количеству синхронизированных реплик (min.insync.replicas). Достаточно было того, чтобы сообщение сохранялось на оставшихся ISR. Этот пример наглядно демонстрирует, как на кластер Kafka влияет стратегия подтверждений в сочетании со свойством топиков min.insync.replicas. Мы еще рассмотрим, как именно продюсер отправляет данные и как ведет себя в подобных ситуациях, в одной из следующих глав. Если не задать min.insync. replicas или установить значение 1 при количестве ISR, равном 1, то данные могут потеряться, даже если задано acks=all, поскольку acks=all в этом случае дает тот же результат, что и acks=1. Поэтому следует установить разумное значение для min.insync.replicas. Теперь можно снова запустить брокеры. СОВЕТ Обычно мы рекомендуем устанавливать для параметра min.insync.replicas значение 2 при коэффициенте репликации 3, но необходимо тщательно анализировать сценарии сбоев. Если требуется повышенная отказоустойчивость — например, мы хотим даже при потере двух брокеров иметь еще один в запасе, — следует установить коэффициент репликации 4 и минимальное количество синхронизированных реплик min.insync.replicas — 2.
   Глава 5. Надежность 91 5.1.3. Гарантии доставки сообщений в Kafka Мы уже видели, что при acks=0 невозможно гарантировать доставку сообщения брокерам, но мы точно знаем, что дублирую­ щие сообщения не появятся, поскольку acks=0 дает гарантию доставки не более одного раза (рис. 5.2). Обычно мы предпочитаем более надежную Рис. 5.2. acks=0 гарантирует доставку конфигурацию, поэтому устанавливаем сообщения не более одного раза acks=all. Остался еще один вопрос: что будет, если сообщение сохранено, но подтверждение не дошло до продюсера? Он отправляет сообщение повторно, поскольку обязан предположить, что сообщение не удалось сохранить (рис. 5.3). В результате сообщение дважды сохраняется в кластере, а мы об этом не знаем. Если речь идет о датчике температуры (см. пример выше), то такое повторное сохранение не должно стать проблемой, но предположим, что сообщение представляет банковскую транзакцию. Такое поведение может привести к двойному списанию или зачислению средств, а это уже недопустимо. Рис. 5.3. Проблемы возникают, когда продюсер ожидает подтверждений, но они теряются по пути от лидера к продюсеру, хотя данные были записаны. В этой ситуации продюсер отправляет сообщение повторно, и оно появляется в логе дважды, возможно, даже в неправильном порядке (не показано)
   92 Часть II. Основные концепции На данном этапе стоит рассмотреть типы гарантий доставки сообщений в распределенных системах: не более одного раза, хотя бы один раз и ровно один раз. «Не более одного раза»: гарантирует, что сообщение будет доставлено максимум один раз и не будет дублироваться. В этом случае доставка вообще не гарантирована. Такой тип соответствует настройке acks=0. Этот вариант можно применять, например, при непрерывной отправке большого количества показаний, где важно избежать повторов, а потеря отдельных значений некритична. Гарантия «хотя бы один раз» достигается путем установки для параметра min.insync.replicas разумного значения (например, 2), а для acks — all. В этом случае сообщение гарантированно доставляется, но возможны дубликаты, что приводит к описанной выше проблеме. Прежде чем в Kafka появилась семантика «ровно один раз», этот вариант выбирался для большинства приложений. Гарантия «ровно один раз» обеспечивает сохранение сообщения строго один раз, причем в правильном порядке, с помощью математического свойства идемпотентности. Оно означает, что при многократном вызове функции или операции с одинаковыми входными данными мы всегда получаем тот же результат. В Kafka идемпотентность реализована так: продюсер назначает последовательный идентификатор для каждого сообщения. До версии Kafka 3.3 эта функция по умолчанию была отключена для продюсеров и сообщения отправлялись без последовательных идентификаторов: $ kafka-console-producer.sh \ --topic products.prices.changelog.min-isr-2 \ --bootstrap-server localhost:9092 \ --producer-property acks=all \ --producer-property enable.idempotence=true Как и подтверждения, идемпотентность можно включить аргументом --producerproperty (enable.idempotence=true). ПРИМЕЧАНИЕ Для идемпотентности требуются следующие настройки: acks=all, max.in.flight.requests.per.connection не больше 5 (по умолчанию 5) и retries больше 0. Количество повторных попыток по умолчанию — 2 147 483 647 (MAX_LONG), но не беспокойтесь: настройка времени доставки предотвращает такую настойчивую отправку сообщений. Настройка max.in.flight.requests.per.connection указывает количество неподтвержденных запросов, которое можно отправить брокеру. Если идемпотентность включена, то лидер партиции убеждается, что сообщение получено и поступило в правильном порядке. Если сообщение уже сохранено, то повторно отправляется только подтверждение (рис. 5.4).
   Глава 5. Надежность 93 Рис. 5.4. При включенной идемпотентности продюсер отправляет с каждым сообщением свой ID, а также порядковый ID сообщения, чтобы брокер замечал пропущенные сообщения, нарушение порядка или повторные отправки, а затем принимал меры Если сообщения приходят в неправильном порядке, то лидер отправляет продюсеру отрицательное подтверждение (negative ACK — NACK), после чего тот повторяет отправку сообщения (рис. 5.5). Идемпотентность помогает обеспечить корректный порядок записи сообщений внутри партиции, а также наличие каждого сообщения ровно в одном экземпляре. Она по умолчанию была отключена до Kafka 3.0, чтобы сообщество успело обновить клиенты. Ее влияние на производительность незначительно. СОВЕТ Настоятельно не рекомендуем отключать идемпотентность. Если возникнут проблемы с производительностью, то они, скорее всего, не связаны с идемпотент­ ностью — сначала устраните другие причины.
   94 Часть II. Основные концепции Рис. 5.5. Если сообщение получено не в том порядке, то брокер отправляет продюсеру NACK. Таким образом гарантируется правильный порядок сообщений. Например, предотвращается ситуация, когда сообщение Б приходит раньше сообщения A 5.2. Транзакции В этом разделе мы поговорим об основных принципах управления транзакциями в Kafka. Транзакции в нем крайне важны для обеспечения согласованности и надежности данных, поскольку позволяют атомарно сохранять данные в нескольких партициях и корректно обрабатывать сообщения. Мы рассмотрим ключевые этапы инициализации, выполнения и поддержания транзакций, раскрывая механизмы Kafka, позволяющие гарантировать целостность транзакций даже в сложных сценариях. Правильно сохранять сообщения в партициях — это хорошо, но обычно эти данные нужны для дальнейшей обработки. Идемпотентность продюсера не влияет на потребителей или сторонние приложения. При неправильной реализации сообщения все-таки могут дублироваться и теряться. Взглянув на процесс потребления сообщений в Kafka более пристально, мы заметим, что каждая операция чтения одновременно является операцией записи — поскольку нам нужно как-то зафиксировать смещение. Kafka позволяет хранить смещения в самой системе, но это не обязательно.
   Глава 5. Надежность 95 5.2.1. Транзакции в базе данных Если данные из Kafka нужно передавать в систему базы данных строго один раз, то можно применить два варианта требуемой семантики. Во-первых, мы можем использовать потребитель Kafka как обычно и фиксировать смещения в Kafka, предварительно записывая данные в базу данных. При этом мы должны гарантировать, что операция записи в базу данных является идемпотентной, то есть перед каждой операцией записи мы проверяем, есть там эти данные или нет, и записываем только в том случае, если их еще нет. Самый простой способ достичь этого — использовать операторы UPSERT. В PostgreSQL получится примерно следующее: INSERT INTO products_price_changelog (id, timestamp, name, price) VALUES (…) ON CONFLICT DO NOTHING; Во-вторых, можно хранить смещения в самой базе данных, а не в Kafka. Если мы будем записывать данные вместе со смещением в рамках одной транзакции, то гарантируем, что каждое сообщение из Kafka будет сохранено в базе данных ровно один раз, так как смещения записываются вместе с данными атомарно. В PostgreSQL это можно реализовать следующим образом: BEGIN TRANSACTION; INSERT INTO products_price_changelog (id, timestamp, name, price) VALUES (…); INSERT INTO offsets (topic, partition, offset) VALUES ('products_price_changelog', 0, 123); COMMIT; Но что, если мы хотим считать данные из одного топика Kafka, обработать их и записать в другой? В этом случае также требуется способ атомарно записывать данные в несколько партиций, а именно в партиции целевых топиков и партиции топика __consumer_offsets (рис. 5.6). Процесс чтения данных, их обработки и записи в другой топик настолько распространен, что Kafka Streams предоставляет библиотеку для максимального упрощения этой задачи. Рис. 5.6. Транзакции в Kafka позволяют атомарно записывать данные в несколько партиций. Либо все сообщения будут записаны, либо ни одно
   96 Часть II. Основные концепции 5.2.2. Транзакции в Kafka Для надежной реализации транзакций в Kafka необходимы два компонента: метод надежной отправки сообщений в отдельные партиции (с помощью идемпотентных процедур) и способ атомарной записи сообщений в несколько партиций одновременно, чтобы записать либо все сообщения из транзакции, либо ни одного. Мы можем использовать транзакции в коде аналогично тому, как используем их в базе данных. Сначала мы указываем продюсеру, что хотим задействовать транзакции. Затем начинаем транзакцию, записываем сообщения и при необходимости фиксируем смещение. Наконец, фиксируем саму транзакцию, чтобы она завершилась успешно. Только после этого потребители смогут обработать записанные сообщения. При отмене транзакции мы не удаляем записанные сообщения из партиций, а помечаем их так, чтобы потребители их игнорировали. Классический пример использования транзакций — перевод денег между пользователями. Например, Боб переводит деньги Алисе. Для этого необходимо уменьшить баланс Боба на сумму перевода и увеличить баланс Алисы на ту же сумму. Оба действия должны выполняться атомарно: либо перевод успешен и счета обоих пользователей обновлены, либо изменения не применяются ни к одному из счетов. В Kafka эту логику можно реализовать, например, с помощью такого кода на Python: Импортируем библиотеку Python confluent_kafka from confluent_kafka import Producer producer = Producer({ 'bootstrap.servers': 'localhost:9092', 'acks': 'all', 'enable.idempotence': True, Обеспечиваем совместимость продюсера с продюсерами Java 'partitioner': 'murmur2_random', }) 'transactional.id': 'transaction-1', producer.init_transactions() producer.begin_transaction() ID транзакционного продюсера Инициализация транзакционного продюсера producer.produce("customer.balance", key="bob", value="-10") producer.produce("customer.balance", key="alice", value="+10") producer.commit_transaction() Рекомендованные параметры для надежных продюсеров Начало транзакции Обычная генерация сообщений Фиксация транзакции Мы импортируем библиотеку Kafka для нашего языка программирования — confluent-kafka . Это библиотека Python, официально поддерживаемая Confluent. Библиотека основана на librdkafka, предпочтительной библиотеке C
   Глава 5. Надежность 97 для Kafka. Сначала мы инициализируем продюсер с помощью bootstrap.ser­ vers с партиционером murmur2_random, чтобы поддержать совместимость с Java. Кроме того, необходимо указать уникальный transactional.id для этого продюсера. Не должно быть продюсеров с одинаковым transactional.id, поскольку они будут мешать друг другу. Используя этот идентификатор, Kafka помечает предыдущий продюсер как «зомби»1, если появляется более новый продюсер с тем же transactional.id. Прежде чем выполнять транзакции, инициализируем продюсер с помощью команды init_transactions(). Теперь запустим транз­ акцию (begin_transaction()), запишем сообщения (produce()) и завершим транзакцию (commit_transaction()). Для отмены транзакции можно вызвать abort_transaction(). Если в приложении есть и потребитель, и продюсер с гарантией строго однократной обработки сообщений, то дело усложняется. В Kafka только продюсеры могут участвовать в транзакциях, так что, если мы фиксируем смещения прямо в потребителе, это не считается частью транзакции, что может привести к потере сообщений или их дублированию. Для решения этой проблемы нужно отключить в потребителе автоматическую фиксацию смещений, указывая для параметра enable.auto.commit значение False . Теперь потребитель не будет фиксировать смещения при обработке сообщений. Вместо потребления сообщения мы используем функцию send_ offsets_to_transaction() в продюсере, чтобы зафиксировать смещение в той же транзакции, в которой мы отправляем сообщение. Благодаря этому фиксация смещения и отправка сообщения выполняются атомарно, что гарантирует их совместное применение — или совместный откат. Обработка сообщения и его смещения в транзакции происходит на стороне продюсера, что гарантирует ее однократность. 5.2.3. Транзакции и потребители В потребителе для параметра isolation.level нужно обязательно установить значение read_committed, иначе потребитель будет читать незафиксированные сообщения и наши транзакции не будут иметь никакого смысла. СОВЕТ Мы рекомендуем на всех потребителях устанавливать для параметра isolation.level значение read_committed, даже если вы пока не планируете применять транзакции. Если в будущем вы передумаете, то будете очень благодарны себе за то, что не придется переделывать все потребители. Но зачем вообще устанавливать уровень изоляции и как работают такие транз­ акции? Kafka не изобретает велосипед, а использует разновидность протокола 1 В состоянии неопределенности. — Примеч. науч. ред.
   98 Часть II. Основные концепции двухфазной фиксации (Two-Phase Commit protocol, 2PC), чтобы автоматически записывать сообщения в несколько партиций. Представим, что планируем путешествие и хотим забронировать отель и билеты на самолет одновременно, но знаем, что иногда в процессе возникают ошибки. Мы не хотим остаться с билетами, но без отеля, или наоборот. Поэтому мы звоним в отель и резервируем номер с возможностью отмены. Конечно, отель не будет ждать бесконечно, поэтому мы обещаем перезвонить через 10 минут для подтверждения брони. Администратор соглашается. Затем мы покупаем авиабилеты как обычно. Если купить билеты не получилось, то мы звоним в отель и отменяем бронь или просто ждем 10 минут, пока она не аннулируется автоматически. Если купить билеты удалось, то мы звоним в отель и подтверждаем бронь. Примерно такой же подход Kafka применяет для транзакций. Сначала мы регистрируем партиции, в которые хотим записать сообщения, у одного из брокеров, координирующих транзакцию. Затем записываем данные в эти партиции обычным образом, но помечаем сообщения как предварительные. После завершения транзакции мы уведомляем координатор транзакции, который записывает маркеры фиксации в партиции, чтобы подтвердить транзакцию. Если она отменяется, то ее координатор записывает маркеры отмены. ПРИМЕЧАНИЕ Координатор транзакций гарантирует их надежность: даже при сбоях брокеров или продюсеров целостность данных не нарушается. Брокер гарантирует, что потребитель с уровнем read_committed получает только зафиксированные сообщения. На рис. 5.7 показаны три простых сценария. В первом случае у нас есть сообщения с начатой, но еще не зафиксированной или отмененной транзакцией. В такой ситуации брокер прекращает отправку сообщений потребителю, как если бы никаких сообщений не поступало вовсе. Даже сообщения, не относящиеся к транзакциям, не могут быть прочитаны, поскольку брокер обязан сохранять порядок сообщений. ПРИМЕЧАНИЕ Потребитель не знает ни о каких транзакциях — текущих, зафиксированных или отмененных, — поскольку за них отвечает только брокер. Во втором случае, если транзакция зафиксирована, все сообщения между первым сообщением транзакции и маркером фиксации становятся доступными для потребления. В третьем случае, если транзакция отменена, все сообщения, относящиеся к этой транзакции, пропускаются. Сообщения, не входящие в транз­ акцию, передаются потребителю в правильном порядке и могут обрабатываться как обычно. ПРИМЕЧАНИЕ Если перед маркером фиксации есть другая текущая транзакция, то потребитель сможет получить только сообщения до ее первого сообщения. Необхо­ димо дождаться ее завершения или прерывания.
   Глава 5. Надежность 99 Рис. 5.7. Потребитель перестает получать новые сообщения, если транзакция остается незавершенной. Брокер отправляет данные только до начала незавершенной транзакции и удерживает все последующие сообщения, в том числе не связанные с транзакцией. Порядок сообщений гарантируется. При получении подтверждения транзакции все сообщения разблокируются и передаются потребителю. Если транзакция отменяется, то потребитель не получает сообщения, принадлежащие отмененной транзакции, но остальные сообщения обрабатываются в обычном режиме Такой подход, конечно, влияет на производительность. Координатор транзакций должен записывать дополнительные сообщения для каждой партиции, поэтому издержки зависят от количества партиций, участвующих в транзакции, но не от количества записанных сообщений. Получается, что для очень коротких транзакций (в миллисекундном диапазоне) издержки будут значительными, но по мере увеличения длительности транзакций они сокращаются. Например, при внедрении транзакций в Kafka разработчики измерили, что для транзакций длительностью 100 миллисекунд при максимальной скорости генерации сообщений издержки составляли около 3 %. Однако для более коротких транз­ акций (10 миллисекунд) издержки также составляли около 3 миллисекунд, то есть 30 % от общего времени. 5.3. Репликация и принцип «лидер — фолловеры» В предыдущих главах мы много говорили о репликации и партициях в Kafka. Мы уже знаем, что Kafka управляет репликами по принципу «лидер — фолловеры». В этом разделе мы подробнее рассмотрим, как этот принцип реализован в Kafka. Мы увидим, как брокер становится лидером партиции и какие задачи с этим связаны. Вдобавок мы разберем, как фолловеры и лидеры взаимодействуют друг с другом и что происходит при отказе лидера.
   100 Часть II. Основные концепции Рассмотрим пример. Для начала создадим топик products.prices.changelog.re­ plication-3: $ kafka-topics.sh --create \ --topic products.prices.changelog.replication-3 \ --replication-factor 3 \ --partitions 3 \ --config min.insync.replicas=2 \ --bootstrap-server localhost:9092 Created topic products.prices.changelog.replication-3. Посмотрим, как партиции распределились по брокерам: $ kafka-topics.sh --describe \ --topic products.prices.changelog.replication-3 \ --bootstrap-server localhost:9092 Topic: products.prices.changelog.replication-3 PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=2 Topic: products.prices.changelog.replication-3 Partition: 0 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1 Elr: Topic: products.prices.changelog.replication-3 Partition: 1 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2 Elr: Topic: products.prices.changelog.replication-3 Partition: 2 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 Elr: Мы установили коэффициент репликации 3 и у нас всего три брокера, поэтому каждая из трех партиций реплицируется на каждый из трех брокеров. Лидеры партиций равномерно распределены между брокерами в целях балансировки нагрузки. Брокер 1 выступает лидером для партиции 2, брокер 2 — для партиции 0, а брокер 3 — для партиции 1 (рис. 5.8). Рис. 5.8. Распределение партиций топика между тремя брокерами. Брокер 2 выступает лидером для партиции 0, брокер 3 — для партиции 1, а брокер 1 — для партиции 2. Мы установили коэффициент репликации 3, и у нас всего три брокера, поэтому каждая из трех партиций оказывается на каждом брокере
   Глава 5. Надежность 101 В разделе 5.1 о подтверждениях уже упоминалось, что продюсер всегда отправляет сообщения лидеру партиции, а тот, в зависимости от стратегии подтверждений, ожидает получения сообщения фолловерами, прежде чем отправлять подтверждение продюсеру. Но как сообщения передаются от лидера к фол­ловерам? Брокеры Kafka делегируют максимальный объем работы клиентам. С точки зрения Kafka (или лидера партиции) фолловеры являются потребителями, которые запрашивают сообщения с помощью специального запроса fetch (рис. 5.9). Как и обычные потребители, фолловеры сообщают лидеру о своем текущем смещении, то есть позиции чтения в логе. Благодаря этому лидер узнает, когда фолловер обработал сообщение, и отправляет подтверждение продюсеру. Рис. 5.9. Фолловеры непрерывно получают данные от лидера, чтобы поддерживать синхронизацию и быть наготове, если придется взять на себя роль лидера в случае его отказа Интересный момент возникает при отказе брокера. Чтобы увидеть такую ситуацию, остановим брокер 3, используя скрипт kafka -broker-stop.sh, и снова посмотрим на топик: $ kafka-broker-stop.sh 3 $ kafka-topics.sh --describe \ --topic products.prices.changelog.replication-3 \ --bootstrap-server localhost:9092 Topic: products.prices.changelog.replication-3 PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=2 Topic: products.prices.changelog.replication-3 Partition: 0 Leader: 2 Replicas: 2,3,1 Isr: 2,1 Elr: Topic: products.prices.changelog.replication-3 Partition: 1 Leader: 1 Replicas: 3,1,2 Isr: 1,2 Elr: Topic: products.prices.changelog.replication-3 Partition: 2 Leader: 1 Replicas: 1,2,3 Isr: 1,2 Elr: ПРИМЕЧАНИЕ В зависимости от распределения партиций в кластере перед созданием топика products.prices.changelog.replication-3 не каждый брокер может быть лидером одной из партиций. В этом случае убедитесь, что остановленный брокер действительно назначен лидером топика, чтобы вы могли воспроизвести пример. Мы видим, что брокер 3 по-прежнему есть в списке реплик, но больше не отображается в списке ISR. Если брокер, являющийся лидером партиций, выходит
   102 Часть II. Основные концепции из строя, то Kafka (или контроллер) должен назначить нового лидера для «осиротевших» партиций, иначе топик будет недоступен. Брокер 1 взял на себя роль лидера для партиции 1 (рис. 5.10). Не каждая реплика может стать новым лидером: только ISR или ELR (eligible leader replicas — «кандидаты на роль лидера»). О причинах и критериях определения этого статуса мы подробно поговорим в главе 8. Рис. 5.10. Контроллер обнаружил отказ брокера 3. Поскольку лидер для партиции 1 вышел из строя, то контроллер выбирает новым лидером другой брокер (например, брокер 1), находящийся в синхронизации с этой партицией В Kafka с помощью скрипта kafka-topics.sh можно просмотреть все топики или партиции, у которых не хватает реплик. Для этого передаем аргументы --describe и --under-replicated-partitions: $ kafka-topics.sh --describe \ --bootstrap-server localhost:9092 \ --under-replicated-partitions Topic: products.prices.changelog.replication-3 Partition: 0 Leader: 2 Replicas: 2,3,1 Isr: 2,1 Topic: products.prices.changelog.replication-3 Partition: 1 Leader: 1 Replicas: 3,1,2 Isr: 1,2 Topic: products.prices.changelog.replication-3 Partition: 2 Leader: 1 Replicas: 1,2,3 Isr: 1,2 Все партиции топика products.prices.changelog .replication-3 теперь ожидаемо имеют недостаточно реплик. Мы можем с помощью флага --under-minisr-partitions вывести все партиции, у которых сейчас слишком мало ISR — меньше, чем требует настройка min.insync.replicas. Но в данном случае никакие
   Глава 5. Надежность 103 партиции мы не увидим, так как у нас остались две ISR, что соответствует требованию min.insync.replicas. Обе команды крайне полезны для диагностики кластера Kafka, поскольку позволяют быстро выявлять проблемы с топиками. Теперь рассмотрим, что происходит, когда брокер возвращается в строй. Для перезапуска брокера снова выполним скрипт kafka-server-start.sh. Проверим текущее состояние топика: $ kafka-server-start.sh ~/kafka/config/kafka3.properties $ kafka-topics.sh --describe \ --topic products.prices.changelog.replication-3 \ --bootstrap-server localhost:9092 Topic: products.prices.changelog.replication-3 PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=2 Topic: products.prices.changelog.replication-3 Partition: 0 Leader: 2 Replicas: 2,3,1 Isr: 2,1,3 Topic: products.prices.changelog.replication-3 Partition: 1 Leader: 1 Replicas: 3,1,2 Isr: 1,2,3 Topic: products.prices.changelog.replication-3 Partition: 2 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 Мы снова видим брокер 3 в списке ISR, но брокер 1 остается лидером партиции 1, а брокер 3 довольствуется ролью фолловера (рис. 5.11). Рис. 5.11. Хотя брокер 3 вернулся, он не становится тут же лидером для партиции 1 Брокер 3 должен сначала синхронизироваться. По умолчанию он только через несколько минут автоматически становится лидером для партиций, для которых является предпочтительным лидером. Такой лидер указан первым в списке реплик. В зависимости от конфигурации кластера перераспределение лидеров может происходить автоматически через определенное время или требовать
   104 Часть II. Основные концепции ручного вмешательства. Запустим перебалансировку лидеров вручную, используя скрипт kafka-leader-election.sh: $ kafka-leader-election.sh \ --election-type=preferred \ --all-topic-partitions \ --bootstrap-server localhost:9092 Successfully completed preferred leader election for partitions products.prices. changelog.replication-3-1 В выходных данных (products.prices.changelog.replication-3-1) мы видим партиции и топики, у которых выбран предпочтительный лидер. Номер партиции соединяется через дефис с именем соответствующего топика. Значение для аргумента election-type нужно выбирать осторожно. Варианта два: предпочтительный (preferred) и нечистый (unclean). Первый вариант означает, что система попытается восстановить исходного и предпочтительного лидера партиции в качестве действующего. Нечистого лидера следует выбирать только в крайних ситуациях. В каких, например? Как упоминалось ранее, при отказе лидера партиции Kafka определяет нового лидера из списка синхронизированных реплик (ISR). А что, если ISR не останется? В таком случае Kafka по умолчанию не сможет определить нового лидера, а значит, партиция будет недоступна ни для записи, ни для чтения сообщений. Если мы согласны на нечистого лидера, то новым лидером можно выбрать даже несинхронизированную реплику. Такие реплики вряд ли содержат все сообщения, поэтому будьте готовы к потере данных. Не зря метод называют нечистым — unclean . Выбор нечистого лидера можно добавить в конфигурации брокера (unclean.leader.election.enable). Но лучше так не делать. Команду kafkaleader-election.sh можно применить к конкретному топику (--topic pro­ ducts.pri­ces.change­log.replication-3) или ко всем (--all-topic -partitions). А если вы решились на unclean-leader-election, то обязательно укажите конкретный топик! ПРЕДУПРЕЖДЕНИЕ Нечистого лидера следует выбирать только в экстренных ситуациях. И даже тогда только для некоторых топиков. Вернемся к нашему топику: $ kafka-topics.sh \ --describe \ --topic products.prices.changelog.replication-3 \ --bootstrap-server localhost:9092 Topic: products.prices.changelog.replication-3 PartitionCount: 3 ReplicationFactor: 3 Configs: min.insync.replicas=2 Topic: products.prices.changelog.replication-3 Partition: 0 Leader: 2 Replicas: 2,3,1 Isr: 2,1,3 Topic: products.prices.changelog.replication-3 Partition: 1 Leader: 3 Replicas: 3,1,2 Isr: 1,2,3 Topic: products.prices.changelog.replication-3 Partition: 2 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
   Глава 5. Надежность 105 Как видите, брокер 3 снова стал лидером партиции 1, а брокер 1 остался обычным фолловером (рис. 5.12). Рис. 5.12. После выбора предпочитаемого лидера (вручную или автоматически) брокер 3 снова становится лидером для партиции 1, а брокер 1 назначается ее фолловером Резюме Подтверждения (ACKs) — основа надежности в Kafka. Подтверждения не являются обязательными и настраиваются для каждого продюсера. Продюсеры могут не ждать подтверждений (acks=0), если вы готовы пожертвовать устойчивостью ради скорости. Продюсеры могут получать подтверждения после того, как лидеру будет доставлено сообщение (acks=1), или после репликации на все синхронизированные реплики (acks=all), причем последний вариант теперь используется по умолчанию как наиболее надежный. Kafka предоставляет три варианта гарантий доставки сообщений: не более одного раза, хотя бы один раз и ровно один раз. Гарантия «не более одного раза» достигается при acks=0; «не менее одного раза» — при acks=all и достаточном минимуме синхронизированных реплик; а «ровно один раз» требует включения идемпотентности и установки acks=all. Транзакции в Kafka позволяют выполнять атомарные записи в несколько партиций, гарантируя, что все сообщения транзакции будут записаны вместе или не записаны вовсе, что обеспечивает целостность данных. Kafka использует идемпотентные продюсеры для надежной отправки сообщений и вариант протокола двухфазной фиксации, чтобы управлять
   106 Часть II. Основные концепции маркерами фиксации транзакций и гарантировать строго однократную обработку сообщений. У потребителей для уровня изоляции isolation.level должно быть установлено значение read_committed, чтобы избежать чтения незафиксированных сообщений и обрабатывать только полностью завершенные транзакции в целях сохранения целостности данных. Координатор транзакций Kafka обеспечивает надежность даже при сбоях брокеров или продюсеров, хотя это влечет дополнительные издержки. Для каждой партиции один брокер выступает лидером, обрабатывая все запросы, а фолловеры реплицируют данные, получая их от лидера. Если лидер партиции выходит из строя, то его роль берет на себя синхронизированная реплика (ISR). После возвращения в состояние синхронизации предыдущий лидер может снова стать лидером, поскольку Kafka стремится восстанавливать исходного — предпочтительного — лидера. При критических сбоях Kafka может выбрать нечистого лидера, разрешив несинхронизированным репликам становиться лидерами, несмотря на возможную потерю данных. Принцип «лидер — фолловеры» в Kafka обеспечивает высокую доступность и отказоустойчивость при сбоях.
6 Производительность В этой главе 3 Настройки топиков для повышения производительности. 3 Определение оптимального количества партиций для топика. 3 Настройки брокера Kafka, влияющие на производительность. 3 Оптимизация производительности продюсеров и потребителей. В предыдущей главе мы рассмотрели, как надежно отправлять сообщения в Kafka. В этой главе мы поговорим о его оптимизации. Как с его помощью достичь заявленной производительности? Как и в случае с надежностью, сначала необходимо определить, что для нас важно. Производительность можно оценивать по-разному. Нас интересует пропускная способность? Она измеряется в байтах в секунду и часто играет ключевую роль. Сколько кило-, мега- или гигабайт данных в секунду обрабатывает наша система? Говоря только о пропускной способности, мы часто упускаем из виду задержку, хотя она обычно важнее. Не стоит недооценивать пропускную способность грузовика, перевозящего microSD. Она колоссальна! Современная карта microSD весом менее 1 грамма может хранить более 1 Тбайт данных. Это 1000 Тбайт (1 Пбайт) на килограмм и 1 000 000 Тбайт (1 Эбайт) на тонну — невероятная плотность хранения данных, достигнутая благодаря современным технологиям. Обычно, правда, нас гораздо больше волнует задержка в системе, чем теоретическая максимальная пропускная способность. Особенно сегодня, когда пользователи ожидают получать ответы
   108 Часть II. Основные концепции моментально. В 2008 году Amazon подсчитал, что каждые дополнительные 100 миллисекунд времени загрузки приводят к потере до 1 % выручки (https://mng.bz/QD16). Пропускная способность и задержка очень важны для ИТ-систем, но есть еще один фактор, который нельзя упускать из виду, — ресурсоемкость системы. Обычно наши ресурсы так или иначе ограниченны. Мы стараемся использовать их экономно, чтобы избежать перерасхода и минимизировать воздействие ИТ-инфраструктуры на окружающую среду. Kafka — гибко настраиваемая система, и мы должны понимать, как оптимизировать ее работу. На наших курсах по Kafka участники с удовольствием тестируют пределы системы. Особенно им нравится повышать пропускную способность с 0,2 Мбайт/с до более чем 200 Мбайт/с на одном компьютере среднего размера. Как в Kafka настраивается производительность? Если вкратце, то он изначально оптимизирован так, чтобы многие его элементы были высокопроизводительными (рис. 6.1), однако необходимо уделять внимание настройкам всех компонентов. Рис. 6.1. Производительность Kafka можно настраивать повсюду: в конфигурации топиков, брокеров и всех клиентов Оптимизация Kafka начинается с базовых принципов. Это продукт XXI века, поэтому разработчики исходят из того, что жесткие диски относительно дешевы, оперативную память можно установить в больших объемах, а отдельные ИТ-системы не отличаются высокой надежностью. Поэтому мы даже не пытаемся любой ценой избежать сбоев оборудования, а спокойно принимаем их, зная, что Kafka продолжит эффективно функционировать. Кроме того, он избегает универсальных решений, позволяя обеспечивать разные уровни производительности и надежности в рамках одного кластера. С архитектурной точки зрения Kafka изначально оптимизирован для высокой производительности. В основе Kafka лежит лог. Как упоминалось в предыдущих главах, лог — одна из простейших структур данных. Шаблоны доступа к логам очень предсказуемы, что позволяет Kafka
   Глава 6. Производительность 109 работать на высоком уровне производительности даже на классических вращающихся дисках. Это связано с тем, что, хотя SSD превосходят классические жесткие диски почти по всем параметрам, они значительно дороже. Напомним, что Kafka передает данные в любых форматах, при этом отказываясь от многих функций, характерных для других систем обмена сообщениями. Даже не пытаясь интерпретировать сообщения, Kafka выигрывает в производительности, поскольку сама система обмена сообщениями выполняет минимум задач, а основная работа перекладывается на клиентов. 6.1. Настройка топиков для повышения производительности Прежде чем приступать к детальной настройке брокеров и клиентов, нужно убедиться, что топики настроены правильно. В первых главах мы обсуждали, что топики делятся на партиции для горизонтального масштабирования, которое поддерживает параллельную обработку в системе и устраняет узкие места в виде потребителей. Кроме того, при таком подходе можно обрабатывать данных больше, чем на одном сервере, хотя при этом важно контролировать порядок сообщений. 6.1.1. Масштабирование и балансировка нагрузки Количество партиций влияет не только на то, как потребители отправляют сообщения и как нагрузка распределяется между брокерами, но, что важнее, на масштабирование потребителей. В принципе, множество независимых потребителей могут читать сообщения из одного топика. Вспомним пример из главы 4, подраздела 4.2.2, где один потребитель анализирует влияние цен на продажи, читая топик products.prices.changelog, а другой обновляет цены в интернет-магазине. В то же время мы не хотим, чтобы эти независимые потребители мешали друг другу. Этого легко добиться, так как потребители явно запрашивают у Kafka все новые цены, начиная с определенного смещения. Если каждый независимый потребитель самостоятельно управляет своими смещениями, то они не будут конфликтовать. Теперь рассмотрим ситуацию, когда у нас много партиций, но только один потребитель, который обрабатывает все сообщения. Для масштабирования потребителей и управления смещениями в Kafka используются уже знакомые нам группы потребителей — несколько экземпляров одного потребителя, между которыми Kafka разделяет работу. Мы не можем произвольно распределять сообщения между потребителями группы — вместо этого целые партиции назначаются конкретным потребителям, чтобы гарантировать порядок сообщений (рис. 6.2). Партиции не только помогают распределять нагрузку между брокерами, но и позволяют масштабировать группы потребителей, чтобы сделать возможной параллельную обработку данных. Правда, такое разделение вводит некоторые ограничения. Брокеры Kafka гарантируют, что сообщения хранятся в партиции
   110 Часть II. Основные концепции в том же порядке, в котором они поступили. Порядок между разными партициями не гарантируется. Это означает, что если несколько продюсеров записывают данные в один топик, порядок сообщений от разных продюсеров не сохраняется. По порядку будут расположены только те сообщения, которые поступают от одного продюсера в одну партицию. Рис. 6.2. В Kafka топики разделяются на партиции, которые распределяются между брокерами для улучшения балансировки нагрузки и параллелизации. Продюсеры распределяют сообщения по партициям, основываясь на ключе или на принципе round-robin. На стороне потребителя группы потребителей обрабатывают партиции параллельно. Разные группы потребителей изолированы друг от друга Кроме того, гарантии порядка сообщений действуют, только если задано enable.idempotence=true, как мы видели в главе 5. Эта настройка обеспечивает уникальность сообщений, поскольку комбинирует идентификатор продюсера и порядковый идентификатор, предотвращая конфликты при получении и обработке сообщений в Kafka. Без этой конфигурации даже одиночный продюсер может столкнуться с нарушением порядка при повторной отправке или сбое. Важно также определить оптимальное количество партиций для конкретного сценария. Может показаться, что чем больше партиций, тем лучше, но не все так однозначно. 6.1.2. Определение необходимого количества партиций Сначала нужно понять, где узкое место: на стороне потребителя или в самом Kafka? Обычно виноват потребитель. Количество партиций определяется исходя из количества потребителей, требуемого для обработки сообщений (за исключением сценариев с большими данными). Например, если один потребитель
   Глава 6. Производительность 111 тратит около 100 миллисекунд на обработку сообщения (скажем, при записи в медленное хранилище), то может обрабатывать десять сообщений в секунду. Для обработки 100 сообщений в секунду при пиковой нагрузке потребуется минимум десять потребителей и, соответственно, десять партиций. ПРИМЕЧАНИЕ Иногда узкое место возникает во внешних системах, а не в Kafka. Например, если производительность потребителя ограничена медленным хранилищем, то увеличение количества потребителей или партиций в Kafka ничего не даст. Обязательно выясните, с чем связано узкое место: с Kafka или внешними системами. Чтобы получить дополнительные возможности, рекомендуется настроить чуть больше партиций, чем требуется для производительной работы потребителей. Лучше выбирать числа, которые легко делятся, чтобы упростить масштабирование потребителей. В идеале эта делимость должна распространяться и на количество брокеров, хотя обычно общее количество партиций превышает количество брокеров. По нашему опыту, начать лучше с 12 партиций — обычно этого достаточно для большинства сценариев. Для решения этих задач необходимо ответить на два ключевых вопроса. Первый: достаточно ли текущего количества партиций или нам потребуется более 12 параллельных потребителей в одной группе, обрабатывающих данные одновременно? В этом случае мы пытаемся достичь очень высокой пропускной способности. При необходимости мы удваиваем количество партиций и проверяем, хватает ли нам 24 параллельных потребителей. Второй вопрос: что будет, если партиций слишком много? Дополнительные партиции повышают скорость обработки, но усложняют систему. Каждая партиция потребляет ресурсы клиента, например оперативную память, особенно когда клиенты взаимодействуют с несколькими партициями в разных топиках. Раньше управление множеством партиций в ZooKeeper приводило к длительным периодам недоступности во время сбоев, поскольку перераспределение лидеров для каждой партиции требовало много времени. Для сотен или тысяч партиций счет мог идти на минуты. Если партиций слишком много, то они перегружают кластер Kafka, потребляя слишком много ресурсов процессора, памяти и обработки файлов. Кроме того, усложняется сама система, что потенциально может отразиться на производительности и замедлить восстановление при отказах брокеров. Избыточные партиции усложняют обслуживание, например мониторинг и устранение неисправностей. Поэтому так важно соблюдать оптимальный баланс партиций, чтобы избежать ненужных издержек, но при этом добиться желаемой производительности. Далее необходимо решить: оптимизировать количество партиций для каждого сценария или использовать стандартное значение. В главе 12 мы увидим, что эффективное объединение двух топиков возможно при наличии у них одинакового количества партиций.
   112 Часть II. Основные концепции СОВЕТ Учитывая эти факторы, мы часто рекомендуем использовать стандартное количество партиций (например, 12) для всех сценариев, даже если поначалу это слишком много. Для топиков, которым требуется высокая пропускная способность, партиции можно добавить, но не забывайте о дополнительных издержках. Примечательно, что некоторые облачные провайдеры (например, Confluent) формируют расценки на основе количества партиций, поэтому следует все точно рассчитать. При настройке кластеров Kafka важно знать об ограничениях партиционирования и быть в курсе лучших практик, чтобы достичь оптимальной производительности и потребления ресурсов. Хотя в Kafka нет строгих ограничений на количество партиций, лучше соблюдать общие рекомендации, особенно в конфигурациях с ZooKeeper: не более 4000 партиций на брокер, при этом общее количество партиций в кластере не должно превышать 200 000. KRaft позволяет создавать кластеры гораздо большего размера, так как он эффективнее управляет метаданными. Крайне важно оценивать необходимость дополнительных партиций, поскольку даже несколько партиций при максимальной пропускной способности могут превысить возможности других систем, таких как реляционные базы данных или MongoDB. Гибкое разделение на партиции — преимущество Kafka, но нужно учитывать и другие практические соображения, чтобы обеспечить как можно более эффективную работу системы. На момент написания книги разрабатывается новая функция очередей Queues for Kafka с общими группами (share groups), поддерживающими совместное потреб­ ление записей из топиков. Благодаря общим группам количество потребителей может превышать количество партиций, то есть теперь для достижения высокой степени параллелизма не требуется создавать так много партиций. Особенно хорошо этот подход будет работать в сценариях с относительно медленными потребителями, где было бы удобно использовать что-то вроде очередей. Правда, возникает важный вопрос: как гарантировать порядок сообщений в общей партиции? Kafka обеспечивает порядок внутри партиции для традиционных групп потребителей, но общие группы могут не сохранять строгий порядок, ведь к одной партиции обращается несколько потребителей. Такое поведение аналогично традиционным системам очередей, где порядок не так критичен. ПРЕДУПРЕЖДЕНИЕ Общие группы дают больше возможностей в определенных сценариях, но не заменяют традиционное партиционирование там, где требуется высокая пропускная способность, особенно если важно соблюдать порядок. В таких случаях используются традиционные группы потребителей. 6.1.3. Изменение количества партиций Мы так подробно рассматриваем количество партиций потому, что его не так-то просто изменить. Во всяком случае, если при этом мы хотим сохранить порядок сообщений. В Kafka количество партиций можно только увеличить. Уменьшить их для топика невозможно, поскольку Kafka не будет знать, что делать
   Глава 6. Производительность 113 с сообщениями в удаленных партициях. Если просто переместить их в другие партиции, то будет нарушен порядок сообщений. При увеличении количества новые партиции добавляются пустыми, что сразу вызывает дисбаланс в распределении данных. Это не так страшно, если данные хранятся недолго, например несколько дней: как только срок хранения истекает и старые сообщения удаляются, объемы данных в партициях естественным образом выравниваются. Гораздо больше нас беспокоит нарушение порядка сообщений при добавлении новых партиций. Дело в том, что Kafka вычисляет партицию для каждого сообщения по остатку от деления хеша ключа на количество партиций. Если это количество меняется, то результаты вычислений будут другими, так что сообщения с одинаковым ключом теперь будут направляться в другие партиции. Нарушение порядка сообщений сохраняется до тех пор, пока старые данные, соответствующие предыдущей схеме партиционирования, не будут удалены по истечении срока хранения. На рис. 6.3 показано, как изменение количества партиций влияет на распределение сообщений. Рис. 6.3. После изменения количества партиций номер партиции для конкретного ключа сообщения обычно меняется. В этом примере сообщения в кружке теперь направляются в партицию 2 вместо партиции 1, а сообщения в квадратике — в партицию 1 вместо партиции 0 Конечно, в некоторых сценариях это не проблема, но все же следует дважды подумать, прежде чем менять количество партиций после создания топика. Если ключи не используются (например, порядок данных в топике неважен), то увеличение количества партиций не вызывает проблем, разве что сообщения будут распределены неравномерно.
   114 Часть II. Основные концепции Если порядок сообщений критичен, но мы все равно хотим увеличить количество партиций (или уменьшить, если ошиблись в расчетах), то единственным вариантом будет создать новый топик и удалить старый. Порядок действий зависит от сценария использования. В простейшем сценарии система обрабатывает сообщения сразу после записи, и у нас есть возможность выделить время на техническое обслуживание. Мы останавливаем продюсер, дожидаемся обработки всех сообщений, удаляем старый топик и создаем его заново. В том случае если остановка системы недопустима, но данные не хранятся в топике бессрочно, создается новый топик с нужным количеством партиций. Продюсер переключается на него, а потребители завершают обработку старого топика, после чего переходят на новый. После миграции всех потребителей старый топик можно удалить. Если данные хранятся бессрочно, то миграция обойдется дороже. Сначала создается новый топик с требуемой конфигурацией. Затем данные копируются из старого топика в новый (например, через Kafka Streams) с сохранением порядка сообщений, если указаны ключи. Когда все данные будут скопированы, потребители переводятся на новый топик. Это сложный процесс, поскольку надо будет сопоставить смещения в старых и новых партициях (рис. 6.4). В противном случае потребители могут что-то пропустить или обработать некоторые сообщения дважды. Наконец, все продюсеры переводятся на новый топик, а старый удаляется. Рис. 6.4. Сообщение 7 в треугольнике ранее хранилось в смещении 3 партиции 0. После создания нового топика с тремя партициями и миграции данных это сообщение теперь находится в смещении 1 партиции 1
   Глава 6. Производительность 115 СОВЕТ Для идемпотентного потребления можно использовать идентификатор события, по которому системы определяют, было ли событие обработано, и предотвращают дублирование. ПРЕДУПРЕЖДЕНИЕ Как видите, изменение количества партиций после создания топика — трудоемкий и ненадежный процесс, поэтому лучше все продумывать заранее. Однако создание нового топика и миграция данных — выполнимая задача. Заодно можно пересмотреть и улучшить конфигурацию, соглашения по именованию, форматы данных (например, перейти с JSON на Avro или Protobuf). 6.2. Производительность продюсера Теперь, когда вы знаете, как оптимизировать производительность топиков, перей­дем к продюсеру, брокеру и потребителю. Поскольку продюсер — это точка, в которой сообщения попадают в кластер Kafka, то его настройка существенно влияет на производительность и использование ресурсов всей системы. 6.2.1. Конфигурация продюсера Для создания сообщений продюсеры обычно используют либо библиотеку Kafka для Java, либо, если написаны на другом языке, библиотеку на основе librdkafka. Программный код обычно передает только сообщение со значением и необязательным ключом. Библиотека затем решает, в какую партицию должно быть отправлено это сообщение. Можно вдобавок реализовать пользовательские алгоритмы партиционирования, но лучше этого избегать. Может показаться нелогичным, что мы разрешаем продюсеру самому выбирать партицию для сообщения, но таким образом мы снижаем нагрузку на брокеры, которым не приходится координировать распределение, и повышаем производительность. Такой подход дает продюсерам возможность реализовывать собственные алгоритмы партиционирования в редких случаях, когда это необходимо. Kafka повышает скорость передачи за счет группировки нескольких сообщений в батч перед отправкой в брокер. Крупные батчи позволяют сократить количество сетевых запросов и повысить эффективность. Батчи можно настраивать отдельно для каждого продюсера с помощью двух параметров: batch.size и linger.ms. Параметр batch.size задает максимальный размер батча. По умолчанию 16 Кбайт (16 384 байта). Параметр linger.ms определяет, как долго продюсер должен ждать сообщения перед отправкой батча. По умолчанию установлено значение 0, то есть продюсер отправляет сообщения как можно быстрее. Даже при значении 0 по умолчанию батчи успевают создаваться, поскольку сообщения не всегда отправляются так же быстро, как генерируются. Это позволяет использовать преимущества естественной группировки, когда несколько сообщений помещаются в один батч. Для сценариев, где немедленная доставка
   116 Часть II. Основные концепции не так важна, можно улучшить пропускную способность, увеличив значения для batch.size (в байтах) и linger.ms (в миллисекундах). На рис. 6.5 показан процесс создания батчей. Рис. 6.5. Продюсер группирует в батчи сообщения для каждого топика и партиции. Сообщение отправляется сразу после заполнения батча или истечения времени linger.ms По нашему опыту, значение для batch.size по умолчанию (16 Кбайт) слишком мало для большинства сценариев. Если увеличить размер батча, то можно значительно повысить эффективность обработки и хранения данных, так как крупные батчи снижают издержки на обработку отдельных сообщений. Максимально допустимый batch.size определяется параметром max.message.bytes, который по умолчанию позволяет увеличить размер до 1 Мбайт. Нуля миллисекунд для linger.ms тоже слишком мало. Если установить хотя бы 10 миллисекунд, то можно увеличить пропускную способность и сократить задержку, особенно для продюсеров, отправляющих более 100 сообщений в секунду. Если сообщений меньше, то значение linger.ms можно еще увеличить, хотя это может негативно повлиять на задержку. Обычно значение linger.ms не так критично, если только речь не идет о кластерах с высокой пропускной способностью. А вот увеличение batch.size значительно повышает производительность и снижает нагрузку на кластер благодаря сокращению количества сетевых запросов. СОВЕТ Для большинства сценариев мы рекомендуем установить для batch.size до 1 Мбайт, а для linger.ms — 10 миллисекунд. Всегда отслеживайте метрики производительности системы после изменения этих параметров, чтобы убедиться, что конфигурация оптимальна для вашей рабочей нагрузки.
   Глава 6. Производительность 117 Мы уже рассматривали подтверждения в главе 5 и видели, что с их помощью можно управлять надежностью или производительностью доставки сообщений от продюсера в Kafka. Производительность можно повысить в ущерб надежности, указав, что ACK равно 1 или даже 0, однако выбор значения ACK в основном зависит от требований к надежности, а не к производительности. Кроме того, можно настроить сжатие, чтобы не только значительно повысить производительность среды Kafka, но и сократить потребление ресурсов. Тип сжатия выбирается с помощью параметра compression.type. По умолчанию сжатие отключено, но Kafka поддерживает несколько алгоритмов: none, gzip, snappy, lz4 и zstd. Выбор зависит от требований к степени и времени сжатия, а также времени распаковки. Обычно варианты zstd или lz4 обеспечивают наилучший баланс между пропускной способностью и потреблением ресурсов процессора. Вместо того чтобы сжимать отдельные сообщения, Kafka сжимает целые батчи на всем пути от продюсера к потребителю. Это возможно благодаря тому, что продюсер упаковывает все сообщения для одной партиции в один батч, а сообщения для другой партиции собираются в другой. Более того, создание батчей обычно повышает эффективность сжатия. Продюсер сжимает весь батч целиком и в таком виде отправляет его лидеру. Тот сохраняет его без изменений на локальный жесткий диск и передает фолловерам. Только при получении батча потребитель распаковывает его и разделяет на отдельные сообщения (рис. 6.6). При таком подходе сжатие не просто однократно уменьшает объем данных, но и снижает нагрузку на сеть в нескольких точках, а также минимизирует занимаемое место на жестком диске. Рис. 6.6. При включении сжатия продюсер сжимает целые батчи. Батчи не только передаются по сети в сжатом виде, но и хранятся так на брокерах. Только потребитель распаковывает батчи для извлечения отдельных сообщений
   118 Часть II. Основные концепции Важно отметить, как Kafka обрабатывает смещения внутри сжатых батчей. Если потребителю требуется начать чтение с определенного смещения внутри сжатого батча, то он получит батч целиком, распакует его и начнет обработку с нужного сообщения. Такая реализация соответствует философии Kafka, в которой данные рассматриваются как неизменяемые логи, а брокеру не требуется парсить или изменять содержимое сообщений. Кроме того, если одни продюсеры записывают в топик сжатые сообщения, а другие — обычные, то Kafka корректно обрабатывает эту ситуацию. Каждый продюсер указывает собственные настройки сжатия, и Kafka не требует единообразия. Таким образом, один продюсер может отправлять сжатые батчи, а другой — несжатые сообщения, и оба варианта могут сосуществовать в рамках одной партиции. Однако смешанные настройки сжатия могут привести к неоднородной пропускной способности и эффективности хранения, поскольку сжатые батчи занимают меньше места и создают меньшую нагрузку на сеть по сравнению с несжатыми. В целях оптимальной производительности рекомендуется стандартизировать настройки сжатия для всех продюсеров в одном топике. 6.2.2. Тестирование производительности продюсера Лучший способ проверить производительность продюсера — использовать инструмент kafka-producer-perf-test.sh, который позволяет тестировать влияние различных настроек конфигурации и типов сообщений на производительность. Важно помнить, что такие тесты не заменяют проверку производительности, выполняемую с реальными продюсерами. Для полноценного тестирования лучше подходят такие инструменты, как Apache JMeter (https://jmeter.apache.org/). ПРЕДУПРЕЖДЕНИЕ Инструмент kafka-producer-perf-test.sh в зависимости от настроек может генерировать огромное количество сообщений, создавая гигабайты данных на диске. Запустить kafka-producer-perf-test.sh проще всего с помощью команды, показанной ниже. Предполагается, что топик performance-test уже создан. $ kafka-producer-perf-test.sh \ --topic performance-test \ --num-records 1000000 \ --record-size 10000 \ --throughput -1 \ --producer-props bootstrap.servers=localhost:9092 Здесь мы генерируем 1 000 000 сообщений (--num-records) по 10 000 байт каждое (--record-size), не ограничивая пропускную способность (--throughput -1), то есть сохраняя максимально возможную скорость. Сообщения записываются в топик performance-test (--topic). Указываем bootstrap-сервер с помощью
   Глава 6. Производительность 119 флага --producer-props. После выполнения команды получаем результат оценки, который выглядит примерно так: 39481 records sent, 7896.2 records/sec (75.30 MB/sec), 238.1 ms avg latency, 399.0 ms max latency. 62616 records sent, 12523.2 records/sec (119.43 MB/sec), 164.1 ms avg latency, 253.0 ms max latency. [...] 1000000 records sent, 12317.546345 records/sec (117.47 MB/sec), 165.43 msavg latency, 399.00 ms max latency, 157 ms 50th, 211 ms 95th, 287 ms 99th, 353 ms 99.9th. Посмотрим, что мы получили. Команда выводит текущую статистику каждые несколько секунд. При выполнении скрипта kafka-producer-perf-test.sh поначалу дело идет небыстро, но через несколько выводов пропускная способность и задержка улучшаются. Это обычно связано с прогревом виртуальной машины Java (Java Virtual Machine, JVM), которая постепенно оптимизирует выполнение кода и требует время на выделение буферов и других объектов в начале процесса. В нашем примере после отправки выбранного количества сообщений можно проанализировать результаты. Мы видим, что Kafka отправлял примерно 12 317 сообщений в секунду с общей пропускной способностью 117,47 Мбайт в секунду. Средняя задержка составила 165 миллисекунд. Если выполнить тест производительности только один раз, то интерпретировать результат следует с осторожностью. Лучше провести тест несколько раз и сравнить результаты. Но даже однократный запуск дает примерное представление о возможных направлениях оптимизации. Например, мы знаем, что увеличение размера батчей вместе с увеличением времени ожидания повышает пропускную способность. Эти настройки задаются с помощью флага --producer-props. Попробуем: $ kafka-producer-perf-test.sh \ --topic performance-test \ --num-records 1000000 \ --record-size 10000 \ --throughput -1 \ --producer-props bootstrap.servers=localhost:9092 \ batch.size=100000 linger.ms=100 ... 1000000 records sent, 45879.977978 records/sec (437.55 MB/sec), 64.61 ms avg latency, 279.00 ms max latency, 58 ms 50th, 101 ms 95th, 126 ms 99th, 178 ms 99.9th. Здесь видно, что вместо первоначальных 117 Мбайт/с мы достигаем полной пропускной способности в 437,55 Мбайт/с. Интересно, что задержка также снижается с исходных 165 до 65 миллисекунд. Видимо, в прошлый раз батч был слишком маленьким для этого сценария. Напомним, что по умолчанию размер батча установлен на 16 Кбайт. Если генерировать сообщения по 10 Кбайт, то батчи не создаются вовсе, что приводит к увеличению задержки.
   120 Часть II. Основные концепции Этот пример еще раз демонстрирует, насколько важно тестировать производительность в конкретном кластере с учетом определенных сценариев. В табл. 6.1 содержится информация о настройках для оптимизации производительности продюсера, но она не заменяет реальные тесты с данными и условиями, приближенными к рабочим. Каждый сценарий уникален, и настройки всегда следует выбирать тщательно. Таблица 6.1. Настройки продюсера Параметр конфигурации Описание acks 0 — высокая пропускная способность, возможна потеря данных. 1 — пропускная способность чуть ниже, как и вероятность потери данных. -1/all — пропускная способность еще чуть ниже, высокая надежность enable.idempotence false — не рекомендуется. true — гарантирует порядок и уникальность сообщений, работает только с acks=-1 compression.type none — без сжатия. gzip — сжатие, выше нагрузка на ЦП. zstd, snappy, lz4 — более производительные, чем gzip batch.size Размер батча в байтах: влияет на задержку и пропускную способность. Не бойтесь устанавливать для batch.size большие значения (максимум: 1 Мбайт) linger.ms Время ожидания сообщений для батча: чем оно больше, тем больше задержка, зато выше пропускная способность 6.3. Конфигурация и оптимизация брокеров Брокеры представляют собой центральный компонент Kafka. Все данные хранятся в брокерах, клиенты взаимодействуют с брокерами, а сами брокеры управляют собой через координационный кластер. Брокеры стремятся выполнять минимум операций самостоятельно и делегируют клиентам как можно больше задач, для которых важна производительность. В конечном счете брокеры Kafka просто передают байты: с сетевых сокетов на диск (при записи) и с диска в сетевые сокеты (при чтении). Многие системы баз данных пытаются обходить операционную систему для ускорения доступа к данным. Kafka, напротив, работает вместе с операционной системой, стараясь упростить ей задачу хранения необходимых данных. Ключевая идея — последовательное чтение и запись. Паттерны доступа Kafka очень предсказуемы: данные записываются только в конец файла, а считываются
   Глава 6. Производительность 121 последовательно. Даже на классических жестких дисках эти режимы обеспечивают высокую производительность. Однако Kafka применяет дополнительные оптимизации. Ранее уже упоминалось, что брокеры не фиксируют записанные данные в файловой системе, то есть данные записываются только в кэш страниц оперативной памяти, без принудительной записи на диск. Kafka использует фоновые механизмы операционной системы, которые выполняют эту операцию позже. При этом подтверждение отправляется продюсеру сразу после записи в оперативную память, даже если данные могут потеряться при сбое питания. На первый взгляд это кажется рискованным, но надежность Kafka обеспечивается не мгновенной записью на диск, а репликацией данных между несколькими брокерами. Предполагается, что все брокеры не выйдут из строя одновременно. Если брокеры размещены на разных компьютерах в разных сегментах центра обработки данных, то такое допущение обычно справедливо. Формат данных в Kafka одинаков повсюду: данные, созданные продюсером, передаются по сети, записываются на диск и принимаются потребителем без изменений. Поэтому брокеры могут использовать функцию ядра Linux под названием zero-copy transfer — передача с нулевым копированием. По сути, данные, сгенерированные продюсером, должны быть записаны из сетевого сокета в оперативной памяти в кэш страниц оперативной памяти, но поскольку данные идентичны, то ядру Linux требуется всего лишь изменить несколько указателей, чтобы данные оказались в кэше страниц без физического перемещения. Однако эта оптимизация применима только в сценариях без использования TLS, так как процессы шифрования добавляют этапы, а значит, воспользоваться преимуществами zero-copy не получится. 6.3.1. Оптимизация брокеров Как повлиять на производительность брокеров? Настройки Kafka по умолчанию на стороне брокера уже ориентированы на высокую производительность. Перед запуском Kafka рекомендуется настроить некоторые параметры операционной системы. Особенно важно увеличить максимальное количество открытых файловых дескрипторов, поскольку брокеры хранят каждый сегмент лога в каждой партиции как открытый файл, а они быстро накапливаются. Кроме того, следует настроить параметры виртуальной памяти, чтобы операционная система сразу начала записывать данные на диск в фоновом режиме и по возможности не блокировала процессы брокеров. Рекомендуется отключить или минимизировать своппинг (подкачку страниц) в операционной системе, а также внести изменения на сетевом уровне. Например, при использовании TLS для защиты трафика количество сетевых потоков (num.network.threads) следует увеличить с трех до шести. При использовании нескольких дисков количество потоков ввода-вывода (num.io.threads)
   122 Часть II. Основные концепции должно соответствовать количеству дисков, что позволит оптимизировать пропускную способность. Для большого количества продюсеров и потребителей увеличение максимального количества очередей запросов (queued.max.requests) выше стандартного значения 500 (в соответствии с количеством клиентских подключений) помогает сохранить производительность. Эти настройки в основ­ном рекомендуются для продакшена с большими объемами данных. Для сложных конфигураций целесообразно обратиться к профессионалам, так как в этой книге мы не рассматриваем все возможные сценарии. СОВЕТ На момент написания книги Cloudera предоставляет хороший обзор рекомендуемых параметров: https://mng.bz/XxQ9. 6.3.2. Определение количества брокеров и их размера Отметим, что повлиять на эти параметры можно не во всех сценариях. Например, при запуске Kafka в среде Kubernetes обычно невозможно изменить эти значения. Зато можно тщательно отслеживать метрики, чтобы своевременно обнаруживать достижение определенных лимитов и принимать меры. Базовое правило любой оптимизации: сначала понять, затем измерить и только потом оптимизировать. Это особенно актуально для Kafka, так как конфигурация брокера по умолчанию уже достаточно оптимизирована, но следует заранее продумать количество брокеров. Обычно небольшие окружения начинаются с трех брокеров. Это позволяет использовать коэффициент репликации 3 для партиций и гарантировать бесперебойную работу при обслуживании одного брокера, даже если произойдет отказ еще одного. Количество брокеров зависит от наших компьютеров. Сколько оперативной памяти и дискового пространства требуется? С какой скоростью будут передаваться данные? Какое сетевое подключение используется? Как долго должны храниться данные? По сути, нужно найти оптимальный баланс между слишком большими и слишком маленькими брокерами. Чем больше у нас брокеров, тем меньше нас беспокоит отказ отдельного брокера, но тем выше административные издержки. Кроме того, не стоит выбирать слишком маленькие компьютеры, так как сам Kafka потребляет некоторое количество ЦП и памяти, и этот объем ресурсов становится менее значимым с увеличением размера компьютеров. Например, если у нас компьютеры на 2 Тбайт, а мы обрабатываем около 1 Тбайт данных в день и храним их семь дней, то нам потребуется минимум четыре брокера для хранения 7 Тбайт данных. Но это без учета репликации. С коэффициентом репликации 3 у нас будет 21 Тбайт данных, а значит, брокеров должно быть 11. Кроме того, при таком объеме данных следует учитывать ожидаемую скорость передачи данных и возможности сетевого интерфейса. СОВЕТ Небольшие системы обычно начинаются с трех брокеров, а затем расширяются по мере необходимости.
   Глава 6. Производительность 123 6.4. Производительность потребителей В отличие от многих традиционных систем обмена сообщениями брокеры не отправляют данные потребителям, а потребители запрашивают данные из Kafka. Потребители сами решают, когда запрашивать данные, какие именно данные получать и в каком объеме. При таком подходе потребители никогда не перегружаются, если только в программный код не закралась ошибка. 6.4.1. Конфигурация потребителей Как и для продюсеров, для потребителей тоже можно оптимизировать пропускную способность и задержку. Для этого в настройках потребителя доступны два параметра. Первый, fetch.min.bytes, дает брокеру задание ожидать как минимум указанное количество байтов перед отправкой ответа потребителю. По умолчанию fetch.min.bytes=1, поэтому брокер отправляет ответ сразу при появлении хотя бы 1 байта новых сообщений. Брокер передает не отдельные сообщения, а целые батчи или даже несколько батчей сразу. Конечно, брокер не будет ждать вечно перед отправкой ответа, поэтому потребитель может использовать параметр fetch.max.wait.ms для настройки максимального времени ожидания брокера, даже если данных меньше, чем fetch.min.bytes. По умолчанию установлено 500 миллисекунд, а значит, даже при отсутствии новых сообщений потребитель получит ответ на запрос через 500 миллисекунд. После получения ответа и обработки сообщений потребитель запрашивает следующие данные. Естественно, запрос выполняется с увеличенным смещением, если сообщения были обработаны. ПРЕДУПРЕЖДЕНИЕ Ни в коем случае не устанавливайте для fetch.min.bytes или fetch.max.wait.ms значение 0, чтобы не перегрузить брокеры запросами. Если брокер сразу отвечает на каждый запрос, а потребитель мгновенно запрашивает еще данные, то нагрузка будет слишком высокой. Учитывая, что запросы отправляются параллельно для каждой партиции, а задержка между клиентом и брокером может составлять всего 1 миллисекунду, даже один потребитель и один топик могут генерировать до 10 000 запросов в секунду. 6.4.2. Тестирование производительности потребителей Производительность потребителей тоже можно тестировать. Для этой цели возьмем топик, в который мы записывали сообщения с помощью kafka-producerperf-test.sh. Инструмент для тестирования производительности потребителей называется kafka-consumer-perf-test.sh: $ kafka-consumer-perf-test.sh \ --topic performance-test \ --messages 1000000 \ --bootstrap-server localhost:9092
   124 Часть II. Основные концепции Поведение этой команды немного отличается от поведения kafka-producerperf-test.sh. Мы не можем изменить конфигурацию потребителя из командной строки и должны передать файл конфигурации. Например, чтобы установить для fetch.min.bytes значение 1 Мбайт, а для fetch.max.wait.ms явно задать 500 миллисекунд, мы создаем файл consumer.properties с таким содержимым: fetch.min.bytes: 1000000 fetch.max.wait.ms: 500 Снова запускаем kafka-consumer-perf-test.sh с файлом конфигурации: $ kafka-consumer-perf-test.sh \ --topic performance-test \ --consumer.config ./consumer.properties \ --messages 1000000 \ --bootstrap-server localhost:9092 Из выходных данных мало что понятно: start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec 2025-02-01 10:59:37:208, [...] Мы опустили полный вывод, так как он объемный. Удобнее посмотреть на результаты двух тестов потребителя (табл. 6.2). Таблица 6.2. Данные о производительности потребителя Название Результаты со значениями по умолчанию Результаты со свойствами нашего потребителя start.time 2025-02-01 10:44:18:30 2025-02-01 10:59:37:208 end.time 2025-02-01 10:44:35:742 2025-02-01 10:59:50:200 data.consumed.in.MB 9536.7432 9536.7432 MB.sec 547.0512 734.0473 data.consumed.in.nMsg 1000000 1000000 nMsg.sec 57362.4735 76970.4433 Даже глядя на табличную форму, придется приложить усилия, чтобы понять, что показывает тест производительности. Строки с start.time и end.time показывают, сколько времени заняло выполнение команд. Благодаря использованию настроенного файла consumer.properties команды выполняются 13 секунд вместо 17. Обе команды обрабатывают одинаковое количество сообщений (data.con­sumed.in.nMsg=1000000) и данных (data.consumed.in.nMB=9536.7432), около 9,5 Гбайт. Таким образом, при использовании нашего файла конфигурации достигается скорость чтения около 734 Мбайт/с — против 547 Мбайт/с (MB.sec) со значениями по умолчанию. Остальные значения актуальны только
   Глава 6. Производительность 125 в случае применения групп потребителей. Если их нет (а у нас нет), то их можно игнорировать, так как эти данные нам неинтересны. С измененными настройками производительность потребителя заметно возросла. И все же важно помнить, что производительность потребителя обычно ограничивается не Kafka, а скоростью обработки данных потребителем. Кроме того, наши тесты производительности показывают, что потребитель значительно опережает продюсера. Продюсер достигал скорости до 437 Мбайт/с, потребитель обрабатывает данные на 734 Мбайт/с. ПРИМЕЧАНИЕ Фактические значения и разница в производительности сильно зависят от конфигурации оборудования. Особенно в локальной системе Kafka, как у нас, разница может быть относительно небольшой. Проведенные тесты не являются репрезентативными и показывают лишь примерные значения, поэтому интерпретируйте результаты осторожно. Для реальных тестов производительности следует использовать данные, приближенные к реальным, и запускать Kafka в окружении, аналогичном продакшену. В наших тестах использовался один топик с одной партицией и без репликации. Такая конфигурация исключает дополнительные затраты на репликацию данных, но не позволяет распараллеливать операции, поскольку для этого потребовалось бы несколько партиций. Мы видели, что отдельные потребители часто получают данные беспрепятственно. Узким местом обычно становится не сам Kafka, а либо сетевое соединение, либо дальнейшая обработка в коде. Для по-настоящему производительной работы потребителей одного экземпляра часто недостаточно — лучше создать группу потребителей, чтобы обеспечить масштабирование и распараллеливание обработки данных. Кроме того, такие группы необходимы для хранения смещений с использованием встроенных механизмов Kafka. Обычно это означает, что группы потребителей используются даже при наличии одного потребителя — это избавляет от необходимости самостоятельно управлять смещениями. Удалим топик performance-test, так как он занимает много места на диске: $ kafka-topics.sh \ --delete \ --topic performance-test \ --bootstrap-server localhost:9092 Резюме Высокая пропускная способность не гарантирует низкую задержку, но оба параметра могут быть одинаково важными. Партицирование позволяет распределять нагрузку и тем самым повышать производительность.
   126 Часть II. Основные концепции Стратегия партицирования заключается в выявлении узких мест в производительности у потребителей или в самом Kafka и последующей корректировке параметров партиций. Следует балансировать количество партиций, учитывая потребление оперативной памяти клиента и операционной сложности. Рекомендуется начинать с 12 партиций по умолчанию, увеличивая их количество при необходимости, чтобы достичь высокой пропускной способности, учитывая влияние на операции и издержки. После создания топика количество партиций нельзя уменьшать. Увеличение количества партиций может привести к нарушению порядка сообщений. Группа потребителей распределяет нагрузку между участниками группы. Объединение сообщений в батчи увеличивает пропускную способность — но и задержку тоже. Батчи настраиваются с помощью параметров batch.size и linger.ms. Продюсеры могут сжимать батчи, чтобы снизить требуемую пропускную способность, но это может увеличить задержку. Параметр acks=all немного снижает производительность продюсеров, как и idempotence. Брокеры не распаковывают батчи — это задача потребителя. В большинстве случаев брокеры не требуют дополнительной настройки. Брокеры открывают файловые дескрипторы для каждой партиции. Kafka сильно зависит от операционной системы, что требует выполнения особых оптимизаций ОС, необходимых для обеспечения максимальной производительности. Производительность потребителей в основном зависит от их количества в группе, а также от параметров fetch.max.wait.ms и fetch.min.bytes.
Часть III Внутренние механизмы Kafka В этой части мы подробно рассмотрим некоторые более сложные темы, касающиеся архитектуры и функциональности Apache Kafka: управление кластером, процесс производства и сохранения сообщений, механизмы их потребления, а также методы удаления старых или устаревших данных. Разбирая эти темы, мы выясним, как Kafka обеспечивает масштабируемость, отказоустойчивость и эффективность при управлении потоками данных в реальном времени. Вы узнаете, как оптимизировать свою систему Kafka и решать сложные операционные задачи. В главе 7 мы исследуем управление кластером Kafka, а именно обеспечение стабильности, масштабируемости и отказоустойчивости внутри кластера. Глава 8 посвящена тому, как Kafka обрабатывает производство и сохранение сообщений — от сериализации до репликации. В главе 9 рассматриваются модель потребления данных в Kafka, управление смещениями, а также принципы распределения нагрузки между группами потребителей. Наконец, в главе 10 мы обсудим механизмы очистки сообщений, подробно разбирая, как удаление данных по времени, размеру или ключу поддерживает производительность и гарантирует целостность данных.
7 Управление кластером В этой главе 3 Управление кластером с помощью KRaft и ZooKeeper. 3 Миграция с ZooKeeper на KRaft. 3 Подключение клиентов к кластеру Kafka. В предыдущих главах мы рассматривали Kafka как распределенную систему и неоднократно отмечали, что координация в таких системах всегда сопряжена с трудностями, причем не только в самом Kafka, но и у потребителей в группах. К счастью, Kafka берет на себя бо́льшую часть работы, и нам не нужно беспокоиться о надежном распределении партиций между потребителями. Но как это происходит и зачем нам управление кластером? Основная задача, которую решают системы управления кластерами, — достижение консенсуса в распределенных системах. Пока брокер только один, все просто. Не нужно ни с кем согласовывать действия: этот брокер является лидером для всех партиций, всегда синхронизирован, выступает координатором всех групп потребителей и т. д. Но как обеспечить согласованность при добавлении новых брокеров? Кто будет распределять партиции между брокерами, отслеживать их работоспособность и исключать брокеры из кластера, если они перестают отвечать? Кто хранит текущую конфигурацию кластера? В Kafka один брокер действует как контроллер для этих метаданных и выдает инструкции всем брокерам. Этот контроллер мы могли бы назначить при запуске
   Глава 7. Управление кластером 129 Kafka, но что произойдет, если он выйдет из строя, станет работать слишком медленно или столкнется с другими проблемами? За последние годы Kafka значительно изменился, и новшества коснулись в том числе метода управления метаданными и достижения консенсуса. Предыдущий инструмент — Apache ZooKeeper (https://zookeeper.apache.org/) — уступил место новому подходу. Мы считаем, что старые версии Kafka, с ZooKeeper, будут использоваться еще долго, поэтому рассмотрим в этой главе оба варианта. В последнем разделе мы рассмотрим процесс подключения к кластеру Kafka: обработку запросов метаданных и установление надежных соединений в распределенной среде, необходимых для бесшовной интеграции и бесперебойного взаимодействия в экосистеме Kafka, а также иные важные архитектурные и эксплуатационные детали. 7.1. Управление кластером с помощью Apache Kafka Raft Начиная с Kafka 3.3.0, Kafka Raft (KRaft; https://kafka.apache.org/documentation/­ #kraft) — новый метод координации в Kafka — готов к использованию в продакшене, а с версии Kafka 3.5.0 ZooKeeper объявлен устаревшим. Поддержка кластеров с ZooKeeper прекращена в Kafka 4.0+. Как следует из названия, KRaft основан на протоколе Raft, впервые описанном Диего Онгаро и Джоном Аустерхаутом в статье 2014 года In Search of an Understandable Consensus Algorithm. Достижение консенсуса в распределенных системах — сложная задача, и сообществу Kafka потребовалось много времени на реализацию нового подхода. До появления KRaft использовался ZooKeeper, который мы подробнее рассмотрим в разделе 7.2. Это был не самый удобный вариант. Например, нам приходилось обслуживать две распределенные системы — Kafka и ZooKeeper. Что еще хуже, ZooKeeper работал слишком медленно для Kafka и ограничивал масштабируемость партиций, снижая надежность системы. Дело не в том, что ZooKeeper плох сам по себе, просто он предоставлял абстракции поверх своей внутренней структуры логов. Kafka и сам отлично справляется с обработкой логов, так что дополнительные абстракции излишни. Итак, сообществу Kafka потребовалась новая архитектура. Теперь у брокеров Kafka может быть две роли: контроллер и брокер. Эти названия нам не нравятся, так как в экосистеме Kafka они могут иметь разный смысл. Если брокеру назначена роль контроллера, то он становится участником координационного кластера. Как и в ZooKeeper, контроллеров должно быть нечетное количество. Для локальных окружений разработки достаточно одного контроллераброкера, но без гарантий отказоустойчивости. Чтобы пережить сбой одного
   130 Часть III. Внутренние механизмы Kafka контроллера-брокера, необходимо три таких контроллера, а при пяти кластер выдержит сбой двух брокеров одновременно. Координационный кластер отвечает за выбор активного контроллера (аналог контроллера в архитектуре с ZooKeeper), который отслеживает состояние обычных брокеров, управляет партициями, назначает лидеров и изменяет эти назначения при сбоях. Неактивные контроллеры называются резервными — при отказе активного контроллера один из них сразу берет на себя его функции. В кластере с ZooKeeper при сбое контроллера проблемы продолжались по несколько минут (в зависимости от размера кластера). Для координации используется новый топик __cluster_metadata, который хранит все метаданные, ранее находившиеся в ZooKeeper и необходимые для работы Kafka. Топик реплицируется на всех брокерах независимо от их роли, так что каждый брокер знает, что ему делать. Обычные брокеры выполняют рутинные задачи, которые мы обсуждали ранее: принимают запросы от продюсеров и потребителей, реплицируют партиции и отвечают за хранение всех данных в Kafka. На рис. 7.1 показана рекомендуемая минимальная конфигурация кластера для продакшена. Рис. 7.1. В Kafka 3.3 ZooKeeper был заменен координационным кластером на основе протокола консенсуса Raft
   Глава 7. Управление кластером 131 СОВЕТ Для повышения надежности рекомендуется использовать три или пять узлов-контроллеров, а также минимум три обычных брокера. Назначение четырех контроллеров не дает преимуществ по сравнению с тремя, так как в обоих случаях можно компенсировать отказ только одного контроллера. Разделение контроллеров и брокеров упрощает масштабирование брокеров и общее управление кластером. Если речь не о продакшене, то один брокер может выполнять обе роли для экономии ресурсов, пусть даже в ущерб гибкости. В локальных окружениях разработки можно запустить один процесс JVM, объединяющий брокер и контроллер в комбинированном режиме. 7.2. Управление кластером с помощью ZooKeeper До KRaft в Kafka за управление кластером отвечал ZooKeeper. Хотя поддержка ZooKeeper была удалена в Kafka 4.0+, многие кластеры пока продолжают его использовать. СОВЕТ Рекомендуется как можно скорее перевести такие кластеры на KRaft. ZooKeeper — отдельный проект Apache, применяемый в Hadoop MapReduce, Neo4j и многих других продуктах. ZooKeeper выглядит как иерархическое хранилище ключей и значений. Мы создаем, читаем и изменяем ZNode, почти как файлы и папки в операционной системе. Ключевая особенность ZooKeeper — гарантия идентичности состояния на всех узлах. Например, при одновременной записи в ZNode с нескольких клиентов будет только один победитель и все клиенты увидят одинаковый результат. В Kafka этот механизм помогает выбрать контроллер: при отсутствии ZNode контроллера брокеры пытаются записать в этот узел свой идентификатор, и тот, кому это удастся, становится контроллером. Кроме того, ZooKeeper отслеживает подключенные клиенты, позволяя обнаруживать сбои брокеров. Для надежной координации изменений используется протокол консенсуса ZooKeeper Atomic Broadcast (ZAB), модификация Paxos, по которому для достижения консенсуса большинство узлов ZooKeeper должны согласовать значение, а подчиненные узлы с другим мнением или вообще без мнения должны будут принять преобладающее значение. Помимо вышесказанного, Kafka хранит в ZooKeeper метаданные кластера, например распределение партиций между брокерами и назначения лидеров партиций. Лидеры записывают в ZooKeeper информацию о синхронизированных фолловерах. Поскольку в ZooKeeper хранятся списки ACL и другая информация, связанная с безопасностью, то требуется дополнительная защита этого компонента.
   132 Часть III. Внутренние механизмы Kafka На рис. 7.2 показан пример кластера Kafka с ZooKeeper, где кворум ZooKeeper состоит из трех узлов, а кластер Kafka содержит четыре брокера. Контроллер в этой архитектуре представляет собой один из брокеров, который также выполняет кое-какие задачи по управлению. Этот подход отличается от протокола KRaft, где активный контроллер всегда является частью отдельного кластера контроллеров. В случае сбоя этого контроллера его функции переходят к другому брокеру. Остальные брокеры тоже напрямую взаимодействуют с ZooKeeper, а стрелки обозначают только главные пути коммуникации. Рис. 7.2. До появления KRaft кластер Kafka состоял из нескольких брокеров и кворума ZooKeeper, обычно содержащего три или пять узлов. Контроллер был просто одним из брокеров. При его отказе другой брокер брал управление на себя Без ZooKeeper вряд ли Apache Kafka вообще появился бы в привычном нам виде, так как ZooKeeper решает множество сложных задач, которые трудно было бы реализовать без него. Однако зависимость от ZooKeeper создает и серьезные проблемы. В его архитектуре приоритет отдается надежности и консенсусу, а не скорости, поэтому он работает относительно медленно и ограничивает производительность Kafka в некоторых сценариях. Администраторам ZooKeeper тоже
   Глава 7. Управление кластером 133 усложняет работу, поскольку обслуживание и понимание этой дополнительной и часто незнакомой системы требуют значительных усилий. Возникает противоречие между философией Kafka — использованием лога как элегантной структуры данных для обмена сообщениями между системами — и зависимостью от ZooKeeper, вызванной необходимостью хранить собственные метаданные. Эта зависимость создает узкие места в производительности и проблемы согласованности. Чтобы снизить влияние на производительность, брокеры часто хранят метаданные локально и не обращаются к ZooKeeper при каждой операции. Такой подход повышает риск несогласованности — локально кешированные данные могут устареть, если брокеры не синхронизируются с ZooKeeper. Когда брокеры действуют на основе устаревшей информации, могут возникать проблемы. Например, контроллер может назначить лидером партиции брокер, который уже недоступен, или некорректно распределить реплики на основе устаревших данных из ZooKeeper. Это происходит потому, что определенные операции брокеров (обновление состояний партиций или управление конфигурациями топиков) выполняются напрямую в ZooKeeper, полностью минуя контроллер. Для поддержания производительности контроллер не отслеживает изменения в ZooKeeper непрерывно, а опрашивает систему периодически. Такой механизм опроса создает временную задержку, в течение которой контроллер может пропустить критические изменения или среагировать на них с опозданием. Из-за задержек решения могут приниматься на основе устаревшей информации, что ставит под угрозу согласованность и надежность кластера Kafka. 7.3. Миграция с ZooKeeper на KRaft Преимущества KRaft перед ZooKeeper уже очевидны, поэтому следующим действием станет миграция с ZooKeeper на KRaft. К счастью, останавливать систему не придется, но потребуются тщательное планирование и реализация. В общих чертах: сначала нужно обновить кластер Kafka до последней версии, которая еще поддерживает ZooKeeper. Для этого можно использовать стандартную процедуру обновления, описанную в документации Apache Kafka. Затем выполняется миграция с ZooKeeper на KRaft, как описано в этом разделе. Наконец, следует обновить кластер до последней версии Kafka, снова используя стандартную процедуру. Есть одно ограничение: при миграции с ZooKeeper на KRaft понадобится выделенный кластер контроллеров KRaft, поэтому количество требуемых компьютеров остается неизменным. Но это не должно помешать нам перейти на KRaft.
   134 Часть III. Внутренние механизмы Kafka Подробно процедура миграции описана в официальной документации (https:// mng.bz/yW6p). Здесь мы рассмотрим процесс в общих чертах. Сначала необходимо подготовить требуемое количество брокеров-контроллеров. Обычно начинают с трех или пяти дополнительных брокеров, настроенных как контроллеры KRaft. Важно, чтобы у этих контроллеров был тот же идентификатор кластера, что и у кластера ZooKeeper, а режим миграции был включен (zookeeper.metadata.migration.enable=true). Кроме того, нужно передать в ZooKeeper параметры подключения (zookeeper.connect=<Zookeeper address>). Теперь добавим конфигурацию, связанную с миграцией, к существующим брокерам. Для этого активизируем режим миграции (zookeeper.metadata.migra­ tion.enable=true) и укажем информацию о подключении к новым контроллерам. После настройки всех брокеров начинается миграция. Новые узлы контроллеров будут реплицировать данные из ZooKeeper, и через некоторое время брокеры отправят в лог следующее сообщение о миграции метаданных: Completed migration of metadata from ZooKeeper to KRaft Обратите внимание, что обычные брокеры все еще находятся в режиме ZooKeeper, и на данном этапе можно без проблем вернуться к нему. Инструкции можно найти в документации. Теперь необходимо перевести обычные брокеры (не контроллеры) в режим KRaft. Для этого нужно отключить режим миграции и удалить настройки подключения к ZooKeeper из конфигурации, а также установить параметр process.roles=broker на каждом обычном брокере. СОВЕТ Рекомендуется оставить кластер в этом состоянии на одну-две недели, чтобы убедиться, что все работает корректно. На данном этапе все еще можно вернуться к работе с ZooKeeper. Если все работает корректно, то можно переключиться на KRaft, но будьте осторожны — это точка невозврата. Отключите режим миграции метаданных на контроллерах и удалите настройки подключения к ZooKeeper из конфигурации. Затем постепенно перезапустите контроллеры, и миграция будет завершена. Kafka теперь работает с KRaft. 7.4. Подключение к кластеру Kafka Итак, мы переходим к ключевому моменту — к подключению к кластеру Kafka. Процесс может показаться простым, но он тесно связан с рассмотренными ранее сложными механизмами координации кластера и управления метаданными. Анализируя эти взаимодействия, мы лучше поймем принципы распределенной
   Глава 7. Управление кластером 135 работы Kafka. Этот разбор поможет преодолеть разрыв между теорией и практикой, показывая, как принципы архитектуры Kafka реализуются в реальных сценариях. В традиционных системах баз данных все устроено просто: мы указываем параметры подключения к базе данных и устанавливаем соединение. Kafka — распределенная система, состоящая из множества брокеров и координационного кластера. Кроме того, лидеры партиций обычно не расположены на одном брокере. Информация о том, какой брокер является лидером для конкретной партиции в конкретном топике, хранится в координационном кластере Kafka. Каждый брокер кластера кэширует эти данные. В ранних версиях Kafka клиенты запрашивали эту информацию напрямую из кворума ZooKeeper. Начиная с Kafka 0.10.0, метаданные запрашиваются непосредственно у любого брокера Kafka через специальный запрос метаданных. Рассмотрим команду, которая выводит список всех топиков: $ kafka-topics.sh \ --list \ --bootstrap-server localhost:9092 Мы передаем URL одного из брокеров Kafka в параметре --bootstrap-server. В нашем тестовом окружении из приложения A это брокер 1. Мы могли бы указать любой другой брокер, команда все равно сработала бы. Однако в продакшене не рекомендуется напрямую обращаться к одному брокеру, так как клиенты перестанут запускаться, если указанный брокер станет недоступен. Вместо этого рекомендуется указывать все брокеры в параметре --bootstrap-server: $ kafka-topics.sh \ --list \ --bootstrap-server \ localhost:9092,localhost:9093,localhost:9094 Когда брокеров много, указывать их вручную неудобно. Нам все равно, к какому именно брокеру клиент подключается изначально, поэтому для обработки запросов метаданных можно использовать балансировщик нагрузки или организовать распределение через систему DNS. При этом важно, чтобы брокеры сохраняли собственные доступные IP-адреса или DNS-имена. Клиент отправляет запрос метаданных на один из bootrstrap-серверов, запрашивая информацию о наборе топиков. Например, если потребитель должен получать данные из топика products.prices.changelog, то в ответе на запрос метаданных будет содержаться информация обо всех брокерах кластера и о том, какой брокер является лидером для каждой партиции этого топика. На следующем этапе клиент подключается к соответствующим лидерам партиций и начинает получать сообщения. Постоянно обновлять метаданные
   136 Часть III. Внутренние механизмы Kafka не требуется — при критическом изменении (например, при отказе брокералидера одной из потребляемых партиций) клиент сам все узнает, когда при попытке получить данные от лидера возникнет проблема (например, лидер будет недоступен). И тогда клиент отправит новый запрос метаданных на один из bootstrap-серверов. Нам неважно, какой брокер получит этот запрос, поэтому процесс подключения можно упростить с помощью DNS, балансировщика нагрузки или виртуальных IP-адресов, чтобы не пришлось указывать как можно больше адресов брокеров во всех клиентах. При отказе брокера Kafka назначает новым лидером одну из реплик партиции. Контроллер кластера Kafka обнаруживает сбой и запускает процесс выбора лидера. После избрания нового лидера метаданные обновляются и клиенты смогут получить актуальную информацию для подключения. На короткий период выбора лидера клиенты могут столкнуться с небольшой задержкой при получении сообщений, но потом взаимодействие возобновляется в обычном режиме. Запросив метаданные, клиенты напрямую взаимодействуют с соответствующими брокерами. Этот процесс показан на рис. 7.3. Рис. 7.3. Прежде чем клиенты смогут отправлять или получать сообщения, им необходимо определить, за какие данные отвечает каждый брокер. Для этого они запрашивают метаданные
   Глава 7. Управление кластером 137 Резюме В Kafka 3.3.0 появился KRaft — новый метод координации на основе протокола Raft, который полностью заменил ZooKeeper, начиная с Kafka 3.5.0. Протокол Raft управляет сложным процессом достижения консенсуса в распределенной системе Kafka. KRaft интегрирует координацию непосредственно в брокеры Kafka — теперь можно отказаться от отдельной системы ZooKeeper, которая ограничивала масштабируемость и усложняла работу. Контроллеры в KRaft управляют назначением партиций, выборами лидеров и механизмами отработки отказов, обеспечивая стабильность кластера и упрощая масштабирование и управление кластерами Kafka. KRaft использует топик __cluster_metadata для согласованного хранения метаданных на всех брокерах, что упрощает операции с кластером и повышает надежность по сравнению с использованием ZooKeeper. Несмотря на отказ от поддержки ZooKeeper в Kafka 4.0+, множество кластеров Kafka до сих пор используют эту систему. Рекомендуется как можно скорее перенести такие кластеры на KRaft, чтобы повысить производительность и упростить управление. ZooKeeper выполняет в Kafka важные задачи, например выбор контроллера и хранение метаданных (назначение партиций, информация о лидерах и т. д.), обеспечивая согласованное состояние всех узлов. ZooKeeper гарантирует надежность при поддержании согласованного состояния, но из-за ограничений производительности и операционной сложности в Kafka был реализован улучшенный подход — KRaft. Перенос метаданных из ZooKeeper в KRaft требует тщательного планирования, зато выполняется без прерывания работы. Сначала обновите кластер Kafka до последней версии с поддержкой ZooKeeper. Подготовьте три или пять новых брокеров в качестве контроллеров KRaft с тем же идентификатором кластера, что и у кластера ZooKeeper. Активизируйте режим миграции и укажите параметры подключения к ZooKeeper. Настройте существующие брокеры для переноса метаданных, включив режим миграции и указав подключения к новым контроллерам. Убедитесь в успешном переносе метаданных. Переведите обычные брокеры в режим KRaft, изменив конфигурации, протестировав стабильность и завершив миграцию. Отключите режим миграции на контроллерах и удалите подключения к ZooKeeper для полного перехода на KRaft.
   138 Часть III. Внутренние механизмы Kafka Подключение к кластеру Kafka подразумевает запрос метаданных, необходимый для определения ролей брокеров и лидеров партиций в кластере. Клиенты подключаются через bootstrap-сервер — обычно один из брокеров Kafka, указанных в параметре --bootstrap-server, который предоставляет начальные метаданные. Чтобы обеспечить отказоустойчивость, в продакшене следует указывать несколько брокеров в параметре --bootstrap-server или использовать DNS для балансировки нагрузки. При сбое брокера клиенты могут отправить новый запрос метаданных на bootstrap-сервер, чтобы узнать новых лидеров партиций и подключиться к ним, а затем продолжить отправку или потребление сообщений без перерыва.
8 Создание и сохранение сообщений В этой главе 3 Сериализация и партиционирование в Kafka. 3 Обработка подтверждений и взаимодействие с брокерами. 3 Прием и сохранение сообщений. 3 Оптимизация в брокерах Kafka. 3 Структуры данных и файлов Kafka. 3 Механизмы репликации и производительность системы. В этой главе мы более подробно рассмотрим создание и сохранение сообщений — ключевых компонентов распределенной архитектуры данных Apache Kafka. Вы увидите, как Kafka управляет сериализацией данных, партиционированием, обработкой подтверждений и взаимодействием с брокерами. Эти процессы обеспечивают надежность и масштабируемость при обработке данных в реальном времени. Понимание этих моментов помогает оптимизировать прием сообщений, их сохранение и общую производительность в экосистеме Kafka. Анализ структур данных и файлов Kafka, механизмов репликации и их влияния на эффективность системы позволяет понять, как эти фундаментальные элементы обеспечивают надежность и производительность Kafka в современных конвейерах данных.
   140 Часть III. Внутренние механизмы Kafka 8.1. Продюсер Обычно продюсеры используют официальную библиотеку Java для Kafka или — если продюсер не работает на виртуальной машине Java — библиотеку на основе библиотеки C librdkafka (https://github.com/confluentinc/librdkafka). СОВЕТ Мы не рекомендуем использовать другие библиотеки. Несмотря на кажущуюся простоту, в них часто не хватает важных функций и оптимизаций. Об особенностях разработки для Kafka можно написать не одну книгу, но мы не ставим целью создать руководство для разработчиков, поэтому лишь кратко затронем наиболее важные детали. Для большей наглядности мы в наших примерах кода в основном используем Python. В Java и других языках программирования основные концепции взаимодействия с Kafka очень схожи. 8.1.1. Генерация сообщений С помощью библиотеки Python confluent-kafka можно отправлять в Kafka сообщения, используя такой код: from confluent_kafka import Producer producer = Producer({ 'bootstrap.servers': 'localhost:9092', Рекомендуемая конфигурация 'acks': -1, для надежных продюсеров 'enable.idempotence': True, 'partitioner': 'murmur2_random', Продюсер должен быть совместим с Java }) Определяет функцию обратного вызова def delivery_report(err, msg): if err is not None: Обработка ошибок print(f"Delivery failed for record {msg.key()}: {err}") return print(f"Record {msg.key()} successfully produced to {msg.topic()} [{msg.partition()}] at offset {msg.offset()}") producer.produce( "products.prices.changelog", key="cola", value="2", on_delivery=delivery_report) Топик для записи Использует функцию обратного вызова Для взаимодействия с Kafka создадим продюсер и передадим его самый важный параметр — bootstrap.servers. Если мы одновременно используем библиотеки Java и библиотеки на основе librdkafka, то нужно вручную задать для хешфункции партиционера в библиотеках librdkafka значение murmur2_random, которое используется в библиотеке Java. По умолчанию librdkafka применяет другую хеш-функцию. В худшем случае разные алгоритмы партицирования не позволят гарантировать порядок сообщений, так как данные с одинаковыми ключами будут распределяться в разные партиции.
   Глава 8. Создание и сохранение сообщений 141 После инициализации продюсера можно начинать отправку сообщений. В Python для этого используется метод produce(). В Java мы сначала вручную создаем сообщение, которое требуется отправить, а затем передаем его в методе send(). Мы передаем продюсеру сообщение, которое нужно отправить, и топик, куда оно должно быть записано. Дополнительно можно передать обратный вызов (callback) — здесь это delivery_report, — вызываемый после получения подтверждения (ACK) о доставке сообщения. Наше сообщение состоит из обязательного значения и необязательного ключа. Для отправки данных в метод produce() необходимо как минимум указать топик и значение. Что происходит между вызовом produce() и обратным вызовом? Много всего, но, к счастью, бо́льшую часть работы берет на себя библиотека Kafka. Общий процесс продюсера показан на рис. 8.1. Рис. 8.1. Что происходит при создании сообщения. После вызова метода produce() (1) данные сериализуются с помощью сериализатора (2). Партиционер (3) определяет, в какую партицию записать сообщение, и сохраняет его в буфере (4) для этой партиции. Когда накапливается достаточное количество сообщений, продюсер отправляет (5) целые батчи соответствующим брокерам и, в зависимости от настроек подтверждения, ожидает подтверждение (6). Если оно получено вовремя (7), то вызывается функция обратного вызова успешной обработки. Если подтверждение не получено (8), то в течение некоторого времени предпринимаются повторные попытки (9). При возникновении ошибки библиотека Kafka генерирует исключение (10)
   142 Часть III. Внутренние механизмы Kafka 8.1.2. Процесс производства сообщений Мы уже говорили, что Kafka не интерпретирует содержимое сообщений, а работает только с байтовыми массивами. Ответственность за сериализацию данных лежит на продюсере. В Python внутренняя логика скрыта, так как библиотека ожидает строку или байтовый массив в качестве входного значения, а затем строки преобразуются в байтовые массивы с помощью строкового сериализатора. Обычно библиотеки Kafka поддерживают разные классы сериализаторов по умолчанию, например JSON, Protocol buffers (Protobuf, https://developers.google.com/ protocol-buffers/) или Apache Avro (http://avro.apache.org/). Если используется другой формат данных, то можно также относительно легко реализовать собственные сериализаторы (и десериализаторы). Эти форматы мы подробно рассмотрим в главе 13, где обсудим их роль в управлении схемами данных. Как только ключи и значения становятся доступны в виде байтовых массивов, партиционер определяет, в какую партицию должно быть отправлено сообщение. Конечно, можно явно указать конкретную партицию в методе produce(), но без веской причины так делать не стоит, а лучше задействовать встроенные механизмы партиционирования. Как уже неоднократно упоминалось, партиционер определяет партицию на основе хеш-значения ключа или, если ключ отсутствует, использует принцип round-robin. Для каждой партиции и каждого топика, в которые записываются данные, существует буфер, куда партиционер помещает сообщения. По умолчанию общий размер буфера составляет 32 МиБ (настройка buffer.memory в продюсере). Из этого буфера формируются батчи с максимальным размером batch.size (стандартно 16 КиБ). По умолчанию продюсеры не ожидают заполнения батчей перед отправкой данных брокеру — Kafka стремится передавать сообщения брокерам как можно быстрее. Создавать батчи целесообразно, если данные генерируются быстрее, чем успевают отправляться отдельные сообщения. Как только сообщения попадают в буфер, их можно отправлять брокерам. При этом сообщения отправляются только лидерам партиций. Чаще всего количество партиций превышает количество брокеров. Вместо того чтобы отправлять отдельные сообщения, продюсер группирует их в более крупные батчи для каждой партиции и передает их брокерам единым блоком. 8.1.3. Продюсер и подтверждения Дальше все зависит от настроек подтверждений. Если в продюсере установлено acks=0, то он отправляет сообщение и не интересуется его дальнейшей судьбой. Сообщение обычно доставляется, но гарантий нет. В большинстве случаев, особенно если данные важны, мы устанавливаем acks=all, чтобы гарантировать, что данные не только достигли лидера (как при acks=1), но и успешно реплицировались. Подробно о подтверждениях мы говорили в главе 5.
   Глава 8. Создание и сохранение сообщений 143 Если мы даем продюсеру указание ожидать подтверждение, то он ждет его до истечения тайм-аута отправленного запроса. Если подтверждение не поступило в течение этого времени или от брокера получена ошибка, то продюсер считает доставку неуспешной и пытается отправить сообщение повторно (рис. 8.2). Рис. 8.2. В продюсере можно определить разные тайм-ауты. Самые важные из них: linger.ms для настройки ожидания батчей и delivery.timeout.ms, чтобы указать, когда библиотека Kafka прекращает повторные попытки доставки сообщений и генерирует ошибку Теперь подробнее рассмотрим тайм-ауты. Метод produce() (или send()) отвечает за запись сообщений в буфер. Если тот заполняется, то поток продюсера ждет, пока место освободится. Время ожидания регулируется параметром max.block.ms (по умолчанию — 60 секунд). Это означает, что при заполненном буфере поток продюсера блокируется и продолжает попытки отправить сообщения, пока место не освободится или пока не пройдет 60 секунд. Перед отправкой Kafka группирует сообщения в батчи. Батч ждет новых сообщений linger.ms миллисекунд (по умолчанию — 0). После формирования батч отправляется брокеру, а продюсер ожидает подтверждения. Тайм-аут ожидания ответа контролируется параметром request.timeout.ms (по умолчанию — 30 секунд). Если подтверждение не получено в указанный срок или возникает ошибка, то продюсер повторяет попытку отправки через retry.backoff.ms (по умолчанию — 100 миллисекунд). Если продюсеру не удается отправить сообщение в течение delivery.timeout.ms (по умолчанию — 2 минуты), то он прекращает попытки и выдает исключение, которое должно обрабатываться приложением. Как действовать в этом случае? Не следует бездумно отправлять сообщения снова и снова, так как кластер Kafka, вероятно, неработоспособен. Обработку повторных попыток лучше поручить библиотеке Kafka — она
   144 Часть III. Внутренние механизмы Kafka позаботится о корректном порядке сообщений, например, если включен параметр enable.idempotence. Для исключений рекомендуется использовать шаблон Circuit Breaker (www.martinfowler.com/bliki/CircuitBreaker.html) или другие методы обработки подобных ошибок. 8.2. Брокер В предыдущем разделе мы подробно рассмотрели, как библиотека продюсера Kafka отправляет сообщения и обрабатывает ошибки. Kafka делегирует большу́ю часть работы клиентам, чтобы брокеры выполняли минимум задач. При отправке сообщений лидер партиции должен корректно принять их, при необходимости проверить наличие прав на запись в партиции, а затем максимально быстро записать сообщения на диск. Далее сообщения нужно распределить по фолловерам, но, к счастью, ответственность за это лежит на самих фолловерах. Если продюсер настроен так, что ему требуется гарантия доставки сообщений, то в конце лидер отправляет продюсеру подтверждение. 8.2.1. Прием и сохранение сообщений Хотя весь процесс выглядит довольно просто, брокеры Kafka представляют собой очень сложные системы, чтобы эффективно и надежно выполнять эти задачи. На рис. 8.3 показана общая схема процессов внутри брокера. Рис. 8.3. При получении брокером запроса от продюсера (1) запрос помещается в очередь (2), обрабатывается потоками ввода-вывода (3) и записывается в кэш страниц (4). Операционная система позже сохраняет данные на диск. Затем сообщение остается в состоянии ожидания (5), пока не получит подтверждение от всех синхронизированных фолловеров (6). Когда ожидание завершается (7), ответ помещается в очередь ответов (8) и отправляется продюсеру одним из сетевых потоков (9)
   Глава 8. Создание и сохранение сообщений 145 Сетевые потоки отвечают за прием сообщений от продюсеров и их запись в очередь запросов после успешной авторизации. Если потоки ввода-вывода не перегружены, то сообщения ненадолго задерживаются в очереди запросов, после чего потоки ввода-вывода записывают их в нужное место в файловой системе — в конец текущего сегмента лога для соответствующей партиции. Kafka не гарантирует физического сохранения сообщений на диск — эта задача возлагается на операционную систему. Сегменты лога — это файлы, в которых Kafka хранит сообщения для партиции. Подробнее структуру данных в Kafka мы рассмотрим в следующем разделе. СОВЕТ Следует по возможности избегать блокирующей синхронизации. Операционная система должна самостоятельно записывать данные из кэша страниц на диск в фоновом режиме. Кстати, утверждение о том, что Kafka вообще не заботится о сохранении сообщений на жестком диске, не совсем верно. Мы можем влиять на операционную систему, и для этого в конфигурации предусмотрено два параметра: flush.ms указывает, через сколько миллисекунд Kafka инициирует принудительный вызов fsync, а flush.messages определяет, после какого количества сообщений выполняется fsync. По умолчанию оба параметра установлены на максимально возможное значение (тип данных long). (Если бы кластер проработал достаточно долго, то Kafka рано или поздно запустил бы fsync. Если ориентироваться на значение flush.ms, например, то это случилось бы всего через 300 миллионов лет!) Даже если теоретически эти настройки могут повысить надежность кластера, на данном этапе мы настоятельно не рекомендуем их менять, поскольку это повлечет значительные потери производительности. Надежность в Kafka также достигается за счет репликации. Оптимизация производительности, при которой данные не записываются на диск независимо, имеет свою цену: необходимо тщательно продумывать развертывание брокеров. Например, если все реплики партиции развернуты на одном хосте виртуальной машины и он выходит из строя, то данные будут гарантированно потеряны. Поэтому брокеры следует распределять как можно более равномерно между доступными системами. Кроме того, рекомендуется использовать функцию распределения по стойкам в Kafka, которую мы подробно рассмотрим в главе 9. 8.2.2. Брокеры и подтверждения После сохранения сообщений в файловой системе брокер должен решить: отправить ответ продюсеру сразу или дождаться подтверждения от других брокеров. Здесь используется механизм чистилища (purgatory), которое выступает временным хранилищем ответов, пока брокер ожидает дополнительной информации — например, подтверждений от фолловеров. Как только все подтверждения получены, брокер записывает ответ в очередь ответов, и сетевые потоки отправляют подтверждение продюсеру.
   146 Часть III. Внутренние механизмы Kafka 8.3. Структуры данных и файлов К этому моменту вы уже знаете, как сообщения производятся и обрабатываются брокером. В этом разделе мы поговорим о данных и файловых структурах Kafka. Мы обсудим типы данных, которые хранит Kafka, и их организацию на брокерах. 8.3.1. Метаданные, контрольные точки и топики При настройке тестового окружения мы создали отдельный каталог для каждого из трех брокеров по пути ~/kafka/data/kafka<Broker ID>. Именно здесь брокеры хранят все свои данные. Для наглядности мы в этой главе полностью реорганизовали кластер Kafka, удалив старые каталоги и создав новые. Рассмотрим содержимое каталога, соответствующего брокеру с ID 1, с помощью ls: $ ls -1 ~/kafka/data/kafka1/ __cluster_metadata-0 bootstrap.checkpoint cleaner-offset-checkpoint log-start-offset-checkpoint meta.properties recovery-point-offset-checkpoint replication-offset-checkpoint Всего в новом кластере Kafka шесть файлов в одной папке. Файл boot­strap.check­ point и папка __cluster_metadata-0, которая содержит данные для партиции 0 топика __cluster_metadata, используются протоколом Kafka Raft (KRaft). Мы опустим их в следующих выводах, так как не будем углубляться в детали. Файл meta.properties содержит идентификаторы нашего брокера, кластера Kafka, каталога, а также версию метаданных, чтобы Kafka понимал, как интерпретировать соответствующие файлы. Остальные файлы содержат только два нуля. Первый ноль обозначает версию метаданных, а второй указывает, сколько строк фактической информации следует за ним. Поскольку кластер создан только что и у нас нет топиков (кроме __cluster_metadata), то мы видим лишь нули. Прежде чем узнать, какая информация хранится в этих файлах контрольных точек, создадим топик products.prices.changelog.file-structure. Указываем стандартный коэффициент репликации 3 и распределяем топик по трем партициям: $ kafka-topics.sh \ --create \ --topic products.prices.changelog.file-structure \ --partitions 3 \ --replication-factor 3 \ --bootstrap-server localhost:9092 Created topic products.prices.changelog.file-structure.
   Глава 8. Создание и сохранение сообщений 147 Топик создан. Посмотрим на содержимое файла replication-offset-checkpoint: $ cat ~/kafka/data/kafka1/replication-offset-checkpoint 0 3 products.prices.changelog.file-structure 0 0 products.prices.changelog.file-structure 2 0 products.prices.changelog.file-structure 1 0 Версия метаданных по-прежнему 0 (строка 1 файла), но теперь файл содержит три строки информации (строка 2). Остальные строки файла начинаются с имени только что созданного топика. Второй столбец указывает на партицию, а третий содержит смещение. Смещения обозначают позицию в логе соответствующей партиции, до которой сообщения уже успешно реплицированы на фолловеры. Более подробно об этом мы поговорим в следующем разделе главы. Поскольку сообщения еще не были созданы, то текущая позиция в логе, до которой выполнена репликация, равна 0. Файл recovery-offset-checkpoint имеет схожую структуру, но здесь смещения указывают на позицию в логе, до которой сообщения успешно сохранены, то есть записаны из оперативной памяти на жесткий диск операционной системой. Файл log-start-offset-checkpoint содержит информацию о смещении первого сообщения в логе или партициях. Первое сообщение в логе не обязательно имеет смещение 0, поскольку Kafka может автоматически очищать лог, удаляя старые и ненужные сообщения, чтобы предотвратить бесконечное накопление данных. Оставшийся файл — cleaner-offset-checkpoint — содержит смещения партиций, до которых очиститель логов Kafka сжал сообщения. Подробности очистки логов и доступные настройки мы рассмотрим в главе 10. 8.3.2. Каталог партиций После создания топика products.prices.changelog.file-structure его метаданные добавились в имеющиеся файлы и был создан вложенный каталог для каждой партиции в каталоге данных брокера (products.prices.changelog.filestructure-0 , productsprices.changelog.file-structure-1, products.pri­ ces.changelog.file-structure-2): $ ls -1 ~/kafka/data/kafka1/ cleaner-offset-checkpoint products.prices.changelog.file-structure-0/ products.prices.changelog.file-structure-1/ products.prices.changelog.file-structure-2/ log-start-offset-checkpoint meta.properties recovery-point-offset-checkpoint replication-offset-checkpoint
   148 Часть III. Внутренние механизмы Kafka Строго говоря, файл создается не для каждой партиции на каждом брокере, а только для тех, которые назначены конкретному брокеру. Поскольку коэффициент репликации равен 3, а в кластере три брокера, то вложенный каталог был создан на каждом из брокеров (или в файловых путях каждого брокера) для всех трех партиций топика products.prices.changelog.file-structure. Рассмотрим файлы партиции 0: $ ls -1 ~/kafka/data/kafka1/products.prices.changelog.file-structure-0 00000000000000000000.index 00000000000000000000.log 00000000000000000000.timeindex leader-epoch-checkpoint partition.metadata В каталоге партиции хранятся пять файлов. Файл partition.metadata содержит версию метаданных и идентификатор топика (topic_id): $ cat ~/kafka/data/kafka1/\ products.prices.changelog.file-structure-0/partition.metadata version: 0 topic_id: q4RDr2lTR8y1Wy6eYUpl6g Файл leader-epoch-checkpoint имеет схожую структуру с другими файлами контрольных точек. В первой строке содержится версия метаданных, а во второй — количество строк информации. На лидере партиции файл будет выглядеть не так, как на фолловерах. Проверим, как у нас распределены лидеры: $ kafka-topics.sh --describe \ --topic products.prices.changelog.file-structure \ --bootstrap-server localhost:9092 Topic: products.prices.changelog.file-structure TopicId: q4RDr2lTR8y1Wy6eYUpl6g PartitionCount: 3 ReplicationFactor: 3 Configs: Topic: products.prices.changelog.file-structure Partition: 0 Leader: 1 Replicas: 1,3,2 Isr: 1,3,2 Topic: products.prices.changelog.file-structure Partition: 1 Leader: 2 Replicas: 2,1,3 Isr: 2,1,3 Topic: products.prices.changelog.file-structure Partition: 2 Leader: 3 Replicas: 3,2,1 Isr: 3,2,1 Брокер 1 — лидер партиции 0, поэтому заглянем в файл leader-epoch-checkpoint в партиции 0 на брокере 1: $ cat ~/kafka/data/kafka1/\ products.prices.changelog.file-structure-0/leader-epoch-checkpoint 0 1 0 0 ПРИМЕЧАНИЕ У вас лидером партиции 0 может быть другой брокер.
   Глава 8. Создание и сохранение сообщений 149 Версия метаданных по-прежнему 0 (строка 1), а файл содержит одну строку информации (строка 2). Сама информация в этом файле представлена двумя нулями. Первый ноль обозначает количество предыдущих лидеров партиции, а второй — текущее смещение лога на момент выбора лидера. Остальные партиции на брокере 1 на данный момент также содержат только два нуля. Эпоха лидера (leader epoch) увеличивается при каждой его смене и позволяет предотвращать ситуации, когда два брокера случайно объявят себя лидерами для одной партиции. Рассмотрим пример, чтобы увидеть, при каких обстоятельствах это может произойти и как эпоха лидера предотвращает такие случаи. С помощью скрипта kafka-broker-stop.sh из приложения A останавливаем брокер с ID 3, который является лидером партиции 2, и снова проверяем содержимое файла leaderepoch-checkpoint: $ kafka-broker-stop.sh 3 $ cat ~/kafka/data/kafka1/\ products.prices.changelog.file-structure-0/leader-epoch-checkpoint 0 1 1 0 Kafka замечает, что один из брокеров стал недоступен, и запускает выборы нового лидера партиции 2. Эпоха лидера увеличивается на 1 для каждой партиции. Мы могли бы с помощью kafka-topics.sh --describe теперь посмотреть, кого выбрали лидером, но в нашем примере это неважно. Перезапустим брокер с ID 3: $ ~/kafka/bin/kafka-server-start.sh -daemon ~/kafka/config/kafka3.properties Здесь как раз и пригодится концепция эпохи лидера. Она гарантирует, что только текущий лидер партиции может распространять сообщения. Представим, например, что брокер 3 даже не понял, что ненадолго отключался, и продолжает считать себя лидером партиции 2. При репликации сообщений брокер всегда отправляет текущую эпоху лидера, и если это число устарело, то остальные брокеры игнорируют сообщения. Брокер 3 до сих пор живет в эпоху лидера 0, поэтому его сообщения отклоняются — сейчас идет эпоха лидера 1. Конечно, брокер 3 не будет вечно отправлять отклоняемые сообщения — через некоторое время он получит информацию о текущем лидере партиции и актуальных эпохах лидера. Впоследствии брокер 3 снова становится лидером партиции 2, так как Kafka по умолчанию каждые 5 минут пытается восстановить предпочтительных лидеров (leader.imbalance.check.interval.seconds). Этот процесс можно ускорить, запустив kafka-leader-election.sh, как мы делали это в главе 5: $ kafka-leader-election.sh \ --election-type=preferred \ --all-topic-partitions \ --bootstrap-server localhost:9092
   150 Часть III. Внутренние механизмы Kafka В завершение примера заглянем в файл leader-epoch-checkpoint в партиции 2 (products.prices.changelog.file-structure-2) на брокере 3 (kafka3): $ cat ~/kafka/data/kafka3/\ products.prices.changelog.file-structure-2/leader-epoch-checkpoint 0 1 2 0 Для партиции 2 значение снова увеличилось, поскольку брокер 3 вернул себе роль лидера. Для остальных партиций значения остались прежними, так как у них лидер не менялся. Создадим несколько сообщений в топике products.prices.changelog.filestructure и перейдем к другим файлам в каталоге партиции: $ kafka-console-producer.sh \ --topic products.prices.changelog.file-structure \ --bootstrap-server localhost:9092 > cola 2 > coffee pads 8 […] > cola 1 Еще раз взглянем на смещения репликации: $ cat ~/kafka/data/kafka1/replication-offset-checkpoint 0 3 products.prices.changelog.file-structure 0 8 products.prices.changelog.file-structure 2 2 products.prices.changelog.file-structure 1 2 Как видим, сообщения уже реплицированы. Они не были равномерно распределены между партициями, поскольку продюсер отправлял их батчами. В зависимости от того, как быстро мы вводили сообщения, некоторые из них попали в один батч и, следовательно, в одну партицию. Это привело к небольшому дисбалансу в распределении нагрузки. ПРИМЕЧАНИЕ Если бы мы использовали ключи, то сообщения распределялись бы по ним. При отсутствии ключей, возможно, придется даже перезапустить продюсер, чтобы отправить данные в несколько партиций. 8.3.3. Данные логов и индексы Теперь мы знаем, что данные реплицированы, но где и как они хранятся? Обратимся к оставшимся файлам в каталоге партиции. Файл с расширением .log содержит наши сообщения. Скрипт kafka-dump-log.sh позволяет заглянуть в журнал — достаточно передать исследуемый сегмент лога через аргумент --files. Посмотрим на содержимое файла 00000000000000000000.log в партиции 0 топика
   Глава 8. Создание и сохранение сообщений 151 products.prices.changelog.file-structure. Мы выбрали эту партицию, так как в ней оказалось большинство сообщений: $ kafka-dump-log.sh \ --files ~/kafka/data/kafka1/\ products.prices.changelog.file-structure-0/0[…]000.log Dumping ~/kafka/data/kafka1/ products.prices.changelog.file-structure-0/0[…]000.log Log starting offset: 0 baseOffset: 0 lastOffset: 1 count: 1 […] position: 0 CreateTime: 1738455138490 size: 68 […] crc: 1092464612 […] Здесь указаны некоторые метаданные файла лога. Для удобства мы немного сократили вывод команды и объясним только наиболее интересные моменты. Мы видим смещение, с которого начинается сегмент лога (Log starting offset), а затем получаем обзор отдельных батчей: смещение первого и последнего сообщений соответствующего батча (baseOffset и lastOffset), а также общее количество сообщений в батче (count). Кроме того, мы видим временну́ю метку (CreateTime) и контрольную сумму батча (crc). Размер батча в байтах указан в атрибуте size, а position указывает позицию в байтах внутри сегмента, где начинается соответствующий батч. Вдобавок каждый батч содержит некоторые метаданные объемом 60 байт, что неудивительно, учитывая обилие информации, предоставляемой скриптом kafka-dump-log.sh. Здесь также видно, что батчи заметно повышают производительность Kafka, особенно при работе с множеством небольших сообщений. Полезная нагрузка самих сообщений занимает всего несколько байтов (в зависимости от содержимого), так как сообщения внутри отдельных батчей содержат дополнительные метаданные. Если мы передадим скрипту kafka-dump-log.sh аргумент --print-data-log, то получим полную информацию об отдельных сообщениях, в том числе само сообщение. Пока опустим этот вывод. Файл с расширением .index ускоряет поиск сообщений в логе, так как Kafka просто сохраняет все сообщения или батчи последовательно. Не будь у нас индекса, нам пришлось бы декодировать весь сегмент каждый раз, когда потребитель запрашивает сообщения, и затем искать соответствующее смещение. Поскольку это неэффективно, то Kafka сохраняет в журнале байтовую позицию, связанную с соответствующими смещениями. Так происходит не для каждого смещения, а только если в лог было добавлено не менее 4096 байт с момента последней записи в индексе. Это значение можно настроить в конфигурации топика с помощью параметра index.inter­val.bytes. Кроме того, можно изменить значение по умолчанию для кластера Kafka в конфигурации брокеров с помощью параметра log.index.interval.bytes. Вдобавок мы можем просмотреть содержимое индексного файла с помощью kafka-dump-log.sh.
   152 Часть III. Внутренние механизмы Kafka СОВЕТ Если уменьшить интервал индексации, то можно подойти ближе к нужной позиции в логе, но индекс будет быстрее нарастать. Значение по умолчанию — 4 КиБ — предлагает оптимальный баланс в подавляющем большинстве случаев. Файл с расширением .timeindex имеет почти ту же задачу и функцию, что и индексный файл, но здесь смещение сопоставляется не с позицией в байтах, а с временно́й меткой, чтобы можно было считать сообщения за определенный день. Часто этот файл используется для сброса смещений потребителей на начало дня, чтобы можно было заново получить все сообщения за этот день. 8.3.4. Сегменты В этой главе мы уже несколько раз упоминали сегменты. Как известно, сообщения в Kafka распределяются по топикам, которые делятся на партиции. Те, в свою очередь, разделяются на сегменты, каждый из которых состоит из файлов логов и индексов, рассмотренных ранее. Общая структура данных показана на рис. 8.4. Рис. 8.4. Топики разделяются на партиции. Партиции создают сегменты по мере поступления сообщений. Сегменты содержат файлы лога, индексные файлы и файл снимка Такое разделение необходимо, поскольку партиции со временем увеличиваются и могут достигать в объеме десятков гигабайт или даже терабайт. Огромные файлы логов или индексов гораздо сложнее обрабатывать. Правда, в большей степени это связано не с потреблением сообщений, а с механизмом их очистки в Kafka. Подробнее об этом поговорим в главе 10. Кроме того, стоит обратить внимание на имена файлов логов и индексов, поскольку они соответствуют первому смещению в сегменте. Когда именно Kafka создает новый сегмент? За это отвечают два важных параметра: достижение сегментом определенного размера (по умолчанию — 1 ГиБ) и определенного возраста (по умолчанию — семь дней). Эти параметры задаются в конфигурации топика. С помощью segment.ms мы задаем время в миллисекундах, по истечении которого создается новый сегмент.
   Глава 8. Создание и сохранение сообщений 153 Параметр segment.bytes задает максимальный размер сегмента в байтах. Разумеется, мы можем изменить соответствующие параметры по умолчанию для топиков в конфигурации брокера (log.segment.bytes и log.roll.ms). Для проверки сократим максимальный возраст сегмента до 60 секунд, с помощью скрипта kafka-configs.sh изменив конфигурацию топика products.pri­ ces.changelog.file-structure: $ kafka-configs.sh \ --alter \ --topic products.prices.changelog.file-structure \ --add-config segment.ms=60000 \ --bootstrap-server localhost:9092 Изменив конфигурацию, отправим несколько сообщений, чтобы Kafka создал новый сегмент. Снова выполним скрипт kafka-console-producer.sh: $ kafka-console-producer.sh \ --topic products.prices.changelog.file-structure \ --bootstrap-server localhost:9092 > cola 2 > energy drink 5 […] > cola 3 Kafka должен был создать новый сегмент. Проверим, что получилось, посмотрев на список файлов в партиции: $ ls -1 ~/kafka/data/kafka1/products.prices.changelog.file-structure-0 00000000000000000000.index 00000000000000000000.log 00000000000000000000.timeindex 00000000000000000003.index 00000000000000000003.log 00000000000000000003.snapshot 00000000000000000003.timeindex leader-epoch-checkpoint Как и ожидалось, в партиции стало больше файлов логов и индексов, а еще добавился файл с расширением .snapshot. В нашем примере он просто указывает на начальное смещение текущего сегмента. Кроме того, этот файл используется для управления идемпотентными продюсерами в Kafka (см. главу 5). 8.3.5. Удаленные топики Снова удалим топик, но не ради очистки, а чтобы изучить сам процесс. Выполним скрипт kafka-topics.sh: $ kafka-topics.sh \ --delete \ --topic products.prices.changelog.file-structure \ --bootstrap-server localhost:9092
   154 Часть III. Внутренние механизмы Kafka Kafka не удаляет файлы напрямую, а сначала помечает их на удаление: $ ls -1 ~/kafka/data/kafka1/ cleaner-offset-checkpoint products.prices.changelog.file-structure-0.<UniqueID>-delete/ products.prices.changelog.file-structure-1.<UniqueID >-delete/ products.prices.changelog.file-structure-2.<UniqueID>-delete/ log-start-offset-checkpoint meta.properties recovery-point-offset-checkpoint replication-offset-checkpoint Kafka изменяет имена каталогов партиций, добавляя к исходным каталогам специальное расширение .<UniqueID>-delete. Через минуту каталоги окончательно удаляются. В течение этого периода теоретически можно отменить удаление топика и сохранить данные, если вы случайно удалили что-то не то. Время задержки перед удалением можно настроить с помощью параметра file.delete.delay.ms в конфигурации отдельного топика или глобально для всего кластера, задав log.segment.delete.delay.ms. 8.4. Репликация В этом разделе мы рассмотрим механизмы репликации Kafka, особенно синхронизированные реплики (in-sync replicas, ISR). Репликация обеспечивает отказоустойчивость и масштабируемость, храня копии данных на нескольких брокерах. ISR критически важны для согласованности и доступности данных. Кроме того, мы разберем концепцию high watermark (HWM), которая позволяет определять точки фиксации сообщений, и проанализируем влияние задержек репликации на потребление и производство данных. Вы получите полное представление о том, как Kafka управляет репликацией, обеспечивая надежность и производительность в распределенных окружениях обработки данных. 8.4.1. Синхронизированные реплики Данные в Kafka реплицируются, когда фолловеры через регулярные интервалы получают данные с лидера. Он отслеживает, какие фолловеры получили данные и когда. Мы уже знаем, что при acks=all мы ждем, пока все ISR не получат сообщение. Но все фолловеры мы не ждем, чтобы не тормозить целый кластер при проблемах с одним брокером. Для этого мы определяем ISR. Синхронизированной считается реплика, которая получала сообщения от лидера в течение последних 30 секунд. Интервал можно настроить с помощью параметра replica.lag.time.max.ms в конфигурации брокера. Лидер всегда остается
   Глава 8. Создание и сохранение сообщений 155 синхронизированным. По умолчанию фолловеры запрашивают новые данные у лидера каждые 500 миллисекунд (интервал настраивается благодаря параметру replica.fetch.wait.max.ms на брокере-фолловере). Если реплика не успевает получить все сообщения до конечного смещения лога (log end offset, LEO) в течение заданного интервала, то лидер исключает ее из списка ISR партиции. LEO указывает на позицию последнего полученного сообщения в партиции и служит маркером конца лога, а также позволяет выявлять медленные реплики, которые могут снижать производительность кластера. Кроме того, реплика немедленно удаляется из ISR, если не отвечает на контрольные сигналы (heartbeat) — периодические сообщения, которые посылают брокеры, чтобы подтвердить свою работоспособность. Если реплика не отправляет сигнал в течение ожидаемого интервала (например, при сбое), то считается рассинхронизированной и удаляется из списка ISR. Этот механизм обеспечивает отказоустойчивость Kafka, позволяя системе работать даже при недоступности отдельных брокеров. 8.4.2. High Watermark Как лидер узнает, что сообщения успешно реплицированы на фолловер, и какую роль в этом играет LEO? Как только лидер или фолловер партиции получает новое сообщение, они обновляют соответствующий LEO. Когда фолловер отправляет запрос на получение данных (fetch) лидеру, запрос также содержит текущий LEO фолловера — аналогично тому, как обычные потребители передают свое текущее смещение. Так лидер определяет, получил ли фолловер все сообщения, или какие-то из них отсутствуют и требуют отправки. На данном этапе появляется еще одно смещение: high watermark (HWM). Это позиция в журнале, до которой все синхронизированные реплики уже получили сообщения (рис. 8.5). Таким образом, HWM соответствует наименьшему LEO среди всех синхронизированных реплик. Кроме того, в Kafka сообщение считается успешно зафиксированным, только когда все синхронизированные реплики получили его, то есть Рис. 8.5. HWM соответствует смещению, которое все ISR успели реплицировать и подтвердить лидеру. Нижний фолловер не синхронизирован и не учитывается при расчете HWM
   156 Часть III. Внутренние механизмы Kafka когда метка HWM больше или равна позиции сообщения в логе независимо от стратегии подтверждения. Вдобавок лидер передает текущую HWM фолловерам во время получения данных. HWM в Kafka выполняет еще одну важную функцию: потребители могут читать только те сообщения, которые с точки зрения Kafka являются зафиксированными. Эту границу в логе как раз и определяет HWM. Репликация в Kafka полностью асинхронна, поэтому новое сообщение может стать доступным для потребителей через несколько секунд после отправки, в зависимости от настроек. В нормальном состоянии взаимодействие с Kafka происходит почти в реальном времени, с задержками в миллисекундном диапазоне. ПРЕДУПРЕЖДЕНИЕ Если количество синхронизированных реплик меньше значения min.insync.replicas, то HWM не увеличивается и потребление новых сообщений блокируется до тех пор, пока не восстановится минимальное количество синхронизированных реплик. Рассмотрим репликацию сообщения поэтапно. Когда лидер партиции получает новое сообщение, сначала увеличивается LEO лидера. При следующем запросе данных от фолловеров лидер обнаруживает, что им не хватает сообщения, и отправляет его соответствующим фолловерам. При следующем запросе данных он определяет, что все фолловеры получили сообщения, так как их LEO совпадает с LEO лидера, после чего увеличивает HWM и передает новое значение фолловерам вместе с ответом на запрос данных. Теперь рассмотрим процесс с точки зрения фолловера (рис. 8.6). Если фолловер хочет прочитать сообщения, то отправляет запрос данных с текущей позицией в логе (своим LEO) лидеру соответствующей партиции. Лидер отправляет сообщения фолловеру и заменяет смещение fetch-запроса (last com­ mitted offset, LCO — «последнее зафиксированное смещение»), сохраненное для этого фолловера, смещением, которое фолловер отправил с fetch-запросом. На данном этапе лидеру неважно, будут ли сообщения успешно доставлены. Рис. 8.6. Последнее зафиксированное смещение (LCO) фолловера Если фолловер не получил сообщение, то до и после запроса данных (текущая просто отправляет новый запрос брокеру позиция). HWM указывает последнее с тем же смещением, а брокер повторно смещение, зафиксированное всеми отправляет сообщения. Такой подход ISR. Поскольку фолловер отстает, снижает сложность для брокеров, по- то его LCO ниже HWM. LEO немного скольку им не нужно беспокоиться об опережает HWM, так как новые обработке ошибок. сообщения продолжают поступать
   Глава 8. Создание и сохранение сообщений 157 8.4.3. Влияние задержек при репликации В завершение этого раздела посмотрим, что будет, если синхронизированная реплика начинает отставать, то есть не успевает вовремя копировать сообщения от лидера. Пока реплика остается синхронизированной, она влияет на метку HWM и, следовательно, на то, какие сообщения доступны потребителям. В результате, если ISR начинает отставать, потребители больше не могут получать сообщения почти в реальном времени. Через 30 секунд (или иной заданный период отставания) реплика исключается из списка ISR, HWM, вероятно, совершит большой скачок вперед, а потребители догоняют поток сообщений. Кроме того, потребление сообщений возможно до тех пор, пока существует лидер партиции и минимум реплик, заданный значением min.insync.replicas. Если текущий лидер партиции выходит из строя, то потребление сообщений временно останавливается — до выбора нового лидера. Если мы поторопимся и сократим максимальное отставание до 2 секунд, то возникнут новые проблемы. Асинхронная репликация вызывает небольшие временные задержки между фолловерами и лидерами, в итоге реплики будут часто исключаться из списка ISR и добавляться обратно, что приведет к лишним издержкам. Более того, слишком быстрое удаление реплик из списка ISR увеличивает затраты на отслеживание, поскольку ISR управляются контроллером, а также повышает риск несоблюдения лимита min.insync.replicas. Если количество синхронизированных реплик опускается ниже заданного минимума, то производство новых сообщений при acks=all становится невозможным из-за недостатка доступных ISR. Кроме того, HWM не будет продвигаться, поэтому, даже если мы не указали acks=all, потребители не смогут получать новые сообщения. Иногда возникает соблазн изменить минимальное количество ISR, чтобы повысить производительность, но мы настоятельно не рекомендуем делать это. Крайне важно установить здесь разумное значение, чтобы обеспечить надежность кластера Kafka. Kafka не занимается фактическим сохранением сообщений, а делегирует эту задачу операционной системе. Если лидер является единственной синхронизированной репликой и внезапно выходит из строя, то возникает риск потребления данных, которые еще не были сохранены в файловой системе. СОВЕТ Чтобы повысить надежность, минимальное количество синхронизированных реплик всегда следует устанавливать на разумное значение, например 2, при коэффициенте репликации 3. В 2023 году в Kafka появилась функция под названием eligible leader replica (ELR) — кандидат на роль лидера. Когда количество ISR опускается ниже
   158 Часть III. Внутренние механизмы Kafka min.insync.replicas, HWM не продвигается. В этом случае реплики, не входя- щие в синхронизированный набор, но достигшие HWM, считаются подходящими для того, чтобы в случае сбоя стать лидером партиции. Эта функция не может полностью защитить от потери сообщений при acks=0 или acks=1, но гарантирует, что ранее потребленные сообщения не будут потеряны во время сбоя. Резюме Продюсеры в Kafka обычно используют либо официальную библиотеку Java для Kafka, либо librdkafka. Следует избегать использования других библио­ тек — в них может не быть нужных функций и оптимизаций. Рабочий процесс продюсера состоит из сериализации, партиционирования и управления буфером. Обработка подтверждений (ACK) подразумевает настройку тайм-аутов и использование механизмов повторных попыток. Брокеры Kafka делегируют большу́ю часть работы клиентам, фокусируясь на приеме сообщений и их эффективном сохранении. При получении запроса на запись брокер сохраняет данные в кэш страниц операционной системы, при необходимости ожидая репликации данных фолловерами перед отправкой подтверждения. Сетевые потоки управляют приемом сообщений, помещая их в очередь для записи в файловую систему потоками ввода-вывода. Kafka доверяет операционной системе сохранять сообщения на диск, но пользователи могут настраивать интервалы сохранения. Компоненты брокера можно оптимизировать и настроить для конкретных сценариев, при этом для сложных окружений рекомендуется профессиональная поддержка. Структуры данных в Kafka — метаданные, контрольные точки и топики — организуют данные внутри брокеров. Партиции разделяют топики на сегменты, каждый из которых содержит файлы логов и индексов для эффективного поиска данных. Данные логов и индексы оптимизируют хранение и извлечение сообщений внутри сегментов. Сегменты, создаваемые по размеру или времени, управляют ростом партиций и повышают эффективность хранения данных. Репликация предполагает получение данных фолловерами от лидеров, необходимое для поддержания актуальности всех брокеров. Синхронизированные реплики (ISR) — это фолловеры, получившие все сообщения от лидера в заданный временной интервал.
   Глава 8. Создание и сохранение сообщений 159 Конечное смещение журнала (LEO) указывает позицию последнего полученного сообщения, определяя статус ISR. Метка HWM обозначает смещение, реплицированное и подтвержденное всеми ISR, и влияет на доступность сообщений для потребления. Задержки репликации могут замедлить или остановить потребление и производство сообщений, а отставание ISR влияет на HWM и доступность данных для потребителей. Настройка дополнительных параметров, таких как replica.lag.time.max.ms и acks=all, может влиять на эффективность репликации и производительность системы, а потому требует тщательной конфигурации для достижения баланса.
9 Потребление сообщений В этой главе 3 Модель потребления на основе fetch в Kafka. 3 Управление смещениями. 3 Роль групп потребителей. 3 Координация распределения задач в Kafka. 3 Принцип работы Range Assignor и Round Robin Assignor. 3 Статическое членство и Cooperative Sticky Assignor. В этой главе мы подробно исследуем процесс потребления сообщений в Kafka. Начнем с базовых принципов модели потребления на основе fetch, объяснив, как потребители взаимодействуют с кластером Kafka, чтобы получать сообщения. Углубляясь в тему, мы разберем механизмы управления смещениями, обсудив, как потребители указывают партиции и управляют своим прогрессом чтения в Kafka. Далее мы рассмотрим протокол перебалансировки и увидим, как Kafka координирует распределение партиций между участниками группы потребителей. В ходе обсуждения мы поговорим о стратегиях назначения партиций и распределения нагрузки, например Range Assignor и Round Robin Assignor.
   Глава 9. Потребление сообщений 161 Кроме того, мы проанализируем расширенные функции, такие как статические членства и Cooperative Sticky Assignor, помогающие оптимизировать поведение перебалансировки и обеспечить бесперебойную работу групп потребителей в Kafka. К концу главы вы получите полное представление о процессе потребления сообщений в Kafka и тонкостях эффективного чтения данных. 9.1. Получение сообщений В простейшем случае потребитель получает данные из одного или нескольких топиков. Он уже подключился к кластеру Kafka, отправил запрос метаданных и из ответа узнал, какие брокеры являются лидерами для каких партиций. 9.1.1. Запросы fetch Чтобы получать сообщения, потребитель отправляет лидерам запрос на это, указывая партицию, из которой он хочет получать данные, и смещение, с которого брокер должен начать отправку сообщений. Эту ситуацию можно смоделировать с помощью скрипта kafka-console-consumer.sh. ПРИМЕЧАНИЕ В предыдущей главе мы создали кластер Kafka повторно, поэтому сначала нужно снова создать топик products.prices.changelog и отправить несколько сообщений. Возможно, потребуется изменить номер партиции в команде, поскольку неизвестно, в какой партиции окажутся сообщения. Или можно сразу создать топик с одной партицией. $ kafka-console-consumer.sh \ --topic products.prices.changelog \ --offset 0 \ --partition 0 \ --bootstrap-server localhost:9092 coffee pads 10 coffee pads 11 coffee pads 12 coffee pads 10 Для этого необходимо явно указать целевую партицию и смещение, с которого требуется начать получение сообщений. Обычно мы не задаем партицию или смещение вручную — этим занимается библиотека Kafka. В главе 1 вы уже встречали флаг --from-beginning. По умолчанию скрипт kafka-console-consumer.sh начинает чтение с конца партиции, а флаг --from-beginning отсылает его к началу. Библиотека Kafka использует аналогичные настройки по умолчанию. При запуске потребителя чтение начинается с последних сообщений. Если мы хотим читать сообщения с начала партиции, то должны задать для параметра конфигурации auto.offset.reset значение earliest (по умолчанию — latest).
   162 Часть III. Внутренние механизмы Kafka 9.1.2. Получение данных из ближайшей реплики Ранее мы говорили, что потребитель запрашивает сообщения только у лидера партиции. Это не совсем верно: начиная с Kafka Improvement Proposal 392 (KIP-392), потребители могут получать данные из ближайшей реплики. ПРИМЕЧАНИЕ Kafka Improvement Proposal (KIP) — это структурированный документ, в котором участники сообщества Apache Kafka предлагают и обсуждают новые функции или изменения для организованной разработки. Поданное предложение обсуждается сообществом, а после утверждения реализуется в Kafka. Как Kafka определяет ближайшую реплику? В Kafka брокерам можно назначать идентификаторы стойки или местоположения (broker.rack). Стойка обычно представляет собой физическую или логическую группу серверов, часто в пределах одного центра обработки данных или зоны доступности, что позволяет Kafka учитывать расположение брокеров. Изначально эта конфигурация предназначалась только для информирования Kafka о местоположении брокеров, чтобы система могла равномерно распределять реплики между доменами отказоустойчивости. KIP-392 добавляет аналогичную опцию для клиентов (client.rack), позволяя им выбирать конкретную реплику для получения сообщений (рис. 9.1). Рис. 9.1. Потребители получают данные из ближайшей реплики (в той же стойке), но продюсеры по-прежнему отправляют данные только лидеру партиции 9.2. Обработка fetch-запросов от потребителей брокером Прежде чем детально рассмотреть, как группы потребителей помогают сохранять смещения, узнаем, что должен сделать брокер, чтобы дать потребителю корректный ответ. По сути, процесс обработки запроса на получение данных
   Глава 9. Потребление сообщений 163 брокером очень похож на обработку запроса на запись, только вместо того, чтобы записывать данные в файловую систему, брокер считывает их. Он начинает с тех же сетевых потоков, которые принимают запрос и помещают его в очередь запросов для буферизации. Однако в этом случае потоки ввода-вывода не записывают батчи на диск, а читают их оттуда. В идеале брокеру даже не приходится обращаться к диску, так как сообщения могут оставаться в кэше страницы и считываться из него напрямую. При потреблении сообщений также используется чистилище (очередь ожидания запросов), но вместо того, чтобы ждать ответы от других брокеров, оно ожидает поступления новых сообщений, если их запросит потребитель. Это поведение настраивается на стороне потребителя, так как разные потребители могут иметь разные требования к задержкам и пропускной способности. По умолчанию брокер отправляет немедленный ответ потребителю, когда доступно хотя бы одно новое сообщение, поскольку для fetch.min.bytes установлено значение 1. Если новых сообщений нет, то брокер не будет ждать бесконечно — за это отвечает параметр максимального времени ожидания fetch.max.wait.ms, для которого по умолчанию установлено 500 миллисекунд. Как обсуждалось в главе 6, эти параметры можно оптимизировать, чтобы повысить производительность потребителя. Решив отправить ответ, брокер помещает его в очередь ответов, откуда сетевой поток может передать ответ потребителю. Весь процесс показан на рис. 9.2. Рис. 9.2. Брокер выполняет схожий процесс при получении запроса на чтение и запроса на запись данных, просто вместо того, чтобы записывать данные, он их считывает. При этом нам не приходится ждать другие брокеры, зато можно ждать другие сообщения, как указывают параметры fetch.max.wait.ms и fetch.min .bytes
   164 Часть III. Внутренние механизмы Kafka 9.3. Смещения и потребитель В традиционных системах обмена сообщениями сама система управляет тем, какие сообщения отправляются конкретным потребителям. В Kafka потребитель самостоятельно отслеживает, какие сообщения он уже прочитал из каждой партиции, используя смещения. При создании сообщения ему присваивается уникальное смещение: 0 для первого сообщения в партиции, 1 — для второго и т. д. Брокер Kafka увеличивает смещение для каждого нового сообщения по мере их создания. Даже если позже одно сообщение будет удалено (например, в ходе плановой очистки), смещения остальных останутся неизменными. В результате в последовательности смещений могут возникать пропуски, но это не влияет на обработку сообщений, поскольку Kafka просто отправляет следующее доступное сообщение с текущим смещением. 9.3.1. Управление смещениями Для потребителя смещение указывает, какое сообщение нужно прочитать следующим. В разделе 9.1 мы упомянули настройку auto.offset.reset, которая указывает потребителю, что делать, если текущее смещение для партиции неизвестно. Например, начать чтение с самого старого сообщения (auto.of­ fset.reset=­ earliest), то есть с наименьшего смещения, или с конца партиции (auto.offset.reset=latest). Где потребители хранят смещения, чтобы возобновить чтение с последней позиции после перезапуска? В некоторых случаях смещения вообще не требуется сохранять. Например, если потребитель заполняет кэш в памяти данными из Kafka, то при его сбое данные теряются и кэш необходимо заполнить заново. В этом сценарии хранить смещения нет необходимости. В большинстве случаев мы сохраняем, обрабатываем или передаем данные из Kafka куда-то еще, а значит, нам нужно хранить смещения. Например, локально, на диске сервиса. При каждом перезапуске сервис считывает сохраненное смещение с диска и запрашивает сообщения, начиная с этой точки. Это удобно, например, если данные Kafka должны храниться в локальной базе данных или в кэше, но часто мы не отслеживаем состояние сервисов, поэтому отпадает необходимость в постоянном хранении смещений внутри сервиса. Если хранить их в сервисе, то горизонтальное масштабирование будет затруднено, так как новые экземпляры сервиса не будут знать о последнем смещении, поэтому не все потребители смогут начать с нужной точки. Еще один вариант — хранить смещения во внешней системе. Например, при записи данных из Kafka в базу данных можно создать дополнительную таблицу для смещений и даже записывать данные вместе со смещениями в рамках транзакции,
   Глава 9. Потребление сообщений 165 тем самым гарантируя для потребителя доставку сообщения ровно один раз, так как либо прочитанное сообщение записывается в базу данных со смещением, либо не записывается ни то ни другое. Кстати, именно этот подход используют многие коннекторы Kafka Connect для обеспечения гарантии exactly-once. Вообще-то, у нас уже есть система для хранения данных, а смещения — это тоже данные. Чтобы нам не приходилось управлять смещениями, Kafka предоставляет встроенную поддержку их обработки через сжатый топик __consumer_offsets. Он гарантирует, что старые смещения для каждой группы потребителей регулярно удаляются с помощью сжатия в целях экономии места. Подробнее о сжатии мы поговорим в следующей главе. Kafka не знает, каким потребителям принадлежат те или иные смещения, поэтому мы должны их указывать. Вместо хранения смещений отдельно для каждого потребителя Kafka связывает их с группой потребителей. Поэтому мы можем горизонтально масштабировать потребители, ведь несколько экземпляров из одной группы потребителей совместно отвечают за обработку сообщений из одного набора партиций. 9.3.2. Смещения в Kafka Создадим топик products.prices-offsets с двумя партициями: $ kafka-topics.sh \ --create \ --topic products.prices-offsets \ --partitions 2 \ --replication-factor 3 \ --bootstrap-server localhost:9092 Created topic products.prices-offsets. Запишем в топик несколько сообщений: $ kafka-console-producer.sh \ --topic products.prices-offsets \ --property parse.key=true \ --property key.separator=: \ --bootstrap-server localhost:9092 > coffee pads:10 > cola:2 > energy drink:4 > coffee pads:11 > coffee pads:12 > energy drink:4 Запустим потребитель Kafka и укажем группу: $ kafka-console-consumer.sh \ --topic products.prices-offsets \ --bootstrap-server localhost:9092 \
   166 Часть III. Внутренние механизмы Kafka --group products.prices.monitoring \ --from-beginning \ --property print.key=true \ --property key.separator=":" energy drink:4 energy drink:3 coffee pads:10 cola:2 coffee pads:11 coffee pads:12 ПРИМЕЧАНИЕ Мы видим, что порядок считывания сообщений потребителем отличается от порядка их записи продюсером. Дело в том, что сообщения с ключом energy drink попадают в одну партицию, а сообщения с ключами cola или coffee pads — в другую. Кроме того, в нашем упрощенном примере потребитель, скорее всего, сначала выведет все сообщения из одной партиции, а затем из другой. Если прервать выполнение команды, нажав Ctrl+C, а потом перезапустить ее, то новые сообщения отображаться не будут, а значит, смещения были где-то сохранены. Для проверки используем утилиту kafka-consumer-groups.sh, которая выводит информацию о группе. Выполним ее в новом терминале: $ kafka-consumer-groups.sh \ --group products.prices.monitoring \ --describe \ --bootstrap-server localhost:9092 Для наглядности вывод утилиты kafka-consumer-groups.sh о группе потребителей products.prices.monitoring показан в табл. 9.1. Таблица 9.1. Описание группы потребителей products.prices.changelog-monitoring Группа products.prices.monitoring products.prices.monitoring Топик products.prices-offsets products.prices-offsets Партиция 0 1 Текущее смещение 2 4 LEO 2 4 Отставание 0 0 ID потребителя console-consumer-<ID> console-consumer-<ID> Хост /127.0.0.1 /127.0.0.1 ID клиента console-consumer console-consumer Мы видим, что группа потребителей читает топик products.prices-offsets, как ожидалось, по одной записи на каждую партицию. У партиции 0 конечное
   Глава 9. Потребление сообщений 167 смещение журнала (LEO) равно 2, то есть следующее сообщение будет записано в позицию 2. Это значение LEO совпадает с текущим смещением группы, а значит, все сообщения в данной партиции были прочитаны. Следовательно, отставание составляет 0 . Ключевой метрикой для потребителей является именно это отставание, показывающее количество непрочитанных сообщений. Значение 0 подтверждает, что все данные успешно обработаны. В партиции 1 ситуация аналогична, но здесь только одно сообщение. Идентификатор потребителя генерируется автоматически для распознавания участников группы. Был запущен только один потребитель, поэтому его ID совпадает для обеих партиций. Поле host содержит IP-адрес этого потребителя. ПРИМЕЧАНИЕ Идентификатор client.id настраивается на стороне приложения-­ потребителя. Это удобно для логирования, сбора метрик и отслеживания. Если остановить потребитель, отправить новые сообщения через продюсер, а затем повторно выполнить команду kafka-consumer-groups.sh, то значения LEO и отставания изменятся в зависимости от новых сообщений (табл. 9.2). Таблица 9.2. Описание группы потребителей products.prices.changelog-monitoring после записи дополнительных сообщений Группа products.prices.monitoring products.prices.monitoring Топик products.prices-offsets products.prices-offsets Партиция 0 1 Текущее смещение 2 4 LEO 4 7 Отставание 2 3 Если бы мы перезапустили потребитель, то смогли бы прочитать новые сообщения и отставание снова уменьшилось бы до 0. Даже если нам не требуется много потребителей, группы потребителей остаются удобным и надежным методом хранения смещений. ПРЕДУПРЕЖДЕНИЕ Идентификатор группы определяет, какая группа потребителей отвечает за потребление и фиксацию конкретных смещений. Если случайно указать неправильный идентификатор группы (скопировать его с ошибкой или забыть поменять параметр группы), то один потребитель может перезаписать смещения другого, то есть «украдет» смещения и начнет обрабатывать сообщения, предназначенные для другого потребителя. На этапах разработки или тестирования это может вызвать проблемы, а в продакшене приведет к таким серьезным последствиям, как потеря данных или нарушение согласованности обработки.
   168 Часть III. Внутренние механизмы Kafka 9.4. Группы потребителей Kafka Мы уже неоднократно упоминали группы потребителей, которые используются для горизонтального масштабирования потребителей, а в предыдущем разделе расссказали, что с их помощью можно удобно управлять смещениями в Kafka. 9.4.1. Управление группами потребителей Использование групп потребителей не требует применения дополнительных компонентов — достаточно задать идентификатор группы в настройках потребителей. Потребители с одинаковым идентификатором группы образуют группу. Kafka разрешает разным сервисам многократно считывать одни и те же сообщения. Например, во введении мы создали топик products.prices.changelog и запустили два сервиса: аналитики продаж и обновления цен. Эти две группы потребителей независимо друг от друга считывали сообщения из топика, распределяя партиции внутри своих групп (рис. 9.3). Рис. 9.3. Группа потребителей состоит из любого количества участников, которые распределяют между собой партиции. Разные группы потребителей изолированы и не взаимодействуют друг с другом. Брокеры Kafka помогают потребителям координировать распределение задач
   Глава 9. Потребление сообщений 169 Как потребители внутри группы узнают друг о друге и делят партиции между собой? Потребителям необходимо каким-то образом согласовывать свою работу. В главе 4 мы обсуждали распределенные системы и помним, что координация — сложная задача. Зато у нас уже есть отказоустойчивая распределенная система — сами брокеры Kafka. Вместо создания отдельного координационного кластера для каждой группы потребителей брокеры Kafka помогают согласовывать их действия. Позже вы узнаете, что группы потребителей не единственный компонент, который Kafka поддерживает таким образом, — брокеры помогают координировать кластеры Kafka Connect и приложения Kafka Streams. Для этого существует специальный протокол перебалансировки — Kafka Rebalance Protocol, который позволяет распределять ресурсы (например, партиции, задачи Kafka Streams или Kafka Connect) между участниками группы. Здесь Kafka также следует своей философии и делегирует как можно больше работы клиентам. Сам Kafka не углубляется в детали того, что именно координируется. ПРИМЕЧАНИЕ На момент написания книги ведется работа над реализацией нового поколения протокола перераспределения групп потребителей (KIP-848). Новая версия значительно упростит работу групп потребителей и Kafka Streams. Текущая версия будет доступна для конкретных сценариев использования, но мы рекомендуем использовать обновленный протокол сразу после его выпуска в версии 4.0.0. Как работает протокол перебалансировки Kafka? На стороне Kafka нам нужен центральный экземпляр, ответственный за конкретную группу. Этот брокер будет координатором группы. Для более равномерного распределения нагрузки координатором группы выступает не единственный брокер — группы распределяются между брокерами-координаторами по уже знакомому нам шаблону: с помощью идентификатора. Этот протокол показан на рис. 9.4. Когда потребители хотят присоединиться к группе, они сначала отправляют запрос на вступление (1) координатору группы. Координатор группы управляет группой и ее участниками. Запрос на вступление служит точкой синхронизации: ответ не отправляется до тех пор, пока все потенциальные участники группы не зарегистрируются у координатора. Если в группе уже есть участники, то координатор группы временно удаляет их, чтобы они могли повторно присоединиться с новым запросом. В течение этого процесса потребители еще не являются членами группы и не могут потреблять сообщения. Для идентификации координатора группы потребитель сначала должен отправить запрос FindCoordinator любому брокеру в кластере Kafka. Брокер в ответ указывает хост и порт координатора запрошенной группы потребителей.
   170 Часть III. Внутренние механизмы Kafka Рис. 9.4. Протокол перебалансировки Kafka позволяет группе распределять некоторые ресурсы между участниками, например партиции потребителей. В этом процессе брокер выступает координатором группы, а один из потребителей становится лидером группы Узнав, какой брокер координирует его группу, потребитель может напрямую взаимодействовать с ним при выполнении операций управления группой. Например, отправить ему запрос JoinGroup на вступление в группу и запуск перебалансировки, сигнал Heartbeat, чтобы подтвердить свою активность, или SyncGroup, чтобы узнать о назначениях партиций после перераспределения. Когда участники группы присоединяются к ней, первый присоединившийся участник назначается лидером группы (2). Он отвечает за координацию распределения задач внутри группы, в частности за определение того, какой участник будет обрабатывать какую партицию. Лидер отправляет план распределения партиций координатору группы (3). Координатор, в свою очередь, передает эти назначения остальным участникам группы (4). На данном этапе участники группы могут возобновить потребление сообщений из назначенных им партиций. После формирования группы координатор должен отслеживать активность участников. Для этого каждый потребитель отправляет сигналы Heartbeat через регулярные интервалы (5). Если участник не отправлял сигналы в течение
   Глава 9. Потребление сообщений 171 заданного периода (обычно трех интервалов), то координатор считает его неактивным и исключает из группы. Интервалы можно настроить с помощью параметра heartbeat.interval.ms, а время, после которого потребитель считается неактивным, регулируется параметром session.timeout.ms. Однако в большинстве случаев стандартных настроек достаточно. Таким образом, координатор не распускает группу при получении запроса на вступление, а координирует добавление новых участников и обеспечивает распределение партиций между ними. При формировании группы потребителей первый присоединившийся потребитель становится лидером, ответственным за назначение партиций. Координатор группы управляет участниками группы, отслеживая их активность через сигналы Heartbeat и распределяя партиции между ними. 9.4.2. Распределение партиций между потребителями Kafka предоставляет несколько способов распределения партиций между потребителями. Например, параметр потребителя partition.assignment.strategy. Лидер группы отвечает за назначение партиций во время перераспределения и передачу этих данных координатору группы. Остальные потребители просто следуют назначениям лидера. ПРЕДУПРЕЖДЕНИЕ Все потребители в одной группе должны использовать одинаковую стратегию распределения партиций. Применение разных стратегий может привести к непредсказуемым изменениям при перераспределении, а значит, к проблемам с согласованностью данных и производительностью. По умолчанию в Kafka используется Range Assignor. Эта стратегия предполагает, что все топики имеют одинаковое количество партиций. Range Assignor гарантирует, что партиция 0 всех топиков будет назначена одному потребителю, партиция 1 — другому и т. д. Например, если у нас два топика с двумя партициями и три потребителя (рис. 9.5), то мы назначаем партиции 0 обоих топиков потребителю 1, партиции 1 обоих топиков — потребителю 2, а потребитель 3 не получает ничего. Такая стратегия удобна при совместной обработке данных из нескольких топиков. Например, данные из топика products.prices.changelog мы хотим дополнить информацией о продажах из топика products.sales, чтобы провести аналитику. Если в обоих топиках используется ключ в виде названия товара, то сообщения для одного товара всегда попадают в одну партицию, будь то в топике products.prices.changelog или в топике products.sales. то есть товары из партиции 0 топика products.prices.changelog соответствуют товарам из
   172 Часть III. Внутренние механизмы Kafka партиции 0 топика products.sales. Такое поведение называется соединением потоков (stream join) и активно используется в Kafka Streams. Рис. 9.5. При распределении по умолчанию одна и та же партиция разных топиков всегда обрабатывается одним и тем же потребителем. Это не всегда оптимально с точки зрения ресурсов, но такой подход упрощает выполнение соединений (join) между данными из разных топиков ПРИМЕЧАНИЕ Несмотря на возможность реализации простых соединений через Range Assignor, не рекомендуется делать это напрямую в потребителе. Для соединения данных из нескольких топиков используйте специализированные библиотеки потоковой обработки, например Kafka Streams. При чтении из нескольких топиков с помощью простого потребителя часто не требуется корреляция данных между топиками и даже можно не использовать ключи. В этом случае можно избежать ограничений Range Assignor, используя вместо него стратегию Round Robin Assignor, которая равномерно распределяет партиции всех топиков между потребителями (рис. 9.6). Мы помним, что во время перераспределения чтение сообщений прекращается, а после потребителю могут назначиться другие партиции. Это означает, что потребители должны очистить любое внутреннее состояние (например, кэши) и зафиксировать свои смещения перед началом перераспределения. В нашем
   Глава 9. Потребление сообщений 173 примере, если потребитель 1 выходит из строя, каждому потребителю будут назначены совершенно другие партиции. Этого можно избежать с помощью стратегии Sticky Assignor. Вместо того чтобы полностью переназначать партиции при каждом изменении, Sticky Assignor старается минимизировать изменения. В остальном он работает аналогично Round Robin Assignor. Рис. 9.6. Round Robin Assignor (и более продвинутые Sticky и Cooperative Sticky Assignor, описанные далее) равномерно распределяет нагрузку между участниками группы потребителей Sticky Assignor работает значительно лучше Round Robin Assignor, поскольку избегает полного перераспределения, но все равно останавливает все процессы на это время. Для решения этой проблемы в Kafka 3.0 была представлена улучшенная стратегия — Cooperative Sticky Assignor. Не требуя полной остановки системы, этот кооперативный протокол позволяет потребителям продолжать обработку партиций, назначение которых не меняется. Рассмотрим, как он работает. На рис. 9.7 группа потребителей из двух участников читает сообщения из трех партиций. В группу вступает новый потребитель. При использовании Sticky Assignor всем участникам пришлось бы остановить обработку, дождаться перераспределения, а затем возобновить работу. При использовании кооперативного протокола группа продолжает считывать сообщения в нормальном режиме. В ходе первого перераспределения назначение
   174 Часть III. Внутренние механизмы Kafka «забирает» третью партицию у потребителя 2. Естественно, потребление для третьей партиции останавливается, но это перераспределение немедленно запускает следующее, где ничья партиция 3 назначается новому потребителю 3. При таком подходе не приходится останавливать потребление для всех партиций только потому, что надо переназначить одну из них. Рис. 9.7. Cooperative Sticky Assignor избегает полной остановки процессов и выполняет перераспределения в два этапа, а не за один. Во время первого перераспределения потребители освобождают партиции, которые будут переназначены другим потребителям; во время второго освободившиеся партиции назначаются потребителям СОВЕТ Пока в вашей системе Kafka не станет доступен протокол перераспределения групп потребителей нового поколения в версии 4.0.0, рекомендуем использовать стратегию Cooperative Sticky Assignor в группах потребителей. Таким образом, протокол перераспределения Kafka позволяет распределять ресурсы (например, партиции или задачи коннекторов) между участниками группы. Во время выполнения протокола все процессы приходится приостанавливать, поэтому перераспределения являются очень затратными. Они запускаются, например, когда новые участники хотят присоединиться к группе, когда участники намеренно или случайно покидают группу или когда в топике меняется количество партиций. Например, в полностью автоматизированном окружении последовательный перезапуск трех потребителей в группе потребителей приведет к шести перераспределениям: по одному при остановке каждого потребителя и еще по одному при его повторном запуске. 9.4.3. Статическое членство Для предотвращения избыточных перераспределений в Kafka 2.3 была представлена концепция статического членства. Предполагается, что инфраструктура полностью автоматизирована и при сбое потребителя Kafka немедленно перезапускается. Это характерно для облачных сред и Kubernetes. Вместо того
   Глава 9. Потребление сообщений 175 чтобы активизировать перераспределение при каждом перезапуске потребителя, значение session.timeout.ms нужно увеличивать до значительной величины (например, до нескольких минут). Дополнительно каждому потребителю присваивается уникальный идентификатор, который задается с помощью параметра group.instance.id. Необходимо гарантировать, что после перезапуска потребитель получит тот же идентификатор. Этого можно достичь, например, с помощью Kubernetes StatefulSet или установки в качестве идентификатора имени хоста в облаке, если оно не меняется после перезапуска. При таких настройках перераспределение выполняется, только когда мы добавляем новые потребители, потребитель неактивен в течение нескольких минут или количество партиций меняется. Такой подход значительно оптимизирует использование потребителей и предотвращает серьезные перерывы в обработке данных. Резюме В Kafka потребители отправляют запросы (fetch) для получения сообщений. Брокеры Kafka выполняют бо́льшую часть работы по координации, минимизируя нагрузку. Брокеры Kafka используют идентификаторы стоек, чтобы обеспечить отказоустойчивость и распределение нагрузки. Группы потребителей управляют смещениями и распределяют нагрузку между потребителями. Смещения хранятся в Kafka как часть метаданных группы потребителей в специальном внутреннем топике __consumer_offsets. Смещения позволяют отслеживать уже обработанные сообщения. Протокол перебалансировки Kafka координирует распределение задач между участниками группы потребителей. Группы потребителей обеспечивают параллельную обработку благодаря распределению партиций между потребителями. Перераспределение происходит при изменении состава группы или количества партиций в топиках, а также при сбоях потребителей. Range Assignor и Round Robin Assignor — стратегии распределения партиций. Range Assignor гарантирует, что одни и те же партиции разных топиков обрабатываются одним потребителем.
   176 Часть III. Внутренние механизмы Kafka Round Robin Assignor равномерно распределяет партиции, если нам не нужно соединять данные из разных топиков. Статическое членство и Cooperative Sticky Assignor оптимизируют поведение при перебалансировках. Статическое членство снижает частоту перераспределений благодаря увеличенным тайм-аутам сессий и уникальным идентификаторам экземпляров группы. Cooperative Sticky Assignor повышает эффективность перераспределений путем постепенного достижения желаемого состояния.
10 Очистка сообщений В этой главе 3 Механизмы очистки сообщений в Kafka. 3 Параметры управления хранением сообщений. 3 Обработка устаревших данных в Kafka. В Kafka управление жизненным циклом сообщений — залог высокой производительности системы и целостности данных. В этой главе мы подробно разберем два ключевых подхода: удаление логов по возрасту или размеру (log retention) и сжатие логов (log compaction). Удаление реализуется просто и подстраивается под разные сценарии, в том числе под требования комплаенса и управление данными. При сжатии логов выборочно удаляются устаревшие данные на основе ключей — сохраняется только последнее сообщение для каждого ключа. Понимая принципы и настройки очистки логов, пользователи Kafka могут эффективно управлять политиками хранения сообщений, адаптируя их под конкретные задачи, чтобы оптимизировать использование хранилища и гарантировать точность данных по всей системе. 10.1. Зачем удалять сообщения Прежде чем углубляться в детали очистки сообщений в Kafka, рассмотрим, зачем вообще нужно удалять сообщения и что будет, если никогда этого не делать. Одна из причин — заполнение хранилища. Теоретически можно хранить
   178 Часть III. Внутренние механизмы Kafka все сообщения бесконечно, но лог будет разрастаться бесконтрольно и быстро займет все доступное пространство. Другая причина — производительность. Чем больше лог, тем больше времени требуется для обработки всех сообщений, причем значительная их часть уже давно потеряла актуальность. Возможно, главная причина удаления данных — они больше не нужны или их запрещено хранить по юридическим причинам. Однако нужно действовать осторожно, чтобы случайно не удалить нужные сообщения. 10.2. Методы очистки в Kafka Как мы уже сказали в начале этой главы, для очистки в Kafka используются два подхода: удаление логов по возрасту/размеру и сжатие логов. При удалении логов по возрасту удаляются все сообщения, существующие определенное время, то есть созданные до конкретного момента. При сжатии логов удаляются устаревшие данные (рис. 10.1). Устаревшие сообщения определяются по ключу — сохраняется только последнее сообщение для каждого ключа. Сжатие логов работает, только если мы назначаем ключи сообщениям. Рис. 10.1. Очиститель логов всегда обрабатывает данные по сегментам. При использовании политики удаления по возрасту сегмент целиком удаляется, когда его самое новое сообщение достигает заданного возраста. Политика сжатия требует удалять только сообщения с ключами, имеющими более новые версии. Методы можно комбинировать У этих двух методов есть свои преимущества и недостатки. При необходимости их можно комбинировать. Удаление логов по возрасту отличается простотой реализации и низкой нагрузкой на брокеры, так как для удаления достаточно проверить возраст сообщений. Это очень удобно во многих сценариях. Например, в целях защиты данных мы можем гарантировать автоматическое удаление сообщений по истечении определенного срока. Если мы храним данные с сенсоров, то через некоторое время они теряют актуальность и их лучше удалить. Логи программ тоже не требуется хранить бесконечно.
   Глава 10. Очистка сообщений 179 Если Kafka используется исключительно как система обмена сообщениями, то сообщения можно спокойно удалять через короткое время. Выборочно удалять данные по возрасту (времени существования) невозможно и нужно проявлять осторожность, чтобы случайно не стереть важную информацию только потому, что она старая. А вот сжатие логов поддерживает выборочное удаление. Допустим, у нас есть топик для хранения адресов клиентов. Каждый раз при изменении адреса создается новое сообщение с соответствующим ключом клиента. Старый адрес с этого момента, скорее всего, становится неактуальным, но при этом мы должны гарантировать, что текущий адрес не будет удален только потому, что указан очень давно. Сжатие логов идеально подходит для подобных сценариев, так как гарантирует сохранение последнего сообщения для каждого ключа и автоматическое удаление устаревшей информации. Недостаток сжатия логов — относительно высокие издержки, поскольку приходится сканировать весь лог в поисках сообщений, которые можно удалить. Можно применять один из методов или комбинировать их, указывая настройки отдельно для каждого топика с помощью cleanup.policy или устанавливая значение по умолчанию для всех топиков, используя log.cleanup.policy . По умолчанию в Kafka активизировано удаление логов по возрасту (log.clean­ up.policy=delete). В следующих двух разделах мы подробно рассмотрим эти два подхода в Kafka. 10.3. Удаление логов по возрасту и размеру В этом разделе мы на примерах рассмотрим, когда Kafka удаляет данные и как настроить очистку логов в соответствии с потребностями. Для начала создадим топик products.prices-retention: $ kafka-topics.sh \ --create \ --topic products.prices-retention \ --partitions 3 \ --replication-factor 3 \ --bootstrap-server localhost:9092 Created topic products.prices-retention. Отправим в него несколько сообщений, которые потом будем удалять: $ kafka-console-producer.sh \ --topic products.prices-retention \ --bootstrap-server localhost:9092 > cola 2 > coffee pads 8 […] > energy drink 5
   180 Часть III. Внутренние механизмы Kafka В партиции мы увидим файлы, знакомые нам по главе 8: $ ls -1 ~/kafka/data/kafka1/products.prices-retention-0 00000000000000000000.index 00000000000000000000.log 00000000000000000000.timeindex leader-epoch-checkpoint partition.metadata 10.3.1. Когда происходит очистка логов Удалить лог по возрасту или размеру в Kafka можно двумя способами. При первом мы можем настроить размер партиции, при превышении которого запускается очистка и удаляется самый старый сегмент. Для этого мы указываем значение параметра retention.bytes в байтах. По умолчанию значение равно -1, то есть удаление по размеру партиции отключено, так как этот вариант применяется редко. Второй способ — удаление сообщений или сегментов по истечении заданного периода, называемого временем хранения. Для этого используется параметр retention.ms в миллисекундах. По умолчанию Kafka удаляет сообщения или сегменты, если последнее сообщение в сегменте старше семи дней (рис. 10.2). Рис. 10.2. Если сегмент превысил 1 Гбайт или если последнее сообщение было отправлено С помощью параметров log.retention.bytes и log.retention.ms можно изменить настройки по умолчанию для кластера. Эти параметры можно комбинировать, чтобы удалять старые сообщения и при этом контролировать размер топика или партиции. Испытаем этот метод, изменив параметры топика products.prices-retention скриптом kafka-configs.sh и задав для параметра retention.ms 60 секунд: $ kafka-configs.sh --alter \ --topic products.prices-retention \ --add-config retention.ms=60000 \ --bootstrap-server localhost:9092 Completed updating config for topic products.prices-retention.
   Глава 10. Очистка сообщений 181 Заглянув в партицию, мы увидим изменения: $ ls -1 ~/kafka/data/kafka1/products.prices-retention-0 00000000000000000004.index 00000000000000000004.log 00000000000000000004.snapshot 00000000000000000004.timeindex leader-epoch-checkpoint partition.metadata ПРИМЕЧАНИЕ Если новых файлов нет, то мы поторопились с проверкой или следует проверить другую партицию. Кроме того, могут отображаться старые файлы с расширением .deleted. Как видим, исходные файлы с созданными сообщениями исчезли, поскольку последнее сообщение было отправлено более 60 секунд назад, то есть последнее сообщение в сегменте (а значит, и сам он) оказалось старше установленного периода хранения в 60 секунд. Поскольку партиция всегда содержит как минимум один сегмент, был создан новый сегмент с текущим смещением в качестве имени файла. Кроме того, в соответствии с текущими смещениями были скорректированы эпоха лидера и контрольные точки восстановления. Последующие сообщения попадают в новый сегмент. Через 60 секунд этот сегмент будет удален и появится новый. Настройки удаления по возрасту и размеру зависят от того, как часто мы создаем новый сегмент. Рассмотрим пример. Предположим, по требованию регуляторов сообщения разрешено хранить не больше семи дней. Казалось бы, можно установить для retention.ms значение, соответствующее семи дням, но этого недостаточно, так как период хранения относится к самому новому сообщению в сегменте. Пока сообщения в сегмент записываются регулярно, он не состаривается и все сообщения хранятся. Следовательно, необходимо сначала обеспечить регулярное создание новых сегментов. Это можно сделать, как упоминалось в главе 8, с помощью параметра segment.ms. Во-вторых, требуется установить retention.ms так, чтобы самое старое сообщение в сегменте удалялось не позднее максимально разрешенного периода хранения. Наконец, сумма segment.ms и retention.ms не должна превышать семи дней, хотя это будет не на 100 % точно, так как по умолчанию Kafka проверяет возможность удаления сегментов только каждые 5 минут, а значит, это время нужно вычесть. Частоту проверки можно изменить параметром log.retention.check.interval.ms. Эти семь дней (или семь дней минус пять минут) мы можем по-разному распределить между segment.ms и retention.ms. Например, если новый сегмент
   182 Часть III. Внутренние механизмы Kafka создается каждые шесть дней, то максимальное значение retention.ms составит 23 часа 55 минут. В этом случае сегмент при удалении будет содержать сообщения возрастом от одного до семи дней. Если мы создаем новый сегмент каждый день, то для retention.ms нужно установить 5 дней 23 часа 55 минут, а удаляться будут сообщения возрастом от шести до семи дней. Таким образом, чем чаще мы создаем новые сегменты, тем точнее можем удалять сообщения и тем выше можем установить retention.ms, что позволяет хранить сообщения дольше. СОВЕТ В старых версиях Kafka для удаления всех сообщений в топике рекомендовалось установить период хранения на 0, дождаться удаления, а затем восстановить исходное значение. Метод рабочий, но мы рекомендуем использовать Kafka Admin API для удаления сообщений в топике. 10.3.2. Хранение смещений Поскольку смещения групп потребителей также хранятся в топике Kafka, смещения неактивных потребителей лучше удалять. За это отвечают очиститель логов и настройка брокера offsets.retention.minutes, которая определяет, через какое время удаляются смещения неактивных групп потребителей. По умолчанию это происходит через семь дней. Не рекомендуем уменьшать этот период, так как группы потребителей могут потерять свои смещения, если остановить их из-за ошибки, исправление которой займет больше недели. Кроме того, смещения занимают совсем мало места, так что это значение можно и увеличить. 10.4. Сжатие логов Сжатие логов позволяет находить и удалять устаревшие данные на основе их ключей (рис. 10.3). В отличие от предыдущего этот метод гарантирует сохранение как минимум последней записи для каждого ключа в логе. Рис. 10.3. Сжатие логов позволяет экономить пространство, поскольку предыдущее сообщение для ключа удаляется, если существует более новое
   Глава 10. Очистка сообщений 183 Рассмотрим наглядный пример. Создадим новый топик — products.prices- compaction: $ kafka-topics.sh \ --create \ --topic products.prices-compaction \ --partitions 3 \ --replication-factor 3 \ --config cleanup.policy=compact \ --bootstrap-server localhost:9092 Created topic products.prices-compaction. На этот раз мы явно задаем политику сжатия с помощью параметра --config (--config cleanup.policy=compact), так как обычно в качестве политики очистки по умолчанию для топика задано удаление по возрасту и размеру. Создадим сообщение: $ kafka-console-producer.sh \ --topic products.prices-compaction \ --bootstrap-server localhost:9092 > cola 2 > ERROR when sending message to topic products.prices-compaction with key: null, value: 6 bytes with error: (…) org.apache.kafka.common.InvalidRecordException: Compacted topic cannot accept message without key in topic partition products.prices-compaction-1 На этот раз Kafka не дает отправить сообщение просто так: поскольку мы удаляем сообщения по ключу, то брокер отклоняет все сообщения без ключа. Попробуем еще раз отправить сообщения со свойствами продюсера parse.key и key.separator, назначив сообщениям ключи: $ kafka-console-producer.sh \ --topic products.prices-compaction \ --property parse.key=true \ --property key.separator=: \ --bootstrap-server localhost:9092 > cola:2 > coffee pads:10 […] > energy drink:5 Используя скрипт kafka-dump-log.sh, заглянем в лог для партиции 0. Если добавить --print-data-log, то команда отображает содержимое каждого батча, чтобы можно было просмотреть ключи и значения в сообщении: $ kafka-dump-log.sh \ --print-data-log \ --files ~/kafka/data/kafka1/products.prices-compaction-0/0[……] 000.log Dumping ~/kafka/data/kafka1/products.prices-compaction -0/0[……]000.log Log starting offset: 0 baseOffset: 0 lastOffset: 0 count: 1 […] position: 0 CreateTime: 1738488072332 size: 81 […] crc: 906930835 | offset: 0 CreateTime: 1738488072332 keysize: 12 valuesize: 1 sequence: 0 headerKeys: [] key: energy drink payload: 5
   184 Часть III. Внутренние механизмы Kafka В нашем примере только сообщения с ключом energy drink попали в партицию 0. Создадим еще одно сообщение с ключом energy drink: $ kafka-console-producer.sh \ --topic products.prices-compaction \ --property parse.key=true \ --property key.separator=: \ --bootstrap-server localhost:9092 > energy drink:3 Снова заглянем в лог. Разумеется, новое сообщение с тем же ключом оказывается в партиции 0: $ kafka-dump-log.sh \ --print-data-log \ --files ~/kafka/data/kafka1/products.prices-compaction-0/0[……]000.log Dumping ~/kafka/data/kafka1/products.prices-compaction-0/0[……]000.log Log starting offset: 0 […] baseOffset: 3 lastOffset: 3 count: 1 […] position: 243 CreateTime: 1738488316569 size: 81 […] crc: 3247897454 | offset: 0 CreateTime: 1738488316569 keysize: 12 valuesize: 1 sequence: 0 headerKeys: [] key: energy drink payload: 3 10.4.1. Когда происходит очистка логов при использовании сжатия Несмотря на сжатие логов, устаревшее сообщение никуда не девается. Разве Kafka не должен был его удалить? ПРИМЕЧАНИЕ На данном этапе мы также могли бы использовать скрипт kafkaconsole-consumer.sh, чтобы проверить, какие сообщения все еще можно получить и какие были удалены в результате сжатия логов. Мы все еще видим устаревшие сообщения, поскольку при сжатии логов, как и при их удалении по возрасту и размеру, процесс не происходит непрерывно. Во-первых, по умолчанию очиститель логов проверяет сообщения каждые 15 секунд (log.cleaner.backoff.ms). Во-вторых, искать устаревшие сообщения во всем логе каждые 15 секунд неэффективно и практически невозможно. Поэтому очиститель логов использует дополнительные параметры, чтобы определить, можно ли сжать лог. С помощью параметра min.cleanable.dirty.ratio мы можем указать минимальное соотношение между неочищенным логом и всем логом, при достижении которого запускается очиститель. Сегмент считается неочищенным, если никогда не сжимался. По умолчанию соотношение между неочищенными сегментами и всем логом должно составлять не менее 0,5, чтобы в партиции снова можно было выполнить сжатие. Мы можем изменить это значение по умолчанию, используя
   Глава 10. Очистка сообщений 185 параметр log.cleaner.min.cleanable.dirty.ratio. Чем ниже это значение, тем чаще лог будет сжиматься и тем меньше пространства будут занимать устаревшие сообщения, так как устаревших сообщений может быть не больше, чем новых сообщений в несжатых сегментах. Например, при значении 0,5 используется вдвое больше необходимого пространства хранения, а при значении 0,2 дополнительные затраты на хранение составляют не более 25 %. Так почему бы просто не установить ноль или почти ноль? Сколько места можно было бы сэкономить! Мы так не делаем, поскольку сжатие логов требует много ресурсов на чтение всей партиции и брокерам пришлось бы только этим и заниматься. Однако соотношение неочищенных логов не единственный параметр, по которому очиститель логов определяет возможность сжатия. С помощью параметров max.compaction.lag.ms и min.compaction.lag.ms можно указать, через какой максимальный период сообщение должно быть сжато и как долго сообщения не должны подвергаться сжатию. Первый параметр используется для регулярного сжатия логов с небольшим потоком данных, тогда как второй гарантирует сохранение сообщений как минимум в течение определенного периода, позволяя потребителям прочитать все сообщения, даже если они тео­ ретически устарели. По умолчанию для минимального периода установлено значение 0 (log.cleaner.min.compaction.lag.ms=0), а максимальный период задают редко. Значение по умолчанию составляет около 300 миллионов лет (log.cleaner.max.compaction.lag .ms=9223372036854775807). Как показано на рис. 10.4, очиститель логов считает партицию подходящей для сжатия, если в ней есть несжатые сообщения старше максимального периода сжатия или если коэффициент неочищенных данных превышает порог и существуют несжатые сообщения старше минимальной задержки сжатия. Кроме того, коэффициент неочищенных данных служит еще одной цели: он определяет приоритет сжатия логов, если несколько партиций одновременно подходят для этой процедуры. Рис. 10.4. По умолчанию сжатие активизируется, когда количество неочищенных сегментов превышает количество очищенных. Часто полезно задавать максимальную задержку сжатия
   186 Часть III. Внутренние механизмы Kafka Вернемся к первоначальному вопросу: почему партиция из предыдущего примера еще не подверглась сжатию? Мы знаем, что оно уже должно было произойти, но очиститель логов (или механизм сжатия в Kafka) всегда игнорирует текущий сегмент. Причина относительно проста: сжатие логов изменяет сегменты или полностью перестраивает партицию. Применение сжатия к текущему сегменту, куда записываются новые сообщения, может нарушить согласованность. Поэтому нужно создать новый сегмент. Для этого можно использовать все тот же параметр segment.ms, но аналогичного эффекта можно достичь и с помощью параметра max.compaction.lag.ms. Он автоматически создает новый сегмент, если самое старое сообщение в сегменте старше заданного максимального времени задержки. Изменим конфигурацию топика products.prices-compaction и зададим для параметра max.compaction.lag.ms 60 секунд. $ kafka-configs.sh \ --alter \ --topic products.prices-compaction \ --add-config max.compaction.lag.ms=60000 \ --bootstrap-server localhost:9092 Однако этого недостаточно для создания нового сегмента. Как и в случае с segment.ms, сначала необходимо создать новое сообщение — только после этого будет выполнена проверка того, нужно ли создать новый сегмент. С помощью скрипта kafka-console-producer.sh создадим для партиции еще сообщение: $ kafka-console-producer.sh \ --topic products.prices-compaction \ --property parse.key=true \ --property key.separator=: \ --bootstrap-server localhost:9092 > energy drink:6 Заглянем в партицию и увидим, что создан новый сегмент: $ ls -1 ~/kafka/data/kafka1/products.prices-compaction-0 00000000000000000000.index 00000000000000000000.log 00000000000000000000.timeindex 00000000000000000004.index 00000000000000000004.log 00000000000000000004.snapshot 00000000000000000004.timeindex leader-epoch-checkpoint partition.metadata ПРИМЕЧАНИЕ Если бы мы действовали быстро, то могли бы заметить дополнительные файлы с расширением .deleted.
   Глава 10. Очистка сообщений 187 10.4.2. Как работает очиститель логов Самые внимательные читатели на данном этапе могли даже в реальном времени наблюдать работу очистителя логов, когда исходный сегмент со стартовым смещением 0 сначала помечался для удаления (добавлялось расширение .deleted), а затем удалялся. Так работает очиститель логов: сначала он анализирует несжатые сегменты и сохраняет последнее смещение для каждого ключа, затем начинает чтение с самого старого сообщения. Очиститель логов игнорирует устаревшие ключи или сообщения (те, для которых существует более новая запись) и записывает очищенные сообщения в новый сегмент. Когда тот заполняется, то заменяет собой старые. Именно по этой причине очистителю логов требуется дополнительное пространство для хранения максимум одного сегмента. Посмотрим на содержимое нашего лога: $ kafka-dump-log.sh \ --print-data-log \ --files ~/kafka/data/kafka1/products.prices-compaction-0/0[……]000.log Dumping ~/kafka/data/kafka1/products.prices-compaction-0/0[……]000.log Log starting offset: 0 […] | offset: 3 CreateTime: 1738488316569 keysize: 12 valuesize: 1 sequence: 0 headerKeys: [] key: energy drink payload: 3 Старые сообщения действительно были удалены, но сообщение 3 осталось, ведь новый сегмент не подвергался сжатию логов и полностью исключен из процесса. Быстро отправим еще два сообщения подряд и посмотрим, что будет, а потом немного подождем очистки логов: $ kafka-console-producer.sh \ --topic products.prices-compaction \ --property parse.key=true \ --property key.separator=: \ --bootstrap-server localhost:9092 > energy drink:5 > energy drink:6 При проверке партиции мы, как и ожидалось, снова видим изменения: $ ls -1 ~/kafka/data/kafka1/products.prices-compaction-0 00000000000000000000.index 00000000000000000000.log 00000000000000000000.timeindex 00000000000000000005.index 00000000000000000005.log 00000000000000000005.snapshot 00000000000000000005.timeindex leader-epoch-checkpoint partition.metadata
   188 Часть III. Внутренние механизмы Kafka С помощью скрипта kafka-dump-log.sh посмотрим содержимое двух сегментов: $ kafka-dump-log.sh \ --print-data-log \ --files ~/kafka/data/kafka1/products.prices-compaction-0/0[……]005.log Dumping ~/kafka/data/kafka1/products.prices-compaction-0/0[……]005.log Log starting offset: 5 […] | offset: 5 CreateTime: 1738489003023 keysize: 12 valuesize: 1 sequence: 0 headerKeys: [] key: energy drink payload: 5 […] | offset: 6 CreateTime: 1738489005780 keysize: 12 valuesize: 1 sequence: 0 headerKeys: [] key: energy drink payload: 6 Текущий сегмент содержит два сообщения, которые мы только что создали. Он не был сжат, иначе мы видели бы только последнее сообщение. Следовательно, новейший сегмент может содержать любое количество сообщений для ключа: $ kafka-dump-log.sh \ --print-data-log \ --files ~/kafka/data/kafka1/products.prices-compaction-0/0[……]000.log Dumping ~/kafka/data/kafka1/products.prices-compaction-0/0[……]000.log Log starting offset: 0 […] | offset: 4 CreateTime: 1738488639215 keysize: 12 valuesize: 1 sequence: 0 headerKeys: [] key: energy drink payload: 6 Сжатый сегмент теперь содержит ранее созданное сообщение 6 и никаких других сообщений для этого ключа. Даже если бы у нас были другие сегменты, во всех сжатых сегментах мы бы обнаружили не более одного сообщения на ключ. Мы не упомянули о еще одном важном моменте, касающемся сжатия логов: хотя в процессе создаются новые сегменты, смещения и порядок сообщений остаются неизменными. Это логично, так как при сжатии логов мы фактически только удаляем устаревшие записи и объединяем сегменты. Однако это свойство критически важно для обеспечения согласованности. На данном этапе возникает очевидный вопрос: что происходит, когда потребитель пытается прочитать несуществующие смещения или сообщения? Так может случиться даже в нашем небольшом примере, поскольку первая партиция по-прежнему имеет начальное смещение, несмотря на удаленные сообщения. В Kafka для этого есть простое решение. Если смещение не существует, то система просто переходит к следующему доступному сообщению. Таким образом, если потребитель запрашивает сообщение со смещением 0, то этот запрос эквивалентен запросу сообщения со смещением 1.
   Глава 10. Очистка сообщений 189 10.4.3. Tombstone Сжатие логов предоставляет еще одну возможность — не только перезапись устаревших данных, но и выборочное их удаление. Для этого мы создаем tombstone («надгробие»), генерируя сообщение с нагрузкой null. Сжатие логов затем, как обычно, удаляет устаревшие сообщения, связанные с этим ключом, но в этом случае само сообщение-tombstone будет удалено через некоторое время, так что в логе не останется сообщений для этого ключа. По умолчанию Kafka или очиститель логов удаляет tombstone через день, но это значение можно настроить с помощью параметров delete.retention.ms для топика и log.cleaner.delete.retention.ms для всего кластера. Сообщенияtombstone не удаляются немедленно, чтобы дать потребителям возможность обработать удаление в своих процессах. Kafka удаляет их, так как в противном случае в кластере было бы много партиций, состоящих только из сообщенийtombstone с разными неиспользуемыми ключами, а лог был бы слишком большим. Резюме Kafka предлагает два метода очистки сообщений: удаление по возрасту и размеру и сжатие логов на основе ключей. Сжатие логов сохраняет только последнее сообщение для каждого ключа и подходит, например, для обновления данных клиентов. Удаление по возрасту и размеру проще реализовать. Этот вариант подходит для разных сценариев, таких как соблюдение требований к конфиденциальности данных и управление журналами изменений. Оба метода имеют свои недостатки: удаление по возрасту и размеру проще, но работает неточно, а сжатие логов требует больше усилий, но обеспечивает точность данных. Удаление можно настраивать на основе размера партиции или времени ее существования, что позволяет гибко управлять хранилищем. Параметры сжатия логов определяют порог очистки сегментов и гарантируют удаление только устаревших данных. Сжатие логов сохраняет порядок сообщений и смещения, обеспечивая целостность данных. Если потребитель пытается получить несуществующие сообщения, то брокеры просто возвращают следующие доступные сообщения. Очиститель логов Kafka периодически проверяет устаревшие данные и удаляет ненужные сегменты.
   190 Часть III. Внутренние механизмы Kafka Регулярная ротация сегментов обеспечивает эффективное управление логами, предотвращая их разрастание и оптимизируя использование хранилища. Гибкий подход к очистке сообщений в Kafka позволяет адаптировать стратегии управления данными под конкретные сценарии. Сообщения-tombstone позволяют выборочно удалять данные при сжатии логов, повышая эффективность очистки. Сообщения-tombstone в конечном счете удаляются, и время их хранения можно настроить.
Часть IV Kafka в корпоративной среде В части IV мы рассмотрим интеграцию Apache Kafka в корпоративные системы, уделяя внимание как базовым, так и сложным темам. Мы обсудим практические вопросы применения Kafka в крупных системах: подключение внешних систем через Kafka Connect, обработку потоков данных с помощью Kafka Streams, внедрение стратегий управления и обеспечение высокой производительности с использованием эталонной архитектуры Kafka. Кроме того, мы исследуем ключевые области, такие как мониторинг, оповещения, аварийное восстановление и интеграция Kafka в современные архитектуры, обрабатывающие события в реальном времени. Изучая, как Kafka встраивается в рабочие процессы предприятия, и решая эти сложные задачи, организации могут в полной мере использовать потенциал платформы для масштабируемой интеграции, обработки и управления данными, обеспечивая высокую производительность, надежность и отказоустойчивость. В главе 11 мы рассмотрим Kafka Connect — мощный инструмент для интеграции внешних систем с Kafka через коннекторы для баз данных, файловых систем и других источников данных. В главе 12 обсудим потоковую обработку данных с помощью Kafka Streams. Мы подробно поговорим о преобразовании данных в реальном времени, анализе и SQL-подобных запросах для создания гибких архитектур обработки данных. Глава 13 посвящена стратегиям управления, в том числе управлению схемами, практикам безопасности и распределению ресурсов, необходимому для обеспечения надежности и безопасности Kafka в корпоративной среде. В главе 14 представлена эталонная архитектура Kafka с описанием моделей развертывания, требований к оборудованию и рекомендуемых инструментов для эффективного управления кластером.
   192 Часть IV. Kafka в корпоративной среде В главе 15 мы поговорим о мониторинге и оповещениях в Kafka, уделяя особое внимание стратегиям отслеживания ключевых метрик брокеров, клиентов и фреймворков (Kafka Streams, Kafka Connect), предназначенным для поддержания производительности и надежности. В главе 16 мы рассмотрим стратегии восстановления после аварий, а также отказоустойчивость, репликацию и механизмы восстановления для снижения рисков потери данных и сбоев систем. В главе 17 мы сравним Kafka с другими технологиями. Мы проанализируем его роль в современных архитектурах и сопоставим его с такими инструментами, как REST API и реляционные СУБД. Наконец, в главе 18 мы исследуем место Kafka в современных корпоративных архи­ тектурах, в частности интеграцию в Data Mesh и событийно-ориентированные системы, а также типичные ошибки и лучшие практики внедрения платформы.
11 Интеграция внешних систем с помощью Kafka Connect В этой главе 3 Интеграция Kafka с источниками и приемниками данных. 3 Настройка коннекторов и исполнителей для оптимального потока данных. 3 Изучение REST API для управления Kafka Connect. 3 Создание и изменение коннекторов. 3 Использование коннекторов Java Database Connectivity Source и Debezium. В большинстве случаев мы не внедряем Kafka изолированно, а подключаем его к таким системам, как базы данных и системы обмена сообщениями. Kafka используется в типовых сценариях. Например, мы передаем данные из подготовленных таблиц базы данных в конкретные топики или записываем данные из некоторых топиков в файл. Конечно, мы всегда можем вручную написать свои продюсеры и потребители для перемещения данных в Kafka и из него, но это долго, и в результате часто получаются ненадежные системы, которые сложно масштабировать. Даже если требования простые (например, мы хотим передавать данные между системами), возникает множество особых случаев, которые необходимо учитывать. Для подобных ситуаций можно использовать Kafka Connect.
   194 Часть IV. Kafka в корпоративной среде 11.1. Что такое Kafka Connect Kafka Connect — это фреймворк для записи данных из сторонних систем в Kafka и передачи данных из Kafka в другие системы (рис. 11.1). Коннекторы-­источники (source) — это, по сути, продюсеры, которые сначала считывают данные из внешних систем, а коннекторы-приемники (sink) — это потребители, которые записывают данные во внешние системы. Фреймворк Kafka Connect является частью Apache Kafka и, как сам Kafka, распространяется с открытым исходным кодом под лицензией Apache 2.0. Рис. 11.1. Kafka Connect интегрирует сторонние системы с Kafka Kafka Connect предлагает стандартизированный инструмент для перемещения данных — такой же масштабируемый, отказоустойчивый и ориентированный на целостность и производительность, как и сам Kafka. Существуют коннекторы для самых разных систем: базы данных (PostgreSQL, SQLite, MySQL), хранилища объектов в облачных средах (Amazon S3, Azure Blob Storage), протоколы обмена сообщениями (MQTT, JMS), системы хранения данных (Snowflake, Amazon Redshift). На момент написания книги только в Confluent Hub представлено около 200 коннекторов. Кроме того, коннекторы можно найти и в других источниках. Основная идея Kafka Connect — обеспечить бесшовную интеграцию с внешними источниками и приемниками данных, особенно с теми, которые можно представить в виде разделенных потоков. Это означает, что такое деление позволяет обеспечить эффективное масштабирование и обработку больших объемов данных. Каждый поток содержит связанные данные, которые должны сохранять целостность и не могут делиться дальше. В Kafka для этого топики делятся на партиции, чтобы данные распределялись между потребителями (рис. 11.2).
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 195 Рис. 11.2. Разделенный поток использует партиции для распараллеливания, а смещения — для запоминания последнего прочитанного сообщения В базах данных отдельные таблицы можно рассматривать как потоки данных, которые нельзя легко разделить без ущерба для гарантии порядка и других важных свойств (рис. 11.3). Рис. 11.3. Разделенные потоки в базах данных. Мы можем рассматривать таблицы как независимые потоки данных и использовать, например, столбцы с порядковыми номерами для отслеживания строк, которые уже были обработаны Kafka Connect В файловой системе, например, эти потоки данных могут соответствовать отдельным файлам. Для разделенных потоков требуется механизм отслеживания прочитанных и непрочитанных данных.
   196 Часть IV. Kafka в корпоративной среде Поскольку ожидается, что эти потоки данных будут очень длинными, то мы не можем просто запоминать, какие сообщения уже прочитаны. Как и в Kafka, мы полагаемся на смещения — позиции внутри потока данных. В базах данных можно использовать последовательные идентификаторы или временны́е метки, чтобы отслеживать время последнего изменения строки. В файлах можно отмечать последнюю прочитанную строку или позицию в байтах. 11.2. Кластер Kafka Connect: распределенный режим Распределенный режим позволяет использовать все преимущества Kafka Connect. В этом режиме фреймворк хранит все данные конфигурации и внутренние смещения в топиках Kafka, а мы запускаем необходимое количество экземпляров Kafka Connect. Все экземпляры фреймворка, имеющие одинаковый group.id, образуют кластер Kafka Connect. Они координируют действия и распределяют нагрузку между собой, используя протокол перебалансировки Kafka, который мы обсуждали в главе 9 (см. раздел 9.4). В окружениях разработки и некоторых отдельных сценариях (например, если требуется доступ к локальным файлам) целесообразнее использовать автономный режим. Пребывая в нем, Kafka Connect не сохраняет внутренние данные (такие как смещения) в Kafka и не хранит конфигурации. Процесс запускается локально на компьютере разработчика и останавливается после завершения работы. Этот режим подходит для тестирования Kafka Connect в средах разработки, но не обеспечивает масштабируемости и хранения смещений в Kafka. 11.2.1. Настройка кластера Kafka Connect Сначала мы настраиваем исполнитель (worker) Kafka Connect для распределенного режима. Исполнитель в этом фреймворке представляет собой отдельный процесс, работающий в составе кластера. Он отвечает за управление коннекторами и их задачами. Задача (task) в этом контексте — единица работы, например чтение данных из исходной партиции или запись в целевую систему. Каждая задача выполняется независимо и может распределяться между несколькими исполнителями для параллельной обработки. Исполнители Kafka Connect координируют действия, чтобы обеспечить эффективную, отказо­ устойчивую и масштабируемую обработку данных. Для начала создадим файл worker.properties со следующим содержимым: bootstrap.servers=localhost:9092 group.id=connect config.storage.topic=connect-config offset.storage.topic=connect-offset
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 197 status.storage.topic=connect-status key.converter=org.apache.kafka.connect.storage.StringConverter value.converter=org.apache.kafka.connect.storage.StringConverter plugin.path=<PATH-TO-YOUR-USER-DIRECTORY>/kafka/libs/ ПРИМЕЧАНИЕ Необходимо заменить заполнитель в свойстве plugin.path. Мы подробно рассмотрим эти параметры в разделе 11.4, а пока достаточно запустить Kafka Connect, используя такую команду: $ connect-distributed.sh worker.properties Для конфигурирования Kafka Connect предоставляет REST API, который по умолчанию работает на порте 8083. Если топики еще не существуют, то фреймворк автоматически создаст их. Рекомендуемый коэффициент репликации для топиков — 3. Если доступно меньше трех брокеров, то Kafka Connect не сможет запуститься. В тестовых окружениях коэффициент репликации можно уменьшить с помощью параметра config.storage.replication.factor (аналогично для других топиков). 11.2.2. Создание коннектора После запуска Kafka Connect бездействует, пока не будет настроен коннектор. Создание и управление коннекторами осуществляется через REST API. Допустим, у нас есть файл customers.txt с именами клиентов, который нужно импортировать в Kafka. Для этого можно использовать встроенный коннектор FileStreamSource. Не рекомендуется применять его в продакшене из-за ограниченности и слабой обработки ошибок. Для подобных задач лучше подходит коннектор FilePulse (GitHub: https://mng.bz/MDAQ). Для примера создадим файл source-connector.json: { } "name": "customers-source", "config": { "connector.class": "FileStreamSource", "tasks.max": "1", "file": "/tmp/customers.txt", "topic": "customers" } Здесь мы настраиваем коннектор с именем customers-source, который будет использовать класс FileStreamSource. Поскольку мы читаем только из одного файла, то масштабировать коннектор не получится, поэтому для параметра tasks.max мы устанавливаем 1. Мы хотим записывать данные из файла /tmp/ customers.txt в топик customers.
   198 Часть IV. Kafka в корпоративной среде СОВЕТ В продакшене и реальных условиях мы рекомендуем явно указывать классы конвертеров в конфигурациях коннекторов, а не использовать настройки исполнителя по умолчанию. С помощью следующей команды curl мы отправляем этот файл конфигурации через запрос POST в Kafka Connect: $ curl -X POST -H "Content-Type: application/json" \ --data @source-connector.json \ http://localhost:8083/connectors 11.2.3. Тестирование коннектора Мы можем запросить все коннекторы в кластере Kafka Connect по адресу http:// localhost:8083/connectors: $ curl http://localhost:8083/connectors ["customers-source"] Проверим текущий статус коннектора: $ curl http://localhost:8083/connectors/customers-source/status | jq { "name": "customers-source", "connector": { "state": "RUNNING", "worker_id": "127.0.0.1:8083" }, "tasks": [ { "id": 0, "state": "RUNNING", "worker_id": "127.0.0.1:8083" } ], "type": "source" } СОВЕТ Мы используем jq для форматирования вывода. Мы видим, что коннектор находится в состоянии RUNNING и работает на исполнителе 127.0.0.1:8083. Кроме того, отображаются все задачи. Мы определили только одну задачу, поэтому она выполняется на том же исполнителе, что и сам коннектор. Однако при детальном анализе логов Kafka Connect видно, что не все работает бесперебойно, как показывает конечная точка /status: WARN [customers-source|task-0] Couldn't find file /tmp/customers. txt for FileStreamSourceTask, sleeping to wait for it to be created (org.apache.kafka.connect.file.FileStreamSourceTask:119)
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 199 Запишем данные в файл /tmp/customers.txt в другом окне терминала: $ echo "Linus Torvalds" >> /tmp/customers.txt $ echo "Steve Jobs" >> /tmp/customers.txt Теперь мы можем запустить потребитель параллельно, чтобы наблюдать, что Kafka Connect записывает в топик customers: $ kafka-console-consumer.sh \ --topic customers \ --from-beginning \ --bootstrap-server localhost:9092 Если все работает корректно, то мы увидим двух клиентов: Linus Torvalds Steve Jobs Если эти данные не отображаются, то проверьте конечную точку /status или загляните в логи Kafka Connect, чтобы понять, что пошло не так. По мере записи новых данных в файл они будут появляться в потребителе. При перезапуске Kafka Connect коннектор не станет заново читать данные с начала файла — он запоминает позицию в файле и обрабатывает только новые записи. ПРЕДУПРЕЖДЕНИЕ Коннектор запоминает последнюю прочитанную строку, поэтому изменение существующих данных в файле не приведет к появлению новых сообщений в Kafka. Коннектор обнаруживает только добавляемые данные. 11.3. Масштабируемость и отказоустойчивость Kafka Connect Как Kafka Connect обеспечивает масштабирование коннекторов и отказо­ устойчивость? При использовании одного только коннектора FileStreamSource мало что получится, так как он просто читает файлы с локального диска и они не будут доступны на других исполнителях Kafka Connect. ПРЕДУПРЕЖДЕНИЕ Не рекомендуем использовать FileStreamSource в продакшене. Он служит в качестве примера и не считается полноценным и зрелым решением. Кластер Kafka Connect состоит из одного или нескольких исполнителей, работающих в распределенном режиме с одинаковым group.id (рис. 11.4). Эти исполнители координируются друг с другом по аналогии с потребителями в группе, используя протокол перебалансировки Kafka. При сбое исполнителя или при добавлении нового исполнители перераспределяют задачи между собой. В кластере Kafka Connect можно одновременно запускать несколько коннекторов. Например, можно читать одни данные из внешней базы и параллельно записывать другие в стороннюю систему.
   200 Часть IV. Kafka в корпоративной среде Рис. 11.4. Кластер Kafka Connect состоит из одного или нескольких исполнителей. Задачи распределяются между исполнителями, а Kafka Connect использует три топика для хранения своего внутреннего состояния Если мы хотим масштабировать кластер Kafka Connect, то достаточно добавить еще исполнители с тем же group.id. Сами коннекторы разделяют работу на задачи, которые выполняются независимо. Например, при импорте данных из 12 таблиц базы данных с параметром tasks.max = 4 каждая задача будет отвечать за три таблицы. Kafka Connect распределяет эти задачи между исполнителями, обеспечивая равномерную нагрузку и позволяя быстро перемещать большие объемы данных в Kafka и из него. СОВЕТ Есть много готовых коннекторов, и мы настоятельно рекомендуем использовать их. Если для вашего сценария нет подходящего коннектора, но структуры данных можно эффективно представить в виде разделенных потоков, то лучше создать свой коннектор Kafka Connect, чем пытаться воспроизвести этот функционал с помощью продюсеров и потребителей. 11.4. Конфигурация исполнителей В примере для распределенного режима мы уже рассматривали минималистичную конфигурацию для исполнителей Kafka Connect. В этом разделе мы обсудим доступные опции для настройки кластера Kafka Connect. В Kafka Connect есть два типа конфигураций.
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 201 Конфигурация исполнителя определяет параметры самого исполнителя, на котором работает Kafka Connect, и применяется ко всем его коннекторам. Конфигурация коннектора предоставляется через REST API и определяет параметры конкретных коннекторов, работающих на исполнителях. Конфигурация Kafka Connect распределяется между исполнителями через внутренний топик конфигурации. Посмотрим более внимательно на файл конфигурации worker.properties, который мы создали чуть раньше: bootstrap.servers=localhost:9092 group.id=connect config.storage.topic=connect-config offset.storage.topic=connect-offset status.storage.topic=connect-status key.converter=org.apache.kafka.connect.storage.StringConverter value.converter=org.apache.kafka.connect.storage.StringConverter plugin.path=<PATH-TO-YOUR-USER-DIRECTORY>/kafka/libs/ Как вы уже знаете, свойство bootstrap.servers определяет адрес кластера Kafka. Обратите внимание на параметр group.id. Может показаться, что он связан с группами потребителей в традиционном понимании, однако на самом деле он указывает имя или уникальный идентификатор для кластера (лучше было бы назвать его connect.id). Все исполнители Kafka Connect, использующие один и тот же group.id, входят в один кластер Kafka Connect. Различные топики storage используются для управления кластером Kafka Connect. Топик конфигурации (config.storage.topic) хранит конфигурации отдельных коннекторов, тогда как топик статусов (status.storage.topic) используется для мониторинга коннекторов и содержит информацию об отдельных задачах. Топик смещений (offset.storage.topic) хранит информацию о смещениях исходных коннекторов, например идентификатор записи в базе данных. Классы конвертеров, используемые для кодирования и декодирования сообщений, определяются параметрами key.converter и value.converter. Помимо StringConverter, существуют классы для Avro, Protobuf, JSON Schema, простого JSON и байтовых массивов. Конвертеры для Avro, Protobuf и JSON Schema требуют наличия Schema Registry, который мы подробно рассмотрим позже. СОВЕТ В конфигурации коннектора указывать конвертеры желательно, а в конфигурации исполнителя — обязательно. В продакшене и реальных условиях рекомендуем использовать JsonConverter или AvroConverter в качестве конвертеров значений по умолчанию вместо StringConverter. Некоторые коннекторы, например JSONConverter, предоставляют дополнительные свойства. Так, у JSONConverter есть параметр key/value.conver­ ter.schemas.enable , позволяющий включать или отключать запись схемы
   202 Часть IV. Kafka в корпоративной среде в каждом сообщении. Это приводит к тому, что сообщения получаются очень многословными, поэтому рекомендуем использовать конвертер с поддержкой реестра схем, например io.confluent.connect.avro.AvroConverter. Этот конвертер предоставляет дополнительные настройки, такие как адрес реестра схем key/value.converter.schema.registry.url. Последний параметр из нашего вводного примера — plugin.path, с помощью которого Kafka Connect ищет плагины для коннекторов, конвертеров или преобразований. Можно указывать несколько путей через запятую. Теперь рассмотрим другие важные параметры. Kafka Connect может автоматически создавать топики, если они не существуют. В зависимости от подхода к управлению кластером Kafka Connect или Kafka в целом, такое поведение может быть нежелательным. Например, если требуется полный контроль над топиками и управление ими в виде IaC (Infrastructure as Code — инфраструктура как код). Автоматическое создание топиков можно контролировать с помощью параметра topic.creation.enable, который включен по умолчанию. В главе 5 мы рассмотрели гарантии строго однократной доставки сообщений. Kafka Connect поддерживает эту гарантию для коннекторов-источников (продюсеров в Kafka). Для этого надо включить (enabled) параметр exactly.once.sour­ ce.support, который по умолчанию отключен. Если требуется изменить это поведение для существующего кластера Kafka Connect, то необходимо сделать промежуточное действие: сначала установить параметр в значение preparing, а затем в enabled. Поведение потребителей Kafka Connect тоже можно настраивать, используя различные параметры. С помощью session.timeout.ms можно указать, как долго исполнитель в кластере считается неактивным. Более низкое значение сократит потенциальное время простоя, но может привести к ненужным и длительным перебалансировкам групп потребителей. Параметр request.timeout.ms устанавливает максимальное время ожидания ответа. При превышении этого времени Kafka Connect считает запрос неудавшимся и повторяет его. Более короткий тайм-аут помогает быстрее реагировать на ошибки. Однако при низком качестве сети или временно высокой нагрузке на брокеры передача данных может занимать несколько секунд, особенно при больших объемах информации в одном запросе. Если задать для request.timeout.ms слишком низкое значение, то повторных попыток может быть слишком много. Возможно, запросы никогда не будут помечены как успешные, так что коннекторы не смогут обработать данные. Подобные проблемы могут возникнуть в связи с параметрами offset.flush.in­ terval.ms и offset.flush.timeout.ms, с помощью которых мы настраиваем, как часто Kafka Connect записывает сообщения на диск и сколько времени ему отводится на эту операцию.
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 203 СОВЕТ Если у нас много небольших сообщений, то запись на диск может занимать больше времени, поэтому рекомендуется выполнять ее чаще или увеличить тайм-аут. В противном случае есть риск создать бесконечный цикл, который можно определить по следующему сообщению в логах кластера Kafka Connect: Failed to flush, timed out while waiting for producer to flush outstanding x messages (Не удалось записать данные на диск: превышено время ожидания записи x сообщений продюсером). Кроме того, можно задать общие конфигурации для продюсеров и потребителей исполнителей, например fetch.max.bytes или fetch.max.wait.ms. Конфигурация коннектора позволяет переопределить общие параметры кластера Kafka Connect, что особенно полезно для упомянутых выше параметров. Параметр connector.client.config.override.policy указывает, какие параметры можно переопределить. Затем в конкретной конфигурации коннектора можно настроить параметры, используя префиксы producer.override и consumer.override, например consumer.override.fetch.max.bytes. Дополнительно можно использовать провайдеры конфигураций, чтобы применять переменные в отдельных параметрах. Это полезно с точки зрения безопасности. В config.providers задается список псевдонимов для провайдеров конфигураций, а в config.providers.<name>.class указывается соответствующий класс. Его можно настроить с помощью config.providers.<name>.param.<param>, что позволяет использовать один и тот же класс многократно с разными настройками под разными именами. Далее мы рассмотрим параметры конфигурации, характерные для коннекторов. Параметры, связанные с аутентификацией и авторизацией, мы обсудим в главе 13. Наконец, обратим внимание на параметр listeners, с помощью которого мы настраиваем порт, хост и протокол для входящих подключений REST API. Если не указать ничего, то по умолчанию Kafka Connect использует порт 8083 и протокол HTTP. 11.5. REST API Kafka Connect Изучив параметры конфигурации кластера Kafka Connect, перейдем к управлению кластером и коннекторами. Для этих целей Kafka Connect предоставляет REST API, с которым мы кратко ознакомились в начале главы. Важно отметить, что REST API Kafka Connect не предоставляет готовую аутен­тификацию и авторизацию. Можно настроить базовую аутентификацию, но тогда любой пользователь с доступом к кластеру получит все разрешения. В продакшене люди не должны обращаться к этому API. Лучше используйте для управления коннекторами конвейер непрерывной интеграции/непрерывного развертывания (CI/CD).
   204 Часть IV. Kafka в корпоративной среде 11.5.1. Состояние кластера Kafka Connect По корневому URL можно получить информацию о версии Kafka, используемой кластером Kafka Connect и исполнителями, в том числе идентификатор коммита Git и идентификатор подключенного кластера Kafka: $ curl http://localhost:8083 | jq { "version": "3.9.0", "commit": "a60e31147e6b01ee", "kafka_cluster_id": "y9uk3gYxSu6sGKfGRDgjGQ" } Обратите внимание, что информация упакована в объект JSON. REST API Kafka Connect полностью основан на JSON, то есть Kafka Connect ожидает JSON-объект в запросах и возвращает ответ в таком же виде. Идентификатор кластера относится исключительно к кластеру Kafka, используемому для управления нашим кластером Kafka Connect. Обычно это тот же кластер, в который продюсеры отправляют данные или из которого потребители получают данные. Мы можем проверить успешность загрузки всех необходимых плагинов Kafka Connect по пути /connector-plugins: $ curl http://localhost:8083/connector-plugins | jq [ { "class": "org.apache.kafka.connect.file.FileStreamSinkConnector", "type": "sink", "version": "3.9.0" }, { "class": "org.apache.kafka.connect.file.FileStreamSourceConnector", "type": "source", "version": "3.9.0" }, … ] Здесь мы видим, например, плагины FileStream, необходимые для коннекторов. Если требуемый плагин для коннектора отсутствует, то мы также получим соответствующее сообщение об ошибке при создании коннектора и список установленных плагинов. Список текущих запущенных коннекторов можно получить по пути /connectors: $ curl http://localhost:8083/connectors | jq [ "customers-source" ]
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 205 Мы можем внимательнее посмотреть на любой коннектор по пути /connectors/ <connector>: $ curl http://localhost:8083/connectors/customers-source | jq { "name": "customers-source", "config": { "connector.class": "FileStreamSource", "file": "/tmp/customers.txt", "tasks.max": "1", "name": "customers-source", "topic": "customers" }, "tasks": [ { "connector": "customers-source", "task": 0 } ], "type": "source" } В ответ мы получаем информацию о типе (source) и конфигурации (config) коннектора, а также список задач. Эту информацию можно просмотреть для всех коннекторов, вызвав /connectors?expand=info. Если нужна только текущая конфигурация, то ее можно просмотреть с помощью /connectors/<name>/config. Подробная информация о текущем статусе отдельных задач и исполнителях, на которых они выполняются, доступна по пути /connectors/<connector>/status: $ curl http://localhost:8083/connectors/customers-source/status | jq { "name": "customers-source", "connector": { "state": "RUNNING", "worker_id": "127.0.0.1:8083" }, "tasks": [ { "id": 0, "state": "RUNNING", "worker_id": "127.0.0.1:8083" } ], "type": "source" } Исполнители идентифицируются по адресу, по которому они доступны. Кроме того, мы получаем информацию о состоянии каждой задачи (running — выполняется, paused — приостановлено, failed — сбой), в том числе описания
   206 Часть IV. Kafka в корпоративной среде ошибок. Аналогично информации о коннекторах мы можем просмотреть статус всех коннекторов одновременно по пути /connectors?expand=status. Подробная информация о задачах коннектора доступна по пути /connectors/<connector>/ tasks, а статус отдельной задачи — по пути /connectors/<connector>/tasks/<id>/ status. Еще одна полезная функция Kafka Connect — возможность просмотра топиков, фактически используемых коннектором, по пути /connectors/<connector>/ topics: $ curl http://localhost:8083/connectors/customers-source/topics | jq { "customers-source": { "topics": [ "customers" ] } } Мы можем сбросить статус с помощью PUT /connectors/<connector>/topics/ reset. Это бывает удобно после проведения работ по обслуживанию, но ни на что не влияет. 11.5.2. Создание, изменение и удаление коннекторов Мы уже знаем, какие детали о коннекторах можно запросить через REST API, а теперь рассмотрим, как создавать, изменять и удалять коннекторы, настраивая подключение к внешним источникам данных. Для начала удалим коннектор customers-source: $ curl -X DELETE http://localhost:8083/connectors/customers-source Если все хорошо, то в ответ мы получим код HTTP 204. Если коннектор не существует, то мы получим код HTTP 404. Создадим коннектор заново: вызовем POST /connectors и передадим коннектор в виде JSON-объекта, который состоит только из имени коннектора и его конфигурации: $ cat source-connector.json { "name": "customers-source", "config": { "connector.class": "FileStreamSource", "tasks.max": "1", "file": "/tmp/customers.txt", "topic": "customers" } } $ curl -X POST -H "Content-Type: application/json" \ --data @source-connector.json \ http://localhost:8083/connectors
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 207 Если коннектор успешно создан, то мы получаем такой же ответ, как и при вызове GET /connectors/<connector>. Если мы попытаемся создать существующий коннектор, то система вернет сообщение об ошибке. Конфигурацию созданного коннектора можно изменять. Например, если исходный файл для коннектора должен теперь быть /tmp/new_customers.txt вместо /tmp/customers.txt, то мы легко можем скорректировать настройки, изменив параметр file. Для этой цели создадим файл source-connector-config.json: { } "connector.class": "FileStreamSource", "tasks.max": "1", "file": "/tmp/new_customers.txt", "topic": "customers" Конфигурацию можно менять с помощью PUT по пути /connectors/<connector>/ config. Ответ будет таким же, как при первом создании коннектора: $ curl -X PUT -H "Content-Type: application/json" \ --data @source-connector-config.json \ http://localhost:8083/connectors/customers-source/config Коннекторы можно приостанавливать, например, на время обслуживания сторонних систем, к которым они подключаются, чтобы не произошел сбой или в Kafka случайно не записались какие-то данные: $ curl -X PUT http://localhost:8083/connectors/customers-source/pause $ curl http://localhost:8083/connectors/customers-source/status | jq { "name": "customers-source", "connector": { "state": "PAUSED", "worker_id": "127.0.0.1:8083" }, "tasks": [ { "id": 0, "state": "PAUSED ", "worker_id": "127.0.0.1:8083" } ], "type": "source" } Затем коннектор можно запустить заново: $ curl -X PUT http://localhost:8083/connectors/customers-source/resume У коннектора и его задач могут возникать сбои. Kafka Connect справляется с большинством временных ошибок, используя механизмы повторных попыток, но иногда этого недостаточно — например, при длительных проблемах с сетью
   208 Часть IV. Kafka в корпоративной среде или из-за проблем со структурой данных сообщений. В таких случаях необходимо устранить проблему и перезапустить коннектор и его задачи через POST: $ curl -X POST http://localhost:8083/connectors/customers-source/\ restart?includeTasks=true&onlyFailed=true С помощью флагов includeTasks и onlyFailed мы гарантируем, что перезапускается не только сам коннектор, но и все задачи, находящиеся в состоянии сбоя. Кроме того, можно перезапустить только отдельные задачи через POST /con­ nectors/<connector>/tasks/<taskId>/restart. 11.6. Конфигурация коннектора В этом разделе мы рассмотрим параметры конфигурации, доступные для коннекторов. Еще раз вернемся к нашему коннектору customers-source: { } "name": "customers-source", "config": { "connector.class": "FileStreamSource", "tasks.max": "1", "file": "/tmp/customers.txt", "topic": "customers" } Пожалуй, самый важный и часто самый сложный параметр для настройки коннектора — его имя (name). Оно используется для идентификации коннектора и является частью пути в REST API Kafka Connect. Имя — единственный параметр метаданных коннектора, поэтому очень важно использовать надежную схему именования, особенно когда кластер Kafka Connect управляет большим количеством коннекторов. 11.6.1. Общая конфигурация коннектора Класс коннектора (connector.class) определяет его тип. Классы коннекторов часто имеют специфические параметры, например параметр file для коннектора FileStream-Source. Другие классы коннекторов: org.apache.kafka.connect.mirror.MirrorSourceConnector; io.confluent.connect.jdbc.JdbcSourceConnector; io.confluent.connect.jdbc.JdbcSinkConnector; io.aiven.connect.jdbc.JdbcSourceConnector; io.aiven.connect.jdbc.JdbcSinkConnector; io.debezium.connector.postgresql.PostgresConnector.
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 209 Коннекторы бывают двух типов. Коннекторы-источники предназначены для чтения данных из внешних систем и отправки их в Kafka, тогда как коннекторыприемники потребляют данные из Kafka и записывают их во внешние системы. MirrorMaker — исключение, поскольку реплицирует данные из одного кластера Kafka в другой. Параметр tasks.max определяет максимальное количество задач, на которые может разделяться нагрузка коннектора, что обеспечивает повышение уровня параллелизации. Однако фактическое количество задач часто ограничивается самим коннектором или структурой обрабатываемых данных, например количеством партиций в исходных топиках. Если установить tasks.max выше этого предела, то параллелизация не увеличится. У коннекторов-приемников есть параметр topics, который определяет список топиков для потребления. Параметр topic.regex позволяет задавать регулярное выражение для выбора топиков. Это удобно, если нужно читать сообщения из множества топиков или если список топиков динамически меняется. Для корректной работы требуется единая схема именования топиков, поэтому очень важно продумывать имена и соблюдать соглашения об именовании. Для коннекторов-приемников (потребляющих данные из Kafka) по умолчанию создается группа потребителей с префиксом connect и именем коннектора. Однако это значение можно переопределить с помощью параметра consumer.group.id. (Необходимо разрешить переопределение этого параметра в конфигурации исполнителя.) В конфигурации коннектора можно с помощью параметра exactly.once.sup­ port указать, требуется ли в кластере Kafka Connect поддержка семантики строго однократной доставки (required), или она необязательна (requested). По умолчанию выбран второй вариант. Если поддержка exactly-once обязательна, но не обеспечена кластером Kafka Connect (настройка исполнителя exactly.once.source.support), то произойдет сбой коннектора. Кроме того, Kafka Connect поддерживает преобразование отдельных сообщений перед их записью из внешних систем в Kafka или из него во внешние системы. Мы рассмотрим несколько таких примеров в разделе 11.7. 11.6.2. Обработка ошибок в Kafka Connect Как и любое приложение, Kafka Connect может столкнуться с проблемами при обработке данных. Наиболее распространенные причины ошибок — вре́менные сетевые сбои или некорректные данные. Чтобы не пришлось останавливать весь коннектор из-за незначительных сбоев, можно указать различные настройки обработки ошибок в зависимости от сценария. По умолчанию Kafka Connect останавливает любой коннектор при возникновении ошибки, то есть отказоустойчивость нулевая. Это поведение можно
   210 Часть IV. Kafka в корпоративной среде контролировать с помощью параметра errors.tolerance. Если задать значение all вместо none, то проблемные записи просто пропускаются. При настройке этого параметра рекомендуется также установить для errors.log.enable значение true, иначе в лог будут записываться только ошибки, которые нельзя пропускать, и мы не сможем проанализировать проблемную операцию и, возможно, исправить ошибку вручную. СОВЕТ В зависимости от сценария рекомендуется настроить оповещения и уведомления, если мы велели пропускать проблемные записи. Параметр errors.log.include.messages позволяет указать, следует ли вносить саму проблемную запись в журнал ошибок вместе с сообщением об ошибке. По умолчанию эта функция отключена, поскольку такая запись может содержать конфиденциальные данные, которые не должны бесконтрольно попадать в лог ошибок. Для коннектора-приемника сообщение можно отправлять в очередь недоставленных сообщений (dead letter queue), которую следует предварительно настроить с помощью errors.deadletterqueue.topic.name. Помимо некорректных записей, коннектор может завершиться с ошибкой в случае возникновения проблем с доступом к внешним системам. В таких ситуа­ циях можно настроить повторные попытки с помощью параметров errors.re­try.timeout и errors.retry.delay.max.ms. Параметр errors.retry.timeout задает максимальное время в миллисекундах для повторного выполнения неудачных операций. По умолчанию установлено 0, то есть повторные попытки не предпринимаются. Параметр errors.retry.delay.max.ms определяет максимальный интервал между повторными попытками. Отдельные коннекторы могут иметь собственные параметры для управления повторными попытками, например коннекторы-источники и приемники JDBC. Для настройки повторных попыток подключения к базе данных можно использовать параметр connection.attempts, который указывает количество попыток, и connection.backoff.ms, задающий интервал между ними. По умолчанию JDBC-коннектор выполняет три попытки с интервалом 10 секунд, прежде чем подключение к базе данных завершается сбоем. СОВЕТ Рекомендуется выделять JDBC-коннекторам больше времени для восстановления подключения к базе данных в случае сбоев, так как стандартной настройки 30 секунд может быть недостаточно во время технического обслуживания базы данных или временных проблем с сетью. Увеличение этого интервала помогает избежать длительных простоев, поскольку после сбоя коннектора требуется (вручную) перезапустить его задачи для возобновления работы. Кроме того, коннектор-приемник JDBC позволяет повторять операции в случае сбоев. Количество повторных попыток перед сбоем или пропуском записи, а также интервал между попытками можно настроить с помощью параметров max.retries
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 211 и retry.backoff.ms. Это полезно, например, если соответствующие записи в базе данных временно заблокированы. По умолчанию коннектор-приемник JDBC пытается записать данные в базу данных десять раз с интервалом 3 секунды между попытками. 11.7. Преобразования отдельных сообщений Просто переместить данные из точки А в точку Б часто недостаточно, особенно когда данные требуют преобразования, обработки и маршрутизации по разным направлениям. Хотя Kafka Connect не является полноценным инструментом для извлечения, преобразования и загрузки (extract, transform, load, ETL), он поддерживает простые преобразования с помощью механизма преобразований отдельных сообщений (Single Message Transformations, SMT). SMT работают как для приемников, так и для источников, и могут использоваться для переименования полей, маскировки данных и даже переноса данных из значения сообщения в ключ. На рис. 11.5 показано, где происходит преобразование. В коннекторе-источнике сам коннектор извлекает данные из внешнего источника и преобразует их во внутреннюю структуру данных Kafka Connect. Мы можем применить любое количество преобразований перед тем, как данные будут конвертированы в целевой формат (например, JSON) и записаны в топик Kafka. Для коннектора-источника процесс происходит в обратном направлении: сначала данные конвертируются из формата топика во внутреннюю структуру Kafka Connect, затем применяются SMT, и наконец, данные записываются в коннектор-приемник с учетом класса коннектора. Рис. 11.5. Преобразования отдельных сообщений применимы для обоих типов коннекторов
   212 Часть IV. Kafka в корпоративной среде ПРЕДУПРЕЖДЕНИЕ Не злоупотребляйте SMT. Это не полноценный ETL-инстру­ мент. Для сложных преобразований используйте Kafka Streams или другие фреймворки потоковой обработки. Рассмотрим несколько примеров SMT. Для использования SMT необходимо настроить шаги преобразований в конфигурации коннектора (JSON-файле, отправляемом в REST API Kafka Connect): { } "config": { […] "transforms": "mySMT1,mySMT2,…", "transforms.mySMT1.type": "java.class.name", "transforms.mySMT1.…": "some-config", "transforms.mySMT2.type": "java.class.name2", "transforms.mySMT2.…": "some-config2", […] } Для начала зададим список необходимых преобразований (transforms). Имена можно выбирать произвольно (mySMT1, mySMT2). Затем для каждого преобразования зададим по крайней мере имя класса Java (type: java.class.name) для SMT и при необходимости дополнительные конфигурации. Преобразования будут выполняться по порядку, как указано в списке с разделителем запятой. Посмотрим на другие доступные SMT. С помощью ReplaceField можно переименовывать или удалять поля. Следующее преобразование, например, удаляет поле dropme и переименовывает поле foo в bar: […] "transforms": "rename", "transforms.rename.type": "org.apache.kafka.connect.transforms.ReplaceField$Value", "transforms.rename.exclude": "dropme", "transforms.rename.renames": "foo:bar", […] Многие коннекторы-источники устанавливают только значение сообщения, но не ключ, поэтому мы можем использовать преобразование ValueToKey для извлечения данных из значения в ключ. Проблема в том, что он становится JSON-объектом, содержащим только одну пару «ключ — значение». Например, если у нас есть поле product_id и мы применяем преобразование ValueToKey, чтобы это поле можно было использовать как ключ, то в итоге получим ключ {product_id: <ID of the product>}. С помощью преобразования ExtractField можно извлечь конкретное поле в ключ, так что итоговым ключом станет простая строка, содержащая идентификатор продукта:
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 213 […] "transforms": "moveKey,extractKey", "transforms.moveKey.type": "org.apache.kafka.connect.transforms.ValueToKey", "transforms.moveKey.fields": "product_id", "transforms.extractKey.type": "org.apache.kafka.connect.transforms.ExtractField$Key", "transforms.extractKey.field": "product_id", […] Преобразование MaskField позволяет устанавливать в полях значения null, чтобы конфиденциальная информация не отправлялась в систему. Kafka Connect предоставляет и другие готовые SMT, а некоторые коннекторы (например, Debezium) расширяют этот список. Доступные SMT описаны в документации Apache Kafka: https://mng.bz/rK7B. 11.8. Пример Kafka Connect: коннектор-источник JDBC Выше мы обсудили общие параметры конфигурации коннекторов и специфические настройки JDBC-коннекторов, а сейчас применим знания о Kafka Connect на практике, используя коннектор-источник JDBC (JDBC Source Connector) в качестве образца. Вернемся к нашему примеру с клиентами. Предположим, что раньше все данные клиентов хранились в базе данных. Мы хотим, чтобы они были доступны в Kafka. Как показано на рис. 11.6, мы с помощью коннектора-источника JDBC переносим данные из таблицы customers в топик sqlite_customers. Рис. 11.6. Перенос данных о клиентах из реляционной базы данных в Kafka 11.8.1. Подготовка коннектора-источника JDBC Сначала необходимо подготовить простую базу данных SQLite. Ее можно скачать напрямую с официального сайта или установить через менеджер пакетов.
   214 Часть IV. Kafka в корпоративной среде Инициализируем базу данных SQLite с именем customers.db и создадим в ней таблицу: $ sqlite3 customers.db CREATE TABLE customers( В SQLite id INTEGER PRIMARY KEY AUTOINCREMENT, firstname VARCHAR, lastname VARCHAR, birthdate DATE, lastupdated TIMESTAMP DEFAULT current_timestamp ); В таблице customers хранятся все данные клиентов. Помимо первичного ключа, в таблице есть столбцы с именем, фамилией и датой рождения. Добавим несколько записей в базу, чтобы у нас были данные, которые можно отправить в Kafka: INSERT INTO customers(id, firstname, lastname, birthdate) VALUES (1, 'Steve', 'Jobs', '1955-02-24'), В SQLite (2, 'Bill', 'Gates', '1955-10-28'); Установим коннектор JDBC, который можно скачать с Confluent Hub. Он должен находиться по пути plugin.path, который мы определили в worker.properties. Перезапускаем кластер Kafka Connect. 11.8.2. Настройка коннектора-источника JDBC Создадим конфигурацию для коннектора и сохраним ее как customers-jdbc- source-connector.json: { } "name": "customers-jdbc-source-connector", "config": { "connector.class": "io.confluent.connect.jdbc.JdbcSourceConnector", "connection.url": "jdbc:sqlite:/path/to/customers.db", "value.converter": "org.apache.kafka.connect.json.JsonConverter", "value.converter.schemas.enable": false, "table.types": "TABLE", "table.whitelist": "customers", "topic.prefix": "sqlite_", "mode": "incrementing", "incrementing.column.name": "id", "batch.size": 1000 } Помимо знакомых полей, таких как name, connector.class и batch.size, мы видим новые параметры, характерные для коннектора-источника JDBC. Параметр connection.url выполняет несколько функций: определяет тип подключения (jdbc), тип базы данных (sqlite) и расположение базы данных (/path/to/
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 215 customers.db), которое в нашем случае представляет собой просто путь к файлу SQLite. Мы переопределяем value.converter из worker.properties и указываем org.apache.kafka.connect.json.JsonConverter. Кроме того, отключаем использование схем, так как хотим работать с простым JSON. Параметр table.types задает тип объектов базы данных, которые будут запрашиваться. Помимо TABLE, у нас есть удобная опция VIEW. СОВЕТ Хотя TABLE является значением по умолчанию для table.types, часто лучше явно задавать такие параметры, чтобы избежать неприятных сюрпризов, которые могут возникнуть после обновления версий. Параметр table.whitelist указывает, какие таблицы следует читать, а tab­ le.black­list — какие не читать. В нашем примере мы хотим выгружать в Kafka только таблицу customers. Имя топика Kafka, в который производится запись, формируется из значения topic.prefix и имени таблицы базы данных. В нашем случае префикс sqlite_ и таблица customers образуют топик sqlite_customers. Этот параметр выполняет две важные функции: во-первых, позволяет легко назначать топики коннектору, а во-вторых — предотвращает случайную запись в существующий топик, имя которого совпадает с названием таблицы базы данных. Эта защита особенно полезна, когда мы исключаем таблицы с помощью параметра table.blacklist, поскольку помогает избежать ошибок в ситуациях, когда мы не вполне уверены, какие именно таблицы обрабатываются. Оставшиеся параметры — mode и incrementing.column.name — мы обсудим чуть позже. 11.8.3. Источник-коннектор JDBC Перед созданием коннектора можно открыть другое окно терминала, запустить kafka-console-consumer.sh и считать сообщения из целевого топика sqlite_ customers коннектора: $ kafka-console-consumer.sh --bootstrap-server localhost:9092 \ --topic sqlite_customers --from-beginning [..]sqlite_customers=UNKNOWN_TOPIC_OR_PARTITION[..] Как и ожидалось, топик еще не существует, но автоматически создается по умолчанию, когда начинается чтение сообщений. Создадим коннектор: $ curl -X POST -H "Content-Type: application/json" \ --data @customers-jdbc-source-connector.json \ http://localhost:8083/connectors
   216 Часть IV. Kafka в корпоративной среде После успешной настройки коннектора потребитель kafka-console-consumer.sh начнет читать сообщения: {"id":1,"firstname":"Steve","lastname":"Jobs", birthdate:"1955-02-24"} {"id":2,"firstname":"Bill","lastname":"Gates", birthdate:"1955-10-28"} Как мы видим, Kafka Connect автоматически преобразовал записи базы данных в JSON, используя названия столбцов в качестве ключей объекта. Посмотрим, что будет, если мы добавим новую строку в таблицу базы — намеренно некорректную дату рождения: INSERT INTO customers(id, firstname, lastname, brithdate) VALUES (3, "Linus", "Torvalds", "1869-12-28"); В SQLite В потребителе kafka-console-consumer.sh новая запись из топика Kafka отображается через несколько секунд: {"id":3,"firstname":"Linus","lastname":"Torvalds", birthdate:"1868-12-28"} Мы хотим изменить имеющуюся запись в базе данных: UPDATE customers SET birthdate="1969-12-28" where id=3;.... В SQLite В этот раз никакие новые сообщения не появляются, поскольку у коннектора выбран режим добавления записей mode=incrementing и столбец добавления incrementing.column.name=ID. При такой конфигурации коннектор отправляет новое сообщение в Kafka только при появлении нового ID, то есть полностью новой записи в таблице базы данных. Kafka Connect запрашивает таблицу базы данных на наличие новых записей, основываясь на последнем сохраненном смещении. Например, если последней обработанной записью была та, ID которой равен 3, то коннектор отправит запрос вида: SELECT * FROM customers WHERE id > 3; Можно было запустить коннектор в режиме отслеживания метки времени mode=timestamp и задать timestamp.column.name=lastupdated. В этом случае коннектор обнаружил бы изменение и записал бы в Kafka сообщение с новой корректной меткой времени. Режимы incrementing и timestamp можно сочетать (timestamp+incrementing). Важно гарантировать, что соответствующие столбцы имеют монотонно возрастающие значения. ПРЕДУПРЕЖДЕНИЕ ID должен строго увеличиваться, поскольку Kafka использует его как возрастающее смещение. Если новой записи будет присвоен меньший ID (например, при удалении записи и повторном использовании ее идентификатора), то коннектор не распознает ее как новую и не отправит в Kafka. Кроме того, коннектор не фиксирует удаление записей, так как это действие также не генерирует новый идентификатор.
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 217 Однако у этого режима есть свои ограничения, поскольку не каждая таблица содержит отдельный столбец с меткой времени. Например, если в таблице базы данных хранятся только адреса клиентов и клиент переезжает в другой город, это изменение не будет отражено в Kafka. Более серьезная проблема заключается в том, что даже при наличии столбца с меткой времени нельзя гарантировать полную передачу всех данных в Kafka. Допустим, в таблице одновременно (согласно записи в столбце метки времени) произошли два изменения. Kafka Connect прочитает первое, но второе может пропустить, поскольку все изменения до указанной метки времени предположительно прочитаны. Вероятность такого сценария зависит от точности используемой метки времени, но риск все же есть. СОВЕТ Единственный способ гарантировать фиксацию всех изменений с помощью коннектора-источника JDBC — использовать режим массовой загрузки. В нем вся таблица полностью загружается в Kafka при каждом обновлении, но это очень неэффективный подход, который мы не можем рекомендовать. Проблема решается использованием систем отслеживания изменений данных, таких как Debezium, которые мы рассмотрим в следующем разделе. 11.9. Пример Kafka Connect: коннектор отслеживания изменений данных В этом разделе мы рассмотрим практические нюансы настройки коннектора для отслеживания изменений данных (change data capture, CDC), а именно кон­ нектора Debezium для PostgreSQL. CDC — это подход, при котором отслеживаются все изменения в источнике данных (например, в таблице базы данных), а не сами данные. Технические варианты реализации зависят от конкретной сторонней системы и варьируются от триггеров событий до логов. Для реляционных баз данных можно отслеживать лог транзакций, куда записываются все изменения. 11.9.1. Подготовка коннектора Debezium для PostgreSQL Перед настройкой коннектора необходимо подготовить базу данных PostgreSQL. Ссылки для загрузки и инструкции по установке сервера PostgreSQL доступны для всех распространенных операционных систем на официальном сайте PostgreSQL. После установки сервера PostgreSQL необходимо задать для параметра wal_level в файле postgresql.conf значение logical, иначе мы не сможем отслеживать нужные изменения: # Linux file path: /etc/postgresql/<version>/main/postgresql.conf # Windows file path: C://Programs/PostgreSQL/<version>/data/postgresql.conf wal_level = logical
   218 Часть IV. Kafka в корпоративной среде Перезапустим сервер или сервис PostgreSQL: $ sudo service postgresql restart ПРИМЕЧАНИЕ Публичные облака, такие как Amazon Web Services (AWS), Microsoft Azure и Google Cloud, также предоставляют поддержку указанных функций. После настройки и запуска сервера PostgreSQL мы создаем базу данных с именем customer и пользователя базы данных customers_user, которого назначаем владельцем базы данных customer. Сначала запускаем интерактивный CLI psql: Запуск psql CLI Создание базы данных customers в Linux sudo -u postgres psql CREATE DATABASE customers; Создание роли customers_user с паролем CREATE ROLE customers_user REPLICATION LOGIN supersecret и привилегией REPLICATION PASSWORD 'supersecret'; ALTER DATABASE customers OWNER TO customers_user; Назначение customers_user \q Выход из psql CLI владельцем базы данных customers Пользователю customers_user нужно дать право REPLICATION, чтобы у него были необходимые разрешения для коннектора CDC. Снова подключаемся к серверу PostgreSQL, на этот раз как пользователь customers_user. Напрямую подключаемся к базе данных customers, создаем таблицу customers и заполняем ее данными: psql -h localhost -U customers_user -W -d customers CREATE TABLE customers ( id SERIAL PRIMARY KEY, firstname VARCHAR, lastname VARCHAR, Создание таблицы birthdate VARCHAR клиентов ); Подключение к базе данных как customers_user INSERT INTO customers (id, firstname, lastname, birthdate) VALUES (1, 'Steve', 'Jobs', '1955-02-24'), Вставка данных (2, 'Bill', 'Gates', '1955-10-28'); в таблицу клиентов \q Выход из psql CLI У нас все еще нет самого важного — коннектора. Инструкции по его скачиванию и настройке можно найти в документации Debezium, в разделе Deployment. Процесс аналогичен установке JDBC-коннектора. 11.9.2. Настройка коннектора Debezium для PostgreSQL Затем мы создаем конфигурацию коннектора customers_debezium_connector.json: { "name": "customers_debezium_connector", "config": { "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
   Глава 11. Интеграция внешних систем с помощью Kafka Connect } } 219 "database.hostname": "localhost", "database.port": "5432", "database.user": "customers_user", "database.password": "supersecret", "database.dbname" : "customers", "value.converter": "org.apache.kafka.connect.json.JsonConverter", "value.converter.schemas.enable": false, "plugin.name": "pgoutput", "publication.autocreate.mode": "filtered", "topic.prefix": "debezium", "table.include.list": "public.customers" Большинство параметров уже знакомы нам по коннектору-источнику JDBC, по крайней мере у них те же названия. Все настройки для подключения к базе данных находятся в различных параметрах database. Мы определяем префикс топика Kafka так же, как и в коннекторе-источнике JDBC. Но параметр для списка разрешенных таблиц в коннекторе Debezium для PostgreSQL называется tab­ le.include.list. С настройками для plugin.name и publication.autocreate.mode мы прежде дела не имели. Оба параметра определяют, как данные извлекаются из базы данных PostgreSQL. 11.9.3. Тестирование коннектора Debezium для PostgreSQL Создадим коннектор: $ curl -X POST -H "Content-Type: application/json" \ --data @customers_debezium_connector.json \ http://localhost:8083/connectors Запустим потребитель kafka-console-consumer.sh: $ kafka-console-consumer.sh --bootstrap-server localhost:9092 \ --topic debezium.public.customers --from-beginning { "before":null, "after":{"id":1,"firstname":"Steve","lastname":"Jobs","birthdate": "1955-02-24"}, "source":{"version":"2.1.2.Final","connector":"postgresql","name": "debezium","ts_ms":1738493972052,"snapshot":"first","db":"customers", "sequence":"[null,\"24287336\"]","schema":"public","table":"customers", "txId":739,"lsn":24287336,"xmin":null}, "op":"r", "ts_ms":1738493972146, "transaction":null } ПРИМЕЧАНИЕ Имя топика debezium.public.customers образовано так: свойство to­ pic.pre­fix (debezium), точка и имя таблицы из списка table.include.list (public.customers) в конфигурации коннектора.
   220 Часть IV. Kafka в корпоративной среде Странно. Выходные данные содержат гораздо больше сведений, чем ожидалось. Debezium добавляет в каждое сообщение дополнительную информацию, показывающую, откуда поступили данные. Рассмотрим поля. Самое важное из них — after (после) — содержит фактические данные измененной строки таблицы. Поле before (до) может описывать предыдущее состояние строки, если это настроено в Debezium и базе данных, хотя такая необходимость возникает редко. Поле source (источник) указывает, откуда получены данные, и предоставляет технические детали. Наконец, op — это операция, запустившая сообщение: c озна­ чает create («создать»), u — update («обновить»), а d — delete («удалить»). Здесь для op указано r, то есть сообщение не сгенерировано операцией в базе данных, а импортировано как начальный снимок таблицы с помощью Debezium. Наконец, ts_ms содержит метку времени операции, а transaction имеет значение true, если операция выполнялась в рамках транзакции базы данных. Добавим еще запись: INSERT INTO customers (id, firstname, lastname, birthdate) VALUES (3, 'Linus', 'Torvalds', '1869-12-28'); Разумеется, эта запись отображается в потребителе kafka-console-consumer.sh, как это было с коннектором-источником JDBC: { } "before":null, "after":{"id":3,"firstname":"Linus","lastname":"Torvalds", "birthdate":"1869-12-28"}, "source":{…}, "op":"c", "ts_ms":1738494107344, "transaction":null Как видите, для op теперь указано c, то есть сообщение было активизировано созданием строки. На этот раз мы хотим исправить запись в таблице: UPDATE customers SET birthdate='1968-12-28' where id=3; В отличие от примера с коннектором-источником JDBC, при использовании коннектора Debezium для PostgreSQL мы видим изменения в Kafka. Изменение было запущено оператором UPDATE, и мы видим, что для op указано u: { } "before":null, "after":{"id":3,"firstname":"Linus","lastname":"Torvalds", "birthdate":"1969-12-28"}, "source":{…}, "op":"u","ts_ms":1738494350114, "transaction":null СОВЕТ В большинстве случаев мы не меняем в Kafka запись целиком. С помощью преобразования ExtractNewRecordState Kafka Connect записывает в Kafka только значение after.
   Глава 11. Интеграция внешних систем с помощью Kafka Connect 221 Резюме Kafka Connect — это фреймворк, предназначенный для масштабируемой и надежной интеграции данных между Kafka и другими системами. Он позволяет легко перемещать большие объемы данных в Kafka и из него с помощью коннекторов. Коннекторы делятся на источники (импорт данных в Kafka) и приемники (экспорт данных из Kafka). Для каждого коннектора можно настроить параметры для конкретных сценариев. Kafka Connect использует REST API для управления конфигурациями коннекторов и исполнителей, а также может настраиваться через файл свойств при запуске. REST API позволяет создавать, обновлять и удалять коннекторы, а также проверять их статус. Конфигурации исполнителей во многом определяют работу Kafka Connect, в том числе распределение ресурсов, распределение задач и общую устойчивость процесса интеграции. Можно настроить обработку ошибок в Kafka Connect, в том числе игнорировать проблемные записи, используя параметр errors.tolerance. Логирование ошибок требуется для диагностики и настраивается с помощью параметра errors.log.enable. Для коннекторов-приемников необрабатываемые данные можно направлять в очередь недоставленных сообщений. Механизмы повторной обработки настраиваются для временных ошибок с помощью параметров errors.retry.timeout и errors.retry.delay.max.ms. Single Message Transformation (SMT) позволяют выполнять простые преобразования данных: переименование полей, маскирование данных или перемещение значений в ключи при передаче. SMT работают для коннекторов-источников и приемников, но не подходят для сложных ETL-задач. SMT настраиваются через JSON в Kafka Connect API с указанием порядка преобразований, типов классов Java и конфигураций. Типичные SMT: Replace-Field (замена поля), ValueToKey (преобразование значения в ключ), ExtractField (извлечение поля) и MaskField (маскирование поля). JDBC-коннекторы обладают специализированными настройками, позволяю­ щими управлять повторными попытками подключения к database и стратегиями задержки. Режим работы коннекторов можно настроить как инкрементный, основанный на метках времени, либо комбинированный — в зависимости от требований к отслеживанию изменений.
   222 Часть IV. Kafka в корпоративной среде Отслеживание изменений (CDC) можно реализовать с помощью таких коннекторов, как Debezium, который следит за изменениями в базе данных. Для эффективной работы CDC в PostgreSQL требуются специальные настройки, например, для параметра wal_level необходимо установить logical. Kafka Connect может автоматически создавать топики Kafka на основе таблиц базы данных, обеспечивая бесшовную интеграцию. Параметр topic.prefix организует топики, генерируемые коннекторами, и предотвращает конфликты имен. В конфигурациях коннекторов можно указать детали подключения к базе данных и фильтрации таблиц, используя параметры типа table.include.list. Гибкость фреймворка позволяет создавать адаптированные решения для различных сценариев интеграции данных, улучшая управление потоками информации.
12 Потоковая обработка В этой главе 3 Фреймворки потоковой обработки. 3 Механизмы разделения и параллелизации в Kafka Streams. 3 Реализация SQL-подобных запросов в потоковой обработке. 3 Примеры использования Kafka Streams. В предыдущих главах мы рассмотрели разные способы передачи данных в Kafka. Продюсеры могут отправлять данные напрямую, особенно если источник данных рядом. Например, на заводском оборудовании установлены продюсеры Kafka, которые посылают данные измерений и события прямо в Kafka, или он используется для сбора логов с серверов или данных о посещениях сайтов. Если нужно собирать данные из баз данных, файлов или облачных сервисов с помощью Kafka, то стоит задействовать Kafka Connect. Вы также увидели, как можно считывать данные из Kafka и предоставлять их внешним системам. Потребители Kafka могут отображать данные напрямую или запускать действия в сторонних системах. При записи данных из Kafka в другие системы мы также рекомендуем использовать Kafka Connect, так как это обычно эффективнее, чем реализовывать собственные потребители. Эти инструменты предлагают множество способов реализовывать высокопроизводительные и полезные системы. Можно налаживать обмен данными между системами почти в реальном времени или создавать современные конвейеры
   224 Часть IV. Kafka в корпоративной среде интеграции. Изначально Kafka использовался для передачи огромных объемов из систем больших данных, таких как Hadoop, для последующей пакетной обработки. Поскольку данные уже находятся в Kafka, а он предоставляет платформу для обмена данными между системами почти в реальном времени и может долго хранить эти данные, то почему бы не анализировать их в реальном времени, а не пакетами? Добро пожаловать в мир потоковой обработки данных! С ее помощью можно обрабатывать непрерывные потоки данных, например в Kafka, в реальном времени. Это позволяет предоставлять аналитику сразу, а не спустя день, как раньше, и немедленно реагировать на изменения в бизнес-процессах. Эта книга не является руководством по разработке для Kafka, поэтому в текущей главе мы представим вашему вниманию базовые концепции потоковой обработки, избегая технических деталей реализации и не приводя слишком много примеров кода. 12.1. Обзор потоковой обработки В главе 2 вы познакомились с использованием потребителей и продюсеров на примере топика products.prices.changelog, который отслеживает изменения цен на товары в интернет-магазине. Топик фиксирует такие события, как обновление цен, скидки, акции и корректировки. В главе 11 мы работали с топиком customers, содержащим основную информацию о клиентах: имя, фамилию и дату рождения. В реальных условиях бывает больше топиков с данными клиентов, например с адресами, историей заказов, способами оплаты. Анализируя эти два топика, можно заметить разницу в типах данных. В топик products.prices.changelog записывались транзакционные данные (изменение цен), а в топик customers — базовые мастер-данные. Kafka подходит для работы с обоими типами данных, но ими необходимо правильно управлять. В интернет-магазине часто возникают проблемы, требующие оперативного решения, например расхождения в ценах на товары. Из-за ошибок в вышестоящих системах или из-за ввода вручную цена может стать слишком высокой, что отпугнет клиентов, или слишком низкой, что приведет к потере прибыли. Используя этот пример, мы рассмотрим потоковую обработку и увидим, как обнаруживать и устранять подобные проблемы почти в реальном времени. Платформа онлайн-магазина должна решить: исправить цену товара или пометить ее. Это решение нужно принять быстро, но не в ущерб точности. Слишком частое изменение цен, выполняемое без необходимости, может привести к потере доверия и снижению продаж, а обнаружение некорректных цен с задержкой может вызвать недовольство клиентов или убытки. На этом примере мы
   Глава 12. Потоковая обработка 225 увидим, как потоковая обработка обеспечивает своевременное и эффективное управление в таких динамичных и важных сценариях. На основе данных из топика products.prices.changelog мы будем определять, что сделать с ценой: исправить или пометить. Некоторые события не зависят от конкретных товаров. Например, при обнаружении системной ошибки в указании цен потребуется, возможно, приостановить некоторые промоакции или внести корректировки по всей платформе. Другие ситуации зависят от конкретных сегментов клиентов или категорий товаров. Например, изменение цены может восприниматься нормально одной группой клиентов, но станет проблемой для другой в зависимости от покупательского поведения или региона. В более сложных сценариях также учитываются дополнительные данные из других клиентских топиков, например история заказов или географическое расположение. 12.1.1. Библиотеки для потоковой обработки данных Прежде чем углубиться в реализацию такой системы, рассмотрим доступные библиотеки для потоковой обработки данных. На момент написания книги зимой 2024/25 года одним из самых популярных решений остается Kafka Streams. Это нативная библиотека Java для Kafka, которая обеспечивает отказоустойчивость и высокую производительность. Вдобавок она поддерживается популярными Java-фреймворками, такими как Spring Boot. Kafka самостоятельно управляет координацией различных экземпляров Kafka Streams, так что в продакшене этой библиотекой часто проще управлять, чем другими решениями. Набирает популярность Apache Flink, который, как и Kafka Streams, основан на Java, но предлагает и библиотеку для Python. Набор функций у Flink гораздо шире, чем у Kafka Streams, но при этом его сложнее эксплуатировать, поскольку он не передает координацию в Kafka и требует собственного координационного слоя. Важным преимуществом Flink является Flink SQL, позволяющий реализовывать логику потоковой обработки с помощью SQL. Это значит, что разработчикам не приходится писать объемный код на Java, поэтому Flink понравится командам, предпочитающим не работать с этим языком. Для любителей Python есть библиотека Faust Streaming, и у нас, авторов этой книги, есть положительный опыт ее использования. Для команд, работающих с Go, существует Goka; ее мы не испытывали. СОВЕТ Мы настоятельно не рекомендуем самостоятельно реализовывать потоковую обработку с помощью потребителей и продюсеров. Лучше воспользуйтесь одной из проверенных библиотек.
   226 Часть IV. Kafka в корпоративной среде 12.1.2. Обработка данных На рис. 12.1 показаны различные подходы к обработке данных в Kafka. В сфере аналитики традиционным методом работы с большими объемами информации остается пакетная обработка. Например, ежедневно в 4:00 утра cron-задача анализирует транзакции за предыдущие сутки и формирует отчеты. Кроме того, многие интеграционные решения применяют пакетную обработку для синхронизации данных между системами. Рис. 12.1. При пакетной обработке большой объем данных обрабатывается единовременно в заданное время, а результаты являются конечными. При обработке событий по одному мы получаем результат почти мгновенно, но при этом достигается только согласованность в конечном счете Пакетная обработка используется не только в классических интеграционных решениях — Kafka также может выступать источником данных для пакетных систем, поскольку они сохраняют определенные преимущества. Почти все крупные организации десятилетиями используют такие системы, и они не потеряли актуальности. В этом сценарии Kafka работает на уровне обмена данными. Конечно, такой подход не раскрывает весь потенциал Kafka, который умеет работать в реальном времени. В мире пакетной обработки данные часто считаются окончательными, и их обработка дает конечный результат. Этот подход удобно применять для подготовки регулярно повторяющихся отчетов с фиксированными периодами, таких как ежемесячные отчеты о доходах или обновлении запасов. Кроме того, при работе пакетных систем в облаке можно использовать более дешевые спотовые инстансы в ночное время, чтобы снизить затраты. Главный недостаток пакетной обработки очевиден — все происходит не в реальном времени. Результатов приходится ждать, причем иногда часами. При потоковой обработке данных, напротив, события обрабатываются по мере их поступления, по одному за раз. В контексте нашего интернет-магазина это может означать, что изменение цены товара отображается сразу. Кроме того, когда товар заканчивается на складе, мы узнаем об этом быстро. Как и мы, наши клиенты не хотят долго ждать — все должно происходить прямо сейчас. Мир живет в реальном времени, и бизнес тоже должен принимать решения мгновенно, а не через несколько часов, когда уже слишком поздно.
   Глава 12. Потоковая обработка 227 Потоковая обработка требует иного мышления, ведь мы имеем дело не с окончательными результатами, а с промежуточными, которые еще могут измениться. Как правило, каждое событие обрабатывается индивидуально и изолированно. Например, получив событие снижения цены на товар — price drop detected for product X ,— мы должны сразу скорректировать цену, указанную на сайте, разослать уведомление о скидке покупателям или отправить оповещение отделу продаж, чтобы там придумали, как на этом заработать. Сложности возникают, когда реакция зависит от других факторов, таких как сегменты покупателей или исторические данные о ценах. Что, если изменение цены выгодно только участникам программы лояльности? В таких случаях требуется доступ к данным из других источников, например к топикам customers или products.prices.changelog. В этом помогают библиотеки потоковой обработки. Но проблема остается: мы всегда работаем с промежуточными состояниями, поэтому можем получать только промежуточные результаты. ПРЕДУПРЕЖДЕНИЕ Операции потоковой обработки согласованы в конечном счете. Но есть очевидное преимущество — больше не нужно ждать результатов часами. 12.2. Потоковые обработчики Потоковые обработчики — это компоненты фреймворков потоковой обработки данных (например, Kafka Streams или Apache Flink), которые применяют определенную логику или преобразования к сообщениям в потоке данных. Это строительные блоки для анализа, фильтрации и преобразования данных в реальном времени по мере их прохождения через систему. Независимо от используемой библиотеки большинство фреймворков потоковой обработки функционируют по схожим принципам: получают входные данные, обрабатывают их по заданной логике и генерируют преобразованные или агрегированные выходные данные. Как оптимально выразить операции, выполняемые потоковыми процессорами? В традиционных продюсерах и потребителях обычно применяется императивное программирование, где явно определены пошаговые инструкции: «если это, тогда то». В функциональном программировании, а также в Kafka Streams, Apache Flink и других потоковых обработчиках, мы определяем многоразовые функции и соединяем их для преобразования данных в конвейере обработки. Этот подход упрощает обработку непрерывных потоков данных, поскольку большая часть низкоуровневой логики абстрагируется. Третий подход — декларативное программирование (например, SQL), где мы указываем, какие данные хотим получить, а база данных или библиотека потоковой обработки выполняет запрос. Возможности SQL в рамках потоковой
   228 Часть IV. Kafka в корпоративной среде обработки мы подробнее рассмотрим в разделе 12.3. А в этом сосредоточимся на функциональном подходе и базовых компонентах, известных как обработчики в Kafka Streams. 12.2.1. Типы обработчиков Сначала необходимо прочитать данные из топика Kafka. В Kafka Streams для этого используется обработчик источника stream(topicName), предоставляемый StreamsBuilder. Например, прочесть данные из топика products.prices.chan­ gelog можно с помощью такого кода: builder.stream("products.prices.changelog") Во Flink процесс сложнее, так как фреймворк поддерживает не только Kafka, но и другие системы, поэтому требуется предварительно настроить источники и приемники данных. Один из простейших обработчиков для потоковой обработки — filter((Key, Value) -> Bool). Функция filter() принимает предикат — функцию, которая выполняется для каждого сообщения в потоке и возвращает поток только с сообщениями, удовлетворяющими условию, то есть где предикат возвращает True. Например, в потоке изменений цен нас могут интересовать лишь те сообщения, в которых новая цена превышает 100: filter((key, value) -> value.price > 100) Функция filter() не меняет данные. Для преобразований используется обработчик map((Key, Value) -> (newKey, newValue)). Функция map() принимает функцию, которая получает сообщение с ключом и значением, а возвращает новое сообщение с обновленными ключом и значением. Если требуется изменить только значение, то следует использовать обработчик mapValues((Key, Value) -> newValue), который не затрагивает ключ. Обработчик mapValues() применяется для конвертации, маскировки или трансформации данных. Например, преобразуем цену из долларов в центы: mapValues((key, value) -> value.price * 100) Выходной поток будет содержать данные о цене в центах. Мы часто используем map() для обработки сырых данных. Например, если мы получаем сообщения без ключа, но со значением {id: "Apple", price: 1.50}, то можем применить следующую функцию для форматирования данных: map((key, value) -> (value.id, value.price)) В результате мы получим сообщение с ключом Apple и значением 1.50 . На рис. 12.2 представлено графическое изображение обработчиков filter() и map().
   Глава 12. Потоковая обработка 229 Рис. 12.2. Обработчики filter() и map() ПРЕДУПРЕЖДЕНИЕ Kafka Streams предоставляет варианты для многих функций. Например, для функции map() существует вариант mapValues(). Поскольку mapValues() не меняет ключ, то лучше использовать его. Изменение ключа часто приводит к потере производительности, так как сообщения приходится перераспределять по партициям. Для записи данных обратно в Kafka фреймворки предлагают различные функции. В Kafka Streams мы просто вызываем обработчик to(topicName). Чтобы записать обработанные цены в топик prices, можно использовать следующий код: map(…).to("prices") Во Flink, как и при чтении из топика, сначала требуется создать коннектор Flink Kafka и подключить его к топику. Обработчики могут принимать не только один входной поток и выдавать один выходной. С помощью merge(otherStream) можно объединить два потока данных. Элементы из обоих потоков попадают в новый поток, аналогично оператору UNION в SQL. Обработчик merge() полезен при объединении однородных данных из разных источников. Например, можно создать центральный кластер Kafka, в котором топики объединяются с данными о продажах из разных филиалов, и использовать следующую функцию для совместной обработки потоков данных о продажах северного и южного филиалов: salesDataNorth.merge(salesDataSouth) Фреймворки потоковой обработки, разумеется, не только предоставляют обработчик merge() для объединения данных, но и поддерживают соединения join, как в базе данных. О них мы поговорим в подразделе 12.4.3. Если данные нужно не объединить, а разделить, то можно воспользоваться обработчиком split(). Можно, конечно, сделать несколько вызовов filter(), но split() очень удобен в некоторых сценариях. Например, если мы хотим поразному обрабатывать данные о продажах товаров в зависимости от их цены: cheap = prices.filter((key, value) -> value.price < 10) midrange = prices.filter((key, value) -> value.price >= 10 && value.price < 100) expensive = prices.filter((key, value) -> value.price >= 100)
   230 Часть IV. Kafka в корпоративной среде То же самое можно реализовать с помощью split(). Для дальнейшей обработки потока ветви подойдет метод Branched.withConsumer(). Отделенный поток можно, например, записать в другой топик. Инициализация разделения Предикат для проверки prices.split() .branch((key, value) -> value.price < 10, withConsumer поддерживает дальнейшую обработку Branched.withConsumer( (stream) -> stream.to("prices-cheap"))) Запись данных в топик .branch((key, value) -> value.price < 100, Еще одна ветвь со своим предикатом Branched.withConsumer( (stream) -> stream.to("prices-midrange"))) .defaultBranch(Branched.withConsumer( (stream) -> stream.to("prices-expensive"))) Ветвь по умолчанию вызывается, если нет совпадений При использовании split() ветви оцениваются сверху вниз, и выполняется та ветвь, где предикат имеет значение True. Такой подход помогает избежать ошибок и дублирования, которые могут возникать при использовании filter(), например, из-за пропуска одного из условий для midrange. Схематическое изображение разделения и объединения потока показано на рис. 12.3. Рис. 12.3. Обработчики разделения и объединения Все рассмотренные до сих пор обработчики анализируют каждое сообщение по отдельности и выполняют действия на его основе. Эти обработчики не сохраняют состояние (stateless), то есть им не требуется внутреннее хранилище или состояние для обработки сообщений. Сообщения часто требуется анализировать в контексте предыдущих, и об этом мы поговорим в подразделе 12.4.2. Как подсчитать количество событий изменения цены для каждого товара? Потоковые обработчики Kafka работают на уровне партиций: разные экземпляры приложения потока отвечают за разные партиции. Благодаря этому обеспечивается высокая степень параллелизма, и экземпляры не мешают работе друг друга. Однако это также означает, что нельзя напрямую определить, например, общее количество сообщений в топике Kafka. Вместо этого подсчитывается количество вхождений по ключу. Если использовать идентификатор товара в качестве
   Глава 12. Потоковая обработка 231 ключа в топике products.prices.changelog, то можно легко определить количество изменений цены (то есть количество событий) для каждого товара: prices.groupByKey() .count() Сначала мы группируем данные в топике по ключу (товару), а затем подсчитываем сообщения для каждого ключа. Обработчик count() хранит состояние (stateful), ведь он отслеживает количество уже прочитанных событий. Даже если состояние здесь небольшое, его все равно необходимо где-то хранить. Мы поговорим об этом подробнее чуть позже. Кроме того, нам необходимо реализовывать более сложные агрегации, например вычисление суммы или среднего значения, но и эту тему мы рассмотрим позднее. Схематическое изображение Рис. 12.4. Обработчик count() обработчика count() показано на рис. 12.4. 12.2.2. Топологии обработчиков Теперь, когда вы имеете представление об отдельных обработчиках, соединим их в более сложную конструкцию. Начнем с простого примера: мы хотим анализировать изменения цен, превышающие критическое значение (например, 10 %), и записывать их в топик prices.significantlyIncreased: builder.stream("prices") .filter((key, value) -> value.price / value.oldPrice > 1.1) .to("prices.significantlyIncreased") Такой тип программного кода в потоковой обработке называется топологией, как показано на рис. 12.5. Эта визуализация помогает в отладке кода и позволяет лучше его понять, при этом разработчику не приходится читать код напрямую. Разумеется, это всего лишь простой и небольшой пример, на самом деле граф может быть гораздо более сложным. Например, мы хотим записывать данные о продажах северного и южного филиалов в центральный топик salesData.aggregated, а затем подсчитывать количество событий покупки для каждого клиента и записывать его в топик salesData.count. Все это мы можем делать, используя такой код: salesDataNorth = builder.stream("salesData.north"); salesDataSouth = builder.stream("salesData.south"); Получение данных из топиков Объединение топиков salesData_aggregated = salesDataNorth.merge( salesDataSouth); salesData_aggregated.to("salesData.aggregated"); Запись данных в топик salesData_aggregated.groupByKey() Подготовка данных к агрегации .count() Преобразование KTable в KStream .toStream() .to("salesData.count"); Агрегация (подсчет) Запись данных в топик
   232 Часть IV. Kafka в корпоративной среде Рис. 12.5. Слева — простая топология, которая фильтрует данные из одного топика и записывает их в другой. Справа — объединение двух топиков с последующей записью результата в отдельный топик и параллельным подсчетом количества сообщений по ключу. Итоговый результат также записывается в топик Сначала мы читаем два топика с помощью builder.stream(), затем объединяем данные через merge(), чтобы записать их в целевой топик с использованием to(). Объединяем данные с помощью groupByKey() и подсчитываем события покупок, используя count(). Результат операции count() представляет собой не поток данных, а таблицу (подробности позже), поэтому мы должны сначала преобразовать его в поток с помощью toStream() и только потом записать в топик, используя to(). Эту логику можно визуализировать, как было показано на рис. 12.5, и увидеть, что граф усложняется. Теперь у нас есть два входных обработчика и данные обрабатываются разными способами: выполняется запись объединенных данных в топик и подсчет событий. 12.3. Потоковая обработка с использованием SQL В книге мы часто проводили параллели между базами данных и Kafka. Используем тот же подход и для потоковой обработки. В реляционных базах данных SQL позволяет эффективно выражать даже сложные операции, которые затем (хотелось бы верить) так же эффективно выполняются СУБД. Часто в нескольких
   Глава 12. Потоковая обработка 233 строках SQL-запросов можно описать то, что потребовало бы множества страниц императивного или функционального кода. Поэтому логично адаптировать SQL для потоковой обработки. Очевидно, что между SQL в базе данных и SQL в Kafka для потоковой обработки есть важные различия: в базах SQL-запросы обычно выполняются по требованию для получения результата, который возвращается более или менее быстро. Конеч­но, существуют базы с поддержкой подписок на запросы, но это исключения. В Kafka мы храним события, а не состояния, то есть потоковый SQL-запрос обычно не возвращает окончательный ответ, а создает поток данных, который потенциально может быть бесконечным. Мы получаем не один ответ, а новый поток данных с изменениями. Большинство фреймворков потоковой обработки также поддерживают таблицы для хранения состояний, поэтому мы можем писать запросы, которые возвращают полный результат на определенный момент времени. В этом разделе мы не собираемся описывать SQL или углубляться в синтаксис Flink SQL. Мы просто посмотрим, как программы, ранее реализованные в императивном коде на Java, могут выглядеть в SQL. Синтаксис зависит от фреймворка. Flink SQL стремится придерживаться стандарта SQL везде, где это возможно, поэтому совместим со многими библиотеками, поддерживающими SQL. Прежде чем выполнять SQL-запросы, создадим таблицы, или потоки, как в базе данных. В отличие от базы мы не создаем пустые таблицы, а связываем их с топиками Kafka. Теперь создадим аналоги на стороне Flink SQL для топика products.prices.changelog: CREATE TABLE productprices ( product_id INT, product_name STRING, price FLOAT, oldPrice FLOAT ) WITH ( 'connector' = 'kafka', 'topic' = 'products.prices.changelog', 'properties.bootstrap.servers' = 'localhost:9092', 'format' = 'json', 'key.format' = 'json', 'key.fields' = 'product_id', 'value.fields-include' = 'EXCEPT_KEY' ); Flink SQL соблюдает стандарт SQL, поэтому не различает потоки и таблицы, а значит, мы можем создавать только таблицы. Тот факт, что идентификатор товара является ключом, незаметен на первый взгляд — это видно только в строке key.fields. Далее мы указываем поля, как обычно. Теперь необходимо настроить источник для этой таблицы. Поскольку Flink поддерживает не только Kafka, мы должны явно указать использование коннектора Kafka. Для этого коннектору
   234 Часть IV. Kafka в корпоративной среде требуется информация о bootstrap-сервере и топике. Наконец, необходимо указать формат данных — JSON. СОВЕТ Несмотря на то что SQL-запрос говорит о создании новой таблицы, сами данные не хранятся в кластере Flink. Однако при чтении данных из этой таблицы запрос перенаправляется в источник данных, которым в этом случае является Kafka. Теперь можно выполнять первые запросы. Например, чтобы просмотреть изменения цен для товаров стоимостью выше 100, мы напишем такой запрос Flink SQL: SELECT * FROM productprices WHERE price > 100; Это похоже на обычный SQL-запрос, но результат возвращается не однократно, а непрерывно. Теперь мы хотим посчитать количество изменений цены для каждого товара: SELECT product_id, COUNT(*) AS num_price_changes FROM productprices GROUP BY product_id; Этот код похож на стандартный SQL-запрос и будет работать аналогично в традиционных базах данных. Сейчас мы не будем углубляться в SQL; вместо этого в следующем разделе мы рассмотрим различные сценарии использования потоковой обработки на примере SQL-запросов. Обычно мы не выполняем SQL-запросы вручную в продакшене и не вписываем их в программный код. Вместо этого мы можем использовать headless-режим в Flink SQL, предоставляя один или несколько SQL-запросов для непрерывного выполнения. Результаты этих запросов можно записать обратно в Kafka, чтобы их могли просматривать и пересылать потребители или Kafka Connect. Поскольку Flink поддерживает другие системы, помимо Kafka, то мы также можем записывать результаты напрямую в другую систему баз данных. СОВЕТ Может показаться, что писать и реализовывать потоковые SQL-запросы очень просто, но следует обратить внимание на их производительность. Мы рекомендуем проверять запросы перед их выполнением в продакшене и анализировать возможные проблемы с производительностью. 12.4. Состояния потоков До сих пор мы рассматривали топики в Kafka как бесконечные потоки данных — у них есть начало (сообщение с наименьшим смещением), но нет конца. Такие потоки идеально подходят для представления постоянно изменяющихся данных — показаний датчиков, банковских транзакций или обновления систем. Мы предполагаем, что новые сообщения могут поступать в любой момент.
Глава 12. Потоковая обработка  235 Хотя потоки помогают эффективно фиксировать текущие изменения, они слишком громоздкие, чтобы по ним можно было определять текущее состояние системы. Например, для расчета последнего статуса компьютера или текущего баланса клиента потребуется обработать все сообщения с начала до последней записи. Понадобится много вычислительных ресурсов, и часто такой подход нецелесообразен. В этом разделе мы рассмотрим, как концепция состояний потоков решает эту проблему. Сначала мы исследуем, как управляются состояния в потоках и как связаны между собой потоки и таблицы. Затем мы углубимся в ключевые техники, такие как агрегации и соединения, которые позволяют извлекать значимые выводы из данных. Наконец, мы применим эти концепции в сложном примере — в системе уведомлений, — чтобы увидеть, как потоковая обработка эффективно решает сложные задачи. 12.4.1. Потоки и таблицы Мы уже неоднократно говорили, что базы данных демонстрируют свое абсолютное преимущество именно в этой области — их таблицы идеально подходят для хранения и запросов текущего состояния системы. Более того, таблицы и потоки данных тесно взаимосвязаны. В главе 11 мы обсуждали Debezium — решение для записи изменений из баз данных в Kafka, которое позволяет преобразовывать статичную таблицу в динамический поток данных (в Kafka). Обратное тоже верно: наблюдая за потоком изменений, мы можем извлечь из него текущее (промежуточное) состояние системы. Эта взаимозаменяемость показана на рис. 12.6. Рис. 12.6. Мы можем преобразовывать потоки в таблицы и наоборот Этот подход применим и в потоковой обработке данных. Часто нам требуется и то и другое: возможность реагировать на изменения в реальном времени, а также запрашивать текущее состояние объектов и систем. Эти две функции должны изначально поддерживаться в окружении потоковой обработки, чтобы нам не приходилось обращаться к внешним базам данных для получения текущего состояния, например, товаров. Фреймворки потоковой обработки разделяют понятия потоков данных и таблиц. В Kafka Streams потоки и таблицы называются KStream и KTable соответственно, а во Flink — DataStream и Table. И потоки данных, и таблицы обычно разделяются, так что каждому экземпляру приложения потоковой обработки доступна только часть данных. Кроме того, таблицы в потоковой обработке не являются
   236 Часть IV. Kafka в корпоративной среде таблицами в традиционном смысле, как в реляционных базах данных, — они содержат только два столбца (ключ и значение), превращаясь, по сути, в хранилище пар «ключ — значение». Из топика можно создавать как потоки данных, так и таблицы. Например, в Kafka Streams мы создаем KStream с помощью builder.stream("topic"), а KTable — с помощью builder.table("topic") . Для топиков с мастер-данными часто применяется сжатие (compaction). При создании таблицы Kafka Streams (или Flink) читает топик с начала, обновляет промежуточное состояние для каждого ключа и сохраняет его. Большинство библиотек потоковой обработки используют RocksDB — высокопроизводительное хранилище пар «ключ — значение», оптимизированное для подобных сценариев. Если в приложении потоковой обработки есть таблицы, это означает, что в них хранится состояние, а следовательно, требуется локальное хранилище. Для предотвращения потери данных при сбоях Kafka Streams сохраняет изменения в топике журнала изменений, где обычно настроено сжатие. Flink также хранит эти состояния в Kafka или в других системах в зависимости от конфигурации. Ранее мы рассматривали практический пример использования таких таблиц: при применении обработчика count() промежуточные состояния счетчика хранятся в таблице. Каждое следующее обновление изменяет значение в таблице и записывается в топик журнала изменений. Значения из таблиц могут запрашиваться другими частями приложения или использоваться для операций соединения (join) и аналогичных задач. К счастью, мы можем применять большинство изученных обработчиков для взаимодействия с таблицами (рис. 12.7). Таблицы можно фильтровать и преобразовывать так же, как потоки данных. Для получения потока данных из таблицы используется обработчик toStream(). В обратном направлении доступно больше вариантов. Например, toTable() — простейший обработчик, который считывает данные и заполняет строку таблицы последним значением для каждого ключа. Кроме того, можно применять count() и другие агрегации. Подробнее об этом — в следующем подразделе. Рис. 12.7. Обработчики map, filter и flatMap работают с KStream и KTable. Агрегации позволяют создавать KTable из KStream, а toStream создает KStream из KTable
   Глава 12. Потоковая обработка 237 12.4.2. Агрегации Для получения текущего состояния из потока данных нужно агрегировать поток. Например, для вычисления текущего баланса банковского счета необходимо обработать все транзакции и сложить суммы операций для каждого идентификатора счета. Агрегация требует двух компонентов: начального состояния и функции добавления. Начальное состояние требуется, если у сообщения нет предыдущего. Это «пустое» состояние для конкретной агрегации. Для count(), sum() и других операций начальное состояние равно 0. В случае умножения начальным состоянием было бы 1 — нейтральный элемент, не влияющий на результат вычислений. Агрегация count() показана на рис. 12.8. Рис. 12.8. Для подсчета количества сообщений по ключу требуется хранилище состояний с текущим счетчиком для каждого ключа. Изменения в хранилище состояний сохраняются в сжатом топике журнала изменений Если мы знаем предыдущее состояние и получаем новое сообщение, то используем функцию добавления для вычисления нового состояния. В случае с count() мы игнорируем содержимое сообщения и просто увеличиваем старое состояние на +1. Для sum() мы добавляем значение сообщения к старому состоянию, чтобы получить новое. Обратите внимание: в потоковых обработчиках агрегация обычно выполняется по ключам — мы не суммируем все сообщения в единое общее состояние, а рассчитываем сумму по ключам, чтобы получить уникальное значение для каждого ключа. Эти состояния хранятся в таблице и могут использоваться в дальнейших вычислениях. Чтобы гарантировать сохранность данных в таблице при сбое потокового обра­ ботчика и не пересчитывать все с начала, большинство фреймворков используют постоянное хранилище для сохранения промежуточных состояний. Kafka Streams применяет упомянутые топики журнала изменений (changelog), а Flink может использовать различные технологии хранения. В табл. 12.1 указаны дополнительные агрегации с примерами реализации. Конечно, простор для творчества тут безграничен. Агрегации удобны, когда требуется анализировать данные в Kafka, например, для выявления трендов и паттернов.
   238 Часть IV. Kafka в корпоративной среде Кроме того, они активно задействуются для биллинга и анализа использования. С помощью агрегации average() можно применять mapValues() к состоянию, чтобы результаты (в том числе промежуточная сумма и счетчик) отображались в таблице, а не как внутренние состояния. Таблица 12.1. Примеры агрегаций и их псевдокодовая реализация Агрегация Тип данных агрегатов Начальное состояние Функция добавления Пример count() Число 0 (agg, value) -> agg + 1 (K1, 2) -> K1: 1 (K2, 1) -> K2: 1 (K2, 2) -> K2: 2 sum() Число 0 (agg, value) -> agg + value (K1, 2) -> K1: 2 (K2, 1) -> K2: 1 (K2, 2) -> K2: 3 toTable() Тип данных значения Null (agg, value) -> value (K1, 2) -> K1: 2 (K2, 1) -> K2: 1 (K2, 2) -> K2: 2 average() Число (sum, count, avg) (agg, value) -> (agg.sum + value, agg.count + 1, newSum/newCount) (K1, 2) -> K1: (2,1,2) (K2, 1) -> K2: (1,1,1) (K2, 2) -> K2: (3,2,1.5) На данном этапе обратите внимание на особенность Kafka Streams: нельзя выполнить агрегацию над KStream напрямую, сначала нужно сгруппировать поток. Обычно для этого применяется функция kstream.groupByKey(). Если требуется сгруппировать поток по другому ключу, то можно использовать функцию kstream.groupBy((key, value) -> newKey). Изменится не только сам ключ — будет создан новый топик для изменения партиционирования, и сообщения будут группироваться по новому ключу. Во Flink агрегации можно применять ко всему потоку данных или группировать по определенному значению. 12.4.3. Соединения потоков Еще одна крайне полезная и часто используемая функция баз данных — соединения (join). Мы можем соединять данные из нескольких таблиц, чтобы, например, определить, какие пользователи купили те или иные товары. При простом соединении мы создаем перекрестную таблицу, где каждая запись из одной таблицы связывается с каждой записью из другой. Затем к перекрестной таблице можно применить фильтр и извлечь полезные записи.
   Глава 12. Потоковая обработка 239 Как использовать соединения в потоковой обработке? Создавать перекрестные таблицы из бесконечных потоков нельзя, ведь мы никогда не получим полный набор данных — новые записи поступают непрерывно. Основная идея соединения в потоковой обработке заключается в поиске соответствий: мы обрабатываем одно сообщение за раз и ищем в состоянии (например, в виде таблицы) соответствующую запись для текущего сообщения. Часто такие соединения в потоковой обработке используются для обогащения данных. Например, у нас есть поток данных о продажах, и мы хотим дополнить его мастер-данными о товарах, чтобы отображались не только названия товаров, но и их производители. Мастер-данные хранятся в сжатом топике и меняются относительно редко, поэтому их легко можно рассматривать как таблицу, где ключ — идентификатор товара, а значение — актуальные мастер-данные о нем. Данные о продажах представляются в виде потока, ключом которого также выступает идентификатор товара. При получении новых данных о продажах мы ищем мастер-данные нужного товара в таблице по идентификатору, а затем выполняем операцию обогащения. В коде это можно реализовать так: Левая сторона соединения: KStream KStream salesData = builder.stream("salesData"); Правая сторона: KTable KTable masterData = builder.table("products"); ValueJoiner enricher = (salesData, masterData) -> { Берет левое и правое значения salesData.manufacturer = masterData.manufacturer; и возвращает результат return salesData; }; KStream salesData_enriched = salesData.join( Выполняет соединение masterData, enricher); На рис. 12.9 показана визуальная схема процесса соединения. Сначала мы создаем поток (builder.stream()) и таблицу (builder.table()). Функция обогащения (Joiner) принимает ключ текущего сообщения и его значение, а также значение из таблицы по этому ключу. Соединение выполняется с помощью .join(). С левой стороны соединения — поток salesData, а справа — таблица masterData товара. Соединяет их функция обогащения. Результатом соединения будет поток. Рис. 12.9. Топология соединения
   240 Часть IV. Kafka в корпоративной среде Но когда выполняется это соединение? Обработчики потоков всегда обрабатывают по сообщению за раз, поэтому без использования состояний невозможно удерживать сообщения и ожидать последующих. Нужен другой подход: получая сообщение от потока sales data, функция соединения ищет соответствующую запись в таблице products и выполняет функцию обогащения. Но если функция соединения получает уведомление об изменении или добавлении записи в таблицу products, то не может найти соответствующую запись в потоке данных о продажах sales data. Это означает, что при изменении таблицы соединение не выполняется. Оно происходит только при появлении новых данных в потоке. Помимо «поток — таблица», есть и другие типы соединений. Например, если у нас есть два топика — клиенты (customers) и карты лояльности (loyalty_cards), — то мы можем обогатить данные о карте лояльности с помощью мастер-данных о клиентах. Перед созданием топиков необходимо продумать ключи. Чтобы потоки данных и таблицы можно было соединить, у них должны быть одинаковое количество партиций и одинаковые ключи. Предположим, что в нашем примере это условие выполнено, а иначе потребовалось бы изменить ключ одного из топиков. Теперь у нас есть два топика мастерданных и мы хотим объединить данные. Для этого представим оба набора данных как таблицы и выполним соединение по (первичному) ключу. При изменении в левой таблице можно найти соответствующий ключ в правой и выполнить соединение. При изменении в правой таблице также можно выполнить поиск соответствий. Таким образом, соединение «таблица — таблица» учитывает изменения с обеих сторон. В коде это можно описать так: KTable loyaltyCards = builder.table("loyalty_cards"); Слева и справа таблицы KTable customers = builder.table("customers"); ValueJoiner joiner = (loyaltyCard, customer) -> { loyaltyCard.customer = customer; ValueJoiner не меняется return loyaltyCard; }; KTable loyaltyCardWithCustomer = loyaltyCards.join(customers, joiner); Мы снова начинаем с инициализации входных данных (builder.table()). Затем определяем, как должно выполняться соединение, и, наконец, выполняем само соединение. Здесь мы используем внутреннее соединение, поскольку наличие карты лояльности без клиента не имеет смысла. Можно ли соединить два потока данных? Мы знаем, что нам нужен способ искать ключ на другой стороне соединения, но в двух бесконечных потоках данных это невозможно, а значит, невозможно и общее соединение «поток — поток». Правда, мы можем ограничить бесконечность временны́м окном. Например, можем хранить данные за последние пять минут и находить в этом окне наши значения в другом потоке. Это полезно, если мы хотим соотносить данные с временем.
   Глава 12. Потоковая обработка 241 Например, мы можем соединить изменения цен на товары (products.prices. changelog) с данными о продажах (salesData), если соответствующие данные поступили в пределах минуты друг от друга. Внутри обработчик потоков хранит данные из обоих потоков в течение минуты, что позволяет нам получать их для последующей фильтрации и анализа. У топиков должны быть одинаковое количество партиций и одинаковые ключи — логично выбрать для этого идентификатор товара: KStream priceChanges = builder.stream( Левая и правая "products.prices.changelog"); KStream salesData = builder.stream("salesData"); стороны — KStream JoinWindows joinWindow = JoinWindows.ofTimeDifferenceWithNoGrace( Duration.ofMinutes(1)); ValueJoiner joiner = (priceChange, saleData) -> Нужно временное окно new PriceSaleInfo(priceChange, saleData); KStream correlated = priceChanges.join(salesData, joiner, joinWindow); Как обычно, мы начинаем с чтения данных из топиков. Затем определяем временно́е окно, равное минуте, в рамках которого хотим коррелировать данные. (В подразделе 12.5.2 мы подробнее рассмотрим возможные временные окна.) Результатом будет поток, который реагирует на изменения в обоих потоках. В отличие от предыдущих соединений в выходной поток поступают сообщения для каждого изменения, при этом каждое сообщение в одном потоке соединяется с каждым сообщением в другом, если у них совпадают ключ и временно́е окно. Можно сказать, что соединения в обработчиках потоков похожи на соединения в базах данных, но работают иначе, так как мы имеем дело с бесконечными потоками данных. В табл. 12.2 указаны наиболее распространенные типы соединений и сторона, изменения на которой запускают функцию соединения. Таблица 12.2. Типы соединений при потоковой обработке Левая сторона Правая сторона На какой стороне изменения запускают соединение Таблица Таблица Слева и справа Поток данных Таблица Поток данных Поток данных Поток данных Слева и справа 12.4.4. Пример использования: уведомления Как же использовать агрегации и соединения для реальных задач? Представьте, что мы хотим отправлять пользователям срочные уведомления. Например, приложение установлено на нескольких устройствах и мы хотим отправлять уведомление на все.
   242 Часть IV. Kafka в корпоративной среде С одной стороны, у нас есть топик уведомлений notifications, куда записываются все уведомления. Ключ этого топика — идентификатор пользователя. Во втором топике, user-devices, хранятся все устройства, связанные с каждым пользователем. Обычно для одного пользователя создается несколько сообщений — для разных устройств. Для отправки уведомления на все устройства сначала необходимо агрегировать всю информацию об устройствах пользователя, затем соединить эти данные с информацией из топика notifications и только после этого отправить результат в топик device-notifications. Эта топология показана на рис. 12.10. Рис. 12.10. Топология потоков обработки: агрегация и соединение В Kafka Streams эту схему можно реализовать так: StreamsBuilder builder = new StreamsBuilder(); KStream<String, Notification> notifications = builder.stream("notifications"); Создание потоков KStream<String, DeviceInfo> userDevicesStream = builder.stream( Kstream из топиков "user-devices"); Исходный элемент — пустой список Initializer initializer = List.of(); Функция добавления: старое состояние + Aggregator adder = (userId, newDevice, aggDevices) -> { + текущий элемент = if (newDevice.isActive) { = новое состояние aggDevices.put(newDevice.deviceId, newDevice);
   Глава 12. Потоковая обработка 243 } else { aggDevices.remove(newDevice.deviceId); } return aggDevices; }; KTable<String, List<DeviceInfo>> userDevicesTable = userDevicesStream .groupByKey() Сначала группировка… .aggregate(initializer, adder); …потом агрегация KStream<String, NotificationWithDeviceList> notificationsWithDevices = notifications.join(userDevicesTable, NotificationWithDeviceList::new); Соединение уведомлений с устройствами deviceNotifications.toStream().to( "device-notifications"); Запись результатов в топик Как обычно, начинаем с создания двух потоков KStream из топиков Kafka: один для notifications, другой для user-devices. Далее мы хотим агрегировать данные из топика user-devices так, чтобы для каждого пользователя формировался список устройств. Как мы уже знаем, для агрегации требуются две функции. Сначала initializer создает начальное состояние для ключей, которые мы еще не видели. В нашем сценарии он создает пустой список, так как мы собираем список устройств. Затем adder принимает предыдущее состояние и текущее значение и возвращает новое состояние. В нашем случае это означает добавление активных устройств в список и удаление неактивных. Мы передаем обе функции в функцию aggregate() и получаем таблицу KTable, в которой указаны все активные устройства для каждого пользователя. Теперь можно соединить топик notifications-topic с этой KTable и записать результат в топик Kafka. Во Flink SQL запрос разделяется на части. Первый запрос создает представление с активными устройствами пользователей — active_user_devices. Для каждого идентификатора пользователя user_id оно содержит список активных устройств: CREATE VIEW active_user_devices AS SELECT user_id, LISTAGG( CASE WHEN is_active = true THEN JSON_OBJECT( 'deviceId' VALUE device_id, 'deviceInfo' VALUE device_info ) END ) as device_list FROM user_devices GROUP BY user_id;
   244 Часть IV. Kafka в корпоративной среде В следующем запросе мы выполняем собственно соединение топика notifica­ tions с active_user_devices: CREATE VIEW device_notifications AS SELECT n.user_id, n.notification_data, d.device_list FROM notifications n JOIN active_user_devices d ON n.user_id = d.user_id; 12.5. Потоковая обработка и время В предыдущем разделе вы уже видели, что время может играть значительную роль в обработке потоков данных. Например, при обнаружении попыток мошеннических транзакций по кредитным картам мы должны действовать быстро. Если пользователь в течение 5 минут три раза вводит неправильный PIN-код, это подозрительно. Возможно, потребуется проверка. Как реализовать этот сценарий с помощью Kafka? 12.5.1. Время относительно Сначала необходимо вспомнить о гарантии порядка в Kafka: внутри партиции сообщения гарантированно находятся в том порядке, в котором они поступают на брокер-лидер. Однако это не означает, что временные метки сообщений расположены по возрастанию. Фактически значение временной метки может варьироваться. В конфигурации Kafka по умолчанию временная метка сообщения устанавливается продюсером в момент вызова метода send(), то есть ответственность за временную метку лежит не на брокере, а на продюсере. Такое поведение обусловлено настройкой log.message.timestamp.type, которая по умолчанию имеет значение CreateTime. При этом CreateTime — это не время события. Расхождение может составлять несколько секунд или даже больше. Поэтому продюсер может сам устанавливать временную метку, хотя менять технические временные метки в Kafka вручную не рекомендуется. Лучше добавить новое поле, представляющее время возникновения события. Обычно это называют временем события. Кроме того, можно настроить брокеры (используя log.message.timestamp.type) и топики (с помощью message.timestamp.type) так, чтобы временная метка сообщения устанавливалась в момент получения сообщения брокером. Преимущество этого подхода в том, что сообщения в партиции будут упорядочены по возрасту, по крайней мере если время не корректируется. Это время добавления в лог — LogAppendTime.
   Глава 12. Потоковая обработка 245 Наконец, существует еще одно время, с которым часто проще всего работать, — время потока. Это время, когда сообщение обрабатывается в обработчике потока. В табл. 12.3 указаны различные типы времени. Таблица 12.3. Время при потоковой обработке Время Кто отвечает Значение Время события Наш код Время, когда произошло событие Время создания Продюсер Время, когда продюсер создал сообщение Время добавления в лог Брокер Время, когда брокер записал сообщение в лог Время потока Потоковые обработчики Время, когда сообщение обработано Важно учитывать эти типы времени при выполнении операций, привязанных к времени. В потоковых обработчиках мы работаем с временны́ми окнами. Например, нам нужно агрегировать данные за последние два часа или сравнить данные из двух разных топиков в определенном промежутке. Фреймворки потоковой обработки позволяют выбирать между типами времени. Соответствующие методы обычно называются TimestampExtractor. Однако, поскольку Kafka не гарантирует расположение временных меток по возрастанию, сообщения могут поступать в партицию «слишком поздно». Без правильной настройки такие сообщения просто игнорируются. Во фреймворках можно задать период ожидания (или допустимое опоздание), чтобы система в течение определенного времени ожидала запаздывающие сообщения и корректно их обрабатывала. 12.5.2. Временные окна Мы уже упоминали временны́ е окна, но не углублялись в подробности. На рис. 12.11 показаны типы таких окон. Самый простой тип временно́го окна — перекатывающееся. Мы задаем длину окна, и, как только одно окно завершается, сразу начинается следующее. Эти окна не перекрываются. Перекатывающиеся окна используются, когда требуется обработать каждое сообщение один раз в рамках временно́го окна. Это удобно для выполнения биллинга или измерения энергопотребления устройства, когда необходимы актуальные показания мощности на определенные моменты времени. Перекатывающиеся окна не помогут узнать, сколько транзакций по кредитным картам произошло за последние 5 минут. Здесь нас интересует непрерывный расчет суммы, а не количество транзакций с первой по вторую минуту — для
   246 Часть IV. Kafka в корпоративной среде таких задач идеально подходят скользящие окна. В этом случае тоже требуется только параметр длины временно́го окна, чтобы выполнять вычисления по формуле: «рассчитать X за последние Y минут». Рис. 12.11. Фреймворки потоковой обработки поддерживают различные типы временны́х окон: перекатывающиеся (tumbling), скользящие (sliding), прыгающие (hopping) и сеансовые (session) Однако если нужен ежедневный отчет, например, о средней выручке за последние семь дней, то скользящие окна не подходят. Для этого Kafka Streams предлагает прыгающие окна. Они настраиваются по двум параметрам: длина временно́го окна (в примере — семь дней) и время, через которое начинается новое окно (в примере — один день). Окно начинается и вычисления выполняются независимо от наличия новых сообщений. Прыгающие окна помогают получить результаты за фиксированный интервал для перекрывающихся периодов. ПРИМЕЧАНИЕ Для близко расположенных интервалов скользящие окна обычно предпочтительнее, так как не приходится часто пересчитывать результат. Прыгающие окна отлично подходят для периодического расчета результатов. Наконец, сеансовые окна закрываются, если сообщения не поступают в течение определенного периода, то есть между двумя окнами образуется разрыв. Как следует из названия, такой подход полезен для анализа пользовательских сеансов.
   Глава 12. Потоковая обработка 247 12.5.3. Пример использования: обнаружение мошенничества Один из классических примеров обработки потоковых данных в реальном времени — моментальное выявление и предотвращение мошеннических действий. Например, если мы знаем, что у приложения высокий риск мошеннических попыток аутентификации, то можно настроить потоковую обработку, которая отслеживает такие попытки практически в реальном времени. Если пользователь совершает более трех неудачных попыток входа в течение минуты, то система может потребовать дополнительное подтверждение личности через push-уведомление или телефонный звонок. Реализация этого сценария показана на схеме, изображенной на рис. 12.12. Все попытки аутентификации записываются в топик auth_requests. Сервис обнаружения мошенничества проверяет совершение как минимум трех попыток в течение минуты. При превышении лимита в топик possible_frauds записывается сообщение. В Kafka Streams эта топология реализуется с помощью такого кода: KStream<String, String> authRequests = builder.stream("auth_requests"); KTable<Windowed<String>, Long> windowedCounts = authRequests Подготовка агрегации путем группировки .groupByKey() .windowedBy(TimeWindows.ofSizeWithNoGrace( На этот раз выполняется Duration.ofMinutes(1))) агрегация по окну .count(); Собственно агрегация KStream<String, Long> possibleFrauds = windowedCounts .toStream() Фильтрация попыток мошенничества .filter((windowedKey, count) -> count > 3) .map((windowedKey, count) -> Распаковка KeyValue.pair(windowedKey.key(), count)); windowedKey possibleFrauds.to("possible_frauds"); Запись попыток мошенничества в выходной топик Сначала создаем поток KStream с именем authRequests для чтения данных из топика. Затем создаем временно́е окно длительностью 1 минута и подсчитываем количество попыток в рамках этого окна. Наконец, фильтруем результаты подсчета, оставляя случаи, где попыток не менее трех, записываем их в топик подозрительных событий possible_frauds. Во Flink SQL этот код можно реализовать так (при условии, что входные и выходные таблицы уже созданы): INSERT INTO possible_frauds SELECT user_id, COUNT(*) AS request_count, TUMBLE_START(request_time, INTERVAL '1' MINUTE) AS window_start, TUMBLE_END(request_time, INTERVAL '1' MINUTE) AS window_end FROM auth_requests GROUP BY user_id, TUMBLE(request_time, INTERVAL '1' MINUTE) HAVING COUNT(*) > 3;
   248 Часть IV. Kafka в корпоративной среде Рис. 12.12. Топология Kafka Streams для обнаружения мошеннических попыток в реальном времени 12.6. Масштабирование Kafka Streams Производительность Kafka во многом основана на параллелизации, достигаемой с помощью партиций. Для распараллеливания обработки потоков и повышения производительности работу нужно разделить на мелкие части, которые могут обрабатываться независимо. Разные фреймворки потоковой обработки реализуют это деление по-разному, и в этом разделе мы сосредоточимся на Kafka Streams. Apache Flink обычно настраивается как отдельный кластер и управляет своими задачами автономно, с некоторыми отличиями от Kafka Streams. Вдобавок существует Flink-оператор, упрощающий развертывание и управление кластерами Flink в средах Kubernetes. Мы не будем подробно рассматривать Flink, но базовые принципы схожи, и понимать эти концепции полезно, чтобы применять их в других контекстах. Kafka Streams, в соответствии с подходом Kafka к параллелизации, полагается на возможность независимой обработки партиций. KStream и KTable делятся на партиции, чтобы их можно было обрабатывать разными задачами.
   Глава 12. Потоковая обработка 249 Задачи — минимальная единица обработки в Kafka Streams. Партиции жестко закрепляются за задачами, а количество задач в приложении зависит исключительно от количества обрабатываемых партиций. ПРИМЕЧАНИЕ Если это напоминает вам Kafka Connect или группы потребителей, то вы все правильно поняли — базовый протокол здесь (почти) такой же. Например, если требуется фильтровать топик с 12 партициями, то Kafka Streams создаст 12 задач — по одной для каждой партиции. Эти задачи распределяются между экземплярами Kafka Streams. Если экземпляр только один, то обрабатывает все 12 задач и, соответственно, все 12 партиций. Если экземпляров шесть, то каждый берет себе по две задачи. Для распределения задач по экземплярам в Kafka Streams используется протокол перебалансировки Kafka. При сбое экземпляра задачи перераспределяются по другим экземплярам. Kafka Streams поддерживает распределение задач не только между экземплярами, но и внутри каждого экземпляра между потоками. Количество потоков на экземпляр настраивается с помощью параметра Kafka Streams num.threads. Задачи распределяются как между экземплярами, так и внутри экземпляра между потоками. Предположим, у нас есть приложение Kafka Streams с девятью задачами, читающими данные из топика. Запускаются два экземпляра: один с двумя потоками, другой — с тремя. Задачи могут разделиться (рис. 12.13). У каждого потока есть свой внутренний потребитель и продюсер. Все потоки, кроме потока 3 на втором экземпляре, обрабатывают по две задачи, каждая из которых выполняет свою топологию и читает сообщения из топика Kafka. У каждой задачи есть свое хранилище состояний. Что происходит с хранилищами состояний? При их использовании Kafka Streams создает отдельное хранилище для каждого обработчика потока на каждую задачу. Если экземпляр выходит из строя и его заменяет другой, то хранилище состояний должно быть восстановлено из внутреннего топика журнала изменений, связанного с этим хранилищем. Внутренний топик считывается, и новое хранилище состояний заполняется соответствующими данными. СОВЕТ Если установить num.standby.replicas=1 или выше, то можно избежать повторного создания хранилищ состояний, так как другой экземпляр создает копию хранилища. При сбое активного экземпляра другой экземпляр может взять на себя его задачи, не воссоздавая хранилище с нуля. Ключи позволяют сохранять связанные сообщения в пределах одной партиции. В Kafka Streams мы уже сталкивались с операциями наподобие selectKey()
   250 Часть IV. Kafka в корпоративной среде и map(), которые меняют ключ. При выполнении таких операций поток (или таблица) помечается для перераспределения по партициям. Если мы применяем к такому потоку операции без сохранения состояния, например filter() или mapValues(), то беспокоиться не нужно — не имеет значения, на каком экземпляре выполняется операция. Рис. 12.13. Приложения Kafka Streams разделяются на задачи, которые распределяются между разными потоками на разных экземплярах. Если один экземпляр выходит из строя, то его задачи передаются другому Но если мы выполняем операцию соединения или агрегации, то Kafka Streams должен гарантировать, что все сообщения обрабатываются задачей, соответствую­ щей ключу. Для этого Kafka Streams создает внутренний топик с суффиксом -repartition. Перед выполнением соединения или агрегации данные записываются в этот топик и немедленно читаются из него. Это значительно увеличивает сетевой трафик и требования к хранилищу, но такой подход гарантирует корректное выполнение соединений и агрегаций. В Kafka Streams мы не можем записать данные в топик и считать их в пределах одной задачи. Получается, нужно разделить топологию (логику обработки) на
   Глава 12. Потоковая обработка 251 две части, называемые субтопологиями. Первая отвечает за начальный этап обработки и записывает данные в топик -repartition. Вторая затем читает данные из этого топика и выполняет дальнейшую обработку. На рис. 12.14 показана топология, где сначала вызывается selectKey(), а затем выполняется count(). Поскольку selectKey() меняет ключ, то требуется перераспределение по партициям, и поэтому топология состоит из двух субтопологий: одна — для части selectKey(), другая — для агрегации. Рис. 12.14. Если сначала выполняется selectKey(), а затем агрегация, например count(), то топологию нужно разделить на несколько субтопологий Резюме Kafka обеспечивает почти мгновенный обмен данными — в отличие от традиционной пакетной обработки с задержками. Kafka Streams, Apache Flink, Faust и Goka — популярные библиотеки потоковой обработки, каждая из которых имеет свои преимущества. В потоковой обработке события обрабатываются по отдельности в реальном времени, но нужно смириться с согласованностью в конечном счете. Потоковые обработчики, такие как в Kafka Streams и Apache Flink, преобразуют, фильтруют и анализируют потоки данных в реальном времени, используя функциональный или декларативный подход к программированию.
   252 Часть IV. Kafka в корпоративной среде Примеры обработчиков: filter() для выбора данных, map() для преобразования, merge() для объединения потоков и split() для разделения данных на основе условий. Обработка с сохранением состояния позволяет выполнять такие операции, как подсчет (count()) и агрегация данных, которые требуют хранения промежуточных состояний. Топологии обработчиков объединяют несколько обработчиков в целях создания сложных рабочих процессов, таких как объединение потоков, фильтрация значительных изменений и агрегация данных для анализа. Streaming SQL позволяет выполнять обработку данных в реальном времени аналогично традиционному SQL, но выводит результаты непрерывно, а не дает окончательный ответ. Flink SQL обеспечивает потоковую обработку на основе SQL путем определения таблиц, связанных с топиками Kafka, вместо хранения данных внутри кластера Flink. Такие запросы, как фильтрация (WHERE), агрегация (GROUP BY) и подсчет (COUNT(*)), работают аналогично SQL в базах данных, но обрабатывают непрерывные потоки данных. В продакшене обычно используется режим headless, в рамках которого предопределенные SQL-запросы выполняются непрерывно, а результаты записываются обратно в Kafka или в другие базы данных. Потоки захватывают непрерывные данные, тогда как таблицы хранят последнее состояние, чтобы упростить запросы. Kafka Streams и Flink поддерживают оба подхода, используя хранилища пар «ключ — значение» и топики журнала изменений для предотвращения потери данных. Агрегации используются для определения текущего состояния, например суммирования транзакций для банковского счета. Результаты сохраняются в таблицах и доступны для последующего анализа. Соединения обогащают данные путем комбинирования потоков с таблицами или другими потоками. Соединения «поток — таблица» активизируются при появлении новых данных в потоке, тогда как соединения «таблица — таблица» активизируются при изменениях с обеих сторон. Соединения «поток — поток» возможны благодаря использованию временны́х окон. Время в Kafka включает в себя время события (момент возникновения события), время создания (момент создания продюсером), время записи в лог (момент записи брокером) и время потока (момент обработки). Временны́е операции в Kafka используют временны́е окна для агрегации или корреляции данных.
   Глава 12. Потоковая обработка 253 Распространенные типы временны́х окон: перекатывающиеся (не перекрываются), прыгающие (перекрываются), скользящие (на основе новых данных) и сеансовые (на основе бездействия). Параллелизация в Kafka Streams основана на разделении задач по партициям. Задачи являются минимальными единицами обработки и распределяются между экземплярами Kafka Streams. Хранилища состояний создаются для каждой задачи, и при сбое экземпляра хранилище восстанавливается из топика журнала изменений. Настройка num.standby.replicas позволяет избежать восстановления благодаря резервным хранилищам состояний. При выполнении таких операций, как selectKey(), происходит перераспределение по партициям. Kafka Streams создает внутренний топик перераспределения для выполнения соединений и агрегаций, чтобы обеспечить корректную обработку на основе ключей. Обработка при этом часто делится на субтопологии.
13 Управление В этой главе 3 Стратегии обеспечения целостности и совместимости данных. 3 Меры безопасности для защиты окружений Kafka. 3 Контроль распределения ресурсов. 3 Предотвращение перегрузки кластера. Эффективное управление крайне важно для любой архитектуры, ориентированной на данные, и Apache Kafka не исключение. Организациям, которые используют Kafka для управления потоками данных в реальном времени, требуется структурированный надзор. При отсутствии структурированного управления схемами могут возникнуть серьезные проблемы с согласованностью данных. Например, изменение схемы продюсера данных (добавление нового поля и т. д.) может нарушить работу нижестоящих обработчиков, не подготовленных к таким изменениям, что приведет к сбоям приложений и дорогостоящим простоям. Аналогичным образом в отсутствие надежных мер безопасности система становится уязвимой для несанкционированного доступа, что может привести к утечкам данных, их подделке или даже потере конфиденциальной информации клиентов. Кроме того, при плохом управлении ресурсы распределяются неравномерно, так что один клиент перегружает кластер Kafka, ухудшая производительность для других пользователей и приложений.
   Глава 13. Управление 255 Вернемся к нашему примеру с интернет-магазином, где Kafka используется для обработки транзакций в реальном времени. При отсутствии управления схемами несогласованность данных о товарах из разных источников может привести к ошибкам в системах учета запасов или к некорректному отображению цен. Если пренебрегать мерами безопасности, то злоумышленники могут перехватить конфиденциальные платежные данные, что приведет к финансовым потерям и репутационному ущербу. Эти сценарии демонстрируют, что правильное управление — не желательный, а обязательный компонент, лежащий в основе надежности и бесперебойной работы бизнес-процессов. Мы начнем эту главу с управления схемами, которое служит основой для согласованности данных и совместимости в Kafka. Правильное управление схемами гарантирует, что продюсеры и потребители данных могут эффективно взаимодействовать друг с другом, даже если схемы меняются. Далее мы углубимся в вопросы безопасности Kafka, обсудив аутентификацию и авторизацию, которые защищают конфиденциальные данные и гарантируют, что просматривать и изменять их смогут только авторизованные пользователи. Кроме того, мы поговорим о том, насколько шифрование — как при передаче данных, так и при их хранении — важно для их целостности и конфиденциальности. Наконец, мы рассмотрим реализацию квот как стратегию управления ресурсами. Квоты помогают поддерживать общее состояние кластера Kafka, предотвращая конкуренцию за ресурсы и гарантируя, что ни один клиент не сможет перегрузить систему. Имея представление об этих компонентах управления, организации смогут создавать более контролируемое и безопасное окружение Kafka, что позволит им раскрыть весь потенциал своих потоков данных, обеспечивая при этом соответствие требованиям и надежность. Глава содержит практические рекомендации и лучшие методы, которые помогут командам выстраивать эффективные системы управления в своих реализациях Kafka. 13.1. Управление схемами Данные всегда имеют схему, даже если она не задокументирована и не согласована. Например, с появлением баз данных NoSQL, таких как MongoDB, утверждалось, что разработку можно значительно ускорить, когда не приходится долго продумывать схемы, как в реляционных базах данных. Однако документы MongoDB тоже имеют схему, даже если она нигде не описывается и никак не контролируется. Читая данные из документа MongoDB, приложение ожидает получить данные в определенном формате. Непредвиденная или несовместимая схема данных приведет к проблемам в приложении. В лучшем случае оно завершится с ошибкой. В худшем — молча примет данные и выдаст некорректные результаты.
   256 Часть IV. Kafka в корпоративной среде Ситуация усугубляется тем, что схемы меняются со временем из-за новых требований или изменившихся предпосылок. В главе 3 мы уже рассмотрели типы сообщений, встречающиеся при работе с Kafka, и используемые форматы данных. В этой главе мы обсудим схемы, взглянув на них через призму управления. 13.1.1. Зачем нужны схемы Мы начинаем с простой записи из примера с интернет-магазином (рис. 13.1). Товар представляет собой JSON-объект, содержащий идентификатор (например, 123), имя (coffee pad), цену (10) и количество на складе (1010). Сначала необходимо определить, где это описано: существует ли каталог со всеми схемами в Kafka и документацией к ним или заинтересованные стороны должны самостоятельно анализировать данные в топиках и воссоздавать схему? Рис. 13.1. Мы начинаем с простого JSON-объекта для товара. На основе этой структуры можно добавлять, удалять или изменять поля. С самого начала следует продумать, какие изменения схемы должны поддерживаться и как будет выглядеть этот процесс Независимо от использования реестра схем (подробнее об этом — позже), мы рекомендуем создать для Kafka централизованное место, в котором схемы для отдельных топиков будут задокументированы и доступны для просмотра. Это может быть корпоративная вики, центральный Git-репозиторий или специально созданная платформа. Способ реализации некритичен — важно знать, что такое место существует. Со временем появятся новые требования к этому продукту данных. Например, понадобится добавить категорию (drinks). Существующие потребители, вероятно, смогут обработать это изменение, игнорируя новое поле. Но что, если появится новый потребитель, которому нужно будет читать исторические данные? Могут возникнуть проблемы. Потом мы захотим удалить поле с уровнем запасов или сохранить его в другом месте. Новые потребители должны легко понимать старые данные, но существующие потребители начнут работать некорректно. Или, например, интернет-магазин выходит на международный рынок и цена должна отображаться в разных валютах. Мы расширяем поле цены, добавляя
   Глава 13. Управление 257 валюту, но возникают проблемы: старые потребители не смогут обрабатывать новые данные, а новые потребители — старые данные. Как видите, стоит с самого начала продумать возможные изменения схем данных. Необходимо обсудить совместимость, поскольку схема данных представляет собой контракт между продюсером (обычно владельцем данных) и потребителем (обычно клиентом данных). Кроме того, схема должна определять, какие изменения можно вносить без согласования, а какие — нет. Многие уже знакомы с этим принципом по реляционным базам данных, особенно когда несколько команд используют общую базу. В некоторых обстоятельствах каждое значительное изменение схемы может потребовать обсуждения и координации со всеми командами. Однако преимущество реляционных баз данных заключается в том, что такие миграции схем часто можно выполнять атомарно. Старые данные переносятся в новую схему, и все клиенты немедленно получают актуальное состояние. Конечно, при больших объемах данных это тоже сопряжено со сложностями. В Kafka такая возможность часто отсутствует. Данные могут храниться в топике длительное время, а потребители в худшем случае должны обрабатывать несколько версий одних и тех же данных. Бывает даже, что несколько продюсеров одновременно записывают данные с разными схемами. К сожалению, перенос старых данных в новую схему в таких условиях требует усилий. СОВЕТ Иногда требуются изменения, нарушающие обратную совместимость. В этом случае часто необходимо создать новый топик, перенести старые данные, а затем выполнить миграцию для продюсеров и потребителей. Процедура аналогична той, которая описана в главе 6 и предназначена для изменения количества партиций. 13.1.2. Уровни совместимости Совместимость можно рассматривать с двух точек зрения: потребителя или продюсера, что часто вызывает путаницу в трактовке. Данные обычно предоставляются командой продюсера (которая, как правило, работает с актуальными версиями), поэтому сосредоточимся на противоположной стороне и рассмотрим совместимость данных с позиции потребителя. Продемонстрируем разные уровни совместимости на примере интернет-магазина. Начинаем со следующей схемы товаров: { } "id": "123", "name": "Coffee Pad", Обязательные поля "price": "10", "stock": 1010, "origin": null Необязательное поле
   258 Часть IV. Kafka в корпоративной среде Нет совместимости С точки зрения продюсера самый простой случай — когда совместимость не учитывается, а потребители вынуждены всегда работать с актуальными версиями. В этом сценарии поля можно свободно добавлять или удалять, но потребители столкнутся с трудностями при чтении исторических данных; а если потребители не обновляются синхронно с продюсерами, то могут возникнуть дополнительные проблемы. Такой подход хорошо работает, если продюсер и потребитель управляются одной командой, а данные в Kafka не хранятся длительное время. Классический пример изменения, нарушающего все уровни совместимости, — переименование полей: { Обязательное поле (изначально: id) "productId": "123", Обязательное поле (изначально: name) "productName": "Coffee Pad", "currentPrice": "10", Обязательное поле (изначально: price) "currentStock": 1010, Обязательное поле (изначально: stock) "countryOfOrigin": "Germany" Обязательное поле } (изначально: необязательное, имя: origin) Потребитель, ожидающий предыдущую версию, не сможет работать с новыми данными, а потребитель, ожидающий новые данные, не сможет обрабатывать старые. Обратная совместимость В этом случае данные структурированы таким образом, что потребители, использующие текущую версию, могут читать и более старые версии данных. Это особенно полезно, например, когда при запуске нового сервиса необходимо прочитать все старые данные. Сложность в том, что при изменении схемы требуется сначала обновить потребители, а затем продюсеры. Поскольку схемой обычно управляет продюсер, то это действие редко вписывается в процессы компании. Пример изменения схемы, которое поддерживается потребителями с обратной совместимостью, — удаление полей: { "id": "123", Обязательные поля "name": "Coffee Pad", "price": "10", Поле stock удалено "origin": null Необязательное поле } Потребитель, знакомый с этой схемой, не должен испытывать проблем со старыми схемами, так как может просто игнорировать поле stock. Однако старый потребитель, ожидающий поле stock, столкнется с проблемами при работе с новой схемой, когда не найдет ожидаемое поле.
   Глава 13. Управление 259 Прямая совместимость С процедурной точки зрения проще, когда схема сначала обновляется продюсером, а потребители адаптируются позже. Минус в том, что мы не сможем гарантировать чтение исторических данных потребителями. Если данные хранятся в Kafka кратковременно, а продюсеры и потребители разрабатываются разными командами, то этот уровень совместимости реализовать обычно проще всего. На практике он охватывает наиболее распространенное изменение схемы — добавление полей, как показано в примере ниже: { } "id": "123", "name": "Coffee Pad", "price": "10", "stock": 1010, "origin": null, "category": "utils" Обязательные поля Необязательное поле Новое обязательное поле Мы добавили поле категории для товара, и старый потребитель может легко читать новые данные, просто игнорируя новое поле. Проблема возникает, когда потребитель, ожидающий новую схему, пытается прочитать исторические данные и не находит в них обязательное поле category. Как вариант решения проблемы можно сделать поле category необязательным или назначить ему значение по умолчанию (например, null). Полная совместимость В идеальном сценарии потребитель может читать как старые, так и новые данные. Такой подход ограничивает эволюцию схемы и часто вынуждает нарушать совместимость, однако во многих окружениях это оправданно. При полной совместимости неважно, кто обновит схему первым — продюсер или потребитель. Ниже показан пример изменения, совместимого в обе стороны: удаление и добавление необязательных полей. { "id": "123456", "name": "Coffee Pad", Обязательные поля "price": "10", "stock": 1010, Удалено необязательное поле origin "nutritionInfo": null } Новое обязательное поле Используя новую схему, старые потребители по-прежнему смогут обрабатывать данные, поскольку проигнорируют поле nutrition-Info, а поле origin и так было необязательным. Новые потребители также смогут работать с исходной схемой, поскольку отсутствующее поле nutritionInfo является необязательным, а поле origin игнорируется.
   260 Часть IV. Kafka в корпоративной среде Обзор уровней совместимости На рис. 13.2 показаны различные уровни совместимости схем. Вдобавок к ним можно определить, должна ли совместимость применяться транзитивно. В этом контексте прямая транзитивность означает, например, что потребитель должен уметь обрабатывать все будущие версии схемы, а не только следующую. В идеале следует обеспечить полную транзитивную совместимость, при которой потребитель может читать все прошлые и будущие данные. Рис. 13.2. Мы рассматриваем совместимость схемы с точки зрения потребителя и версий схем, которые он принимает. Если мы не можем или не хотим гарантировать какой-то вид совместимости, то потребитель будет понимать только свою версию схемы. Потребитель с обратной совместимостью может читать исторические данные, но не способен обрабатывать новые. Потребитель с прямой совместимостью читает новые данные, но не прошлые. Оптимальный вариант — потребитель с совместимостью в обе стороны, способный читать старые и новые данные Чтобы определить необходимый тип совместимости для схемы, нужно понимать, как изменения влияют на совместимость. Обобщенная информация об этом приведена в табл. 13.1. Если версии схем не требуют взаимной совместимости, то мы можем добавлять или удалять поля без особых ограничений. Необязательные поля всегда можно добавлять или удалять, так как это не влияет на уровень совместимости. В целях поддержания обратной совместимости можно удалять обязательные поля, но нельзя добавлять новые. В случае прямой совместимости ситуация обратная: можно добавлять обязательные поля, но нельзя удалять существующие. Если схема должна быть совместима в обоих направлениях, то разрешено только добавлять или удалять необязательные поля. СОВЕТ Рекомендуем централизованно хранить задокументированные схемы отдельных топиков. В этой документации также должен определяться уровень совместимости для схемы. Названия схем могут быть неочевидными, поэтому необходимо разъяснить, как они влияют на поведение клиентов при обновлениях. Сама Kafka не предоставляет механизмов для принудительного соблюдения этих правил. Требуется дополнительный компонент.
   Глава 13. Управление 261 Таблица 13.1. Операции с полями для разных уровней совместимости Операция Нет совместимости Обратная совместимость Прямая совместимость Полная совместимость Удалить обязательное поле Да Да Нет Нет Удалить необязательное поле Да Да Да Да Добавить обязательное поле Да Нет Да Нет Добавить необязательное поле Да Да Да Да 13.1.3. Реестры схем Реестры схем используются для централизованного управления схемами и их применения в экосистеме Kafka. Такой реестр отвечает за хранение и версионирование схем, проверку совместимости при изменениях схем и предоставление этой информации продюсерам и потребителям. Изначально реестры схем разрабатывались для эффективного хранения данных Avro в Kafka, а теперь также поддерживают схему JSON и Protobuf. Реестры выступают центром управления схемами и служат единым источником истины для всех схем в кластере Kafka. Кроме того, они позволяют управлять разными версиями схемы, меняющимися со временем, а также автоматически проверяют совместимость новых версий со старыми. Наконец, они бесшовно интегрируются с продюсерами и потребителями Kafka, гарантируя соответствие схемам. Несмотря на все преимущества, реестры схем имеют свои недостатки. Существую­ щие решения, к сожалению, в основном ориентированы на технические детали и часто не предоставляют достаточно возможностей для документирования или визуализации схем. Кроме того, реестр схем — это дополнительный компонент, повышающий сложность системы, а если он выйдет из строя, то это может повлиять на все продюсеры и потребители. В окружениях с несколькими кластерами использовать реестры схем особенно сложно — требуются тщательное планирование и настройка. Еще одна проблема — идентификаторы схем. У них нет никакого семантического значения, и они назначаются последовательно, что усложняет восстановление реестра при повреждении данных в Kafka. ПРЕДУПРЕЖДЕНИЕ При переносе схем из одного реестра в другой вручную идентификаторы могут измениться, что не позволит выполнить корректную миграцию.
   262 Часть IV. Kafka в корпоративной среде Kafka работает только с байтовыми массивами, поэтому проверка схем выполняется исключительно на стороне клиента. Теоретически злонамеренный или неисправный продюсер может записывать в топик данные, не соответствующие определенной схеме. Снизить этот риск можно разными способами. Один из вариантов — единый шаблон продюсера, при котором право записи в топик предоставляется только команде — владельцу топика. Этот подход повышает безопасность, но при этом значительно ограничивает использование Kafka. Другой метод — валидация схем на стороне брокера, но такой функцио­ нал в Kafka не реализован. Confluent Server и Confluent Cloud в окружениях Confluent нативно предоставляют валидацию схем на стороне брокера, что очень эффективно, но доступно только клиентам Confluent. Альтернатива — прокси для Kafka, выполняющий валидацию схем. Здесь стоит отметить два решения: Conduktor (коммерческий провайдер и пионер в области прокси Kafka с функцией валидации схем) и Kroxylicious (решение с открытым исходным кодом, поддерживаемое Red Hat). Несмотря на новизну, Kroxylicious также предоставляет шифрование данных при хранении и может стать перспективным вариантом. Еще один важный момент — управление версиями схем. В продакшене мы не рекомендуем разрешать продюсерам автоматически создавать новые версии схем — лучше управлять схемами через конвейер развертывания в выбранном реестре схем. Такой подход обеспечивает лучший контроль и отслеживаемость разработки схем, а также снижает риск непреднамеренных изменений, которые могут привести к проблемам совместимости. Несмотря на эти сложности и необходимые меры предосторожности, в настоящее время нет убедительных альтернатив существующим решениям для реестров схем. В зависимости от сценария и инфраструктуры можно выделить три реализации. СОВЕТ Некоторые утверждают, что общая библиотека с моделью схем, используемая продюсерами и потребителями, обеспечивает проверку типов на этапе компиляции. Однако при этом создается жесткая связь между командами и требуются согласованные развертывания между сервисами при изменении схем. Наиболее известное и популярное решение — Confluent Schema Registry, которое мы рекомендуем клиентам, уже использующим платформу Confluent. Для пользователей открытых решений Kafka или других дистрибутивов этот вариант не подойдет, так как требуется лицензия. Альтернативой может быть Karapace Schema Registry — решение от Aiven, совместимое по API с Confluent Schema Registry. Несмотря на ограниченный функционал, Karapace подходит для большинства задач.
   Глава 13. Управление 263 Третий вариант — Apicurio Schema Registry. Это решение не только выполняет функции реестра схем для Kafka, но и поддерживает OpenAPI и AsyncAPI. Apicurio дополнительно предоставляет студию проектирования API и инструменты документирования схем. Наш опыт работы с этим решением пока невелик, но мы рекомендуем изучить его подробнее, так как оно предлагает перспективные возможности для комплексного управления API. 13.1.4. Avro Формат данных Avro напрямую не связан с Kafka, но именно он послужил основой для внедрения реестра схем и стал очень популярным в экосистеме Kafka. Как и широко используемый Protobuf, Avro представляет собой бинарный формат данных. Особенность его в том, что каждое сообщение состоит из двух частей: схемы и непосредственно полезных данных сообщения, то есть сообщение Avro является самодостаточным. Это свойство особенно важно для Kafka, где данные могут храниться дли­ тельное время, и в одном топике часто сосуществуют несколько версий схем. Благодаря своей самодостаточности Avro — идеальный формат в сценариях, когда схемы эволюционируют, но мы должны отслеживать их с течением времени. Схемы Avro обычно описываются в формате JSON. В частности, предыдущий пример можно выразить в виде такой схемы: { } "type": "record", "name": "Product", "namespace": "com.example.supermarket", "fields": [ {"name": "id", "type": "string"}, {"name": "name", "type": "string"}, {"name": "price", "type": "float"}, {"name": "stock", "type": "int"}, {"name": "origin", "type": ["null", "string"], "default": null} ] А так мы можем выразить на Avro следующее сообщение: { } "id": "123", "name": "Coffee Pad", "price": "10", "stock": 1010, "origin": null, "category": "utils" Обязательные Необязательное Обязательное (новое)
   264 Часть IV. Kafka в корпоративной среде При кодировании в бинарном формате полезная нагрузка составляет 35 байт, однако схема, хранящаяся в формате JSON внутри Avro, занимает 292 байта. Это означает, что размер сообщения превышает необходимый в десять раз. Для решения этой проблемы можно не отправлять схему с каждым сообщением, а хранить ее в реестре схем, включая в само сообщение только идентификатор схемы. При таком подходе сообщения в Kafka остаются компактными, и можно отслеживать схему для каждого отдельного сообщения. Мы получаем надежную основу для эволюции схем, не рискуя погрузиться в хаос. 13.2. Безопасность Обеспечение безопасности ИТ-систем — фундаментальная задача любой ИТ-команды. Прежде чем обсуждать методы эффективной защиты в Kafka, необходимо четко определить, от каких угроз мы защищаемся. В первую очередь требуется гарантировать, что доступ к системе имеют только авторизованные пользователи — люди или технические компоненты. Для этого используется аутентификация. Kafka выполняет ее однократно при установке соединения. Для аутентифицированных пользователей необходимо обеспечить выполнение только разрешенных действий, что реализуется через авторизацию. Раньше внутренний сетевой трафик считался безопасным, но сегодня так бывает редко. В облачных средах следует предполагать, что сетевой трафик могут перехватить или изменить, поэтому крайне важно шифровать и подписывать сетевой трафик, чтобы поддерживать целостность данных. Kafka сохраняет данные на дисках, поэтому рекомендуется шифровать данные и при хранении. Однако целесообразность шифрования данных в состоянии покоя может вызывать вопросы, так как системы хранения обычно активны. Шифрование дисков в первую очередь защищает от физических атак, затрудняя доступ к данным при извлечении дисков. Но, учитывая относительную простоту и низкую стоимость шифрования дисков, этим вариантом следует воспользоваться. Такие меры, как сетевое шифрование, подпись данных и шифрование дисков, не защищают от администраторов с правами доступа к серверу или Kafka, поэтому данные в Kafka обычно классифицируются как внутренние и конфиденциальные. Для передачи данных с высоким уровнем конфиденциальности через Kafka требуется сквозное шифрование. На рис. 13.3 показаны ключевые меры безопасности, применимые к Kafka: аутентификация для подключения только авторизованных пользователей; авторизация для ограничения их действий;
   Глава 13. Управление 265 сетевое шифрование и подпись данных для защиты информации при передаче; шифрование дисков для защиты хранимых данных; сквозное шифрование для данных с высоким уровнем конфиденциальности. В следующих подразделах мы рассмотрим, как обеспечить безопасность Kafka на практике. Рис. 13.3. Защита кластера Kafka включает в себя шифрование на транспортном уровне для коммуникации, аутентификацию и авторизацию пользователей, а также шифрование хранимых данных ПРЕДУПРЕЖДЕНИЕ Для старых кластеров Kafka также важно правильно защитить ZooKeeper. В частности, необходимо включить шифрование на транспортном уровне, хотя в старых кластерах Kafka это было сложно реализовать. 13.2.1. Шифрование на транспортном уровне Независимо от выбранного протокола аутентификации и дальнейших действий шифрование на транспортном уровне стало обязательным требованием для большинства ИТ-систем. На практике часто встречаются окружения Kafka, изначально созданные без механизмов безопасности, но необходимость в них возникает позже. К счастью, в этом случае мы можем настроить несколько слушателей, разрешив как зашифрованное, так и незашифрованное взаимодействие в течение переходного периода — например, во время переконфигурации системы. Подробнее мы рассмотрим этот подход в подразделе 13.2.7.
   266 Часть IV. Kafka в корпоративной среде Для шифрования трафика Kafka использует стандартный протокол TLS, но его применение имеет свои недостатки. Помимо сложностей с настройкой, использование TLS снижает производительность Kafka. Как правило, если система уже работает на пределе возможностей инфраструктуры и без TLS, то перед внедрением шифрования необходимо тщательно протестировать производительность. В большинстве известных сценариев с умеренной загрузкой центрального процессора включение TLS приводит к незначительным потерям производительности. В большинстве окружений требования к безопасности обычно предопределены, и инфраструктура должна им соответствовать. В теории настроить TLS нетрудно, однако на практике часто возникают сложности. Первый шаг — выбрать схему внедрения. Обычно шифрование требуется как для взаимодействия между клиентом и брокером, так и для коммуникации между брокерами. TLS основан на асимметричной криптографии с использованием открытых и закрытых ключей. Для каждого брокера генерируется пара ключей (открытый и закрытый), которая затем подписывается в центре сертификации (Certificate Authority, CA). Далее настраивается хранилище ключей для каждого брокера, содержащее соответствующий закрытый ключ. Чтобы клиенты могли проверять подключение к нужному брокеру, мы настраиваем хранилище доверенных сертификатов с открытым ключом CA и распространяем его среди клиентов. Эту же процедуру необходимо выполнить на брокерах для обеспечения взаимного доверия между ними. Теоретически мы могли бы привести здесь команды keytool, чтобы показать правильную настройку TLS, но этот материал занял бы несколько страниц и имел бы низкую обучающую ценность, поэтому мы опустим эту тему. Вы найдете множество подробностей онлайн, а здесь мы покажем только фрагмент конфигурации брокера, иллюстрирующий возможную настройку: Настройка двух слушателей, через которые брокер принимает запросы listeners=PLAINTEXT://$IP:9092,SSL://$IP:9093 Сопоставление имен слушателей listener.security.protocol.map=\ с протоколами шифрования PLAINTEXT:PLAINTEXT,SSL:SSL ssl.keystore.location=/home/user/certs/broker.ks.p12 ssl.keystore.type=PKCS12 Настройка хранилища ssl.keystore.password=keystore-pw ключей ssl.key.password=keystore-pw ssl.truststore.location=\ /home/user/certs/ca.truststore.p12 Настройка хранилища ssl.truststore.password=truststore-pw доверенных сертификатов ssl.truststore.type=PKCS12
   Глава 13. Управление 267 В дополнение к слушателю незашифрованного текста на порте 9092 мы настраи­ ваем второй слушатель, используя TLS на порте 9093. Для слушателей можно выбрать произвольные имена, например, EXTERNAL для внешнего трафика, PLAINTEXT для незашифрованного трафика или SSL — для зашифрованного. Для каждого слушателя необходимо указать соответствующий протокол безопасности. Наконец, настраиваем конфигурацию хранилищ ключей и доверенных сертификатов. 13.2.2. Аутентификация Теперь, когда установлено зашифрованное соединение между клиентами и брокерами, стоит задуматься о проверке подлинности клиентов. Kafka предлагает два варианта: расширить конфигурацию TLS, чтобы клиенты аутентифицировались на брокерах с помощью сертификатов TLS (mutual TLS, mTLS), или использовать один из методов, предоставляемых протоколом SASL (Simple Authentication and Security Layer). Важно подчеркнуть, что аутентификация происходит только во время установки соединения и сохраняется на весь сеанс. Это означает, что если злоумышленник получит контроль над клиентом, то просто удалить пользователя в Kafka будет недостаточно — необходимо также отозвать все его разрешения. Подробнее об этом поговорим в подразделе 13.2.3. Если пользователь не аутентифицирован, то мы все равно можем назначать (или отзывать) разрешения. Позже мы увидим, что авторизация основана на имени принципала, и для неаутентифицированных пользователей это имя User:ANONYMOUS. Популярным вариантом аутентификации является использование mTLS. Обычно TLS применяется для шифрования на транспортном уровне и аутентификации брокеров перед клиентами, поэтому внедрение mTLS для клиентов требует дополнительных усилий. При использовании mTLS клиенты также проходят аутентификацию перед брокерами. Для этого необходимо сгенерировать пару ключей TLS для каждого клиента и получить сертификат, подписанный CA. Такой подход гарантирует взаимное доверие: клиенты убеждаются в подлинности брокеров, а брокеры — в легитимности клиентов. На этапе авторизации требуется указать имя принципала, чтобы можно было назначать или отзывать разрешения. По умолчанию при mTLS этим именем становится вся строка уникального имени (distinguished name, DN), например: CN=alice,OU=myOrgUnit,O=SampleCorp,L=SampleCity,ST=SampleState,C=SampleCountry В большинстве случаев для идентификации пользователя достаточно общего имени (common name, CN). Kafka предоставляет параметр конфигурации listener.name.ssl.ssl.principal.mapping.rules, позволяющий
   268 Часть IV. Kafka в корпоративной среде преобразовывать уникальное имя в имя принципала с помощью регулярных выражений. Мы можем настраивать разные правила, например: listener.name.ssl.ssl.principal.mapping.rules= \ RULE:^CN=([^,]*).*$/$1/L ,\ DEFAULT Здесь из DN извлекается значение CN для последующего использования. Если mTLS не используется, то SASL (фреймворк аутентификации, распространенный в экосистеме Java) предлагает альтернативные методы. Мы не будем описывать их все, скажем лишь, что два самых популярных — это SASL-GSSAPI (аутентификация через Kerberos) и SASL-OAUTHBEARER (аутентификация через OpenID Connect). Оба метода предполагают хранение пользовательских данных во внешних системах. Для большей гибкости Kafka поддерживает SASL-PLAIN, где аутентификация происходит по логину и паролю через специально написанный обратный вызов. Более простой вариант — SASL-SCRAM, механизм запроса-ответа на основе комбинаций имени пользователя и пароля. В этом случае учетные данные хранятся в ZooKeeper или в режиме KRaft, и нам не нужна отдельная система для управления аутентификацией. 13.2.3. Авторизация Итак, у нас есть аутентифицированный принципал и мы знаем, что за клиент взаимодействует с брокером. Следующий шаг — определить, что ему разрешено. При настройке списков контроля доступа (access control list, ACL) рекомендуется придерживаться принципа минимальных привилегий. Рассмотрим его на примере нашего интернет-магазина. Сервису оформления заказов нужны разрешения на запись в топик checkout.changelog для фиксации покупок. Сервису обработки платежей нужны права на чтение этого топика, а также права на чтение для его группы потребителей payment-processor для обработки платежей. Сервис аналитики оформленных заказов, реализованный через Kafka Streams, требует всех разрешений для топиков, имя которых начинается с checkout.analytics.*, поскольку Kafka Streams управляет внутренними топиками, а также разрешение на чтение для топика checkout.changelog для анализа покупательских паттернов. Kafka предоставляет расширяемую систему авторизации, определяющую, разрешено ли конкретному принципалу (по сути — имени аутентифицированного пользователя) выполнять то или иное действие. По умолчанию в Kafka входит только модуль AclAuthorizer, однако некоторые поставщики предлагают модули для управления доступом на основе ролей (role-based access control, RBAC). RBAC удобен в отдельных сценариях, мы считаем его необязательным, поскольку доступ к Kafka обычно предоставляется только техническим пользователям
   Глава 13. Управление 269 и ACL будет достаточно. Естественно, этот процесс должен быть автоматизирован, поэтому далее мы сосредоточимся на модуле ACL. В табл. 13.2 показана структура ACL в Kafka. Каждое правило ACL можно интерпретировать как утверждение: «Принципалу разрешено или запрещено выполнять операцию над ресурсом, идентифицируемым по имени или префиксу». Дополнительно правило может ограничиваться конкретным хостом. Например, можно определить, что принципал User:PriceAnalyticsService имеет разрешение Allow на операции Read для ресурса типа Topic с именем products.pri­ ces.changelog (шаблон: Literal). Таблица 13.2. Структура ACL в Kafka Компонент Параметры Примеры Принципал <type:name> User:PriceAnalyticsService Group:AnalyticsServices Тип ресурса Cluster, Topic, Group, TransactionalId, DelegationToken Topic Тип шаблона Literal, Prefixed Literal Имя ресурса — Literal: products.prices.changelog Prefixed: products.prices Операция Describe, Create, Delete, Alter, Read, Write, DescribeConfigs, AlterConfigs, ClusterAction, IdempotentWrite Read Тип разрешения Allow, Deny Allow Хост <IP Address>, * * Для заданного принципала (и хоста) модуль ACL сначала проверяет, является ли принципал суперадминистратором. Если да, то действие разрешено. В противном случае модуль ищет правило Deny. Если оно существует, то действие отклоняется. Наконец, модуль проверяет наличие правила Allow для этого принципала. Различные компоненты Kafka требуют специальных разрешений для корректной работы. Брокерам необходимы права ClusterAction для кластерного ресурса. Продюсерам требуется как минимум разрешение Topic:Write с дополнительным разрешением Cluster:IdempotentWrite для идемпотентных продюсеров. Кроме того, при использовании транзакций продюсерам нужно разрешение Write для конкретного TransactionalId. Потребителям с простыми требованиями достаточно разрешений Topic:Read для нужных топиков, а также Group:Read для
   270 Часть IV. Kafka в корпоративной среде идентификатора их группы. Приложения Kafka Streams, которые управляют собственными топиками, должны получать разрешения Topic:Create для префикса идентификатора приложения. Настройка автоматизированного конвейера CI/CD для управления пользователями и топиками обычно требует расширенного набора разрешений: Create, Delete, Alter, AlterConfigs, Describe и DescribeConfigs для топика. Конвейеру нужны разрешения Alter и Describe в кластере для управления ACL, а также, возможно, разрешения AlterConfigs и DescribeConfigs в кластере для управления квотами. 13.2.4. Шифрование при хранении Шифрование на транспортном уровне, аутентификация и авторизация считаются обязательными в ИТ-системах. Во многих средах, особенно регулируемых, также важно шифровать данные при хранении на диске. К сожалению, Kafka не предоставляет встроенной поддержки такого шифрования: он полагается на возможности шифрования дисков, предоставляемые операционными системами, системами хранения или облачными провайдерами. Мы считаем, что это не самый безопасный подход, поскольку любой пользователь с администраторским доступом к базовой системе может получить доступ к данным в Kafka. В качестве альтернативы можно использовать прокси-сервер Kafka, например Kroxylicious, который обрабатывает шифрование для продюсеров и дешифрование для потребителей. Хотя это не настоящее сквозное шифрование, мы все же запрещаем доступ администраторов центров обработки данных или облачных провайдеров к незашифрованным данным, пока прокси-сервер остается под нашим контролем (рис. 13.4). Ключевое преимущество подхода с использованием прокси-сервера — дополнительная безопасность без реализации сложного сквозного шифрования на стороне клиента. Рис. 13.4. Прокси-сервер для шифрования может снизить риски при использовании ненадежного провайдера Kafka
   Глава 13. Управление 271 13.2.5. Сквозное шифрование Если конфиденциальность данных критична, то лучше реализовать сквозное шифрование. Kafka не предоставляет встроенной поддержки этого механизма — его должны реализовывать клиенты, то есть продюсеры и потребители. Часто команды, отвечающие за системы обмена сообщениями, такие как Kafka, гарантируют только внутренний уровень конфиденциальности. За повышение уровня должны отвечать команды разработчиков. К сожалению, нам неизвестны широко рекомендуемые или удобные библиотеки, обеспечивающие сквозное шифрование. Для Kafka Connect существует плагин Kryptonite for Kafka Connect, который может шифровать и дешифровать целые сообщения или отдельные поля. Другой подход — использовать прокси Kafka, например Conduktor Proxy или Kroxylicious, которые шифруют входящие сообщения и расшифровывают исходящие. ПРЕДУПРЕЖДЕНИЕ Такой метод не следует называть «сквозным шифрованием», так как шифрование происходит только между прокси. Путь от продюсера к прокси и от прокси к потребителю остается незашифрованным, так что прокси может читать все данные. В конечном счете ответственность за шифрование лежит на продюсерах и потребителях. Мы рекомендуем реализовать шифрование в виде сериализатора или десериализатора, который сначала вызывает обычный сериализатор (например, сериализитор JSON), а затем шифрует полученный сериализованный массив байтов. Обратный процесс выполняется в десериализаторе. Однако такой подход не позволяет шифровать или расшифровывать отдельные поля. Остается важный вопрос: как управлять ключами шифрования. Для этого требуется выделенный процесс, который может реализовываться с помощью таких инструментов, как Vault. СОВЕТ Следует отметить, что после шифрования данные больше нельзя эффективно сжимать, поскольку хорошо зашифрованные данные выглядят как случайный набор символов, который не поддается сжатию. 13.2.6. Безопасность в ZooKeeper Этот подраздел относится только к кластерам на основе ZooKeeper. Этот кворум хранит критически значимую информацию о Kafka, поэтому так важно обеспечить его надлежащую защиту. По умолчанию все данные передаются по сети в незашифрованном виде, включая информацию о топиках, количестве партиций и даже ACL. При использовании SASL-SCRAM имена пользователей
   272 Часть IV. Kafka в корпоративной среде и (хешированные и соленые) пароли также хранятся в ZooKeeper и передаются по незашифрованному соединению. Очевидно, что это не самый безопасный подход. К счастью, начиная с версии 2.5, Kafka поддерживает версию ZooKeeper с TLS. Конфигурация аналогична настройке TLS-шифрования для брокеров Kafka, поэтому подробно останавливаться на ней не будем. Особенно рекомендуем использовать mTLS для аутентификации между брокерами и ZooKeeper, хотя ZooKeeper также поддерживает SASL для аутентификации. Кворум ZooKeeper обычно управляет одним кластером Kafka и не должен использоваться другими системами, поэтому настройка дополнительных ACL не требуется. Клиентам в любом случае должно быть запрещено напрямую обращаться к ZooKeeper. Он крайне чувствителен к задержкам, поэтому при внедрении шифрования контролируйте задержки, иначе пострадает доступность всего кластера. 13.2.7. Защита незащищенного кластера Kafka Мы видели много окружений Kafka, созданных без учета рекомендаций по безопасности или иных практик. Типичный пример — реализация для подтвер­ ждения концепции, которая в итоге становится окончательной и переносится в продакшен. Как показано на рис. 13.5, Kafka, к счастью, позволяет выполнить миграцию с незащищенного кластера на защищенный без прерывания работы. Сначала мы настраиваем дополнительный слушатель с TLS и выбранным механизмом аутентификации на всех брокерах. Для активации этой конфигурации рекомендуется последовательно перезапускать брокеры по одному. После включения новой конфигурации можно перенести взаимодействие между брокерами на новый слушатель, что потребует еще одного перезапуска брокеров. Далее мы перенастраиваем клиенты для подключения через новый слушатель с включенными TLS и настройками аутентификации. Для некоторых методов нужно будет генерировать новые пары ключей или настраивать учетные данные в клиенте. Обычно перенастройка требует перезапуска клиентов. Во время обновления полезно составить список всех пользователей — он понадобится на следующем этапе. После миграции всех клиентов можно отключить старый слушатель. Лучше делать это в рабочий день, когда большинство команд на связи: практика показывает, что администраторы при планировании миграции могут не учесть одну-две команды. Эти команды быстро заметят проблему, что позволит оперативно перенести их клиенты или временно включить старый слушатель до следующего релиза.
   Глава 13. Управление 273 Рис. 13.5. Этапы защиты незащищенного кластера Kafka Теперь, когда все клиенты аутентифицированы, можно приступить к созданию ACL для пользователей. Есть два подхода. Первый — установить allow.everyone.if.no.acl.found=true в конфигурации брокера и постепенно включать ACL для пользователей. При таком подходе мы рискуем позже забыть отключить эту настройку или затянуть миграцию, так как команды могут откладывать эту задачу. На наш взгляд, надежнее сразу создать ACL для всех пользователей и включить их принудительно за раз. Недостаток такого подхода — возможные одновременные сбои во многих сервисах, поэтому крайне важно тщательно проверить ACL и запустить процесс в тестовой среде. После миграции на защищенный кластер команды смогут заняться следующими задачами. Как видно, даже простая миграция требует масштабной координации между командами и может привести к сбоям. СОВЕТ Рекомендуем заранее продумать потенциальные проблемы при создании кластера и в идеале привлечь экспертов. 13.3. Квоты в Kafka: защита кластера от перегрузки Как и в других системах, в Kafka можно использовать квоты для защиты кластера от чрезмерной нагрузки. У квот обычно две цели: справедливое распределение доступных ресурсов и защита системы от неисправных или вредоносных клиентов, которые могут помешать работе других клиентов.
   274 Часть IV. Kafka в корпоративной среде Kafka обрабатывает огромные объемы данных, что только усложняет ситуацию. Например, неправильно настроенные продюсеры могут быстро генерировать огромные объемы данных, заполняя хранилища и останавливая работу Kafka. Аналогично неконтролируемые потребители могут перегрузить брокеры запросами, монополизируя ресурсы и блокируя взаимодействие других клиентов с Kafka. Риск возрастает по мере увеличения размера кластера, а также количества обращающихся к нему команд и клиентов. Для лучшего понимания квот необходимо уточнить, какой клиент может быть ограничен, какие метрики отслеживают брокеры Kafka для выявления нарушений квот и какие меры принимает брокер при превышении квоты. Kafka предоставляет несколько вариантов ограничения клиентов. Можно задать квоты по умолчанию и переопределять их по мере необходимости. Если кластер Kafka не поддерживает аутентификацию, то квоты могут применяться на уровне идентификатора клиента, хотя это не очень практично, так как идентификаторы могут задаваться клиентами произвольно. Лучше устанавливать квоты на уровне пользователя, настраивая их через принципала. СОВЕТ Мы рекомендуем использовать консервативную квоту по умолчанию, которая подходит для большинства клиентов, и при этом создавать отдельные квоты для клиентов, требующих больше ресурсов. Будьте осторожны: без должного мониторинга квоты могут привести к серьезным проблемам. Kafka позволяет по-разному настраивать квоты. С одной стороны, мы можем определить, сколько запросов на отправку или получение данных (в байтах в секунду) может отправлять клиент. С другой стороны, мы можем указать, сколько времени ЦП брокер может выделить клиенту. Оба типа квот могут применяться только на уровне отдельного брокера из-за технических ограничений, то есть клиент теоретически может превысить назначенную квоту в несколько раз, подключившись к большему количеству брокеров. Хотя это затрудняет использование квот в качестве гарантий, их основная цель — защита от перегрузок, а не биллинг. Важно отметить, что использование ЦП рассчитывается для каждого потока, поэтому общая доступная емкость равна сумме сетевых потоков и потоков ввода-вывода: ((num.io.threads + num.network.threads) * 100)% Рассмотрим три сценария назначения квот для нашего интернет-магазина, чтобы понять, как квоты работают в Kafka. Каждый сценарий демонстрирует разные подходы к применению квот для контроля потребления ресурсов.
   Глава 13. Управление 275 В первом сценарии мы устанавливаем базовый уровень, задавая одинаковую квоту по умолчанию для всех клиентов. Квота определяет разумные границы: 1 Мбайт/с для отправки данных продюсерами и 2 Мбайт/с для чтения данных потребителями. Кроме того, клиентам выделяется их доля ресурсов обработки запросов от 100 % скорости обработки: kafka-configs.sh --bootstrap-server localhost:9092 \ --alter \ --entity-type clients \ --entity-default \ --add-config 'producer_byte_rate=1048576,\ consumer_byte_rate=2097152,request_percentage=100' Во втором сценарии мы рассмотрим конкретный продюсер — сервис рассылок. Он срабатывает периодически, так что для предотвращения перегрузки системы мы установим для него более строгую квоту: он может производить сообщения только с половиной стандартной скорости (512 Кбайт/с) и получает только 50 % стандартной доли ресурсов для обработки запросов: kafka-configs.sh --bootstrap-server localhost:9092 \ --alter \ --entity-type clients \ --entity-name newsletter \ --add-config 'producer_byte_rate=524288,request_percentage=50' В третьем сценарии мы рассмотрим систему управления запасами, которой требуется оперативно обрабатывать большие объемы данных, поэтому она получит расширенную квоту. Этому потребителю мы выделим до 5 Мбайт/с (значительно больше стандартной квоты) и двойную долю ресурсов обработки запросов. kafka-configs.sh --bootstrap-server localhost:9092 \ --alter \ --entity-type users \ --entity-name warehouse \ --add-config 'consumer_byte_rate=5242880,request_percentage=200' Во всех случаях мы меняем конфигурацию с помощью команды kafka-configs.sh с параметром --alter. Параметр --add-config задает значения квот, а --entitytype и --entity-name (или --entity-default для квоты по умолчанию) определяют целевой объект конфигурации. Что происходит при нарушении клиентом квоты? Сначала брокер вычисляет, на какое время нужно приостановить клиент для соблюдения квоты, используя небольшие временны́е окна в 1 секунду. Брокер ожидает это время перед отправкой ответа на текущий запрос. Поскольку корректно работающие клиенты имеют настройку max.inflight.requests, то баланс быстро восстанавливается.
   276 Часть IV. Kafka в корпоративной среде Одновременно брокер блокирует соединение с клиентом, чтобы предотвратить дополнительные запросы в этот период, защищаясь от некорректных или вредоносных клиентов. Не забывайте о побочных эффектах при применении квот. Слишком жесткие настройки могут негативно влиять на работу системы в продакшене. Агрессивные ограничения способны вызвать сбои в работе клиентов, что может привести к целому ряду проблем. Например, квота продюсера может заблокировать функцию send() после заполнения внутреннего буфера, в результате из-за медленной отправки сообщений будет истекать время ожидания. Очень важно тщательно отслеживать работу приложений при применении квот. Брокеры Kafka предоставляют метрики времени троттлинга (throttle time) в трех формах: Produce, Fetch и Request. Первые две отслеживают превышение квот пропускной способности, а метрика Request контролирует избыточное использование процессорного времени клиентами. В идеале эти метрики должны быть нулевыми. Значения выше нуля указывают, что брокеры в данный момент ограничивают одного или нескольких клиентов. Вдобавок эти метрики идентифицируют конкретного клиента или пользователя. Поскольку троттлинг обычно сигнализирует о некорректной настройке клиента или аномалии, а не о зло­ намеренной атаке, команда клиента сможет оперативно решить проблему. СОВЕТ Квоты в основном используются для защиты кластера от неправильных конфигураций или блокировки всего кластера одним клиентом. Они не предназначены для искусственного ограничения клиентов. Мы рекомендуем устанавливать квоты с запасом и тщательно отслеживать работу клиентов. Обычно мы советуем вводить квоты, когда все больше команд начинают использовать Kafka, чтобы предотвратить узкие места. Начните с квот по умолчанию, которые подходят большинству клиентов и позволяют обрабатывать пиковые нагрузки. Если отдельным командам требуется больше ресурсов, то они должны определить разумную максимальную нагрузку для своих приложений, после чего можно установить соответствующие квоты. Однако корректная настройка квот — непростая задача, требующая аккуратного подхода. Их цель — защитить сервисы в кластере от некорректно работающих клиентов, но без ущерба для работы сервисов, который может быть вызван излишне строгими ограничениями. ПРЕДУПРЕЖДЕНИЕ Перед внедрением квот необходимо настроить надежный мониторинг, позволяющий получить полную картину состояния кластера и приложений. Без хорошего мониторинга не стоит устанавливать квоты, иначе вреда от них будет больше, чем пользы, поскольку они помешают внедрению Kafka в организации.
   Глава 13. Управление 277 Резюме Правильное управление схемами в Kafka помогает обеспечить согласованность и совместимость. Реестры схем поддерживают централизованное управление схемами данных, упрощая их обновление. Схемы поддерживают версионирование, позволяя продюсерам и потребителям развиваться независимо без критических изменений. Проверки совместимости помогают предотвратить изменения схем, которые могут привести к проблемам с данными. Схемы могут сериализовываться в различных форматах, таких как Avro или JSON, что повышает совместимость между системами. Для защиты данных в Kafka требуется использовать протокол TLS с шифрованием, аутентификацией и авторизацией. Аутентификацию в Kafka можно реализовать с помощью таких механизмов, как SASL, для проверки подлинности клиентов и обеспечения безопасных соединений. Авторизация управляется через списки контроля доступа (ACL), которые определяют, какие пользователи или приложения могут обращаться к конкретным ресурсам Kafka, гарантируя выполнение только разрешенных действий. Шифрование данных при хранении должно осуществляться на уровне систем хранения, так как Kafka не поддерживает эту функцию нативно. Сквозное шифрование рекомендуется для защиты конфиденциальных данных и реализуется на уровне клиента. Управление ключами шифрования требует тщательного подхода, возможно, с использованием таких инструментов, как Vault. ZooKeeper необходимо защищать, так как он хранит критически важные метаданные Kafka и по умолчанию передает данные в незашифрованном виде. Kafka версии 2.5 и выше поддерживает TLS-подключения для безопасного взаимодействия с ZooKeeper, а mTLS рекомендуется для связи между брокером и ZooKeeper. Последовательно настраивая TLS-слушатели и перезапуская брокеры, можно выполнить миграцию с незащищенного кластера Kafka на защищенный, обойдясь без потери доступности. Квоты необходимы для защиты кластеров Kafka от чрезмерной нагрузки, вызванной некорректно настроенными или злонамеренными клиентами.
   278 Часть IV. Kafka в корпоративной среде Квоты могут ограничивать количество запросов на отправку или потребление сообщений, а также время использования центрального процессора на брокерах. Квоты можно настраивать на уровне идентификатора клиента или пользователя. Во втором случае квоты обеспечивают более высокую надежность. Брокеры Kafka отслеживают метрики времени троттлинга, позволяя эффективно выявлять нарушения квот. При настройке квот необходимо соблюдать осторожность, поскольку излишне жесткие ограничения могут нарушить работу сервиса и привести к каскадным сбоям.
14 Эталонная архитектура Kafka В этой главе 3 Инструменты Kafka, включая графические интерфейсы. 3 Концепции GitOps и Cruise Control. 3 Модели развертывания Kafka. 3 Аппаратные требования к брокерам Kafka. Вы уже получили общее представление о Kafka, методах его использования и интеграции в корпоративную ИТ-архитектуру. В этой главе мы поговорим о том, что необходимо для его успешной работы. Вы уже не раз видели, что только его часто недостаточно для достижения бизнес-целей. Обычно, помимо брокеров и координационного кластера, требуются другие компоненты (рис. 14.1). В первую очередь важны приложения, использующие Kafka, например простые продюсеры и потребители, записывающие и считывающие данные. Продюсеры и потребители на Java могут использоваться без ограничений. Как упоминалось в главе 8, для продюсеров на других языках крайне важно применять тот же алгоритм разделения на партиции, что и у продюсеров на Java. Часто нам даже не требуются пользовательские продюсеры и потребители для передачи данных из сторонних систем в Kafka или из него в другие системы. Многие из задач можно элегантно решить с помощью Kafka Connect, обойдясь без написания кода. Преимущества и недостатки этого подхода мы подробно обсудили в главе 11.
   280 Часть IV. Kafka в корпоративной среде Рис. 14.1. Типовая установка Kafka с дополнительными инструментами После попадания данных в Kafka было бы логично обрабатывать их с помощью его инструментов. Для этого можно применить один из многочисленных фреймворков потоковой обработки, например Kafka Streams или Apache Flink, как было описано в главе 12. У данных всегда есть схема, даже если она не задокументирована и не согласована. Мы рекомендуем клиентам продумывать управление схемами с помощью реестров схем или других методов. Эту тему мы рассмотрели в главе 13. 14.1. Полезные компоненты и инструменты В экосистеме Kafka правильные компоненты и инструменты могут существенно повысить эффективность управления потоками данных. В этом разделе мы рассмотрим утилиты и интерфейсы, упрощающие манипуляции данными, мониторинг и управление ресурсами в среде Kafka. Все эти компоненты — от
   Глава 14. Эталонная архитектура Kafka 281 командных утилит до графических интерфейсов — предоставляют важный функционал как для разработки, так и для эксплуатации. Кроме того, мы рассмотрим рекомендации по управлению ресурсами в Kafka, помогающему обеспечить стабильную работу, особенно в автоматизированных и декларативных средах. Эти инструменты позволяют организациям оптимизировать работу с Kafka и эффективнее достигать бизнес-целей. 14.1.1. kcat В начале книги мы рассматривали встроенные скрипты Kafka для записи и чтения данных. Альтернативой стандартному консольному потребителю и продюсеру может стать kcat (ранее — kafkacat). Главная особенность kcat — реализация на базе библиотеки librdkafka, в которой используется C вместо Java. С ней даже пользовательский скрипт запускается почти моментально. Вдобавок kcat поддерживает функции, отсутствующие в стандартных скриптах, например работу с заголовками сообщений. Кроме того, синтаксис команд и параметров в kcat короче и проще для использования. Рассмотрим небольшой пример записи данных в прежний топик products.pri­ ces.changelog: $ > > > > > > kcat -b localhost:9092 -t products.prices.changelog -P energy drink 2 energy drink 3 cola 2 cola 5 energy drink 1 cola 2 Для завершения команды нажмите Ctrl+D. В kcat параметры гораздо короче: -b означает bootstrap-сервер, -t — топик, а -P запускает режим продюсера. Считаем эти данные: $ kcat -b localhost:9092 -t products.prices.changelog -C energy drink 2 energy drink 3 cola 2 cola 5 energy drink 1 cola 2 % Reached end of topic products.prices.changelog [0] at offset 6 Для завершения команды нажмите Ctrl+C. Единственное отличие заключается в изменении режима. -C указывает на потребитель (consumer), хотя мы могли бы даже опустить этот параметр, поскольку kcat самостоятельно пытается определить режим. Прочитав все сообщения, kcat информирует нас об этом. Конечно,
   282 Часть IV. Kafka в корпоративной среде помимо всего вышеописанного, kcat поддерживает множество дополнительных параметров и опций, с которыми можно ознакомиться в документации проекта: https://github.com/edenhill/kcat/. ПРЕДУПРЕЖДЕНИЕ Настоятельно не рекомендуем использовать эти скрипты в продакшене, особенно для записи данных в Kafka. Этим должны заниматься только продюсеры. 14.1.2. Графические интерфейсы пользователя Перемещаться по топикам Kafka с помощью командной строки не слишком удобно и лучше использовать графический интерфейс. Правда, в продакшене его следует задействовать только для просмотра и ни в коем случае не для записи данных или изменения конфигурации. ПРЕДУПРЕЖДЕНИЕ Рекомендуем предоставлять пользователям графического интерфейса права только на чтение. При разработке он, конечно, очень удобен для записи тестовых данных и экспериментов с поведением подключенных систем. Самое важное применение графического интерфейса — исследование данных, то есть просмотр существующих топиков в Kafka и чтение данных из них. Часто графические интерфейсы поддерживают функции поиска и фильтрации данных. При использовании Kafka Connect или Schema Registry бывает удобно просматривать конфигурации и текущее состояние в наглядном виде. Мы настоятельно не рекомендуем вносить изменения в продакшене. Графических интерфейсов для Kafka много, мы рассмотрим лишь несколько популярных вариантов. Одно из самых простых решений — Kafbat Kafka UI (ранее — Kafka UI) от Provectus. Вероятно, это самый мощный открытый интерфейс для Kafka, который поддерживает не только просмотр топиков, но и Kafka Connect, Schema Registry, списки контроля доступа (ACL) и мониторинг групп потребителей. Хотя по функционалу он в чем-то может уступать коммерческим графическим интерфейсам, для большинства сценариев его хватает. Другие известные графические интерфейсы обычно доступны бесплатно только в базовой версии. Самые популярные из них — Kpow, Kadeck и Conduktor Console. Конечно, провайдеры Confluent и Aiven, а также другие облачные сервисы предлагают свои графические интерфейсы для Kafka. ПРЕДУПРЕЖДЕНИЕ Графический интерфейс не может заменить качественную систему мониторинга, поскольку даже самые мощные интерфейсы охватывают лишь часть картины.
   Глава 14. Эталонная архитектура Kafka 283 14.1.3. Управление ресурсами Kafka Как мы уже неоднократно упоминали, начиная с этапа подготовки к продакшену, ресурсы в Kafka должны управляться автоматизированно и декларативно. Создание топиков вручную категорически недопустимо в продакшене. Особенно не рекомендуется использовать для таких операций графические интерфейсы. Предпочтительным подходом является GitOps. В GitOps репозиторий Git считается единственным источником истины, а изменения развертываются через конвейер непрерывного развертывания. Этот подход рекомендуется применять не только для Kafka, но и для ИТ-инфраструктуры в целом. GitOps представляет собой специфическую форму концепции «инфраструктура как код» (Infrastructure as Code, IaC). Для Kafka все необходимые топики, пользователи и ACL создаются в этом репозитории Git, например, через pull- и merge-запросы. После ручной или автоматизированной проверки они автоматически создаются, модифицируются или удаляются в Kafka. К сожалению, универсального решения не существует. Как показывает опыт, большинство клиентов разрабатывают такие системы самостоятельно. Придумать универсальный подход сложно в том числе из-за различий в процессах согласования ресурсов в разных компаниях. Управлять коннекторами Kafka Connect можно с помощью инструмента kcctl. Источником вдохновения для него послужила командная утилита Kubernetes kubectl. Благодаря kcctl создавать и изменять коннекторы значительно проще, чем через REST API напрямую. Конфигурации Connect хранятся в JSON-файлах. Их можно создавать и изменять с помощью команды kcctl apply -f file.json. 14.1.4. Cruise Control for Apache Kafka Одна из главных задач при эксплуатации Kafka — поддержание сбалансированной нагрузки на брокеры. В идеале у всех брокеров должно быть примерно одинаковое количество партиций и лидеров. Что еще важнее, брокеры должны потреблять примерно одинаковые ресурсы процессора, памяти и сети. Для более равномерного распределения нагрузки партиции необходимо перемещать. Поначалу это пытались делать вручную. Известны случаи использования огромных таблиц Microsoft Excel для решения этой проблемы. Однако такой подход непрактичен, особенно для крупных систем. К счастью, существует решение получше: Cruise Control for Apache Kafka от LinkedIn. Оно состоит из нескольких компонентов (рис. 14.2). В первую очередь Cruise Control должен отслеживать отдельные брокеры для создания аналитики на основе данных. Эту задачу выполняет компонент метрик,
   284 Часть IV. Kafka в корпоративной среде который должен быть интегрирован в виде Java Archive (JAR) на каждом брокере. Он записывает текущие измеренные значения нагрузки для брокера, на котором он установлен, в определенный топик Kafka. Рис. 14.2. Cruise Control for Apache Kafka отслеживает и оптимизирует нагрузку на кластер Kafka Cruise Control содержит анализатор, которому можно задавать различные целевые показатели. Анализатор сопоставляет метрики с целевыми показателями и формирует предложение о том, как можно достичь этих показателей. Это предложение Cruise Control предоставляет в целях проверки, и затем оно может выполниться исполнителем. Администраторы Kafka выполняют все эти операции вручную через REST API или интерфейс Cruise Control. Мы советуем изначально придерживаться такого подхода. Если мониторинг обнаружит неравномерное распределение нагрузки, то можно запустить анализатор Cruise Control, проверить предложенный план и выполнить его. Кроме того, Cruise Control предоставляет режим автоматического обнаружения аномалий: при выявлении отклонения от целевых показателей система автоматически проводит анализ и принимает меры. ПРЕДУПРЕЖДЕНИЕ Перемещение партиций между брокерами может вызвать очень высокую нагрузку, поэтому в периоды пиковой загрузки Kafka таких операций стоит избегать. В Cruise Control для каждого ресурса (центральный процессор, оперативная память, диск, сетевая нагрузка) можно задать две ключевые цели: емкость — задается
   Глава 14. Эталонная архитектура Kafka 285 фиксированный порог (например, максимальная загрузка диска), который система стремится поддерживать; распределение ресурса — нагрузка должна распределяться максимально равномерно. В документации описаны и другие настраиваемые цели. Еще одно важное преимущество Cruise Control — автоматическое распределение нагрузки при добавлении новых брокеров или подготовке брокеров к отключению. По умолчанию добавление нового брокера не приводит к автоматическому перераспределению. Cruise Control решает эту задачу, вычисляя оптимальное распределение партиций и лидеров, после чего применяет изменения. При удалении брокера обычно требуется вручную переместить с него партиции перед отключением. Этот трудоемкий процесс можно можно автоматизировать с помощью Cruise Control. 14.2. Окружения развертывания Kafka может работать в различных конфигурациях. В нашей практике мы встречали самостоятельно управляемые кластеры Kafka, Kafka в Kubernetes, а также полностью управляемые решения «Kafka как услуга» (KaaS), где нам даже не было известно количество доступных брокеров. Фундаментально не имеет значения, в какой инфраструктуре работает Kafka, если она надежна и управляется командой экспертов. Если надежная инфраструктура недоступна или команде не хватает знаний для управления Kafka, то, как показывает опыт, в таких случаях целесообразнее платить за управляемый сервис. Важно понимать, что Kafka требует внимания. Даже если это управляемый сервис, необходима команда с глубоким знанием Kafka, которая должна налаживать автоматизацию и сопутствующие процессы и, что самое главное, поддерживать другие команды. Эти задачи редко можно передать сторонним провайдерам. 14.2.1. Kafka на собственном оборудовании компании Классический способ эксплуатации программного обеспечения — в собственном центре обработки данных на выделенном оборудовании. Мы рекомендуем по максимуму автоматизировать все процессы, связанные с Kafka, даже в собственном центре. При таком подходе это непросто, но возможно. Далее мы приведем конкретные рекомендации по оборудованию, однако стоит подчеркнуть, как важно тщательно спланировать развертывание Kafka, особенно в собственной инфраструктуре. Как организовать замену оборудования при сбоях? Что делать, если возникнут дополнительные требования к ресурсам? Помимо продакшен-кластеров,
   286 Часть IV. Kafka в корпоративной среде потребуется развернуть Kafka в окружениях разработки и тестирования. Никогда не используйте продакшен-кластер для тестирования или разработки. Такой подход применим, например, в компании, занимающейся веб-аналитикой, с опытной командой и предсказуемыми рабочими нагрузками. Если зависимость от Kubernetes минимальна, а виртуализация в центре обработки данных ограниченна, то развертывание Kafka на выделенном оборудовании будет оптимальным выбором. Предсказуемые паттерны нагрузки упрощают планирование мощностей, а отсутствие расширенной виртуализации гарантирует максимальную производительность. Специалисты по сопровождению будут эффективно устранять аппаратные сбои и масштабировать систему, обеспечивая надежное функционирование Kafka. Еще один важный фактор при работе в собственном центре обработки данных — распределение брокеров внутри инфраструктуры. Производительность Kafka отчасти так высока потому, что данные не записываются на диск сразу. Зато одновременный отказ нескольких брокеров может привести к потере данных. Брокеры должны быть расположены в разных стойках, а в идеале и в разных пожарных отсеках — это позволяет обеспечить дополнительную надежность. Кроме того, не забывайте о механизме распознавания стоек (rack awareness). В качестве альтернативы можно распределить брокеры между несколькими центрами обработки данных, но важно контролировать задержку между ними — она не должна превышать 30 миллисекунд. 14.2.2. Kafka в виртуализированных окружениях В виртуализированных окружениях внутри собственных центров обработки данных важно соблюдать те же рекомендации, что и при работе с Kafka на физическом оборудовании. Важно обеспечить максимально равномерное распределение брокеров Kafka по хостам виртуальных машин. Сетевые системы хранения, такие как SAN, подходят для Kafka лишь в редких случаях. Если система SAN надежна и обладает высокой производительностью, то может работать корректно, но при малейших сомнениях следует всегда отдавать предпочтение локальным SSD. Особенно важно, чтобы системы хранения справлялись с нагрузкой, генерируе­ мой Kafka. Известны ситуации, когда одновременное сжатие данных несколькими брокерами приводило к перегрузке системы хранения. Этот вариант хорошо подходит организациям с налаженными виртуализированными окружениями в собственных центрах обработки данных. Например, компании с предсказуемыми рабочими нагрузками и сильной командой сопровождения могут запускать Kafka там, где уже управляют другими системами. Используя имеющиеся навыки и инфраструктуру, такие организации интегрируют Kafka в виртуализированные окружения, обеспечивая надежную производительность для обработки данных.
   Глава 14. Эталонная архитектура Kafka 287 14.2.3. Kafka в Kubernetes: Strimzi По мере распространения Docker, а позже Kubernetes все больше приложений начали мигрировать в кластеры Kubernetes. При первых попытках запуска Kafka в Kubernetes многие разработчики скептически относились к этой идее, поскольку в то время сложно было управлять приложениями, хранящими состояние. Сегодня Kafka отлично функционирует в средах Kubernetes — при условии использования актуальных версий Kubernetes и тщательного выбора производительного хранилища. Исходя из опыта, мы настоятельно не рекомендуем использовать GlusterFS или Network File System (NFS). Для организаций, уже использующих Kubernetes, с сильной командой сопровождения и надежной инфраструктурой, реализовать Kafka в Kubernetes будет несложно. В таких случаях мы советуем использовать Strimzi — оператор Kubernetes, который автоматизирует многие аспекты управления Kafka и значительно снижает операционную сложность. Для предприятий Red Hat предлагает AMQ Streams — коммерческую версию Strimzi. Strimzi фактически представляет нашу эталонную архитектуру — он управляет развертыванием кластера Kafka, в том числе координационным кластером (KRaft или ZooKeeper), а также конфигурацией дополнительных компонентов, таких как Kafka Connect, мониторинг или автоматизация через Cruise Control. Кроме того, Strimzi предоставляет расширенные возможности управления ресурсами: администрирование пользователей, топиков, списков ACL и различные варианты авторизации. В ядре Strimzi находится оператор кластера, который контролирует развертывание отдельных компонентов: брокеров, координационных узлов, а также вспомогательных систем вроде Kafka Connect или двух других операторов. Кроме того, Strimzi предоставляет два дополнительных, независимо используемых оператора — оператор пользователей и оператор топиков (вместе — оператор объектов). Он позволяет легко создавать новые топики или пользователей с ACL, генерировать секреты аутентификации и даже управлять парами ключей TLS. Компоненты Strimzi показаны на рис. 14.3. Если у вас есть надежное и производительное окружение Kubernetes, то Strimzi — отличный выбор для самостоятельного управления Kafka. Его архитектура не только упрощает операции, но и предоставляет прочную основу для масштабирования Kafka. Если вы не можете использовать Strimzi напрямую, то рекомендуем изучить его структуру и внедрить ключевые паттерны в ваше развертывание. СОВЕТ При использовании Strimzi два брокера не должны размещаться на одном хосте виртуальной машины или узле Kubernetes. Strimzi позволяет обеспечить это разделение с помощью правил anti-affinity, гарантируя высокую доступность и отказо­ устойчивость.
   288 Часть IV. Kafka в корпоративной среде Рис. 14.3. Архитектура Strimzi Мы не будем углубляться в детали Strimzi — у проекта есть отличная документация: https://strimzi.io/documentation/. 14.2.4. Запуск Kafka в публичном облаке Некоторые облачные провайдеры предлагают Kafka как управляемый сервис, но по разным причинам иногда целесообразно самостоятельно управлять Kafka в облаке. Как показывает опыт, можно вполне эффективно использовать его на одной из трех основных облачных платформ: Amazon Web Services (AWS), Microsoft Azure или Google Cloud. По крайней мере, мы можем быть уверены в надежности базовой инфраструктуры. Хотя мы говорили, что Kafka не всегда хорошо работает на SAN-системах, с этими платформами можно работать спокойно. В большинстве сценариев системы хранения этих провайдеров достаточно надежны и производительны, чтобы их можно было использовать для поддержки Kafka. ПРЕДУПРЕЖДЕНИЕ Выбор ресурсов облачного провайдера для запуска Kafka может существенно повлиять на пропускную способность, задержку и общую производительность. Облачная инфраструктура упрощает физический уровень, но архитектура Kafka в облаке по-прежнему требует тщательного планирования. Для организаций, уже использующих Kubernetes в облаке, запуск Kafka со Strimzi поможет значительно
   Глава 14. Эталонная архитектура Kafka 289 упростить управление и снизить операционные затраты благодаря интеграции Kafka в существующую экосистему Kubernetes. Еще проще будет воспользоваться одним из многочисленных предложений по управляемому Kafka, например управляемыми сервисами Amazon Managed Streaming for Apache Kafka (MSK) и Azure HDInsight. По сравнению с самоуправляемым Kafka, эти сервисы имеют ограничения и при достижении определенного объема данных могут стать значительно дороже. Советуем изучить свои требования и сопоставить их с доступными предложениями. Клиенты, выбирающие управляемые решения Kafka, часто склоняются к более специализированным сервисам от Confluent Cloud или Aiven. У нас есть положительный опыт работы с обоими провайдерами, и мы смело их рекомендуем. Оба провайдера предлагают Kafka в трех облачных окружениях — AWS, Azure и Google Cloud, — а также поддерживают пиринг между виртуальными сетями и Private Link. Кроме того, по запросу можно поручить Aiven управление Kafka в собственном облачном аккаунте, что особенно удобно для регулируемых окружений. Уникальное преимущество Confluent заключается в том, что клиентам не нужно беспокоиться о размерах кластеров, так как провайдер предлагает прозрачный процесс их резервирования. Вдобавок Aiven и Confluent Cloud поддерживают Kafka Connect, Apache Flink и другие сервисы для управления Kafka. Кроме того, оба провайдера оперативно решают проблемы и вопросы клиентов. ПРЕДУПРЕЖДЕНИЕ Управляемые сервисы Kafka обходятся все дороже по мере увеличения объемов данных и нагрузки. Оцените свои потребности и изучите структуру ценообразования, чтобы избежать непредвиденных расходов. Проведите тщательный анализ затрат и выгод, чтобы убедиться, что сервис впишется в ваш бюджет. В целом, если ваша организация уже использует облачную инфраструктуру, Kafka можно легко интегрировать в эту среду — как самостоятельно с помощью Kubernetes, так и через управляемый сервис. Выбор будет зависеть от навыков команды, требований к контролю, а также специфических требований организации к производительности, масштабируемости и стоимости. Принимая решение, оцените ограничения управляемых сервисов (например, сроки хранения данных или пропускную способность) и сопоставьте их с недостатками самостоятельного управления Kafka. 14.3. Требования к оборудованию Производительность и надежность Kafka во многом зависят от базового оборудования, на котором развернута система. Правильная конфигурация оборудования крайне важна для эффективной обработки данных. В этом разделе описаны ключевые аппаратные требования к брокерам Kafka и координационным кластерам с учетом важности распределения ресурсов,
   290 Часть IV. Kafka в корпоративной среде решений для хранения данных и производительности сети. Соблюдение этих рекомендаций позволяет оптимизировать окружения Kafka так, чтобы они обрабатывали различные нагрузки, сохраняя высокую доступность и надежность. 14.3.1. Брокеры Брокеры — центральный компонент Kafka, ведь именно на них хранятся данные. Для надежности рекомендуется начинать как минимум с трех брокеров. В зависимости от размера брокеров и нагрузки количество можно постепенно увеличивать. Необходимые ресурсы во многом зависят от требований. Не стоит ориентироваться только на официальные рекомендации, на практике успешно работают и гораздо меньшие кластеры. При выборе оборудования для брокеров обращайте внимание на производительность и надежность систем хранения и памяти. Выбирайте быстрые и надежные системы хранения данных. Много небольших SSD обычно обес­ печивают более высокую производительность и отказоустойчивость, чем нескольких крупных. СОВЕТ Можно начать с меньшего объема хранилища и расширять его по мере необходимости, масштабируя ресурсы в соответствии с фактическими потребностями. Например, для крупных кластеров Kafka рекомендуется 12 SSD объемом по 1 Тбайт. Технология RAID (за исключением RAID 0, которая распределяет данные между дисками для ускорения работы) не требуется, поскольку Kafka обеспечивает надежность через репликацию. В частности, следует избегать RAID 5 и 6 — они слишком медленные для практического применения. СОВЕТ Начиная с Kafka 3.9, система поддерживает многоуровневое хранилище, что позволяет переносить старые данные в более дешевые системы, такие как объектное хранилище в облаке, и бессрочно хранить данные в Kafka, обходясь без лишних трат. Для оперативной памяти важно различать память для кучи JVM и память, доступную для кэша страниц операционной системы. В самых маленьких конфигурациях можно использовать менее 1 Гбайт оперативной памяти на брокер, но рекомендуется выделять не менее 1 Гбайт кучи для JVM. Даже в крупных кластерах Kafka обычно не требуется более 6 Гбайт для кучи. Оставшаяся доступная память может использоваться операционной системой для кэша страниц. Необходимый объем кэша страниц можно рассчитать, определив, на сколько потребители могут отставать, если не обращаются к (медленным) дискам для получения данных. Предположим, что кластер Kafka с тремя брокерами
   Глава 14. Эталонная архитектура Kafka 291 генерирует 30 Гбайт данных в час, а потребители могут отставать максимум на два часа, не нанося ущерба производительности. Сначала вычисляем объем производимых данных на один брокер: 10 Гбайт в час при условии равномерного распределения. Поскольку потребители могут отставать на два часа, то потребуется как минимум 20 Гбайт кэша страниц. Итого необходимо 26 Гбайт ОЗУ (6 Гбайт для кучи, 20 Гбайт для кэша страниц). Если у брокера 32 Гбайт ОЗУ, то кластер справится с таким сценарием. Еще один важный фактор — быстрая и надежная сеть. Рекомендуется Ethernet с пропускной способностью от 1 Гбит/с и до бесконечности. Если используется сетевое хранилище, то ему необходим выделенный сетевой канал, иначе производительность заметно упадет. Ресурсы центрального процессора не так важны и обычно зависят от используемого объема оперативной памяти. В целом много маломощных ядер лучше, чем мало крупных, поскольку многие операции в Kafka можно выполнять параллельно. Как правило, для брокеров Kafka выбирают инстансы с оптимизацией памяти. Сначала определяется необходимый объем оперативной памяти, а затем рассчитываются ресурсы центрального процессора. Что целесообразнее: много небольших брокеров или несколько крупных? Обычно мы рекомендуем придерживаться золотой середины. Если у нас несколько крупных брокеров, то сбой одного заметно повлияет на всю систему. С другой стороны, чем больше брокеров, тем дороже и сложнее управление. Как правило, брокеры масштабируются на основе оперативной памяти. Советуем придерживаться диапазона 32–64 Гбайт. Меньший объем оперативной памяти не имеет смысла, а прежде, чем переходить на повышенные объемы, следует добавить несколько дополнительных брокеров. Как мы уже знаем, физическое разделение брокеров Kafka — залог отказоустойчивости и безопасности данных. Брокеры не должны размещаться на одной виртуальной машине или в одной стойке, иначе повышается риск потери данных при сбое питания на хосте или в стойке. Функция осведомленности о стойках в Kafka позволяет по возможности распределять брокеры между разными зонами доступности. Однако распределять один кластер Kafka между несколькими облачными регионами обычно не рекомендуется. Задержка между брокерами должна оставаться ниже 30 миллисекунд, поскольку брокеры постоянно взаимодействуют в целях синхронизации метаданных, репликации партиций и поддержания работоспособности кластера. Более высокая задержка между брокерами может значительно ухудшить производительность, пострадают скорость выполнения запросов и надежность. Для глобальных операций эффективнее развертывать независимые кластеры Kafka в разных регионах, а не пытаться создать единый всемирный кластер.
   292 Часть IV. Kafka в корпоративной среде При необходимости региональные кластеры можно связать с помощью инструментов наподобие MirrorMaker для репликации данных. Такой подход позволяет организациям поддерживать высокую производительность, минимизируя сложности глобальной синхронизации. К репликации следует прибегать только при необходимости, так как она увеличивает сложность администрирования и издержки. Решение о создании отдельных кластеров и репликации данных между регионами зависит от конкретных требований, например, к соответствию нормативам или обмену данными между регионами. В большинстве случаев правильно спроектированная архитектура с независимыми региональными кластерами обеспечивает лучшую масштабируемость, изоляцию отказов и производительность, позволяя избегать лишних проблем с синхронизацией, вызванных большими задержками. 14.3.2. Координационный кластер Работа узлов ZooKeeper и KRaft не требует большого количества аппаратных ресурсов. Самое важное — хорошее сетевое соединение, поскольку ZooKeeper особенно чувствителен к задержкам. Сами компьютеры не обязательно должны быть мощными: достаточно 4–8 Гайт оперативной памяти и подходящий процессор. В координационном кластере должно быть нечетное количество узлов. Обычно для небольших окружений используется кластер из трех узлов, а для более крупных — из пяти. Если узла три, то отказ одного из них не повлияет на доступность. Если их пять, то можно пережить отказ двух экземпляров. Резюме Для управления Kafka лучше применять автоматизированные и декларативные подходы, хотя это не обязательно. Создавать топики в продакшене вручную категорически не рекомендуется, как и использовать для этого графические интерфейсы. Рекомендуется применять подход GitOps для управления ресурсами Kafka, рассматривая репозиторий Git как единый источник истины. Ресурсы Kafka, такие как топики, пользователи и ACL, должны создаваться и изменяться через pull- и merge-запросы в репозитории Git. Автоматизированные решения, например kcctl, упрощают управление коннекторами Kafka Connect по сравнению с использованием REST API напрямую. Cruise Control for Apache Kafka позволяет балансировать нагрузку брокеров, перераспределяя партиции на основе потребления ресурсов.
   Глава 14. Эталонная архитектура Kafka 293 Администраторам Kafka следует отслеживать нагрузку на брокеры и вручную запускать анализатор Cruise Control для балансировки. Cruise Control поддерживает автоматическое обнаружение сбоев, что позволяет выполнять упреждающую балансировку при отклонении от целевых показателей. Kafka может развертываться в разной инфраструктуре, такой как самоуправляемые кластеры, Kubernetes или управляемые сервисы. По-настоящему производительно и надежно Kafka будет работать в тщательно спланированной инфраструктуре с квалифицированной командой. Рекомендуется начинать как минимум с трех брокеров — это позволит обеспечить отказоустойчивость и масштабируемость в зависимости от нагрузки. Для хранения предпочтительны надежные и быстрые диски, желательно много небольших SSD вместо нескольких крупных. Kafka поддерживает многоуровневое хранение, позволяющее переносить старые данные в более экономичные решения. Важно рассчитать необходимый объем оперативной памяти: минимум 1 Гбайт для кучи JVM на брокер и адекватный размер кэша страниц в зависимости от скорости генерации данных. Сетевая производительность также важна: минимум 1 Гбит/с Ethernet, а также выделенные сети для хранилища, чтобы предотвратить падение производительности. Физическое разделение брокеров по разным хостам виртуальных машин или стойкам позволяет предотвратить потерю данных при аппаратных сбоях.
15 Мониторинг и оповещения в Kafka В этой главе 3 Поддержка производительности и надежности в Kafka. 3 Ключевые метрики для отслеживания в Kafka. 3 Стратегии оповещений. 3 Особенности мониторинга в различных окружениях развертывания Kafka. Kafka спроектирован как отказоустойчивая система и поэтому отличается высокой надежностью, но иногда проблемы все же возникают. В конце концов, он работает на физическом оборудовании, которое может выходить из строя. Kafka легко компенсирует отказ единичных брокеров или даже нескольких (в зависимости от размера кластера), мы все равно должны гарантировать, что такие сбои не будут затягиваться. В противном случае возникает риск полного отказа кластера, так как с потерей каждого брокера отказоустойчивость снижается. Под ошибкой или сбоем мы подразумеваем любое нарушение полноценной функциональности, которое в худшем случае может привести к полной недоступности системы. Для адекватного реагирования необходимо понимать, где именно возникла проблема и что ее вызывает. В современных сложных ИТ-системах ответить на второй вопрос особенно сложно. Иногда требуется тщательный анализ, так как коренная причина нередко кроется
   Глава 15. Мониторинг и оповещения в Kafka 295 в непредвиденных побочных эффектах, вызванных работой других, в остальном исправных систем. Обеспечить надежное функционирование Kafka, да и ИТ-систем в целом, позволяет качественный мониторинг. Он помогает не только быстрее обнаруживать сбои, но и определять их первопричину, что ускоряет устранение проблем. В этой главе мы подробно рассмотрим мониторинг Kafka, включая брокеры, клиенты и компоненты, такие как Kafka Connect и Kafka Streams, а также метрики, требующие особого внимания. В приложении Б содержится подробное руководство c примером настройки системы мониторинга, выполняемой с использованием Grafana и Prometheus. Мы сможем на практике протестировать метрики, описанные в текущей главе. 15.1. Метрики инфраструктуры При обсуждении мониторинга в Kafka нельзя забывать об оборудовании, на котором работает система, и о ресурсах, которые она потребляет. Брокеры и клиенты быстро столкнутся с проблемами производительности и надежности при недостатке ресурсов центрального процессора, памяти, дискового пространства или пропускной способности сети. При отсутствии комплексного мониторинга инфраструктуры можно потратить часы на поиск несуществующих ошибок в самом Kafka, на его брокерах и клиентах. Диск не должен быть слишком заполнен. При перебалансировке партиций может потребоваться дополнительное место, поэтому нужен резерв. Рекомендуется настраивать оповещения при заполнении дисков более чем на 60 %, чтобы успеть расширить хранилище. ПРИМЕЧАНИЕ Срочность увеличения дискового пространства зависит от того, насколько быстро это можно сделать. Если всего за несколько минут, то порог может быть выше 60 %. Использование сети также должно оставаться ниже 60 %, иначе возникнут сложности с перемещением данных или партиций на новые брокеры при добавлении ресурсов или перебалансировке. Кроме того, при выходе из строя отдельных брокеров может увеличиться нагрузка на сеть. Необходимо контролировать потребление памяти, чтобы иметь возможность обрабатывать неожиданные нагрузки. Kafka активно использует кэш страниц и может работать с небольшим объемом оперативной памяти, но это негативно скажется на производительности. Высокая загрузка центрального процессора также влияет на кластер. Кратковременные пики могут вызвать снижение производительности, а длительная высокая загрузка — заблокировать обработку запросов.
   296 Часть IV. Kafka в корпоративной среде Единичный снимок метрики мало говорит о состоянии системы. При перебалансировке партиций временный скачок потребления ресурсов брокерами — нормальное явление, не требующее вмешательства. Однако стабильно высокие показатели центрального процессора, памяти, сети или хранилища указывают на естественный рост кластера или сервисов, что требует масштабирования инфраструктуры Kafka. 15.2. Метрики брокеров Рассмотрим характерные для Kafka метрики — но не все, а только самые важные. Полный список (без пояснений) вы найдете в официальной документации Kafka. Прежде чем углубиться в детали, рассмотрим соглашения по именованию для метрик Kafka, которые зависят от системы мониторинга. Kafka использует формат именования Java MBeans с иерархической структурой организации метрик. Имена метрик состоят из домена, указывающего источник метрики, и набора определенных атрибутов. Например, для брокеров используются домены kafka_server, kafka_log, kafka_network и kafka_controller. Внутри этих доменов существуют различные подтипы, позволяющие точно идентифицировать метрику. Названия получаются длинными и громоздкими, зато смысл метрики ясен и без обращения к документации. В следующих разделах мы не будем использовать точные названия метрик, а сосредоточимся на концептуальном описании ключевых показателей, выделяя особо важные метрики. Кроме того, мы привели сводные таблицы с указанием основных метрик. 15.2.1. Метрики сервера Kafka Одна из самых важных метрик — under-replicated partitions — отражает количество партиций с несинхронизированными репликами. Это значение должно всегда оставаться нулевым. Постоянное превышение сигнализирует о недоступности брокера. Колебания указывают на возможные проблемы с ресурсами в кластере. Причина часто заключается в неравномерном распределении нагрузки, и эту проблему нужно исправлять как можно скорее. Признаками нарушения баланса служит количество партиций partition count и количество реплик-лидеров leader count на брокере. В нормальном состоянии эти показатели равномерно распределены между всеми брокерами. При отклонении рекомендуется перераспределить партиции между брокерами. На практике некоторые топики бывают гораздо активнее остальных, то есть к ним чаще обращаются для записи и чтения. Количество подключенных к брокеру продюсеров можно отслеживать через метрику producer ID count.
   Глава 15. Мониторинг и оповещения в Kafka 297 Критическая ситуация возникает, если мы достигаем минимально допустимого количества синхронизированных реплик (ISR) — at minimum ISR partition count или даже опускаемся ниже — under minimum ISR partition count. ПРИМЕЧАНИЕ Для топиков с коэффициентом репликации 1 ожидается, что все партиции будут иметь минимальное значение ISR. В этой ситуации продюсеры с конфигурацией acks=all не смогут записывать в них сообщения. В худшем случае лидер партиции становится недоступным для чтения и записи (offline replica count). Обычно новый лидер назначается относительно быстро, что увеличивает значение метрики reassigning partitions на затронутом брокере. В идеале эти метрики должны равняться нулю. Длительное превышение нулевого значения свидетельствует о серьезных проблемах в работе кластера. Все эти метрики относятся к типу replica manager, то есть полное имя метрики partition count — kafka_server_replicamanager_partitioncount. К типу replica fetcher manager относится метрика max lag, которая отображает наибольший разрыв между брокером или репликой-лидером и всеми его фолловерами. Хотя эта метрика не привязана к конкретному фолловеру, топику или партиции, метрика consumer lag, относящаяся к типу fetcher lag, показывает задержку для каждого фолловера, каждого топика и каждой партиции. В идеале оба значения должны быть минимальными, во всяком случае, не больше максимального размера пакета при производстве сообщений. ПРИМЕЧАНИЕ Кратковременная задержка допустима, но она не должна расти. Еще одна важная группа метрик относится к типу broker topic metrics. Здесь собраны многочисленные метрики, связанные с отдельными топиками. Например, существуют различные метрики количества запросов разных типов в топике (produce, fetch), а также сетевой трафик для топика (messages in, bytes in/out, replication bytes in/out, reassignment bytes in/out). По названию метрик видно, что непосредственное производство сообщений создает лишь часть сетевой нагрузки. В частности, перераспределение партиций (reassignment bytes in/out) может временно генерировать значительный трафик, так как сбой влияет на множество партиций. Все эти метрики крайне полезны для выявления причин постоянной высокой нагрузки или даже перегрузки брокеров, особенно для контроля равномерного распределения нагрузки между ними. СОВЕТ При оценке перегруженности брокера большинство метрик Kafka служат лишь индикаторами. Рекомендуем дополнительно проверять общие метрики, например загрузку центрального процессора или использование сети.
   298 Часть IV. Kafka в корпоративной среде Брокеры также собирают статистику по отклоненным сообщениям и причинам их отклонения. Например, фиксируются метрики для записей без ключей в топиках со сжатием, записей с некорректным, циклически избыточным кодом (CRC) и записей с нарушенной последовательностью смещения. СОВЕТ Продюсеры действительно получают соответствующие сообщения об ошибках для отклоненных сообщений, но при некачественном мониторинге приложений эти ошибки могут упускаться. Следует предусмотреть оповещения для разработчиков. Кроме того, существуют метрики типа delayed operation purgatory для отложенных операций. Например, метрика размера чистилища purgatory size показывает количество запросов, ожидающих ответа в текущий момент. Это могут быть запросы продюсера, если лидер все еще ожидает подтверждений от реплик-фолловеров перед фиксацией сообщения, или fetch-запросы потребителей, если не достигнут предел fetch.wait.max.ms или fetch.min.bytes. 15.2.2. Метрики лога Kafka Метрики Kafka log содержат все статистические данные, связанные с логами — файлами, хранящимися на брокерах. Сюда входят метрики log flush stats, в частности гистограмма, показывающая время, необходимое для записи сообщений на диск, и количество операций записи на диск. Высокие значения указывают на проблему с постоянным хранилищем. Метрика offline log directory count типа log manager всегда должна иметь значение 0, поскольку отражает количество недоступных каталогов. Так бывает, например, при отказе диска. Дополнительные метрики, связанные с размером (size), количеством сегментов (number of log segments) и текущими смещениями (log start offset, log end offset), находятся в категории log. Для этих метрик не обязательно настраивать оповещения, они дают ценную информацию о том, как нагрузка на систему меняется со временем. Метрики, связанные с очисткой кластера, находятся в категориях log cleaner и log cleaner manager. 15.2.3. Метрики сети Kafka Важная метрика сети Kafka — network processor average idle percent, относящаяся к типу socket server. Она показывает, какая доля сетевых процессоров сейчас простаивает. Если значение слишком низкое, то это может привести к задержкам в сети. СОВЕТ Стабильно низкое значение (< 0,3) для метрики network processor average idle percent указывает на то, что кластер находится на грани перегрузки или уже перегружен. Как можно скорее добавьте ресурсы, чтобы избежать проблем с производительностью или простоев.
   Глава 15. Мониторинг и оповещения в Kafka 299 Метрики типа request metrics содержат общую, независимую от топика статистику для различных запросов: количество запросов, ошибки, размеры запросов и время их обработки. СОВЕТ Мониторинг этих метрик критически важен для равномерного распределения нагрузки между брокерами и предотвращения перегрузок. 15.2.4. Метрики контроллера Kafka Метрики контроллера Kafka позволяют отслеживать текущее состояние контроллера или координационного кластера. К типу kafka controller относятся метрики, указывающие на состояние кластера Kafka. Метрика active controller указывает, какой брокер в данный момент выступает в роли контроллера. У нее должно быть значение 1 только для одного брокера в кластере и 0 — для остальных. Метрики active broker count и fenced broker count показывают количество активных и неактивных брокеров. Дополнительно отслеживается статистика по общему количеству топиков и партиций и отключенных партиций. Метрика preferred replica imbalance показывает, для какого количества партиций на брокере сейчас установлен непредпочтительный лидер. СОВЕТ Kafka периодически перераспределяет партиции, если для них назначены непредпочтительные лидеры. Порог для выполнения этой операции можно задать в параметре leader.imbalance.per.broker.percentage. По умолчанию установлено 10 %. В табл. 15.1 перечислены ключевые метрики брокеров, а на рис. 15.1 представлен готовый дашборд Kafka от команды Strimzi с детальной информацией о состоянии кластера. Таблица 15.1. Самые важные метрики брокеров Имя Java MBean Описание Пояснения kafka.server:type=ReplicaManager, name=UnderReplicatedPartitions Количество партиций с несинхронизированными репликами Должно быть 0. Если долго выше 0, то брокер не­ доступен kafka.server:type=ReplicaManager, name=AtMinIsrPartitionCount Количество партиций с минимальным количеством ISR Пока некритично, но может сигнализировать о проблемах с репликацией. Если коэффициент репликации для топика — 1, то у всех партиций должно быть минимальное количество ISR Продолжение 
   300 Часть IV. Kafka в корпоративной среде Таблица 15.1 (продолжение) Имя Java MBean Описание Пояснения kafka.server:type=ReplicaManager, name=UnderMinIsrPartitionCount Количество партиций с количеством ISR ниже минимума Критично, так как продюсеры с параметром acks=all не смогут записывать сообщения. Чтение также откладывается kafka.server:type=Fetcher­ LagMetrics,name=ConsumerLag, clientId=([-.\w]+),topic= ([-.\w]+),partition=([0-9]+) Задержка фолловеров по топикам и партициям Кратковременная задержка допустима, но не должна расти. Постоянная задержка указывает на проблемы с производительностью кластера и может повлиять на ISR, так что продюсеры не смогут записывать новые сообщения kafka.network:type=Socket­ Server,name=NetworkProcessorAvg­ IdlePercent Процент незанятых сетевых обработчиков Значение в идеале должно быть выше 0,3. Более низкие показатели указывают на возможную перегрузку кластера и проблемы с производительностью Рис. 15.1. Пример дашборда Kafka на основе Strimzi
   Глава 15. Мониторинг и оповещения в Kafka 301 15.3. Метрики клиента Хотя кластер Kafka с брокерами образует ядро решения, нельзя недооценивать роль клиентов. Даже самый производительный и надежный кластер Kafka бесполезен без клиентов, которые непрерывно производят, обрабатывают или потребляют новые данные. В этом разделе мы углубимся в мониторинг клиентов Kafka, необходиый для поддержания надежности. Мы рассмотрим не только метрики продюсеров и потребителей, но и конкретные метрики для фреймворков Kafka, таких как Kafka Connect и Kafka Streams. 15.3.1. Общие метрики клиентов Мы должны отслеживать инфраструктуру не только брокеров, но и клиентов. Не имея достаточных аппаратных ресурсов, клиенты не смогут работать на оптимальном уровне. Нехватка процессорных или сетевых ресурсов может привести к неэффективной обработке данных, а значит, и медленной обработке сообщений. Продюсеры (будь то простые продюсеры, Kafka Connect или Kafka Streams) начнут отставать, данные в кластерах Kafka будут устаревать, и все это отразится на потребителях. Если проблемы с производительностью возникают у отдельных потребителей, то обычно страдает только соответствующее приложение. Однако при недостатке памяти в лучшем случае клиенты начнут работать медленнее, а в худшем — будут постоянно возникать ошибки из-за нехватки памяти или сбоев. ПРЕДУПРЕЖДЕНИЕ Может показаться, что проблемы с отдельными потребителями не влияют на всю систему, но в итоге они затрагивают приложения, зависимые от этих потребителей. Помимо мониторинга инфраструктуры, существуют характерные для Kafka метрики, которые не зависят от типа клиента. Как и у брокеров, метрикам клиентов присваиваются домены. В данном контексте домены просто обозначают тип клиента: kafka producer, kafka consumer, kafka connect или kafka streams. Им соответствуют типы producer metrics, consumer metrics, connect metrics и stream metrics, хотя в Prometheus тип опускается. Все клиентские метрики можно связать с соответствующими клиентами с помощью метки client ID. Вдобавок существуют метрики, не привязанные к конкретному клиенту, например количество подключений к кластеру Kafka или успешные и неудачные попытки аутентификации. Количество подключений клиента зависит от количества партиций и брокеров, с которыми он взаимодействует, и обычно остается относительно стабильным,
   302 Часть IV. Kafka в корпоративной среде в идеале равномерно распределяясь между отдельными потребителями в группе. Например, если часто меняющееся количество подключений от потре­бителей указывает на постоянные перебалансировки внутри соответствующих групп потребителей. Это, в свою очередь, может сигнализировать о проблемах с отдельными потребителями. Важно отметить, что во время перебалансировки потребление данных невозможно, а значит, данные в приложениях устаревают. Неудачные попытки аутентификации могут указывать на проблемы в системе аутентификации (помимо некорректных учетных данных) и должны оперативно исследоваться и устраняться. Эти метрики помогают поддерживать безопасность и целостность кластера Kafka, а также вовремя обнаруживать потенциальные атаки или ошибки конфигурации. Кроме того, как и у брокеров, есть множество метрик, связанных с запросами и сетевым трафиком. Однако сложно дать общие рекомендации о том, какие значения считать слишком высокими или низкими, поскольку они во многом зависят от конкретных требований и паттернов использования приложений. Тем не менее такие метрики отлично подходят для обнаружения аномалий. Временные пики в этих метриках могут вызывать кратковременные задержки в обработке данных. Кроме того, упомянутые метрики доступны на уровне брокера с префиксом node. Они позволяют отслеживать производительность и трафик отдельных брокеров и вовремя замечать и устранять потенциальные узкие места или перегрузки. В табл. 15.2 приведены наиболее важные общие клиентские метрики. Таблица 15.2. Самые важные общие клиентские метрики Имя Java MBean Описание Пояснения kafka.[producer|consumer| connect]:type=[producer| consumer|connect] -metrics,client-id= ([-.\w]+),name=connection-count Количество подключений клиента к кластеру Kafka Если значение часто меняется, это указывает на проблемы с производительностью, особенно для потребителей, так как это признак постоянной перебалансировки, усугубляющей падение производительности kafka.[producer|consumer| connect]:type=[producer| consumer|connect]-metrics, client-id=([-.\w]+),name= failed-authentication-total Количество не­удачных попыток аутентификации при подключении к кластеру Kafka Проблема конфигурации у клиента (ошибка учетных данных/ конфигурации соединения) или в системе аутентификации кластера Kafka
   Глава 15. Мониторинг и оповещения в Kafka 303 Имя Java MBean Описание Пояснения kafka.[producer|consumer |connect]:type=[producer| consumer|connect]-metrics, client-id=([-.\w]+),name= request-rate Количество запросов на запись или чтение, которые клиент отправляет кластеру Kafka Метрика зависит от приложения/ клиента. Ее мониторинг помогает выявить аномалии в поведении клиента, часто связанные с некорректно работающими приложениями kafka.[producer|consumer |connect]:type=[producer| consumer|connect]-metrics, client-id=([-.\w]+),name= outgoing-byte-rate Скорость исходящей передачи данных (есть метрика для входящей) на клиенте Метрика зависит от приложения/ клиента. Ее мониторинг помогает выявить аномалии в поведении клиента, часто связанные с некорректно работающими приложениями ПРЕДУПРЕЖДЕНИЕ Раннее обнаружение аномалий позволяет выявлять отклонения в работе приложений до того, как они приведут к серьезным проблемам или сбоям. 15.3.2. Метрики продюсера Рассмотрим метрики продюсеров. Многие из них имеют такие же или похожие метрики на стороне брокера. Например, метрика record error rate позволяет определить, отклоняли ли брокеры запросы на запись. Подобные ошибки могут легко заблокировать запись новых сообщений, так как процесс останавливается, поэтому крайне важно быстро выявлять и устранять причину таких ошибок. Частая причина подобных ошибок заключается в том, что максимальный размер батча на стороне продюсера не соответствует максимальному размеру сообщения на стороне брокеров. Вдобавок могут возникать такие проблемы, как временные сетевые сбои, перегрузки или даже полная недоступность брокера. Точную причину ошибок обычно можно найти в логе ошибок продюсера. Крайне важно активно отслеживать и устранять подобные проблемы, чтобы обеспечить бесперебойную работу и надежную отправку данных в кластер Kafka. ПРИМЕЧАНИЕ Если брокер недоступен, то лидер для партиции будет переназначен автоматически и продюсер подключится к новому лидеру. Метрика requests in flight показывает количество ожидающих запросов, на которые еще не ответили брокеры. При подтверждении могут возникать временные задержки, особенно если установлено acks=all. Иногда запросы от брокеров могут задерживаться из-за перегрузки или квот. Такие задержки можно выявить с помощью метрики produce throttle time max.
   304 Часть IV. Kafka в корпоративной среде Кроме того, доступна статистика, которая предоставляет информацию о размере, количестве, задержке и степени сжатия сообщений. Эти метрики позволяют обнаруживать аномалии. Некоторые метрики относятся к конкретным топикам и имеют префикс topic. В табл. 15.3 приведены самые важные метрики продюсеров. Таблица 15.3. Самые важные метрики продюсеров Имя Java MBean Описание Пояснения kafka.producer: type=producer-metrics, client-id="{client-id}", name=record-error-rate Количество ошибок при создании записей у продюсера В продакшене таких ошибок быть не должно. Если они есть, то у брокеров или продюсеров возникли серьезные проблемы — например, в топик со сжатием записываются сообщения без ключа или сообщение превысило максимальный размер kafka.producer: type=producer-metrics, client-id="{client-id}", name=produce-throttletime-avg Задержка запи­ си сообщений из-за троттлинга Помогает обнаружить проблемы с производительностью. Обычно они возникают из-за ограничения скорости записи в связи с квотами kafka.producer: type=producer-metrics, client-id="{client-id}", name=request-latency-avg Средняя задержка запроса. Существуют похожие метрики для подтипов Помогает обнаружить проблемы с производительностью. Внезапное увеличение этой метрики указывает на проблемы в кластере СОВЕТ Метрики record send rate и request latency avg особенно важны для мониторинга производительности продюсера. 15.3.3. Метрики потребителей Теперь рассмотрим ключевые метрики потребителей. Например, time between poll max и last poll seconds ago позволяют отслеживать, когда потребитель в последний раз запрашивал новые сообщения у брокеров. Частота этих запросов зависит от настроек потребителя, например poll.in­ terval.ms, и текущей нагрузки на него. Критическая ситуация возни­кает, когда нагрузка замедляет выполнение запросов, приближаясь к лимиту max.poll.inter­ val.ms. Это приводит к тому, что потребитель считается неактивным с точки зрения своей группы и в ней происходит перебалансировка.
   Глава 15. Мониторинг и оповещения в Kafka 305 СОВЕТ Мониторинг метрик time between poll max и last poll seconds ago помогает выявлять потенциальные проблемы с производительностью прежде, чем они нарушат работу группы потребителей или приложения. Для быстрого решения проблемы можно изменить настройку max.poll.interval.ms или увеличить доступные аппаратные ресурсы. В худшем случае потребуется пересмотреть архитектуру приложения, чтобы улучшить масштабируемость. Очень важно вовремя обнаруживать такие проблемы и принимать меры. Метрики типа coordinator metrics относятся к управлению группами потребителей. Особый интерес представляют метрики перебалансировок. Количество перераспределений показывает метрика rebalances total. Важно следить за сбоями перебалансировок (failed rebalances total) и временем, которое уходит на перераспределение (rebalance latency total и rebalance latency max). Хотя определенное количество перебалансировок может считаться нормальным, высокие значения часто указывают на проблемы внутри группы потребителей. Важно помнить, что при некоторых стратегиях назначения партиций группа потребителей не может обрабатывать новые сообщения во время перебалансировки. СОВЕТ Если выбрана стратегия CooperativeStickyAssignor, то другие потребители в группе могут спокойно продолжать чтение. Продолжительность перебалансировки зависит от приложения и топиков. СОВЕТ В более сложных приложениях, таких как Kafka Streams, перебалансировка иногда может занимать несколько минут. Тщательно отслеживайте метрики перебалансировки, чтобы вовремя замечать и устранять проблемы в приложении. Еще одна полезная метрика — assigned partitions. В идеале партиции внутри группы потребителей должны распределяться равномерно. Когда потребитель не получает партиции в течение длительного времени, это может указывать на возможную необнаруженную ошибку. Существуют метрики, измеряющие количество коммитов (commit total) и их задержку (commit latency). Все эти метрики в основном предназначены для обнаружения аномалий. Метрики, связанные с потреблением сообщений, относятся к типу fetch manager metrics. Одной из ключевых метрик для потребителей является отставание. Отставание (lag) показывает, как далеко потребитель от текущего состояния в топике и партиции. Отставание можно выразить в количестве сообщений (records lag) или как время между последним прочитанным сообщением и новейшим сообщением в партиции. Метрика records lead указывает на опережение потребителя относительно самого старого сообщения в партиции. Кроме того,
   306 Часть IV. Kafka в корпоративной среде доступны метрики по потребленным записям или байтам для конкретных топиков. В табл. 15.4 представлены наиболее важные метрики потребителей. Таблица 15.4. Самые важные метрики потребителя Имя Java MBean Описание Пояснения kafka.consumer:type= consumer-metrics, client-id=([-.\w]+), name=time-between-poll Время между опросами Потребители обычно (в зависимости от реализации) получают новые сообщения сразу после обработки предыдущих. Увеличение этого значения может указывать на проблему с масштабированием (например, работа базы данных замедляется по мере ее роста). Потребители считаются неактивными, если не могут запросить новые сообщения в течение максимально настроенного времени между опросами, что приводит к перебалансировке в группе потребителей и усугублению проблем с производительностью. Мониторинг этой метрики помогает выявлять потенциальные узкие места в производительности до их возникновения kafka.consumer: type=consumer -coordinator-metrics, client-id=([-.\w]+), name=rebalance-rate -per-hour Частота перебалансировок Хотя определенное количество перебалансировок не является поводом для беспокойства (особенно в окружениях Kubernetes), постоянное высокое значение указывает на проблемы в группе потребителей kafka.consumer :type=consumer -coordinator-metrics, client-id=([-.\w]+), name=commit-latency-max Средняя задержка запроса. Существуют похожие метрики для подтипов Помогает обнаружить проблемы с производительностью. Внезапное увеличение этой метрики указывает на проблемы в кластере kafka.consumer:type= consumer-fetch-manager -metrics,partition=" {partition}",topic=" {topic}",client-id=" {client-id}",name= record-lag Отставание потребителя для топика или партиции Это самая важная метрика потребителя, так как она напрямую отражает проблемы с производительностью. В идеале ее значение должно быть 0 или близко к 0 при высокой скорости генерации данных. Постоянно высокое значение указывает на узкое место в производительности потребляющего приложения, что может негативно сказаться на его работе, поскольку потребитель не успевает обрабатывать актуальные сообщения
   Глава 15. Мониторинг и оповещения в Kafka 307 Имя Java MBean Описание Пояснения kafka.consumer:type= consumer-fetch-manager -metrics,partition=" {partition}",topic=" {topic}",client-id=" {client-id}",name=records -lead Количество сообщений, на которое потребитель опережает самые старые сообщения в топике/партиции Эта метрика полезна только в сочетании с отставанием потребителя, так как ее значение сильно зависит от количества записей в топике/партиции. Если значение очень низкое, а отставание высокое, то возникает риск потери данных (в зависимости от политики очистки топика и конфигурации), поскольку записи могут удаляться до того, как потребитель успеет их прочитать ПРЕДУПРЕЖДЕНИЕ Если задержка потребителя (records lag) указывает на то, что потребитель слишком медленно считывает данные, низкое значение records lead создает риск удаления непрочитанных сообщений из кластера Kafka из-за сжатия логов или превышения возраста. Нормальные значения records lead зависят от размера топиков, поэтому назвать универсальный лимит здесь затруднительно. Отслеживайте метрики records lead и records lag или настраивайте индивидуальные лимиты для каждого топика. 15.3.4. Метрики Kafka Connect и Kafka Streams Мы рассмотрели основные аспекты мониторинга стандартных клиентов Kafka, а теперь кратко обсудим особые метрики Kafka Connect. Этот фреймворк предоставляет разные типы метрик. К типу connect worker metrics относятся метрики, показывающие общее состояние кластера Connect, например количество коннекторов и задач на каждого исполнителя. Кроме того, существуют специальные метрики коннекторов, которые показывают количество задач на каждом коннекторе и их статус. Метрики типа connector metrics дают информацию об отдельных задачах, а метрики connector task metrics, source connector task metrics и sink connector task metrics содержат подробности, как аналогичные метрики для потребителей и продюсеров. Стоит отметить метрики task error metrics, которые содержат данные о проблемах в коннекторах или задачах, например количество возникших ошибок или частоту отправки записей в очередь недоставленных сообщений. ПРЕДУПРЕЖДЕНИЕ Рекомендуется тщательно отслеживать как минимум статусы коннекторов. Важно понимать, что у коннекторов могут возникать временные проблемы из-за зависимостей от сторонних систем. Конфигурация коннекторов позволяет настраивать повторные попытки (что, несомненно, полезно для автоматического восстановления после временных сбоев), но не забывайте: увеличение интервалов между повторами задерживает переход коннектора или задачи в статус «сбой».
   308 Часть IV. Kafka в корпоративной среде Наконец, рассмотрим мониторинг для Kafka Streams. Эти метрики дают представление о производительности и поведении приложения Kafka Streams, позволяя заранее обнаруживать потенциальные проблемы и оптимизировать работу приложения. Помимо общих метрик, таких как количество рабочих и нерабочих потоков, существуют различные подкатегории метрик. Метрики типа thread дают общую информацию о каждом потоке, в том числе о количестве созданных или завершенных задач. При нормальной работе приложения Kafka Streams (если это не запуск, остановка или изменение топологии) завершение и повторное создание задач обычно указывает на ошибки или перебалансировку. Если это происходит слишком часто, то рекомендуется детально проанализировать приложение. Кроме того, доступны данные о количестве обработанных записей и времени их обработки, количестве прочитанных сообщений и задержках, возникающих при их потреблении. Метрики задач task подробно описывают отдельные задачи в потоках. Это почти такие же метрики, как и у потоков, только для задач. Кроме того, они содержат важную информацию о необработанных сообщениях (dropped records total). Существуют метрики для отдельных узлов обработчиков (processor node metrics) с детальной информацией об абстрактных обработчиках Kafka Streams в задаче. Эти метрики содержат информацию, например, об объеме произведенных или потребленных данных, о количестве обработанных сообщений и среднем времени такой обработки. Наконец, многочисленные метрики типа state связаны с отдельными хранилищами состояний в Kafka Streams. Например, метрика RocksDB предоставляет информацию о количестве или размере записей в таблицах памяти. В табл. 15.5 представлены самые важные метрики для Kafka Connect и Kafka Streams. Таблица 15.5. Самые важные метрики для Kafka Connect и Kafka Streams Имя Java MBean Описание Пояснения kafka.connect:type= connect-worker-metrics, connector="{connector}", name=connector-failed -task-count Количество задач на коннекторах, завершившихся сбоем Метрика указывает на серьезные проблемы, поскольку остановленная задача означает, что сообщения не обрабатываются коннектором. Проблема может быть временной (например, обслуживание базы данных), и задачу нужно перезапустить, или может вызываться ошибкой обработки kafka.streams:type=stream -metrics,client-id= ([-.\w]+),name=failed -stream-threads Количество потоков, завершившихся сбоем Метрика указывает на серьезные проблемы, поскольку сбой потока означает остановку обработки сообщений
   Глава 15. Мониторинг и оповещения в Kafka 309 Имя Java MBean Описание Пояснения kafka.streams:type=stream -thread-metrics,thread -id=([-.\w]+),name= process-rate Процент обработанных записей Метрика зависит от приложения/клиента. Ее мониторинг помогает выявить аномалии в поведении клиента, часто связанные с некорректной работой приложений kafka.streams:type=stream -task-metrics,thread-id= ([-.\w]+),task-id= ([-.\w]+),name=dropped -records-ratel Процент отброшенных записей Метрика указывает на серьезные проблемы, поскольку записи не могут быть корректно обработаны 15.4. Оповещения После детального изучения аспектов и метрик, которые необходимо отслеживать в Kafka, приведем общие рекомендации по настройке оповещений и обработке уведомлений. Оповещение — это автоматическое уведомление, срабатывающее при достижении заданных пороговых значений одной или нескольких метрик. Оповещения играют ключевую роль в поддержании надежной работы ИТ-систем, так как позволяют автоматически отслеживать широкий спектр метрик и информировать ответственных лиц об аномалиях или сбоях. В некоторых случаях можно даже автоматически принимать корректирующие меры, чтобы минимизировать простои сервисов. В идеале оповещение помогает заранее обнаружить потенциальные проблемы и предотвратить полный отказ системы. 15.4.1. Настройка оповещений на основе метрик К счастью, создавать оповещение для каждой метрики не требуется — это было бы слишком трудоемко и наверняка непродуктивно. Хотя большинство метрик помогают выявить основные причины ошибок, не все они подходят для оповещений. Настроить оповещения для метрик с двумя состояниями («норма» или «сбой») относительно просто, но для других метрик все обычно сложнее. Визуализация помогает найти подходящие метрики или группы метрик и задать для них продуманные пороговые значения. Например, если метрика каждую секунду заметно колеблется, то следует отслеживать усредненное значение за более длительный период. Загрузка процессора, например, может кратковременно достигать 100 %, но если это быстро проходит (через несколько минут или около того), беспокоиться обычно не о чем — и это уж точно не повод будить кого-то посреди ночи.
   310 Часть IV. Kafka в корпоративной среде Сгруппировав подходящие метрики, мы продумываем пороговые значения для оповещений. Рекомендуется начать с консервативного подхода и корректировать значения при частых ложных срабатываниях. Многие современные ИТ-системы обладают функциями самовосстановления. Например, Kubernetes может автоматически перезапустить контейнер при сбое. Поэтому разумно дать системам время на самостоятельное устранение проблемы, прежде чем вмешиваться. Ошибку, разумеется, необходимо в любом случае проанализировать, а не игнорировать. Если визуализация метрики показывает, что для оповещений она не подходит, это не означает, что нужно отказываться от созданных дашбордов или панелей. Часто такие метрики помогают выявить причины других оповещений. Например, метрика количества созданных записей produced records редко сама по себе становится поводом для беспокойства, но если срабатывает оповещение о чрезмерном отставании группы потребителей, то по ней можно понять, с чем связана проблема: с общим сбоем потребителей или аномально большим количеством сообщений для обработки. СОВЕТ В этой главе мы в основном рассматривали метрики, подходящие для оповещений. Некоторые системы автоматически генерируют оповещения, устанавливая пороговые значения на основе истории метрик. Правда, такие автоматические правила часто приводят к ложным срабатываниям, и со временем разработчики начинают полностью их игнорировать, упуская реальные проблемы. К счастью, большинство систем позволяют тонко настраивать пороговые значения. Важно регулярно проверять и корректировать эти настройки. ПРЕДУПРЕЖДЕНИЕ Излишне чувствительная настройка оповещений приводит к большому количеству ложноположительных результатов, которые мы начинаем игнорировать. Это явление известно как усталость от оповещений. 15.4.2. Решение проблем с помощью оповещений Если среди ночи вас разбудило оповещение о том, что сервис или система работают не так, как хотелось бы, то не спешите паниковать. В таких ситуациях у вас обязательно должны быть заготовлены четкие процедуры для эффективного и хладнокровного устранения проблемы. В идеале оповещения должны сопровождаться дополнительной информацией, чтобы их можно было вписать в контекст. Это может быть краткое описание значимости оповещения, так как эксперта по сервису не всегда можно привлечь моментально.
   Глава 15. Мониторинг и оповещения в Kafka 311 Часто устранением ошибок занимаются члены команды, не работающие с сервисом регулярно. Ссылки на соответствующие дашборды помогают быстро определить основную причину, а плейбуки могут даже содержать пошаговые руководства по устранению неполадок, хотя это не всегда целесообразно. Плейбуки следует регулярно обновлять, особенно после инцидентов. В некоторых случаях можно реализовать автоматические обработчики оповещений, которые, например, будут автоматически перезапускать сервисы или масштабировать их. 15.5. Окружения развертывания Kafka и сложности их мониторинга Окружение, в котором развернут Kafka, существенно влияет на операционные задачи и требования к мониторингу. Kafka на физическом оборудовании в вашем центре обработки данных, на виртуальных машинах, в облаке или в виде управляемого сервиса связан с уникальными рисками, которые необходимо устранять в целях обеспечения стабильной производительности и надежности. 15.5.1. Kafka на собственном оборудовании компании В традиционных локальных развертываниях Kafka размещается на физическом оборудовании в центре обработки данных компании. Такая конфигурация дает полный контроль над аппаратным обеспечением и сетевой инфраструктурой, но с ней связаны определенные сложности. Аппаратные сбои, такие как выход дисков из строя, перебои электропитания или сетевые проблемы, могут напрямую влиять на работу Kafka. Ответственность за обслуживание оборудования, отказоустойчивость инфраструктуры и процедуры восстановления полностью лежит на организации. В таких случаях мониторинг должен охватывать не только Kafka, но и оборудование, на котором он работает. Состояние дисков, загрузка центрального процессора, потребление памяти и пропускная способность сети — это ключевые метрики, которые необходимо отслеживать для предотвращения появления узких мест или сбоев. Избыточность, резервное копирование и планы аварийного восстановления помогают снижать риски потери данных или длительных простоев. 15.5.2. Kafka на виртуальных машинах При развертывании Kafka на виртуальных машинах (часто в локальных или частных облачных окружениях) появляется дополнительный уровень абстракции. Виртуальные машины поддерживают гибкость и масштабируемость, но создают дополнительные сложности, такие как конкуренция за ресурсы между разными виртуальными машинами, размещенными на одном физическом
   312 Часть IV. Kafka в корпоративной среде сервере. Гипервизор распределяет ресурсы центрального процессора, памяти и ввода-вывода между несколькими виртуальными машинами, и любая конкуренция за эти ресурсы может привести к снижению производительности Kafka. Мониторинг развертываний виртуальных машин требует внимания как на уровне приложения (брокеры и клиенты Kafka), так и на уровне виртуализации (производительность гипервизора, распределение ресурсов и потенциальная конкуренция). Отслеживание таких метрик, как время ожидания центрального процессора, задержка операций ввода-вывода на диске и свопинг памяти, позволяет поддерживать стабильную производительность Kafka при наличии других рабочих нагрузок на том же физическом хосте. 15.5.3. Kafka в публичном облаке Облачные развертывания обеспечивают эластичность и гибкость, позволяя Kafka легко масштабироваться при изменении рабочих нагрузок. Однако облачные окружения представляют уникальные операционные сложности. Управлять физическим оборудованием не приходится, зато необходимо работать со сложными распределенными системами в общей мультитенантной инфраструктуре. В облаках возникают сетевые задержки, нестабильность производительности экземпляров или региональные сбои, и все это влияет на работу Kafka. В облаке мониторинг должен фокусироваться на задержках в сети, пропускной способности и поведении автоматического масштабирования, чтобы можно было обрабатывать внезапные пиковые нагрузки. Кроме того, важно отслеживать метрики, относящиеся к облаку: состояние инстансов, производительность хранилищ и трафик между регионами. С учетом эфемерности облачных инстансов требуются надежные механизмы репликации данных, резервного копирования и восстановления при сбоях узлов. 15.5.4. Kafka в Kubernetes При развертывании Kafka в Kubernetes эффективный мониторинг позволяет поддерживать производительность и надежность кластера. Ключевые метрики должны собираться через Java Management Extensions (JMX), включая индикаторы состояния брокеров, метрики продюсеров и потребителей, а также данные по топикам. Такие инструменты, как Prometheus для сбора метрик и Grafana для визуализации, позволяют операторам комплексно анализировать работу Kafka. Кроме того, необходимо отслеживать ресурсы Kubernetes — состояние подов, использование ресурсов узлов и общую производительность кластера — для предотвращения проблем, связанных с конкуренцией за ресурсы. Настройка оповещений на основе критических пороговых значений обеспечивает проактивное управление и своевременное реагирование на потенциальные сбои.
   Глава 15. Мониторинг и оповещения в Kafka 313 15.5.5. Kafka как управляемый сервис При использовании управляемых сервисов Kafka, таких как Confluent Cloud или Amazon Managed Streaming for Apache Kafka (Amazon MSK), большинство операционных задач ложится на провайдера. Он отвечает за обслуживание инфраструктуры, масштабирование и обновления, и вам не приходится управлять оборудованием или системами. Но даже в этой ситуации вы должны отслеживать производительность Kafka и обеспечивать надежность на уровне приложений. Здесь ключевыми метриками будет распределение партиций, пропускная способность сообщений, отставание потребителей и задержки. Кроме того, существуют характерные для сервиса ограничения и квоты (например, размер инстанса, пропускная способность сети), требующие мониторинга для предотвращения троттлинга или сбоев. Вы должны понимать соглашения об уровне обслуживания (SLA) и их соответствие бизнес-требованиям, чтобы контролировать соблюдение требуемых показателей доступности и производительности управляемого сервиса. 15.5.6. Вопросы безопасности в различных средах В любой среде развертывания необходимо обеспечивать безопасность. Кластеры Kafka часто подвержены внутренним и внешним угрозам, защита от которых требует тщательного мониторинга контроля доступа, аутентификации и протоколов шифрования. В локальных окружениях операторам Kafka необходимо применять политики безопасности на сетевом и аппаратном уровнях. В облачных и управляемых сервисах безопасность инфраструктуры обеспечивает провайдер, но вы по-прежнему отвечаете за мониторинг защиты брокеров Kafka, топиков и данных в процессе передачи. Метрики, связанные с неудачными попытками аутентификации, несанкционированным доступом и расходами на шифрование, позволяют выявлять потенциальные уязвимости и нарушения безопасности. Резюме Мониторинг Kafka важен для оптимальной производительности и надежности. Мониторинг инфраструктуры позволяет решать проблемы с производительностью из-за недостатка ЦП, памяти, хранилища или пропускной способности сети. Диск и сети должны быть загружены не более чем на 60 %, чтобы можно было эффективно перераспределять партиции и предотвращать проблемы с перемещением данных при сбоях брокеров.
   314 Часть IV. Kafka в корпоративной среде Метрики должны быть вписаны в контекст — если это не вре́менные пики, а длительное потребление ресурсов на высоком уровне, то, возможно, требуется увеличить масштаб. Метрики брокеров Kafka разделены на типы, например kafka_server и kafka_ log, указывающие на их происхождение и значимость. Ключевые метрики, такие как партиции с недостатком реплик и количество ISR ниже минимума, в идеале должны быть равны 0, чтобы мы были уверены в работоспособности брокера и надежности кластера. Метрики типов запросов и отклоненных сообщений помогают выявлять узкие места и недостаточно сбалансированное распределение нагрузки между брокерами. При мониторинге метрик сетевых обработчиков низкий средний процент свободных обработчиков может сигнализировать о перегрузке брокера и необходимости немедленно увеличить ресурсы. Метрики клиентов не менее важны, ведь даже надежный кластер Kafka зависит от клиентов, записывающих и считывающих данные. Общие метрики клиентов указывают на работоспособность инфраструктуры — ограничения центрального процесоора или сети могут вызвать задержки обработки, влияющие на надежность данных. Метрики продюсеров, такие как record error total и request latency avg, помогают выявлять проблемы с записью сообщений. Метрики потребителей, в том числе consumer lag и rebalances, указывают на эффективность чтения и потенциальные задержки. Метрики Kafka Connect показывают состояние коннекторов, подчеркивая необходимость мониторинга зависимостей от внешних систем. Метрики Kafka Streams, такие как количество обработанных записей и длительность обработки, дают представление о производительности приложений и позволяют вовремя обнаруживать проблемы. Оповещения — это автоматические уведомления, которые формируются на основе заданных пороговых значений и помогают вовремя замечать аномалии и сокращать простои. Избегайте усталости от оповещений — устанавливайте продуманные пороговые значения и сглаживайте метрики, чтобы оповещения отражали только реальные проблемы. Дополняйте оповещения контекстной информацией, чтобы эффективнее решать проблемы в стрессовых ситуациях. Развертывание Kafka на физическом оборудовании позволяет все контролировать, но требует мониторинга системных компонентов, например состояния дисков и загрузки центрального процессора.
   Глава 15. Мониторинг и оповещения в Kafka 315 Использовать Kafka на виртуальных машинах сложно из-за конкуренции за ресурсы, и мониторинг должен охватывать как Kafka, так и уровень виртуализации. Облачные окружения обеспечивают масштабируемость, но создают такие проблемы, как задержки в сети и нестабильная производительность инстансов, так что мониторинг должен быть направлен на пропускную способность сети и состояние инстансов. Мониторинг Kafka в Kubernetes требует дополнительных инструментов, таких как Prometheus и Grafana, чтобы отслеживать потребление ресурсов и настраивать оповещения по достижении пороговых значений. В управляемых сервисах Kafka инфраструктурой управляют провайдеры, но разработчики по-прежнему отвечают за мониторинг метрик на уровне приложения, таких как отставание потребителей и пропускная способность сообщений, чтобы контролировать соблюдение SLA.
16 Управление аварийными ситуациями В этой главе 3 Стратегии смягчения последствий отказов в Kafka. 3 Риски потери данных и стратегии обеспечения отказоустойчивости. 3 Синхронизация топиков и списков контроля доступа в MirrorMaker. 3 Архитектуры для оптимального восстановления после сбоев. В этой главе мы поговорим об управлении аварийными ситуациями в Kafka. Последствия аварий и отказа систем могут быть серьезными: убытки вследствие остановленных бизнес-процессов, недовольство клиентов из-за простоя сервисов, а также потенциальные нарушения требований регуляторов при потере данных. Если организация не сможет оперативно восстановиться после сбоя, то пострадает ее репутация. Учитывая серьезные бизнес-риски, мы должны предусмотреть эффективное управление аварийными ситуациями. В этой главе мы обсудим, как снизить влияние различных типов сбоев, возникающих из-за проблем в сети, отказов вычислительных ресурсов или неполадок с хранилищем. Наша цель — обеспечить надежность системы и целостность данных. Понимание характера потенциальных аварий позволяет организациям отличать критические события от менее важных и эффективно приоритизировать
   Глава 16. Управление аварийными ситуациями 317 стратегии. Мы рассмотрим различные варианты восстановления после аварий, ограничения традиционных стратегий резервного копирования, а также потребность в надежных решениях, которые помогут минимизировать потери и нарушение согласованности данных. Кроме того, мы обсудим продвинутые архитектуры, такие как растянутые кластеры и MirrorMaker, которые повышают отказоустойчивость и упрощают процессы восстановления. Понимая эти концепции, организации могут разработать комплексный подход к управлению аварийными ситуациями, чтобы не только защитить инфраструктуру Kafka, но и обеспечить общую операционную устойчивость. 16.1. Источники и виды сбоев На рис. 16.1 показаны ключевые компоненты Kafka и потенциальные точки отказа в нем. В распределенных системах могут возникать разные инфраструктурные сбои: проблемы с сетью, влияющие на передачу сообщений между компонентами; отказ вычислительных ресурсов, затрагивающий брокеры; проблемы в системах хранения, угрожающие потерей данных; сбои на уровне всего центра обработки данных. Помимо инфраструктуры, нужно учитывать и человеческий фактор, добавляющий иное измерение операционного риска. Неправильные конфигурации, ошибки Рис. 16.1. При эксплуатации Kafka и других распределенных систем обслуживания и ненадлежащие операцинеобходимо учитывать множество онные процедуры могут спровоцировать сценариев отказов или усугубить технические сбои, поэтому так важно реализовать надежные защитные механизмы и четко задокументировать процедуры, чтобы свести к минимуму технические и человеческие ошибки. 16.1.1. Сбои в сети Начнем с сети. В распределенных системах, таких как Kafka, периодические проблемы с сетью практически неизбежны. Рассмотрим два типичных сценария. Во-первых, проблемы с сетью между клиентами и брокерами могут мешать отправке сообщений в Kafka. Во-вторых, сетевые сбои между брокерами могут
   318 Часть IV. Kafka в корпоративной среде нарушить взаимодействие между узлами Kafka, что повлияет на репликацию и стабильность кластера. Если клиенты не могут подключиться к брокерам, то в продюсере скапливаются сообщения. Со стороны Kafka мало что можно сделать — только ожидать восстановления сетевого соединения. Как продюсеры и потребители должны обрабатывать такие ситуации? У потребителей все просто — они ожидают устранения сетевой проблемы, а затем продолжают читать сообщения в обычном режиме. У продюсеров есть варианты: мы можем блокировать отправку, буферизовать сообщения или даже удалять их. Или мы можем завершить работу продюсера и уведомить пользователя о том, что система временно недоступна. Иногда это самое простое решение, даже если нам не хочется к нему прибегать. Сетевые проблемы между брокерами особенно опасны, так как могут нарушить механизм репликации Kafka, помешать выборам лидера и даже вызвать проблемы с контроллером. Если брокеры не могут общаться друг с другом, то кластер инициирует ненужные выборы лидера или заданный коэффициент репликации не будет достигнут. Kafka пытается сохранить стабильность кластера, но главная стратегия в таком сценарии — ждать, пока сетевое соединение восстановится. Хотя сетевые проблемы заметно влияют на работу Kafka, обычно они проходят относительно быстро. Иногда даже сами по себе, если канал был временно перегружен или проблема находилась вне зоны нашей ответственности. К счастью, благодаря асинхронной архитектуре и модели сохранения в Kafka сообщения можно отправить или получить снова после устранения проблем с сетью, так что данные не потеряются и согласованность не нарушится. Сетевые проблемы особенно критичны для приложений, работающих в реальном времени, которые не допускают даже кратковременных перерывов в потоке сообщений. Для управления сетевыми сбоями необходимо наладить мониторинг и рассылку оповещений, что позволит быстро обнаружить проблемы и установить четкие операционные процедуры для различных сценариев отказов. Кроме того, желательно поддерживать хорошие отношения с сетевыми инженерами. На стороне Kafka мало что можно сделать для защиты от сетевых сбоев, поэтому необходимо продумать надежную сетевую инфраструктуру и отказоустойчивость. 16.1.2. Сбои вычислительных ресурсов Рассмотрим типичный сценарий: есть крупная система обработки платежей, через которую за час проходят миллионы транзакций. Вдруг, в самый час пик, один из брокеров перестает отвечать. Звучит пугающе, но архитектура Kafka приспособлена к таким сбоям — если, конечно, ее правильно настроить.
   Глава 16. Управление аварийными ситуациями 319 Kafka может выдерживать отказы некоторых брокеров, но фактический порог устойчивости определяется конфигурацией топика min.insync.replicas в сочетании с коэффициентом репликации. Эта настройка работает только со стратегией acks=all для продюсеров. Для простоты предположим, что коэффициент репликации равен количеству брокеров. При коэффициенте репликации 3 и min.insync.replicas=2 для всех топиков кластер может потерять один брокер, и это никак не повлияет на функционирование продюсеров и потребителей. Однако при потере второго брокера продюсеры с acks=all не смогут отправлять сообщения в кластер, а потребители продолжат работу, пока доступна хотя бы одна реплика (лидер). Проблема в том, что вся нагрузка от клиентов перенаправляется на единственный оставшийся брокер. Если брокеров больше трех, то нагрузка на каждый из них будет ниже. В некоторых критически важных сценариях может потребоваться дополнительная надежность. В этом случае можно увеличить коэффициент репликации (и, соответственно, количество брокеров) до четырех, оставив настройку min.insync.replicas=2. При такой конфигурации кластер сможет спокойно пережить отказ двух брокеров. Если останется только один брокер, то потребители смогут продолжить чтение сообщений. ПРЕДУПРЕЖДЕНИЕ Чем больше коэффициент репликации, тем дороже кластер Kafka, а защищает он только от отказов брокеров, но не от более вероятных сбоев, таких как человеческий фактор. Если требуется обеспечить дополнительную надежность, то можно создать несколько кластеров. Восстановление после отказа брокера зависит от причин сбоя. Обычно достаточно обеспечить восстановление работы брокера. При временном сбое это часто происходит автоматически, иногда требуется перезапустить брокер вручную. Если брокер вышел из строя окончательно, например, из-за аппаратной неисправности, то необходимо установить новое оборудование, развернуть Kafka и восстановить конфигурацию, как на прошлом брокере. Ключевой параметр здесь — идентификатор брокера, который должен остаться прежним. Как только контроллер распознает брокер с известным идентификатором, но без данных, начнется репликация отсутствующих данных с лидеров партиций. Через некоторое время брокер синхронизируется и станет лидером для партиций, у которых прошлый брокер был предпочтительным лидером. СОВЕТ Kafka различает брокеров только по идентификатору. Если к кластеру подключается брокер с пустым диском, но знакомым идентификатором, то он продолжит работу как исходный брокер, становясь репликой и лидером для тех же партиций, что и прежний брокер.
   320 Часть IV. Kafka в корпоративной среде 16.1.3. Сбои хранилища С хранилищем могут возникать разные проблемы. Например, заполнение хранилища или тома, куда Kafka сохраняет партиции, кажется простым сценарием, но последствия могут быть очень серьезными. Linux плохо справляется с заполненными томами; в случае с Kafka ситуация еще хуже, поскольку данные сразу записываются на диск, но отвечает за это операционная система. Другими словами, если место в хранилище заканчивается, а в кэше страниц (то есть в памяти) находится много данных, то они могут потеряться. Если все брокеры настроены одинаково и проблема возникает одновременно на всех, то потеря данных может стать необратимой, и здесь мало что можно сделать. Как правило, рекомендуется хранить в томе резервный файл размером 10 Гбайт — при заполнении диска его можно будет удалить, выиграв время для устранения проблемы. СОВЕТ Надо следить за тем, чтобы у Kafka никогда не заканчивалось место на диске. Мы считаем, что лучше остановить Kafka до того, как это произойдет. Конечно, это актуально только в том случае, если вы хотите сохранить данные в Kafka. В виртуализированном окружении или Kubernetes эта проблема часто легко решается увеличением размера диска. В физической инфраструктуре все не так просто, поскольку придется заменить диски. Главное — наладить мониторинг и рассылку оповещений, чтобы быстро обнаруживать проблемы, и установить четкие операционные процедуры для этого сценария. Диск может отказать совсем. В старых версиях Kafka это всегда приводило к отказу брокера. Если у вас несколько дисков на брокер и нет RAID-массива, то можно настроить несколько каталогов для логов. При отказе каталога с логами брокер уведомит контроллер о сбое, но остальные каталоги продолжат работать без проблем. Нам останется только заменить вышедший из строя диск новым. Брокер обнаружит пустой каталог и реплицирует все данные, которые находились на этом диске, с соответствующих лидеров. Через некоторое время синхронизация восстановится. Если из строя выходит центральная система хранения, то Kafka тут бессилен. Необходимо устранить ошибку и, вероятно, перезапустить его. Скорее всего, часть данных будет утеряна, как и в первом сценарии. 16.1.4. Сбои в центре обработки данных Еще одна инфраструктура, которая может дать сбой, — центр обработки данных. Обычно при этом отказывает не только Kafka, но и окружающие системы. Лучше всего не рассматривать эту проблему для работающей системы, а сделать резервные копии критически важных данных и хранить их в другом месте. Если
   Глава 16. Управление аварийными ситуациями 321 ЦОД откажет насовсем, то всю инфраструктуру придется воссоздать где-то еще. Да, простои будут долгими, но если речь не о компании, обслуживающей критическую инфраструктуру, то такой подход вполне допустим. ПРЕДУПРЕЖДЕНИЕ Перед применением описанных здесь решений тщательно подумайте, действительно ли необходимо реализовать этот сценарий. Вероятно, при отказе ЦОД самый серьезной проблемой будет вовсе не Kafka. Растянутый или даже зеркалированный кластер значительно увеличивают сложность и стоимость системы. Итак, мы вас предупредили, а теперь рассмотрим, как защитить Kafka в случае подобных сбоев. Самое простое решение — развернуть кластер Kafka в нескольких ЦОД, создав растянутый (stretched) кластер (рис. 16.2). Раньше мы рассматривали примеры с локальным кластером и несколькими брокерами на одном компьютере, а растянутый кластер устроен наоборот: мы распределяем брокеры и, следовательно, кластер между разными ЦОД. В этом случае при аварии в одном центре кластер Kafka сохраняет работоспособность, так как отказ всего центра будет эквивалентен отказу отдельного брокера. Если только мы не лишимся нескольких ЦОД одновременно, система продолжит функционировать. Рис. 16.2. В растянутом кластере брокеры Kafka размещаются в нескольких ЦОД Вероятность серьезного сбоя в кластере Kafka значительно снижается, так как одновременный отказ двух, трех и более центров маловероятен. Если кластер размещен в одном центре, то при его сбое Kafka полностью перестанет работать.
   322 Часть IV. Kafka в корпоративной среде Растянутые кластеры распределяются минимум по трем ЦОД. Если их будет только два, большинство узлов координационного кластера (на базе ZooKeeper или KRaft) окажутся в одном центре и при его отказе мы потеряем координационный кластер и весь кластер Kafka. СОВЕТ Растянутые кластеры распределяются минимум по трем ЦОД. При двух центрах координационный кластер не сможет достичь кворума, если два из трех узлов выйдут из строя одновременно. Существуют другие подходы, например конфигурация с 2,5 ЦОД, где третий «легкий» ЦОД содержит только один координационный узел, но мы не рекомендуем это решение, если возможны альтернативы. Разумеется, никто не станет строить третий центр только для Kafka, поэтому в некоторых случаях это единственный вариант. Что мы называем центром обработки данных и как далеко центры могут находиться друг от друга? Работа Kafka оптимизирована с учетом сетевой задержки до 30 миллисекунд, поэтому кластер можно растянуть между близко расположенными центрами, например в разных зонах доступности в облаке, но не между разными регионами. Если кластер Kafka распределяется по нескольким регионам, то лучше использовать зеркалирование, как описано в разделе 16.3. Для корректной работы реплики должны быть равномерно распределены между ЦОД. Для этого на брокерах есть параметр broker.rack. Судя по названию параметра, это должен быть идентификатор стойки, но на практике здесь можно указать название центра или другой ключ, описывающий расположение брокера. Если у всех брокеров задан идентификатор стойки, то при создании нового топика Kafka попытается распределить реплики как можно равномернее между разными стойками. Встроенного решения для перераспределения имеющихся топиков по кластеру нет, но можно использовать Cruise Control (см. главу 14). Хотя растянутый кластер повышает отказоустойчивость, он существенно увеличивает стоимость эксплуатации Kafka в облаке из-за трафика между зонами доступности. Хуже того, потребители и продюсеры всегда должны обращаться к лидеру, что только удорожает обмен данными. KIP-392 помогает значительно сократить трафик между ЦОД, позволяя потребителям получать данные из реплик, расположенных в том же центре, — достаточно задать для них client.rack. СОВЕТ Если невозможно распределить кластеры Kafka между разными ЦОД, то следует хотя бы распределить брокеры по разным стойкам в одном центре, чтобы обеспечить максимальную отказоустойчивость. В идеале стойки никак не должны быть связаны — у них должны быть отдельные сетевые подключения и питание. В этом случае также необходимо указывать идентификатор стойки.
   Глава 16. Управление аварийными ситуациями 323 16.2. Резервное копирование в Kafka Для большинства баз данных существуют хорошие стратегии резервного копирования. К сожалению, Kafka — не тот случай. Хотя репликация помогает смягчить последствия серьезных сбоев, ее нельзя считать альтернативой резервному копированию системы. Прежде чем обсуждать подходы к резервному копированию, разберемся, когда оно требуется для Kafka. Многие инсталляции Kafka могут вообще не нуждаться в резервном копи­ ровании, так как данные всегда можно заново импортировать из исходной системы. Например, если мы через Kafka перемещаем данные из основной базы данных в микросервисы, то можем обойтись без резервного копирования Kafka — все данные есть в исходной базе, откуда их можно заново извлечь при сбое Kafka. Если Kafka используется как система обмена сообщениями, где данные нужны всего несколько секунд, то резервное копирование тоже не имеет смысла. Один наш заказчик как-то сказал, что если им вдруг понадобится восстановить резервную копию Kafka, то компания к этому моменту уже все равно разорится. В таких случаях резервное копирование Kafka нецелесообразно. Однако все больше компаний используют Kafka в качестве единого источника истины для критически важных бизнес-данных, и им требуется надежная стратегия на случай сбоев. Самый очевидный подход — периодически создавать снимки файловой системы и хранить их вне ЦОД. Плюс в том, что системные администраторы привыкли к этой процедуре и она уже достаточно налажена, но есть и недостатки. Kafka сам реплицирует данные, поэтому резервная копия будет содержать много избыточных данных, что может стать — или нет — проблемой. Выбрать время для создания резервной копии бывает непросто. Если вы можете сделать снимки всех брокеров приблизительно одновременно и не выполнять масштабные операции (наподобие перемещения партиций между брокерами), то проблем возникнуть не должно. Но представьте ситуацию, когда партиция A находится на брокере 1 и сначала мы создаем резервную копию брокера 2. Затем партиция A перемещается на брокер 2, после чего создается резервная копия брокера 1. В итоге партиция А вообще не попала в резервную копию. Восстановить Kafka из резервной копии относительно просто, особенно если отказали только некоторые брокеры. Но, как и при любом запланированном резервном копировании, будут потеряны все данные, записанные между созданием снимка и моментом сбоя. На этот случай рекомендуем решение для непрерывного резервного копирования. Один из авторов экспериментировал с решением на основе Kafka Connect, которое могло бы корректно обрабатывать эту процедуру, но не реализовал его в продакшене.
   324 Часть IV. Kafka в корпоративной среде Некоторые компании пытаются достичь аналогичного результата, используя Kafka Connect для хранения данных из Kafka в таких сервисах, как Amazon Simple Storage Service (Amazon S3). Подход эффективный, но резервная копия получается неполной, поскольку большинство коннекторов не сохраняют смещения потребителей. Так что при таком подходе можно восстановить все данные, но потребители не смогут продолжить обработку с того же места, где остановились. Альтернативный подход — многоуровневое хранилище в Kafka, но на момент написания эта функция не поддерживала топики со сжатием и топик смещений потребителей. К тому же сложно предсказать, когда сегмент будет перемещен на холодный уровень. Идеального метода не существует. Есть надежда на коммерческое решение Kannika, которое не только обеспечивает резервное копирование и восстановление, но и позволяет клонировать кластеры Kafka — например, для создания тестового окружения на основе продакшена с маскировкой для защиты персональных данных. 16.3. Зеркалирование кластеров Kafka с помощью MirrorMaker Еще один способ защититься от сбоев и потери данных — зеркалирование всего кластера. Вместо выполнения обычного резервного копирования мы непрерывно копируем сообщения из одного кластера в другой, а в отличие от обычной репликации можем зеркалировать кластеры на большие расстояния. Для этой цели используется инструмент MirrorMaker, основанный на уже знакомом вам Kafka Connect. Главное преимущество MirrorMaker — возможность зеркалировать не только топики и сообщения, но и ACL топиков и смещения потребителей. MirrorMaker использует три коннектора Kafka: MirrorSourceConnector отвечает за зеркалирование самих топиков (и ACL); MirrorCheckpointConnector управляет смещениями; MirrorHeartbeatConnector периодически проверяет соединение между кластерами. ПРИМЕЧАНИЕ Confluent Replicator предоставляет проприетарную альтернативу MirrorMaker для зеркалирования кластеров. MirrorMaker предлагает простой и действенный способ зеркалирования кластеров. Далее мы рассмотрим архитектуры, которые можно реализовать с помощью MirrorMaker, и впишем их в контекст восстановления после аварий.
   Глава 16. Управление аварийными ситуациями 325 16.3.1. Активный/пассивный кластер Наиболее простая топология — пара «активный/пассивный» (рис. 16.3). В этой конфигурации у нас есть активный кластер, где записываются и считываются сообщения. Мы зеркалируем его во второй кластер, куда сообщения не записываются напрямую, почему он и называется пассивным. Рис. 16.3. Пара «активный/пассивный» в MirrorMaker Если активный кластер выходит из строя, то пассивный берет на себя его роль. При этом продюсеры начинают отправлять сообщения в бывший пассивный кластер, а потребители читают из него сообщения. Пока что этот подход кажется очень простым. Но что делать после переключения кластеров? Предыдущий активный кластер теперь не синхронизирован с пассивным, и у нас нет резерва на случай отказа этого второго кластера. Самое очевидное решение — удалить предыдущий активный кластер и создать на его месте новый пассивный. Проблема в том, что это необходимо делать после каждого переключения. Нам этот подход не нравится из-за сложности на этапе восстановления, но для компаний, у которых только два ЦОД, это приемлемый вариант. Еще один вариант — миграция данных, например, при переносе между центрами обработки данных или из облака/в облако. Конфигурация «активный/ пассивный» также подходит для сценариев, когда мы хотим переместить
   326 Часть IV. Kafka в корпоративной среде данные из одного места в другое, например, чтобы выполнить анализ в головном офисе. Однако это очень похоже на архитектуру «звезда», которую мы обсудим позже. 16.3.2. Активный/активный кластер В топологии «активный/активный» (рис. 16.4) два равноправных кластера зеркалируют друг друга, то есть MirrorMaker в каждом кластере зеркалирует другой кластер в собственный. Для этого в MirrorMaker 2 была представлена концепция удаленных топиков. Они идентифицируются в соответствии со специальным соглашением об именовании: к названию топика добавляется префикс с именем исходного кластера. Особенность этих топиков заключается в том, что они не зеркалируются MirrorMaker, что предотвращает бесконечные циклы репликации. Рис. 16.4. Топология «активный/активный» в MirrorMaker 2 Кроме того, MirrorMaker гарантирует, что удаленные топики доступны только для чтения, устанавливая соответствующие ACL. Таким образом предотвращается случайная запись данных продюсерами в эти топики, поскольку такие данные никогда не будут реплицированы в другой ЦОД. Как действуют клиенты? В топологии «активный/активный» продюсеры и потребители могут находиться в обоих ЦОД. Продюсеры, как обычно, записывают данные только в локальные топики. У потребителей ситуация сложнее: обычно им приходится считывать данные как из локальных, так и из удаленных топиков.
   Глава 16. Управление аварийными ситуациями 327 Для этого Kafka поддерживает подписки на основе регулярных выражений — потребитель указывает шаблон и подписывается на все топики, соответствующие этому выражению. На первый взгляд все просто, но существуют сервисы, которые потребляют данные из одного топика и записывают в другой. Например, сервис инициирования платежей может читать данные из топика заказов и записывать подтверждения в топик статуса оплаты. Если запустить этот сервис в обоих ЦОД и разрешить ему потреблять данные из локального и удаленного топиков, то каждый заказ, скорее всего, будет оплачен дважды. В топологии «активный/активный» нужны дополнительные меры предосторожности для предотвращения подобных проблем. СОВЕТ Чтобы предотвратить двойную обработку в таких сценариях, как сервисы инициирования платежей, можно использовать идемпотентность и идентификаторы транзакций с дедупликацией. Мы можем полагаться на идемпотентность, то есть наше приложение должно быть спроектировано так, чтобы повторная обработка одного и того же сообщения обходилась без негативных последствий, например, двойной оплаты. Кроме того, благодаря идентификаторам транзакций для дедупликации сервис может гарантировать, что любая транзакция обрабатывается только один раз, даже в конфигурации «активный/активный» с несколькими центрами обработки данных. Главное преимущество этого подхода в том, что при отказе одного ЦОД ничего страшного не случится и нам (почти) не придется ничего делать, поскольку продюсеры продолжат отправлять сообщения в локальные топики, потребители продолжат получать сообщения из локальных топиков, а непрочитанные сообщения будут поступать из удаленных топиков. Как только неисправный центр будет восстановлен, его можно будет запустить, чтобы продолжить работу в прежнем режиме. 16.3.3. Топология «звезда» Последняя архитектура, которую мы рассмотрим в этом разделе, — топология «звезда» (hub-and-spoke) (рис. 16.5). Здесь есть центральный кластер Kafka, который агрегирует данные из нескольких кластеров на периферии. Такая архитектура может применяться, например, когда головной офис компании хочет собирать данные из филиалов, чтобы в дальнейшем проводить централизованный анализ. В этом случае MirrorMaker можно развернуть в центре (или даже по одному экземпляру в каждом филиале), чтобы он собирал необходимые данные с периферии и записывал их в локальный кластер. Затем данные могут обрабатываться локальными потребителями как обычно.
   328 Часть IV. Kafka в корпоративной среде Команды команды команды команды Рис. 16.5. Топология «звезда» с MirrorMaker 2 (Kafka Connect) для репликации топиков из лучей в центр и топика commands из центра в лучи Кроме того, топология «звезда» позволяет передавать данные из центра на периферию. Например, в сети супермаркетов, состоящей из головного офиса и множества магазинов, головной офис может публиковать новые цены в центральном топике, который затем реплицируется в локальные магазины. В свою очередь, магазины публикуют данные о продажах в своих локальных кластерах Kafka, а MirrorMaker реплицирует эти данные в головной офис для расчета общего дохода. Если возникнут проблемы с соединением, то локальные магазины продолжат работу, используя старые цены. Головной офис временно не будет получать данные о продажах, но они никуда не потеряются. Как только соединение восстановится, синхронизация данных произойдет в обоих направлениях и операции возобновятся в штатном режиме. Таким образом, топология «звезда» удобна для организаций с множеством отделений, которые должны продолжать работу даже при потере связи с центральным
   Глава 16. Управление аварийными ситуациями 329 кластером. Помимо супермаркетов, аналогичный подход используют круизные лайнеры для синхронизации данных в порту и автономной работы в море. Такие решения подойдут и для государственных учреждений, которые должны функционировать без доступа к Интернету. ПРИМЕЧАНИЕ MirrorMaker работает асинхронно. Это означает, что последние данные могут быть недоступны, пока MirrorMaker не обработает последние сообщения. Резюме Управление авариями в Kafka сосредоточено на стратегиях обработки сбоев и снижения вероятности катастроф. Три основных типа сбоев в Kafka: сетевые проблемы, неисправности брокеров и отказы постоянного хранилища, которые часто усугубляются под влиянием человеческого фактора. Сетевые сбои типичны для распределенных систем. Проблемы с подключением отдельных клиентов обычно быстро проходят. Неполадки брокеров способны привести к потере данных, если сообщения не были корректно зафиксированы до сбоя. Требуется конфигурация acks=all и достаточное количество синхронизированных реплик, чтобы можно было гарантировать доставку данных. Сбои постоянного хранилища — одна из наиболее серьезных проблем в Kafka. В идеале они затрагивают только один брокер, но при некорректной обработке могут привести к необратимому повреждению данных. Традиционные резервные копии неэффективны в Kafka из-за непрерывной генерации и потребления сообщений, поэтому есть риск потери данных и нарушения согласованности. Растянутые кластеры снижают вероятность полного отказа благодаря тому, что кластер Kafka работает в нескольких ЦОД. Confluent Replicator предоставляет коммерческую альтернативу MirrorMaker для зеркалирования кластеров. В топологии «активный/пассивный» пассивный кластер зеркалирует активный и берет на себя управление при его сбое, но после восстановления активный не занимает прежнее место. Конфигурация «активный/активный» состоит из двух равноправных кластеров, зеркалирующих друг друга, что обеспечивает непрерывную работу без необходимости восстанавливать кластеры при сбое. В конфигурации «активный/активный» потребители могут читать из обоих кластеров и должны корректно агрегировать данные.
   330 Часть IV. Kafka в корпоративной среде Удаленные топики в MirrorMaker 2 предотвращают бесконечные циклы зеркалирования топиков, обеспечивая доступность данных при сбоях. Топология «звезда» характеризуется наличием центрального кластера, агрегирующего данные из локальных кластеров, которые работают независимо даже при недоступности центрального. Топологии «активный/активный» и «звезда» обеспечивают согласованность данных даже при сбоях. В целях повышения отказоустойчивости и производительности рекомендуется избегать конфигурации «активный/пассивный» и отдавать предпочтение паре «активный/активный».
17 Сравнение Kafka с другими технологиями В этой главе 3 Внутренние и внешние данные. 3 Kafka как независимая система. 3 Kafka и фреймворк REST. 3 Kafka и реляционные базы данных. 3 Kafka и событийно-ориентированные архитектуры. Мы подробно обсудили Kafka и его применение в бизнесе, но пока не говорили о том, для каких сценариев он подходит и чем отличается от других технологий. Задача архитекторов и инженеров — выбрать технологию или архитектуру, которая является оптимальной для конкретного сценария и, что особенно важно, подходит под заданные нефункциональные требования. Решение, успешно используемое одной компанией, может не работать в другой, даже в идентичном сценарии. Инновационные технологии, такие как Kafka, помогают решать архитектурные и технические задачи, а также реализовывать бизнес-модели, которые было бы сложно воплотить другими способами. Но в конечном счете важна не сама технология, а то, как она внедряется и как ее принимают пользователи.
   332 Часть IV. Kafka в корпоративной среде Универсальных решений не существует, как и единственно верных. Обычно компании не проектируют архитектуру с нуля, а интегрируют новые решения в имеющийся ИТ-ландшафт. 17.1. Данные внутри и снаружи С какими технологиями можно сравнить Kafka? Чтобы получить ответ на этот вопрос, обратимся к превосходной работе Пэта Хэлланда Data on the Outside versus Data on the Inside (www.cidrdb.org/cidr2005/papers/P12.pdf). Хэлланд описывает различия и сложности, связанные с данными, которые хранятся вне сервисов и внутри них (рис. 17.1). Рис. 17.1. Внутренние данные (данные внутри сервисов) и данные, которыми обмениваются сервисы (данные вне сервисов), различаются Под внутренними данными Хэлланд подразумевает данные, хранимые внутри сервиса в оптимальных для него формате и структуре. Других сервисов эти данные не касаются. Пользователи часто взаимодействуют с такими данными напрямую, поэтому разработчики стараются синхронизировать операции записи и чтения и гарантировать согласованность. Записанные данные считываются моментально, и пользователям не приходится несколько раз нажимать кнопку из-за того, что сервис якобы не отвечает. Чтобы предоставлять пользователям нужный функционал и аналитику, сервису иногда приходится комбинировать и агрегировать различные наборы своих внутренних данных. Так создается тесная связь между сервисом и его моделью
   Глава 17. Сравнение Kafka с другими технологиями 333 данных: любые изменения модели данных с высокой вероятностью потребуют соответствующих изменений в самом сервисе. Эта зависимость накладывает свои ограничения, зато сервис оптимизирует внутренние данные, чтобы обеспечить максимальную производительность и удобство использования. Внешние данные находятся за пределами сервисов. Они структурированы так, чтобы поддерживать обмен между сервисами, поэтому не должны зависеть от конкретных сервисов. Если в сервисе меняется внутренняя модель данных, то другие системы не должны приспосабливаться к ней, поскольку внешние данные имеют иную структуру. В идеале такие данные должны быть защищены от изменения или удаления после создания, ведь, как только сервис создает внешние данные, он должен действовать так, будто другие сервисы уже считали их. Сервисы всегда должны быть слабо связаны с внешними данными. В идеале модели внутренних и внешних данных могут развиваться независимо друг от друга. Это не всегда достижимо, и в таких случаях требуется координация между командами сервисов. Для обработки внешних данных требуется надежная и производительная система — ее сбой может нарушить весь обмен данными внутри компании. Часто для данных внутри сервисов используются реляционные базы или узко­ специализированные технологии, наиболее подходящие для конкретных задач. Для данных вне сервисов применяются системы обмена сообщениями или просто синхронные протоколы, наподобие HTTP-REST, без отдельной системы хранения. Однако во многих компаниях существуют системы управления базами данных, к которым обращается множество различных сервисов, и у такой архитектуры есть свои преимущества и недостатки. Kafka — это система обмена сообщениями, которая упрощает передачу внешних данных между сервисами. Kafka предоставляет надежное и производительное решение для потоковой передачи данных, неизменяемых после создания, гарантируя слабую связанность сервисов. Благодаря модели «публикация — подписка» и архитектуре на основе топиков Kafka идеально подходит для работы с внешними данными. При этом сервисы асинхронно производят и потребляют данные, не завися от внутренних моделей данных друг друга. Это особенно важно для управления потоками данных в микросервисных архитектурах, где важны гибкость и независимость сервисов. 17.2. Kafka и классические системы обмена сообщениями Kafka можно сравнить с классическими системами обмена сообщениями, поскольку он часто используется в этом качестве. Цель системы обмена сообщениями — передавать сообщения от продюсеров к потребителям, желательно максимально эффективно и надежно. Поставщики таких систем обычно предлагают
   334 Часть IV. Kafka в корпоративной среде множество дополнительных функций, в том числе корпоративные сервисные шины (enterprise service bus, ESB), предназначенные для интеграции всех систем компании между собой. Например, SAP Process Integration (SAP PI), Seeburger BIS, Microsoft BizTalk Server и многие другие. Они применяются для интеграции на уровне предприятия, предоставляя инструменты для маршрутизации сообщений, их преобразования и взаимодействия между различными платформами. На рис. 17.2 показаны ключевые особенности ESB, включая маршрутизацию сообщений: продюсер создает сообщение, а система гарантирует его доставку потребителю независимо от расположения систем, юрисдикции или принадлежности к подразделениям компании. ESB управляет доставкой, поэтому продюсеру не приходится этим заниматься. Рис. 17.2. ESB маршрутизируют сообщения между локациями, помогают соблюдать требования по защите данных и поддерживают разнообразные форматы сообщений Кроме того, ESB автоматически преобразуют сообщения в несовместимых форматах, так что нам не приходится что-то менять в продюсерах и потребителях. Системы обмена сообщениями защищают потребителей от перегрузки, уведомляя продюсера о том, что нужно помедленнее генерировать сообщения. Кроме того, ESB часто обеспечивают соответствие требованиям по защите данных и поддерживают широкий спектр форматов, например JSON, XML и SAP IDoc. Правда, управление этими системами настолько трудоемкое, что внесение изменений иногда занимает недели или даже месяцы. 17.2.1. Универсальность Kafka Вы уже могли заметить, что Kafka придерживается иной философии. В отличие от традиционных систем обмена сообщениями он не предоставляет весь функционал по умолчанию — разработчики самостоятельно реализуют все, что необходимо.
   Глава 17. Сравнение Kafka с другими технологиями 335 В классических системах обмена сообщениями, особенно ESB, сервисам не нужно заботиться о коммуникации — они просто отправляют данные на транспортный уровень, который решает остальные задачи. В этом контексте можно сказать, что с точки зрения коммуникации сервисы являются «глупыми», а канал (транспортный уровень) — «умным». В Kafka все наоборот. С точки зрения коммуникаций Kafka глуп — он просто позволяет размещать данные в логах, откуда их могут считывать потребители, пока их не удалил механизм очистки. Сервисы сами определяют, как взаимодействовать с Kafka, какие сообщения читать и как записывать данные. Такой подход обеспечивает несколько уровней разделения. Независимость от платформы. Kafka можно использовать на разных платформах, а сервисы не должны обязательно размещаться в одной инфраструктуре. Продюсеры и потребители могут находиться в совершенно разных системах и при этом эффективно взаимодействовать. Независимость от формата данных. Kafka не требует определенного формата данных, поэтому продюсеры и потребители обмениваются данными в разных форматах (например, JSON, Avro или Protobuf) без ограничений со стороны Kafka. Независимость от языка программирования. Kafka поддерживает множество языков программирования. Он предоставляет клиентские библиотеки для Java, Python, Go и других языков, что позволяет сервисам на разных языках взаимодействовать через Kafka без ограничений. Независимость от потребителя. Продюсеру не нужно знать, кто его потребитель и существует ли он вообще. Модель Kafka полностью разделяет продюсеров и потребителей: продюсеры могут отправлять данные, не учитывая состояние или наличие потребителей. Даже если потребитель сломан, остановлен для обслуживания или перегружен, продюсер спокойно генерирует данные. 17.2.2. Операционная сложность в классических системах обмена сообщениями Технически классические системы обмена сообщениями работают иначе. Они не предназначены для длительного хранения данных, но вынуждены их хранить, чтобы обеспечить отказоустойчивость. Обычно такие системы удаляют данные сразу после их чтения потребителями, то есть потом нельзя будет просто так отследить, какие данные прошли через систему. Если мы захотим подключить новый сервис, которому требуются все данные за прошлые периоды, то должны будем приложить немало усилий — придется заново передать данные через систему обмена сообщениями или найти альтернативный способ заполнения прошлыми значениями. Лог в Kafka упрощает эту задачу, так как при необходимости данные можно хранить сколько угодно.
   336 Часть IV. Kafka в корпоративной среде Если данные удаляются сразу после считывания, то возникают дополнительные сложности. Система обмена сообщениями должна отслеживать состояние потребителей. Обычно она сама передает (push) им данные. Что будет, если потребители находятся на обслуживании или перегружены? Системе нужна сложная логика поведения в таких ситуациях. Неправильная настройка или серьезные сбои могут привести к перегрузке всей системы обмена сообщениями и масштабным простоям. Один из способов предотвратить перегрузку — использовать механизмы обратного давления (backpressure). Если система обнаруживает медленные потребители, то просит продюсеры снизить темп генерации данных. Такой подход создает взаимную зависимость между продюсерами и потребителями (рис. 17.3). Рис. 17.3. Медленный потребитель может ограничить скорость работы продюсера в классических системах обмена сообщениями Если система обмена сообщениями долго хранит данные, то замедлять продюсер не приходится, а ответственность за получение сообщений полностью ложится на потребители. Если потребитель медленный, то просто обрабатывает меньше сообщений и (в идеале) наверстает разницу позже. При сбое потребитель будет перезапущен и сможет самостоятельно прочитать старые данные. Система обмена сообщениями не отслеживает, кто какие данные прочитал. Продюсерам тем более не нужно об этом беспокоиться — вся ответственность лежит на потребителях. 17.2.3. Управление классическими системами обмена сообщениями Еще один важный момент, особенно актуальный для крупных компаний, — назначение ответственных за корректную работу и управление технологией обмена сообщениями. Обычно эти функции выполняет отдельная команда. Наш опыт работы с системами ESB показывает, что такая команда управляет всеми конфигурациями, а внесение изменений обычно требует сложных процедур. Структурированный подход помогает контролировать разрастание системы, но ограничивает гибкость, поскольку на реализацию изменений уходят недели или месяцы. При использовании Kafka сложных конфигураций гораздо меньше. Управление топиками осуществляется через простые соглашения об именовании,
   Глава 17. Сравнение Kafka с другими технологиями 337 а рекомендации по настройке определяются центром компетенций. Управление разрешениями унифицировано во всей системе. Кроме того, предполагается, что у каждой системы данных есть схема, которая должна быть качественно задокументирована. СОВЕТ Рекомендуем внедрить процессы для настройки топиков Kafka, технических пользователей и прав доступа (см. главу 14). Одно из преимуществ Kafka — разработчикам не приходится централизованно управлять логикой преобразований для сопоставления различных форматов. Децентрализация позволяет командам работать независимо, не полагаясь на центр управления, который отвечает за преобразование форматов данных. ПРИМЕЧАНИЕ Подчеркнем еще раз: другие системы обмена сообщениями отлично подходят для многих сценариев. Часто Kafka применяется вместе с другими решения­ ми — например, IBM MQ на крупных предприятиях или Message Queuing Telemetry Transport (MQTT) для Интернета вещей. На практике классические системы обмена сообщениями используются в основном для интеграции имеющихся бизнес-систем — например, для обмена данными между SAP ERP и системами управления производством на промышленных предприятиях. Kafka изначально применялся преимущественно для разработки новых или адаптации имеющихся продуктов под требования клиентов. Некоторые компании используют классические системы для «старого мира», а Kafka — для «нового мира» и с их помощью налаживают взаимодействие между ними. Однако сейчас области применения Kafka расширяются — компании начинают использовать его в сценариях, где ранее доминировали классические системы. В табл. 17.1. сравниваются ограничения классических систем и преимущества Kafka в разных сценариях. Таблица 17.1. Сравнение классических систем обмена сообщениями с Kafka Характеристика Классические системы Преимущества Kafka Интеграция с легаси-системами Часто используется для интеграции с легаси-системами (например, SAP ERP) Kafka идеально подходит для интеграции современных облачных приложений и микросервисов Модель обработки сообщений Обычно синхронно, продюсеры и потребители тесно связаны Kafka поддерживает асинхронную коммуникацию — продюсеры и потребители не синхронизированы Сохраняемость данных Ограниченное хранение — сообщения могут удаляться сразу после доставки Kafka поддерживает долгосрочное хранение и возможность повторной обработки данных Продолжение 
   338 Часть IV. Kafka в корпоративной среде Таблица 17.1 (продолжение) Характеристика Классические системы Преимущества Kafka Масштабируемость Затруднена, особенно в крупных распределенных окружениях Kafka прекрасно масштабируется и может обрабатывать огромные объемы данных благодаря горизонтальному масштабированию Гибкость Негибкая интеграция, часто требуется персональная настройка или промежуточное ПО для преобразования форматов Kafka поддерживает всевозможные форматы данных (JSON, Avro и др.), легко адаптируясь к различным сценариям использования Задержка Высокая из-за синхронизации Kafka разработан для потоковой передачи данных с низкой задержкой и высокой пропускной способностью Потребитель Сильно связан с продюсером, что ограничивает гибкость Kafka обеспечивает слабую связанность. Потребители могут обрабатывать сообщения в своем темпе, даже если временно недоступны Примеры использования В основном подходит для важных и устоявшихся бизнес-приложений Kafka все чаще используется для потоковой обработки данных в реальном времени, событийно-­ ориентированных архитектур и новых сценариев применения 17.3. REST и Kafka HTTP и другие синхронные системы коммуникации часто считаются оптимальным методом для микросервисных архитектур. REST-подобные протоколы повсеместно используются как простой и эффективный способ соединения сервисов. Одно из ключевых преимуществ HTTP-REST — начать работу с протоколом очень просто. Его поддерживает почти каждый язык программирования, а после создания API его можно использовать во многих сценариях. Например, один и тот же API может применяться для обмена данными между фронтендом сервиса и его бэкендом, а также между бэкендом и другими сервисами. 17.3.1. Проблемы синхронного взаимодействия Такой подход кажется совершенно логичным, но вносит значительные сложности. Использование архитектур на основе микросервисов часто объясняют тем, что вносить изменения в монолитные системы очень трудно, поскольку все компоненты зависят друг от друга (рис. 17.4). К сожалению, синхронные протоколы взаимодействия редко решают эту проблему, воспроизводя те же самые
   Глава 17. Сравнение Kafka с другими технологиями 339 запутанные зависимости, которые существовали ранее в монолите. В результате недостатки монолитных систем накладываются на проблемы распределенных. В упомянутой ранее статье Пэт Хэлланд говорит о том, что внутренние данные сервиса следует обрабатывать иначе, чем внешние. При использовании REST внутренние данные сервиса обычно предоставляются через API. Во многих случаях правильнее было бы обмениваться внешними данными через явную и подходящую структуру коммуникации. Рис. 17.4. Синхронные протоколы коммуникации, такие как HTTP-REST, часто создают сильные зависимости между сервисами. Отказ одного сервиса может вызвать эффект домино, приводящий к выходу из строя всей системы Одна из ключевых проблем REST при обмене данными — его синхронность. Синхронное взаимодействие создает временную и физическую связанность. На практике сервисам требуются данные от других сервисов, чтобы давать ответ на запросы. Если другие сервисы, в свою очередь, обращаются к третьим, то нередко возникают проблемы. ПРЕДУПРЕЖДЕНИЕ Сбой одного сервиса может быстро спровоцировать эффект домино, затрагивающий все остальные сервисы. Для решения проблемы можно создать локальный кэш недавно прочитанных данных. Но как поддерживать его актуальность, если уведомления о новых данных не поступают? Это крайне сложная проблема, не имеющая простых решений. 17.3.2. Альтернативные стратегии взаимодействия Альтернатива синхронным каналам связи (HTTP и пр.) для обмена данными между сервисами — асинхронная коммуникация. Она позволяет смягчить последствия сбоя сервиса. Мы не запрашиваем актуальные данные от другого сервиса, а ждем уведомления об изменениях. Такой подход позволяет предварительно вычислять данные при возникновении события и быстрее отвечать на запросы клиентов. Синхронная коммуникация часто необходима для определенных сценариев, например для видеоигр или трейдинга. В свою очередь, асинхронные системы наподобие Kafka имеют преимущество там, где требуется высокая пропускная способность, масштабируемость и слабая связанность между сервисами. Архитектура Kafka особенно эффективна для обработки больших объемов событий и обеспечения надежного потока данных в распределенных системах.
   340 Часть IV. Kafka в корпоративной среде При синхронном взаимодействии на основе REST данные передаются только по явному запросу сервиса. Однако важно уточнить, что REST сам по себе не подразумевает статичности данных. REST — это протокол для ресурсно-­ ориентированных коммуникаций, который поддерживает взаимодействия почти в реальном времени, но имеет ограничения при масштабировании, если данные поступают в огромных объемах или состояние меняется слишком часто. Kafka, наоборот, изначально построен на концепции данных в движении. Когда происходит событие, он передает данные почти в реальном времени, активизируя рабочие процессы по всей системе. Такая событийно-ориентированная архитектура позволяет мгновенно обрабатывать и использовать данные, обеспечивая быстрое и эффективное реагирование. Однако реализация Kafka требует определенных усилий. Для управления централизованной системой обмена сообщениями необходимы дополнительная инфраструктура и навыки. Поддержка двух API — одного для внутреннего использования сервисом и другого для внешнего — усложняет архитектуру. Для небольших проектов или команд эти сложности могут перевесить преимущества, но в крупных организациях с множеством команд и систем вложения часто оказываются оправданными. Еще одно ограничение Kafka — отсутствие централизованного репозитория, к которому можно было бы отправлять запросы. В отличие от реляционных баз, где данные можно анализировать оперативно с помощью SQL, в распределенном логе Kafka не вполне понятно, как это делать. Такие инструменты, как Flink SQL, предоставляют решения для запросов к потокам данных, но по простоте и надежности пока уступают традиционным SQL-движкам. В результате данные из Kafka часто экспортируются в озера и хранилища данных или в другие аналитические системы, где их можно агрегировать и анализировать. 17.4. Реляционные базы данных и Kafka Прежде чем углубиться в сравнение Kafka с реляционными базами данных, подчеркнем: мы, авторы этой книги, ярые сторонники традиционных реляционных баз данных. В личных проектах центральную часть архитектуры у нас обычно занимает PostgreSQL. При выборе системы надо понимать, на какие вопросы она отвечает. Реляционные базы данных обычно отвечают на вопрос «Что у нас есть?» или — более развернуто — «Каково текущее состояние системы?». Очевидно, что это очень полезная информация. 17.4.1. Сильные и слабые стороны реляционных баз данных В базах с данными можно работать полноценно. SQL предоставляет все средства для записи и эффективного выполнения даже самых сложных запросов. Но такой подход приводит к тесной привязке приложений к базе данных. По нашему
   Глава 17. Сравнение Kafka с другими технологиями 341 опыту, базы — лучший вариант для хранения внутренних данных сервиса, обеспечивающий важные гарантии. ПРЕДУПРЕЖДЕНИЕ Если к базе обращается множество сервисов, а данные не имеют четкого деления на «твои» и «мои», то в результате создается шаткая конструкция, малейшее изменение которой может привести к коллапсу (рис. 17.5). В такой ситуации систему лучше вообще не трогать. Рис. 17.5. Реляционные базы данных дают гарантии согласованности, но, когда одну базу используют множество сервисов, это приводит к слишком сильной связанности. Даже небольшие изменения требуют согласования со всеми командами На какой вопрос отвечает Kafka? Мы спрашиваем: «Что было?» — или более развернуто: «Как мы пришли к этому состоянию?» Другими словами, мы изучаем историю. По ней мы восстанавливаем текущее состояние. Когда разные сервисы читают одну и ту же историю, можно создать состояние, необходимое для конкретного сценария. История событий идеально подходит для обмена данными между сервисами, то есть для внешних данных. 17.4.2. Комбинация Kafka и реляционных баз данных в современных архитектурах Мы определенно не призываем заменять базы данных в Kafka, а рекомендуем использовать базы для внутренних данных сервисов, а системы наподобие Kafka — для обмена данными между ними. Этот подход слишком сложный для небольших систем, но чем больше сервисов взаимодействуют друг с другом, тем труднее вносить изменения в централизованную базу данных и тем быстрее окупаются вложения в Kafka.
   342 Часть IV. Kafka в корпоративной среде СОВЕТ С помощью Kafka Connect базы данных можно подключить к Kafka, приложив относительно немного усилий к настройке. Кстати, Kafka и базы данных различаются не так уж сильно. Если присмотреться, то можно заметить, что практически каждая реляционная база данных внутренне основана на логах. Журнал фиксации изменений (commit log) отслеживает все изменения в базе до обновления фактических таблиц. Эти журналы используются в том числе для репликации данных между разными экземплярами баз и восстановления после сбоев. Если сервер базы данных выходит из строя и перезапускается, то журнал фиксации изменений помогает восстанавливать корректное состояние. На основе данных этого журнала можно не только создавать таблицы, но и поддерживать актуальность индексов, материализованных представлений и других структур (рис. 17.6). Рис. 17.6. Локальные материализованные представления с использованием Kafka Топики в Kafka можно рассматривать как аналоги таких журналов, только мы не пытаемся воспроизвести функционал баз данных в Kafka, а перекладываем эти задачи на наши сервисы — в точности как описывал Хэлланд, говоря о данных внутри сервисов и снаружи. Лог — идеальная структура для обмена данными между независимыми сервисами. Внутри самого сервиса мы создаем материализованные представления данных в логе. В отличие от представлений в базе эти представления в сервисе не обязаны быть однородными. Мы можем выбрать для представления технологию, которая лучше всего подходит для каждого сервиса и нашей ИТинфраструктуры. Почему нельзя просто использовать Elasticsearch или Open-Search для поиска данных в логе? Если мы хотим, чтобы данные были легко доступны на сайте с большим трафиком через кэш, то почему бы не хранить данные в кэше в памяти,
   Глава 17. Сравнение Kafka с другими технологиями 343 например в Redis? А для сложных специальных запросов к данным всегда есть реляционная база данных, например PostgreSQL. Нам не придется развертывать даже эти продукты — во многих случаях потоковая обработка с использованием Kafka Streams или Apache Flink в сочетании со stateful-операциями позволяют обходиться без дополнительных систем баз данных. Мы говорили об этом в главе 12. Таким образом, с помощью Kafka можно создать архитектуру, в которой неизменяемые данные хранятся централизованно и предоставляются всем заинтересованным сторонам и каждый сервис может независимо извлекать нужные данные и сохранять их в требуемом формате с помощью локальных мате­ риализованных представлений. В своих докладах и статьях Мартин Клеппман называет это «выворачиванием базы данных наизнанку» (https://mng.bz/nR78). Так он описывает использование лога в качестве центрального элемента для корпоративной архитектуры. На этой основе мы создаем функции баз данных, такие как таблицы, представления, материализованные представления, индексы и т. д., в виде распределенной системы. Мы называем этот архитектурный шаблон центральной нервной системой для данных или платформ потоковой обработки. 17.5. Kafka как ядро платформы потоковой обработки данных Некоторые называют Kafka системой обмена сообщениями, но нам кажется, что это лишь один из возможных вариантов применения. Кому-то достаточно использовать Kafka как распределенный лог. Мы считаем, что Kafka лучше всего описать как ядро платформы потоковой обработки данных. Суть такой платформы в том, что данные здесь всегда находятся в движении. Всякий раз, когда в организации происходит событие — а они происходят постоянно, — система генерирует сообщение, которое записывается в центральный лог. Другие системы получают это событие почти в реальном времени или позже. Мы реагируем оперативно, а не ждем целый день, прежде чем зафиксировать события и учитывать их при принятии решений, как в системах пакетной обработки. Если рассматривать Kafka как ядро платформы потоковой обработки (рис. 17.7), то какие еще системы нам необходимы? В большинстве случаев Kafka не развертывается на пустом месте, а вписывается в инфраструктуру с множеством других систем. Вы уже знаете, что Kafka Connect — это инструмент для записи данных из сторонних систем в Kafka и отправки данных из Kafka в сторонние системы: базы данных, файлы и даже другие системы обмена сообщениями. Нам кажется, что Kafka Connect должен входить в любую архитектуру, где работа Kafka не ограничивается перемещением больших объемов данных между двумя точками.
   344 Часть IV. Kafka в корпоративной среде Рис. 17.7. Kafka как центральная нервная система для данных в организации Данные, поступающие в Kafka, обычно не просто пересылаются по разным направлениям, а еще и обрабатываются. Для этой цели отлично подходит инструмент потоковой обработки, такой как Kafka Streams или Apache Flink, который позволяет почти в реальном времени обрабатывать данные и реагировать на изменения. Обработанные данные затем используются нашими сервисами или даже сервисами других команд в организации. Когда с Kafka работают несколько команд, целый ряд вопросов часто остается без внимания, пока ситуация не становится критической, — речь о проблемах управления Kafka и автоматизации. С одной стороны, следует продумать, как максимально просто и автоматизированно развернуть Kafka, если нужно управлять им самостоятельно. Для повседневного использования не менее важно автоматизировать управление ресурсами и процессами Kafka, ответив на следующие вопросы: Как создаются топики? Какие соглашения об именовании используются? Как создаются пользователи и какие права они получают? Как учетные данные распределяются между сервисами? Как автоматизировать настройку схем и коннекторов? Список вопросов у каждой компании свой, а единственно верные ответы есть не всегда. Мы предпочитаем подход на основе Git, когда команды могут отправлять pull- или merge-запросы, которые проверяются как автоматически, так
   Глава 17. Сравнение Kafka с другими технологиями 345 и людьми, хотя бы для продакшена. Этот метод хорошо себя зарекомендовал, поскольку позволяет находить баланс между гибкостью и удобством сопровождения. Реализовав все необходимые компоненты, мы развиваем Kafka в качестве центральной нервной системы для данных. Правда, даже лучшие рекомендации не помогут, если не соответствуют потребностям организации и не вписываются в общую архитектуру. Одного Kafka недостаточно для перехода от статичной структуры к полноценным потокам данных по всей организации. Резюме ИТ-архитекторы должны выбирать оптимальные технологии для конкретных сценариев, так как у каждой организации свои требования. Внутренние данные тесно связаны с сервисами и требуют высокой согласованности, тогда как внешние не зависят от сервисов и в идеале не могут изменяться. Новые технологии, такие как Kafka, часто встраиваются в существующую ИТ-инфраструктуру, а не развертываются на пустом месте. Классические системы обмена сообщениями ориентированы на эффективную передачу сообщений, но часто требуют централизованного управления и могут усложнять эксплуатацию. В отличие от традиционных систем, которые удаляют данные после чтения, Kafka сохраняет сообщения, поэтому потребители могут независимо обращаться к прошлым данным. Kafka поддерживает децентрализацию, позволяя командам автономно управлять обменом сообщениями в зависимости от меняющихся требований продукта. HTTP-REST упрощает взаимодействие сервисов, но может создавать зависимости, приводящие к системным сбоям. Синхронные протоколы, такие как REST, способны вызывать каскадные отказы, когда сервисы зависят друг от друга. Асинхронная коммуникация через Kafka поддерживает поток данных в реальном времени, но приходится управлять отдельными API для внутренних и внешних данных. Реляционные базы фиксируют текущее состояние данных, но возникает риск тесной связанности, если используется несколько сервисов. Kafka фокусируется на исторической составляющей данных, позволяя сервисам независимо восстанавливать свои состояния.
   346 Часть IV. Kafka в корпоративной среде Рекомендуется использовать реляционные базы для внутренних данных, а Kafka — для обмена данными между сервисами, чтобы архитектура сохраняла гибкость и слабую связанность. Kafka может стать ядром платформы потоковой обработки, обеспечивая передачу данных в реальном времени и быстрое реагирование на события. Инструменты потоковой обработки (Kafka Streams, Apache Flink) вместе с Kafka Connect упрощают обработку данных почти в реальном времени и поддерживают бесшовную интеграцию со сторонними системами. Эффективное управление и автоматизация с использованием Git упрощают сопровождение и повышают гибкость Kafka, когда его использует несколько команд.
18 Корпоративные архитектуры В этой главе 3 Интеграция Kafka в архитектуру Data Mesh. 3 Обработка данных в реальном времени и событийно-ориентированные архитектуры. 3 Типичные антипаттерны при реализации Kafka. В этой главе мы рассмотрим сценарии, в которых раскрывается весь потенциал Kafka и демонстрируется его универсальность для промышленного применения, интеграции данных и аналитики. Kafka обрабатывает огромные объемы данных в реальном времени, позволяя принимать обоснованные решения, оптимизировать процессы и улучшать взаимодействие с клиентами в самых разных организациях — от заводов до фармацевтических компаний. Не забывайте и об ограничениях Kafka. При неправильном применении его преимущества оказываются бесполезны. Поэтому мы также рассмотрим сценарии, в которых Kafka может быть не лучшим выбором, развеем распространенные заблуждения и порекомендуем альтернативные решения. Понимая как сильные стороны Kafka, так и его ограничения, организации могут стратегически внедрять эту технологию, создавая надежные и эффективные системы под свои уникальные потребности.
   348 Часть IV. Kafka в корпоративной среде 18.1. Kafka как основа Data Mesh В последние годы концепции управления данными внутри компаний эволюционируют. В разработке ПО очень популярны микросервисы, а в мире данных постоянно говорят о Data Mesh. Что это такое? 18.1.1. Сложности традиционного управления данными Рассмотрим типичный для многих организаций сценарий. Представьте отдел продаж, который отслеживает взаимодействия с клиентами в системе управления отношениями с клиентами (CRM), и производственный отдел, управляющий операциями на платформе планирования ресурсов предприятия (ERP). Центральной команде инженеров данных поручают интегрировать разрозненные источники в хранилище данных, чтобы можно было составлять отчеты по всему предприятию. Отдел продаж регулярно обновляет свою CRM, добавляя новые поля и переименовывая имеющиеся, но не уведомляя об этом инженеров данных. Производственный отдел, в свою очередь, часто экспортирует неполные данные, что вызвано особенностями системы. У команды инженеров данных нет ни лишних ресурсов, ни знаний о домене, так что она с трудом успевает следить за постоянными изменениями. Конвейеры извлечения, преобразования и загрузки (ETL) часто ломаются, отчеты задерживаются, критически важные бизнес-решения не принимаются вовремя. Страдают все: отделы продаж и производства жалуются на медлительность и бесполезность инженеров данных, а те перегружены работой и не согласны с несправедливыми обвинениями. Отчеты получаются противоречивыми и недостоверными, вызывая вопросы у руководства. Этот сценарий иллюстрирует главные ограничения традиционных подходов к управлению данными: централизованные команды занимаются всеми аспектами интеграции данных и контроля качества, что приводит к появлению узких мест и низкой эффективности и мешает гибкой работе и сотрудничеству. 18.1.2. Принципы Data Mesh Data Mesh переворачивает традиционные представления о распределении обязанностей по управлению данными. Вместо централизованной команды, единолично отвечающей за управление ETL-конвейерами и качеством данных во всех отделах, каждое подразделение берет на себя ответственность за предоставление своих данных другим командам в хорошо задокументированном и качественном формате. Центральная команда по данным переключается на предоставление и обслуживание технической инфраструктуры платформы данных, а также обеспечивает соблюдение правил управления, связанных с контролем доступа, безопасностью и комплаенсом.
   Глава 18. Корпоративные архитектуры 349 Концепция Data Mesh, представленная на рис. 18.1, основана на четырех ключевых принципах, предложенных Жамак Дегани в 2019 году и впервые описанных в ее статье, опубликованной в блоге Мартина Фаулера (https://mng.bz/dXAN). Эти принципы стали общепризнанной основой для реализации архитектуры Data Mesh. Рис. 18.1. Основные принципы Data Mesh Вот четыре ключевых принципа Data Mesh. Владение доменом. Команда по данным не занимается сбором данных с отделов, а сами отделы предоставляют качественные данные в согласованном виде. Подразделения относятся к данным не как к отходам, а как к полезному продукту. Данные как продукт. Данные рассматриваются как продукт наравне с другими продуктами и сервисами. Потребителями этих продуктов могут быть как другие отделы, так и внешние партнеры. Подразделения отвечают за удовлетворение потребностей своих клиентов. Инфраструктура данных с самообслуживанием. Подразделения должны публиковать, потреблять и изменять данные, не используя длительные процедуры или запросы. Команда по данным отвечает за предоставление платформы, на которой — при соблюдении установленных процессов и правил защиты данных — отделы самостоятельно решают свои задачи. Платформа не привязана к конкретным доменам. Децентрализованное управление. Цель управления — максимально упростить использование данных внутри компании в соответствии со всеми требова­ ниями, особенно правилами безопасности. Данные должны быть стандартизированными, описанными, задокументированными и совместимыми. 18.1.3. Data Mesh и традиционные подходы Традиционно команда инженеров данных обеспечивает централизованный сбор информации из всех сервисов в озере данных. В идеальном сценарии озеро данных служит централизованным хранилищем, в котором структурированные
   350 Часть IV. Kafka в корпоративной среде и неструктурированные данные хранятся в сыром виде, находятся в легком доступе, хорошо масштабируются и поддерживают гибкие возможности анализа. Но если данные загружаются без надлежащего управления, организации или контроля качества, то озеро быстро превращается в так называемое болото данных (рис. 18.2). Рис. 18.2. Команда инженеров данных обеспечивает централизованный сбор всех данных из сервисов в озере данных. Поскольку за качеством данных не следят, озеро превращается в болото Данным в болоте не хватает метаданных, документации и продуманных процессов загрузки, что затрудняет их поиск и интерпретацию и снижает достоверность, ведь централизованно поддерживать качество и упорядочивать данные гораздо сложнее. Изначально концепция Data Mesh была предложена для более эффективной организации аналитических данных, но прекрасно применима и к транзакционным данным. Чтобы понять ее ценность, вернемся к нашему примеру. При традиционном подходе каждое подразделение поддерживает свою систему хранения, а центральная команда управляет озером или хранилищем данных. Команда инженеров данных создает ETL-конвейеры, чтобы помещать данные из подразделений в центральный репозиторий. Как видно из примера, подразделения часто не информируют инженеров данных об изменениях структуры, что приводит к нарушению конвейеров и задержкам. Подразделения продолжают работать почти без помех, но у центральной команды по обработке данных процессы выполняются неэффективно и создаются узкие места. В Data Mesh обязанности распределяются совершенно по-другому. Центральная команда по обработке данных уже не занимается интеграцией и качеством данных — она отвечает за предоставление надежной технической платформы и контроль соблюдения правил управления. Подразделения берут на себя всю ответственность за свои данные, рассматривая их как продукт, который должен удовлетворять потребности внутренних и внешних потребителей. Продукты
   Глава 18. Корпоративные архитектуры 351 данных публикуются на центральной платформе и предоставляются для аналитики и других бизнес-процессов. По мере эволюции данных владельцы продуктов данных отвечают за обновление и документирование схем. Если, например, отдел продаж меняет схему данных для поддержки новых функций CRM, то тщательно документирует изменения и информирует другие команды, использующие эти данные. При большом объеме изменений центральная команда по обработке данных помогает изменить схему и обеспечить совместимость между системами. Благодаря такому перераспределению обязанностей в Data Mesh центральная команда по обработке данных уже не тратит все свободное время на срочный ремонт поврежденных конвейеров, а выступает экспертным центром. Они консультируют подразделения по вопросам управления данными, рекомендуя оптимальные структуры. Кроме того, они предоставляют шаблоны для упрощенной реализации функционала продюсеров и потребителей, чтобы все команды вносили свой вклад в экосистему данных. Такой децентрализованный подход, основанный на ключевых принципах Data Mesh — владение доменом, данные как продукт, инфраструктура самообслуживания и децентрализованное управление, — создает более гибкую и масштабируемую систему управления данными, устраняя ограничения традиционных методов. 18.1.4. Роль Kafka в Data Mesh Kafka — популярный инструмент для реализации Data Mesh. Он выступает в качестве центральной платформы для обмена данными в реальном времени по всей организации. Благодаря способности обрабатывать большие объемы данных с низкой задержкой, а также предоставлять экономичное и долгосрочное многоуровневое хранилище Kafka становится идеальным вариантом для архитектуры Data Mesh. Он поддерживает беспрепятственный поток продуктов данных между отделами, которые их производят и потребляют, почти в реальном времени. В архитектуре Data Mesh Kafka выполняет следующие роли (рис. 18.3). Публикация и потребление данных. Kafka предоставляет масштабируемую, надежную и высокопроизводительную платформу для публикации и потребления данных в реальном времени, обеспечивая беспрерывный обмен информацией между отделами. Потоковая передача данных в реальном времени. Kafka поддерживает передачу данных с минимальной задержкой, позволяя отделам оперативно реагировать на изменения и принимать решения на основе актуальной информации. Применение схем. Kafka гарантирует согласованность данных благодаря обязательному применению схем, помогая как продюсерам, так и потребителям
   352 Часть IV. Kafka в корпоративной среде придерживаться готовых структур данных. Такой подход обеспечивает корректное форматирование и совместимость данных в системе, сокращает количество ошибок и повышает надежность. Интеграция управления. Kafka интегрируется с инструментами управления (см. главу 13) для контроля доступа, обеспечения соответствия требованиям и применения политик безопасности. Сюда входят механизмы управления доступом к данным, установка квот и соблюдение требований регуляторов — все это помогает создать безопасную и эффективную среду обмена данными. Рис. 18.3. Kafka в архитектуре Data Mesh Рассмотрим распределение обязанностей между подразделениями в этой архитектуре. Подразделения, производящие данные, они же владельцы данных, отвечают за создание и публикацию своих данных на центральной платформе, которая может быть построена на основе Kafka. Сюда входит управление жизненным циклом данных, обеспечение их качества и соблюдение схем, согласованных с потребителями. Кроме того, владельцы определяют, каким отделам или внешним партнерам предоставляется доступ к их данным, через механизмы контроля доступа. Подразделения, потребляющие данные, запрашивают доступ к конкретным данным через центральную платформу. После получения доступа они обязаны обеспечить соответствие своего программного обеспечения согласованным схемам, правилам защиты данных и другим политикам управления. Команда платформы данных предоставляет инфраструктуру и процессы, позволяющие остальным командам слаженно взаимодействовать, не согласуя с ними каждое изменение. Децентрализованный подход помогает заметно повысить качество данных и упрощает обмен ими внутри компании.
   Глава 18. Корпоративные архитектуры 353 Технологии наподобие Kafka, позволяющие реализовать Data Mesh почти в реальном времени, сокращают задержки при передаче данных между отделами. В итоге компания эффективно обрабатывает большие объемы информации, извлекает из них дополнительную ценность и быстрее адаптируется к изменениям. 18.2. Освобождение данных из основных систем с помощью Kafka Основные системы широко используются в крупных компаниях — будь то банковские услуги, страхование, производство или крупные поставщики услуг. Эти системы выступают в роли критически важных компонентов ключевых процессов компании. Часто это крупные монолитные приложения, основанные на централизованной базе данных. Они поддерживают важнейшие бизнес-процессы на протяжении десятилетий и остаются незаменимыми. Проблема в том, что обычно это очень громоздкие системы, которые крайне сложно адаптируются к современным требованиям клиентов. Например, банки не хотят, чтобы конечные клиенты напрямую обращались к основной банковской системе через мобильное приложение, так как она быстро перегружается. Многие интернет-магазины сталкиваются с аналогичными проблемами, например, при интеграции с системой управления запасами SAP. Основную систему, управляющую ключевыми процессами, необходимо отделить от новых, гибких сервисов, отвечающих актуальным требованиям клиентов. Мы должны определить, как системы будут обмениваться данными, чтобы они поступали в целевую систему практически в реальном времени, не перегружая основную систему. Кроме того, важно обеспечить, чтобы ключевые процессы продолжали обслуживаться основной системой, так как эти системы не исчезнут в одночасье. В экосистеме Kafka популярна идея, что мы освобождаем данные из основных систем и на базе этих данных создаем приложения для решения новых задач (рис. 18.4). Главная сложность в том, как минимизировать взаимодействия с основной системой и при этом избежать дорогих в обслуживании пользовательских разработок. Рис. 18.4. С помощью Kafka можно экономно предоставлять данные из мейнфреймов или основных систем в остальные сервисы
   354 Часть IV. Kafka в корпоративной среде На рис. 18.5 показано, как данные из базы основной системы записываются в Kafka с помощью Debezium через механизм отслеживания изменений данных (CDC). Проблема здесь в том, что данные передаются в Kafka в исходном формате основной системы. Как правило, лишь немногие специалисты понимают эти форматы, поэтому практически невозможно полноценно передавать эти знания другим командам. Кроме того, данные нормализованы, часто распределены по множеству таблиц, содержат различные версии и т. д. Рис. 18.5. Debezium напрямую обращается к журналу фиксации изменений базы данных Это означает, что сырые данные не должны повторно использоваться другими подразделениями — для их приложений необходимо разработать новые структуры данных. Поскольку сырые данные находятся в Kafka, то при необходимости можно создавать дополнительные конвейеры для поддержки новых сценариев. Реализовать эту возможность можно по-разному. В идеале основная система должна нативно предоставлять поток событий, но на практике так бывает редко. Кроме того, можно создать приложение Kafka Streams, которое обрабатывает сырые данные из основной системы и предоставляет их другим приложениям в унифицированном формате. Сложность этого подхода в том, что придется выполнять много операций соединения (join) и использовать хранилища состояний, так что приложение Kafka Streams будет непросто обслуживать. Однако для многих сценариев найти лучшее решение проблематично. Например, у клиента выполняется операция соединения на 1000 строк по 200 таблицам. Как реализовать ее в Kafka Streams? Лучший ответ, пожалуй: никак. Соединения можно выполнить в реляционной базе данных. База основной системы обычно не подходит, поскольку часто и так работает на пределе. Данные можно перенести во вторую реляционную базу с помощью CDC. Это даже может быть другая СУБД. Сложные операции (например, соединения) проще выполнять на SQL, а результаты записывать в исходящие таблицы, которые затем можно передать в Kafka через CDC. Освобождать данные из основных систем и предоставлять их другим сервисам, конечно, полезно, но совсем не просто. Зато этот подход открывает широкие возможности для растущих компаний.
   Глава 18. Корпоративные архитектуры 355 18.3. Kafka для больших данных Изначально Kafka был разработан LinkedIn для передачи больших объемов данных из точки А в точку Б. Мы обобщим это применение в контексте работы с большими данными. Команда LinkedIn искала решение для отправки всех данных, генерируемых на сайте, в центральный кластер Hadoop — каждый клик, комментарий, лайк, по сути, любое взаимодействие пользователей с платформой LinkedIn (рис. 18.6). Эти данные нужно было измерять и анализировать. Рис. 18.6. Kafka изначально создавался в LinkedIn для быстрой, безопасной и эффективной передачи в кластер Hadoop больших объемов данных с источников на сайте и в приложении Требовалось найти или разработать систему, способную надежно и эффективно доставлять данные в Hadoop. Найти подходящее решение не удалось, поэтому команда LinkedIn создала систему, известную сегодня как Kafka. Лог с самого начала был центральным компонентом, которому Kafka во многом обязан своим успехом. Именно благодаря логу удается достичь необходимой пропускной способности, поскольку с данными выполняются относительно простые операции. Даже если в Hadoop возникали проблемы, данные не терялись, так как хранились в логе. Кроме того, даже при значительных колебаниях нагрузки в LinkedIn лог играл роль буфера на пике и позволял надежно доставлять данные в целевую систему. Kafka по-прежнему отлично подходит для этого сценария. Он предполагает передачу больших объемов ценных данных из точки А в точку Б без лишних сложностей. Некоторые клиенты отмечают, что при их объемах данных Kafka требует наименьших операционных затрат среди всех систем в конвейере обработки данных. Конечно, при достижении определенного масштаба возникают интересные задачи. Как оптимизировать системы, чтобы достичь необходимой пропускной способности данных при минимуме усилий и затрат? Как оптимизировать пиковую производительность? Как управлять большим кластером Kafka? На эти вопросы мы попытались в общих чертах ответить в этой книге, но при определенном масштабе придется углубляться в детали. С архитектурной точки зрения этот сценарий довольно прост и в общем сводится к соединению двух (или более) систем.
   356 Часть IV. Kafka в корпоративной среде 18.4. Kafka для промышленного Интернета вещей Kafka может успешно применяться в промышленных средах. Объем данных продолжает расти, где бы они ни генерировались — на заводах, в автомобилях, на традиционных электростанциях или ветряных турбинах. Что делать со всеми этими данными? Одни из них, например данные со складов и из логистических центров, представляет интерес для конечных клиентов. Другие данные требуются для управления качеством, особенно в таких отраслях, как фармацевтика, где точный контроль над производственными процессами — залог выпуска безопасных и высококачественных препаратов. Кроме того, данные помогают анализировать общую эффективность пред­ приятия, отслеживать темпы производства и выявлять узкие места. Некоторые показатели нужны для оптимизации работы оборудования и предотвращения возможных сбоев — эта практика известна как прогнозное техническое обслуживание. Современные организации стараются извлечь из данных максимальную ценность, и для этого нужна возможность просматривать и анализировать их в реальном времени. Многие промышленные процессы требуют мгновенной аналитики, необходимой для принятия решений и контроля систем. Главная сложность — централизовать сбор данных с самых разных компьютеров и устройств и при этом обеспечить их доступность для различных сфер применения. Это идеальный сценарий для Kafka, ведь платформа поддерживает как потоки данных в реальном времени, так и генерацию отчетов на основе пакетов и помогает решать широкий спектр задач промышленного Интернета вещей (Industrial Internet of Things, IIoT). 18.4.1. Применение Kafka в промышленном Интернете вещей Чтобы лучше понять потенциал Kafka в промышленных окружениях, рассмотрим конкретные сценарии, где эта технология становится незаменимым инструментом. Прогнозное техническое обслуживание. Kafka позволяет собирать и передавать в реальном времени данные с датчиков промышленного оборудования, обеспечивая непрерывное отслежвание состояния компьютеров. Данные применяются для обучения моделей прогнозного обслуживания, которые помогают предсказывать сбои до их возникновения и оптимизировать графики ремонта, что приводит к сокращению простоев и снижению эксплуатационных расходов. Автоматизация производства. На умных фабриках Kafka выступает основой для интеграции данных в реальном времени. Технология объединяет IoT-сенсоры на производственных линиях, обеспечивая бесперебойную
   Глава 18. Корпоративные архитектуры 357 коммуникацию между устройствами и компьютерами. Эти данные позволяют автоматизировать процессы — от корректировки параметров оборудования до перераспределения ресурсов в зависимости от спроса, — что дает возможность повысить эффективность и стабильность работы. Оптимизация цепочек поставок. Kafka упрощает поток данных в логистических системах, собирая информацию с IoT-устройств на транспорте, складах и у поставщиков. Организации отслеживают цепочку поставок в реальном времени и динамически вносят корректировки: оптимизируют маршруты доставки, управляют запасами и прогнозируют задержки, повышая общую операционную эффективность. Управление энергопотреблением. Kafka помогает управлять энергопотреблением в промышленных окружениях, таких как электростанции. В реальном времени собирая данные с генерирующего оборудования, например с турбин и генераторов, Kafka помогает оптимизировать выработку энергии. Эти данные позволяют повысить энергоэффективность, сократить потери и улучшить надежность систем. Аналитика производства в реальном времени. На производстве Kafka агрегирует данные с сенсоров в цехах. Анализ этих данных в реальном времени выявляет узкие места, проблемы с контролем качества или неэффективные операции. На основе этой аналитики руководители могут оперативно принимать решения, позволяющие оптимизировать процессы и увеличивать производительность. 18.4.2. Хранение данных Необходимо определить, где и как будут храниться данные. Kafka поддерживает самые разные сценарии. Вы уже знаете, что это система для обмена данными между приложениями почти в реальном времени и для длительного хранения данных. Как долго данные должны храниться в Kafka? Во всяком случае, достаточно долго для того, чтобы заинтересованные системы успели их получить. Следует ли хранить в Kafka исторические данные? В традиционном окружении это дорого, но по мере внедрения многоуровневого хранения такой подход кажется привлекательным: часто используемые данные остаются в Kafka, а менее важные перемещаются в более экономичные хранилища. Это позволяет найти баланс между производительностью и экономией, хотя перенос исторических данных в другую систему создает новые сложности. Например, необходимо гарантировать доступность данных в зависимости от их возраста, что может усложнить процессы их извлечения. В любом случае многоуровневое хранение — это практичное решение, которое устраняет многие из этих проблем и оптимизирует процесс управления данными.
   358 Часть IV. Kafka в корпоративной среде 18.4.3. Интеграция данных и управление доступом Как эффективно передавать данные с оборудования в Kafka? Здесь главная проблема — надежность и производительность самого оборудования и интернет-соединения. Если вычислительных ресурсов и пропускной способности интернета достаточно, то кажется, что лучше будет напрямую записывать данные в Kafka. Это упрощает разработку, но может потребовать больше вычислительной мощности, чем доступно на небольшом микроконтроллере. Кроме того, необходимо продумать, что делать при временной потере соединения с Kafka. И конечно, ненадежным устройствам нужно запретить доступ к Kafka. Часто лучше выбрать более специализированный протокол, например Message Queuing Telemetry Transport (MQTT). Конечные устройства взаимодействуют с MQTT-брокером, который затем перенаправляет данные в Kafka (рис. 18.7). Рис. 18.7. Kafka в качестве интерфейса между MQTT и другими приложениями Снова возникает вопрос о том, куда направлять данные после их попадания в Kafka. Для интеграции аналитических систем обычно подходят существующие коннекторы. Если требуется записывать агрегированные данные в базу данных, также можно задействовать JDBC-коннектор. Для собственного программного обеспечения можно написать свои потребители или поручить дальнейшую обработку данных приложениям Kafka Streams. Важно помнить, что данные порождают спрос. Обращаться к данным в Kafka просто, поэтому мы рекомендуем клиентам с самого начала реализовывать инструменты самообслуживания. При таком подходе пользователи не адресуют команде Kafka множество однотипных запросов, а самостоятельно обращаются к данным в рамках установленных процессов. Часто организации для экономии удаляют старые данные, хотя они могли бы иметь практическую ценность. На производстве, например, они могут понадобиться для обучения алгоритмов. Как уже упоминалось, вопросы безопасности и защиты данных в среде Kafka сопряжены с трудностями. Kafka позволяет настраивать разрешения только на
   Глава 18. Корпоративные архитектуры 359 уровне топиков, поэтому мы, как разработчики, неохотно предоставляем конечным пользователям права на чтение целых топиков. Однако можно предоставить пользователям права на запись для сбора данных. Если требуется детально настроить разрешения, то можно создать отдельный топик для каждого клиента, но для потребительских устройств это нецелесообразно. Лучше предусмотреть управление правами в логике приложения на бэкенде. Еще одним интересным вариантом может быть прокси для Kafka, например Kroxylicious. 18.4.4. Когда использовать несколько кластеров Kafka В данном контексте часто возникает вопрос о том, сколько кластеров Kafka необходимо для эффективного управления данными. Как показывает опыт, ответ во многом зависит от организационных и технических факторов. Кто управляет кластерами? Что требуется разным командам? Иногда целесообразно создать отдельный кластер для сырых данных и дополнительный кластер для данных, прошедших обработку. Несколько кластеров Kafka сложнее администрировать и обслуживать, поэтому надо стремиться к использованию единого централизованного кластера, если только у вас нет технических или организационных ограничений, оправдывающих необходимость создания нескольких кластеров. К числу таких ограничений можно отнести следующие. Изоляция производительности. Разным командам требуются разные уровни производительности или выделенные ресурсы для кластеров Kafka. Допустим, кластер с высокой пропускной способностью и низкой задержкой необходим для обработки данных в реальном времени (например, в системах обнаружения мошенничества), а другой кластер может обслуживать пакетную обработку или архивные данные с менее строгими требованиями к производительности, скажем, для формирования отчетов на основе исторических данных. Безопасность данных. Вопросы безопасности тоже могут потребовать разделения кластеров. Например, отделу кадров требуется изолированный кластер Kafka для соблюдения Общего регламента по защите данных (GDPR) и других требований, поскольку конфиденциальные персональные данные сотрудников должны храниться отдельно от других операционных данных. Производственный отдел может использовать другой кластер с другими политиками безопасности — для операционных данных в реальном времени, связанных с производственными метриками. Территориальное распределение. Организации с командами в разных регионах могут создавать локальные кластеры Kafka для быстрого доступа к данным. Например, компания с офисами в Северной Америке и Европе может обслуживать отдельные кластеры Kafka, чтобы соблюдать региональные
   360 Часть IV. Kafka в корпоративной среде законы о суверенитете данных и снизить задержки благодаря хранению данных ближе к месту их обработки. Масштабируемость и отказоустойчивость. На крупных предприятиях кластеры Kafka можно разделять по масштабу использования, чтобы обеспечить масштабирование и устойчивость к сбоям. Например, глобальный ретейлер, обрабатывающий огромные объемы данных, может выделить отдельные кластеры для транзакционных данных, данных о запасах и аналитики клиентов. При таком подходе каждый кластер развивается независимо, а единый кластер Kafka не перегружается разнородными рабочими нагрузками. 18.5. С чем не стоит путать Kafka Kafka хорошо подходит для многих сценариев — но не для всех. Часто возникает вопрос, можно ли использовать Kafka в качестве базы данных или для синхронной коммуникации. В следующих подразделах мы рассмотрим несколько ситуаций, в которых Kafka будет не лучшим выбором. Всегда взвешивайте все преимущества и недостатки и выбирайте подход, соответствующий конкретным потребностям вашей организации. 18.5.1. Kafka — это не реляционная база данных Вопрос о том, является ли Kafka базой данных, обсуждается с момента его появления. Kafka отлично справляется с распределением данных между системами и благодаря многоуровневому хранилищу подходит для длительного хранения. По многим характеристикам Kafka действительно можно считать базой данных, но это определенно не реляционная база! РБД отображают текущее состояние системы и выполняют сложные запросы, охватывающие несколько таблиц. Хотя Kafka Streams поддерживает соединения данных, Kafka не следует использовать для запросов текущего состояния. Причин несколько. Например, Kafka предоставляет значительно более слабые гарантии по сравнению с реляционными базами данных. РБД обеспечивают полный набор ACID-гарантий (атомарность, согласованность, изоляция, устойчивость), тогда как Kafka гарантирует атомарность операций и высокую согласованность, но не поддерживает изоляцию транзакций и строгую согласованность между несколькими партициями. А главное — Kafka всегда работает асинхронно. Более того, операции объединения в Kafka обходятся значительно дороже, чем в традиционных реляционных базах данных. Kafka отлично подходит для асинхронного обмена данными между сервисами, при этом каждое сообщение должно быть самодостаточным и содержательным. Это означает, что сообщения должны быть денормализованы. В реляционных
   Глава 18. Корпоративные архитектуры 361 базах данных информация, как правило, сильно нормализована и распределена по множеству таблиц. Платформа Kafka плохо приспособлена для денормализации нормализованных данных с помощью крупных соединений. Денормализация — это процесс группирования связанных данных из нескольких таблиц в единую плоскую структуру, обычно путем их соединения. Представим, например, нормализованную базу данных с таблицей клиентов customers и таблицей заказов orders. Денормализация подразумевает сведение информации о клиентах и заказах в одно сообщение, что позволяет избавить от этой работы потребителей. Однако выполнять такие соединения в Kafka или в конвейере обработки в реальном времени будет неэффективно и сложно, особенно при больших объемах данных. В идеале соединение должно выполняться заранее в реляционной базе данных, а затем, например, записываться в Kafka через исходящую таблицу. Правда, это не всегда возможно. Иногда данные необходимо получить в нормализованной форме и затем денормализовать путем соединений. По возможности этого следует избегать, и мы не рекомендуем предоставлять доступ к нормализованным топикам обычным потребителям. Кроме того, денормализованные данные из Kafka будет непросто один к одному записать в реляционную базу. Такие данные в идеале должны быть нормализованы в потребителе заранее. СОВЕТ Kafka подходит для обмена данными между системами, а реляционные базы — для внутренних данных сервисов. Так мы задействуем преимущества каждой технологии и избегаем ее недостатков. Даже в компании, где реализована полноценная архитектура Data Mesh, по-прежнему будет множество баз данных, так как они лучше подходят для хранения внутренних данных сервисов. 18.5.2. Kafka — это не синхронный интерфейс связи Распространенный антипаттерн при использовании асинхронных интерфейсов связи — попытка имитировать синхронность. Например, когда пользователь нажимает кнопку в веб-интерфейсе, данные записываются в Kafka на бэкенде, а система ожидает, что они будут обработаны в другой системе, которая отправит результат через Kafka. В итоге после нажатия кнопки пользователь долго ждет какой-нибудь реакции. Вместо этого, как показано на рис. 18.8, лучше записать действие в синхронную реляционную базу и уведомить пользователя о получении данных. Сами данные затем можно перенести в Kafka с помощью CDC и паттерна Outbox и асинхронно обработать в третьей системе. Завершив обработку, эта система обновляет статус процесса через Kafka, а бэкенд информирует пользователя о прогрессе.
   362 Часть IV. Kafka в корпоративной среде Рис. 18.8. Не следует использовать Kafka для синхронного взаимодействия по схеме «запрос — ответ». Лучше уведомлять пользователей об асинхронных процессах и подтверждать результат по готовности Если бэкенд не требует базу данных, то фронтенд может выполнить синхронный REST-вызов. Бэкенд затем публикует данные в Kafka и подтверждает фронтенду успешный запуск действия. 18.5.3. Kafka — это не платформа для обмена файлами Нас часто спрашивают, как лучше всего обмениваться PDF-файлами через Kafka, ведь PDF — это те же сообщения. Kafka оптимизирован для обмена множеством небольших сообщений (по умолчанию до 1 Мбайт). Как правило, они должны быть машиночитаемыми, поэтому мы не советуем отправлять через Kafka PDF-файлы. Они слишком большие и не соответствуют представлениям о сообщении в Kafka. В этом случае лучше отправлять данные из PDF в машиночитаемом формате (например, в виде JSON-объекта) через Kafka, а после обработки передавать их в сервис генерации PDF. В качестве альтернативы, как показано на рис. 18.9, PDF-файл можно сохранить в объектном хранилище, локально или в облаке, а Kafka может отправить сообщение с указанием адреса, по которому этот файл можно найти. Получив уведомление от Kafka, потребитель заберет PDF-файл из объектного хранилища.
   Глава 18. Корпоративные архитектуры 363 Рис. 18.9. Kafka не подходит для обмена файлами между системами. Лучше задействовать объектное хранилище, а через Kafka отправлять уведомления о загруженном или измененном файле Сообщения по протоколу SOAP (Simple Object Access Protocol) могут превышать 1 Мбайт. В большинстве случаев такое сообщение SOAP содержит тысячи событий, что позволяет семантически разделить его, отправив в Kafka по одному сообщению на каждое событие. Этот подход предпочтительнее как с технической, так и с архитектурной точки зрения. Kafka спроектирован так, что каждое сообщение описывает самостоятельное событие, что значительно упрощает последующие операции потребителей. 18.5.4. Kafka для небольших приложений — под вопросом Kafka отлично подходит для обмена данными между множеством сервисов и особенно между командами. Он уменьшает связанность между сервисами и позволяет командам меньше зависеть друг от друга. Естественно, Kafka — прекрасный вариант и для передачи больших объемов данных из точки А в точку Б. Но если ни одно из этих условий не выполняется и Kafka используется одной командой для обмена небольшими (по меркам Kafka) объемами данных в рамках монолитного приложения, то мы обычно рекомендуем избегать Kafka, даже если сам лог кажется привлекательной концепцией.
   364 Часть IV. Kafka в корпоративной среде Приведем конкретные цифры: если в час поступает менее 1000 сообщений и в течение следующего года их вряд ли станет значительно больше, а данные использует только одна команда, то мы обычно не советуем Kafka. ПРИМЕЧАНИЕ Если Kafka уже является частью корпоративной архитектуры, это меняет дело. Но если нет перспективы других вариантов применения, то лучше не реализовывать Kafka для небольшого приложения. В таких случаях одну или несколько таблиц в базе данных PostgreSQL можно использовать не по назначению — в качестве лога. В PostgreSQL также можно обеспечить быстрый поиск через индексы и гораздо проще задействовать возможности SQL, не полагаясь на дополнительные сложные системы. Когда придет время увеличивать масштаб, значительную часть системы, вероятно, все равно придется переписывать. Если с технической и организационной точек зрения монолита достаточно, то не стоит усложнять систему без необходимости. Ничего плохого в монолите нет, но если вдруг возникнет потребность в масштабировании, то придется пересмотреть архитектуру. Возможно, для повышения производительности будет достаточно индекса базы данных. Конечно, это немодно, но маленькой компании хватит. Такой подход приводит к гораздо меньшему количеству проблем, более быстрой разработке и гораздо большей гибкости. 18.5.5. Kafka не замена хорошей архитектуре Мы часто наблюдали, как организации обращаются к Kafka после неудачных экспериментов с разными платформами обмена сообщениями, надеясь, что он решит их проблемы. По нашему опыту, реальная сложность часто кроется в непонимания того, как правильно выстроить систему и эффективно применять Kafka. Если в организации нет четкого представления о требованиях системы, нефункциональных требованиях, стоимости, времени и масштабируемости, то Kafka не поможет. Волшебства ждать не стоит. Без должного планирования и опытной команды проблемы сохранятся. Даже если вы используете Kafka для одних задач, это не означает, что он подойдет и для всего остального. Некоторые положительные, но проблемные моменты мы обсудили выше. Выбирайте систему, которая лучше всего подходит для конкретного приложения, учитывая особенности каждой компании. Конечно, не всегда разумно внедрять идеальный продукт в каждом сценарии, если при этом придется управлять совершенно новой сложной системой. Иногда допустимо использовать Kafka даже там, где это не лучший выбор. Всегда взвешивайте все «за» и «против».
   Глава 18. Корпоративные архитектуры 365 Хорошая архитектура — главное условие для любых ИТ-систем. Сложность Kafka еще и в том, что он дает пользователям большую свободу. Если в Kafka попадает мусор, то потребитель получит мусор. Kafka не интересует содержание передаваемых сообщений. При отсутствии тщательно спроектированной системной архитектуры это приведет к серьезным проблемам. Мы надеемся, что эта книга и особенно эта глава помогут командам архитекторов обдуманно выбирать, где уместно использовать Kafka, а где — нет. Сначала все продумайте и спланируйте и только потом действуйте. Резюме Data Mesh децентрализует управление данными: подразделения отвечают за свои продукты данных, а команда по работе с данными сосредотачивается на технической поддержке. Ключевые принципы Data Mesh: данные как продукт, владение доменом, инфраструктура самообслуживания и децентрализованное управление. Kafka выступает в качестве центрального хаба для обмена данными в реальном времени внутри Data Mesh, повышая качество данных и ускоряя их перемещение между подразделениями организации. Основные системы критичны, но им не хватает гибкости. Освобождение данных из них с помощью Kafka позволяет создавать гибкие сервисы и сокращать затраты на прямое взаимодействие и обслуживание. Debezium упрощает обмен данными в реальном времени, используя отслеживание изменений данных (CDC), однако для повышения удобства в других приложениях требуется создавать новые структуры данных. Kafka изначально разрабатывался для эффективной передачи огромных объемов в средах больших данных; лог-ориентированная архитектура использовалась для надежной доставки и буферизации пиковых нагрузок. Kafka эффективно обрабатывает растущие объемы данных, поступающие от промышленных приложений, обеспечивая мониторинг в реальном времени, прогнозное обслуживание и централизованный сбор данных для различных сценариев. Многоуровневое хранение в Kafka позволяет организациям найти баланс между производительностью и затратами: часто используемые данные сохраняются, а исторические данные перемещаются в более экономичные хранилища. Эффективная интеграция данных и управление доступом достигаются благодаря использованию протоколов наподобие MQTT для надежной передачи данных, а централизованные кластеры Kafka упрощают администрирование и поддерживают инструменты самообслуживания для команд.
   366 Часть IV. Kafka в корпоративной среде Kafka не является реляционной базой данных и не подходит для сложных запросов или сохранения текущего состояния, поскольку не обеспечивает полных ACID-гарантий, особенно в части транзакционной изоляции. Kafka не является синхронным интерфейсом связи — он работает асинхронно и полагается на реляционные базы данных для мгновенной обратной связи. Kafka не предназначен для обмена файлами (например, PDF), поскольку оптимизирован для передачи машиночитаемых данных или ссылок на файлы, хранящиеся во внешних системах. Kafka не лучший вариант для небольших приложений с маленьким объемом данных. В таких случаях более рационально использовать простые решения — например, базу данных.
Приложения
A Настройка тестового окружения Kafka В этом приложении кратко описана настройка тестового окружения Kafka на одном компьютере (например, на персональном ноутбуке). Эта конфигурация не подходит для продакшена, но идеальна для экспериментов с примерами из книги. В отличие от многих статей в Интернете мы создадим окружение с несколькими узлами, чтобы понаблюдать за работой Kafka при частичных сбоях. На рис. A.1 показано тестовое окружение с тремя брокерами Kafka: брокер 1 на порте 9092, брокер 2 на порте 9093 и брокер 3 на порте 9094. Рис. A.1. Тестовое окружение с тремя брокерами Kafka в двойном режиме
   Приложение A. Настройка тестового окружения Kafka 369 Для простоты настройки брокеры будут работать в двойном режиме, совмещая функции контроллера и обычного брокера. Контроллерам требуются дополнительные слушатели: для брокера 1 — на порте 9192, для брокера 2 — на порте 9193, для брокера 3 — на порте 9194. A.1. Операционные системы Apache Kafka работает на JVM и поддерживает все основные ОС. Эта конфигурация успешно протестирована на macOS 15.2 Sequoia с Apple Silicon и на Ubuntu 24.04 LTS с процессором Intel. Для Windows рекомендуем WSL. A.2. Скачивание Kafka Актуальную версию Kafka можно скачать на странице https://kafka.apache.org/ downloads. На момент написания книги последняя версия — 3.9.0. Kafka по-прежнему написан на Scala, так что названия файлов имеют вид kafka_2.13-3.9.0.tgz, где 2.13 — версия Scala, а 3.9.0 — версия Kafka. Рекомен- дуем всегда использовать последнюю версию Scala. Скачайте файл и распакуйте его в каталог ~/kafka: $ $ $ $ wget "https://downloads.apache.org/kafka/3.9.0/kafka_2.13-3.9.0.tgz" tar xfz kafka_2.13-3.9.0.tgz rm kafka_2.13-3.9.0.tgz mv kafka_2.13-3.9.0/ ~/kafka В каталоге ~/kafka вы увидите несколько подкаталогов: LICENSE, NOTICE — файлы с информацией о лицензии; bin — командные утилиты для работы с Kafka; config — файлы конфигурации; libs — файлы библиотеки Java (*.jar); site-docs — документация. Чтобы вызывать командные утилиты, не указывая полный путь, рекомендуется добавить каталог bin в переменную PATH: $ export PATH=~/kafka/bin:"$PATH" Эту строку также можно добавить в ~/.bashrc, ~/.zshrc или файл оболочки. ПРИМЕЧАНИЕ Kafka 4.0.0 выпущен в марте 2025 года. На момент написания книги все примеры тестировались в Kafka 3.9.0, но мы рассматриваем важные нововведения Kafka 4.0.0: KRaft, очереди и новый протокол перебалансировки.
   370 Приложения A.3. Конфигурация Kafka Создадим файлы конфигурации для брокеров в каталоге ~/kafka/config/ kafka1.properties: Для обычного взаимодействия с брокером — порт 9092, для контроллера — порт 9192. Названия слушателей могут быть произвольными Идентификатор Каталог для хранения файлов брокера (здесь — 1) брокера. Зависит от каталога Строка broker.id=1 подключения log.dirs=<PATH-TO-YOUR-USER-DIRECTORY>/kafka/data/kafka1 нужна всем listeners=PLAINTEXT://:9092,CONTROLLER://:9192 контроллерам process.roles=broker,controller Запуск брокера в двойном режиме controller.quorum.voters=1@localhost:9192,2@localhost:9193,3@localhost:9194 controller.listener.names=CONTROLLER listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT В тестовом окружении мы обходимся без TLS Какой слушатель используется для контроллера? Настроим брокер 2 в ~/kafka/config/kafka2.properties: broker.id=2 log.dirs=<PATH-TO-YOUR-USER-DIRECTORY>/kafka/data/kafka2 listeners=PLAINTEXT://:9093,CONTROLLER://:9193 process.roles=broker,controller controller.quorum.voters=1@localhost:9192,2@localhost:9193,3@localhost:9194 controller.listener.names=CONTROLLER listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT Наконец, настраиваем брокер 3 в ~/kafka/config/kafka3.properties: broker.id=3 log.dirs=<PATH-TO-YOUR-USER-DIRECTORY>/kafka/data/kafka3 listeners=PLAINTEXT://:9094,CONTROLLER://:9194 process.roles=broker,controller controller.quorum.voters=1@localhost:9192,2@localhost:9193,3@localhost:9194 controller.listener.names=CONTROLLER listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT A.4. Подготовка каталогов данных Сначала указываем каталоги, в которых брокеры будут хранить данные. Затем инициализируем (форматируем) каталоги, чтобы использовать их в кластере: создаем идентификатор кластера, чтобы каталог был недоступен для другого кластера. Это защитит от лишних ошибок. $ mkdir -p ~/kafka/data/kafka1 ~/kafka/data/kafka2 \ Создаем каталоги данных ~/kafka/data/kafka3
   Приложение A. Настройка тестового окружения Kafka 371 $ export KAFKA_CLUSTER_ID=\ "$(kafka-storage.sh random-uuid)" Создаем новый ID кластера Kafka $ kafka-storage.sh format -t $KAFKA_CLUSTER_ID \ -c ~/kafka/config/kafka1.properties Formatting metadata directory <PATH-TO-YOUR-USER-DIRECTORY> /kafka/data/kafka1 with metadata.version 3.9-IV0. $ kafka-storage.sh format -t $KAFKA_CLUSTER_ID \ Форматируем -c ~/kafka/config/kafka2.properties каталоги Formatting metadata directory <PATH-TO-YOUR-USER-DIRECTORY> /kafka/data/kafka2 with metadata.version 3.9-IV0. $ kafka-storage.sh format -t $KAFKA_CLUSTER_ID \ -c ~/kafka/config/kafka3.properties Formatting metadata directory <PATH-TO-YOUR-USER-DIRECTORY> /kafka/data/kafka3 with metadata.version 3.9-IV0. A.5. Запуск Kafka Пора запустить брокеры. Сначала запустим брокеры в режиме переднего плана, чтобы увидеть возможные ошибки; если все работает, то запустим брокеры в фоновом режиме как демоны. Запустим первый брокер: ~/kafka/bin/kafka-server-start.sh ~/kafka/config/kafka1.properties Если команда сразу завершится ошибкой, то брокер настроен неправильно. Проверьте конфигурацию и повторите попытку. В противном случае брокер Kafka должен выводить предупреждения о недоступности узлов 2 и 3: WARN [RaftManager nodeId=1] Connection to node 2 (localhost/127.0.0.1:9193) could not be established. Broker may not be available. (org.apache.kafka.clients.NetworkClient) Это логично, поскольку мы еще не запустили остальные брокеры. Откроем новое окно терминала и запустим второй брокер: ~/kafka/bin/kafka-server-start.sh ~/kafka/config/kafka2.properties Мы увидим много сообщений в логах. Брокеры по-прежнему жалуются на отсутствие узла 3, но среди этих сообщений нужно найти следующую строку об успешном запуске: [KafkaRaftServer nodeId=2] Kafka Server started Запустим оставшийся брокер: ~/kafka/bin/kafka-server-start.sh ~/kafka/config/kafka3.properties В логах третьего брокера должна появиться следующая строка, и брокеры перестанут сообщать о невозможности подключения к другим узлам: [KafkaRaftServer nodeId=3] Kafka Server started
   372 Приложения Конечно, можно оставить все как есть, а можно последовательно остановить брокеры и заменить их брокерами в режиме демона. Так мы не сможем видеть логи, зато не придется держать три окна терминала постоянно открытыми: # Остановите брокер 1, нажав Ctrl+C ~/kafka/bin/kafka-server-start.sh \ -daemon ~/kafka/config/kafka1.properties # Остановите брокер 2, нажав Ctrl+C ~/kafka/bin/kafka-server-start.sh \ -daemon ~/kafka/config/kafka2.properties # Остановите брокер 3, нажав Ctrl+C ~/kafka/bin/kafka-server-start.sh -daemon ~/kafka/config/kafka3.properties Запуск брокера в фоновом режиме Проверим, что все работает: $ kafka-broker-api-versions.sh \ --bootstrap-server localhost:9092,localhost:9093,localhost:9094 localhost:9092 (id: 1 rack: null) -> ( # Много текста ) localhost:9093 (id: 2 rack: null) -> ( # Много текста ) localhost:9094 (id: 3 rack: null) -> ( # Много текста ) Мы видим версии API, поддерживаемые каждым брокером, и, что важнее, какие брокеры в сети. A.6. Остановка Kafka Остановить все брокеры Kafka можно с помощью такой команды: ~/kafka/bin/kafka-server-stop.sh Если нужно остановить только один брокер, то создайте новый скрипт ~/kafka/ bin/kafka-broker-stop.sh: #!/bin/bash BROKER_ID="$1" if [ -z "$BROKER_ID" ]; then echo "usage ./kafka-broker-stop.sh [BROKER-ID]" exit 1 fi PIDS=$(ps ax | grep -i 'kafka\.Kafka' | grep java \ | grep "kafka${BROKER_ID}.properties" | grep -v grep | awk '{print $1}')
   Приложение A. Настройка тестового окружения Kafka 373 if [ -z "$PIDS" ]; then echo "No kafka server to stop" exit 1 else kill -s TERM $PIDS fi Остановится только один брокер, идентификатор которого мы указываем. Брокер 1, например, можно остановить, используя такую команду: $ chmod +x ~/kafka/bin/kafka-broker-stop.sh $ ~/kafka/bin/kafka-broker-stop.sh 1 С помощью скрипта kafka-broker-api-versions.sh проверяем, все ли у нас получилось: $ kafka-broker-api-versions.sh \ --bootstrap-server localhost:9092,localhost:9093,localhost:9094 Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Broker may not be available. Брокера 1 нет, зато мы localhost:9093 (id: 2 rack: null) -> ( видим ошибку # Много текста ) localhost:9094 (id: 3 rack: null) -> ( # Много текста ) Можно снова запустить брокер, используя уже знакомую команду, или остановить все окружение. Желаем вам успешной потоковой обработки!
Б Настройка мониторинга В этом приложении мы кратко рассмотрим, как настроить базовую систему мониторинга для сервисов Kafka с помощью Prometheus (https://prometheus.io/) и Grafana (https://grafana.com/). Это не исчерпывающее руководство по мониторингу, но вы получите общее представление о том, как реализовать мониторинг для Kafka. Б.1. Prometheus Prometheus — это система с открытым исходным кодом для мониторинга и генерации оповещений. Он периодически собирает метрики с конечных точек отслеживаемых объектов и сохраняет в своей базе данных временных рядов название метрики и набор меток для идентификации. К тому же Prometheus поддерживает механизмы автоматического обнаружения сервисов для поиска объектов мониторинга. Кроме того, можно отправлять метрики напрямую в Prometheus. Данные можно запрашивать и агрегировать с помощью собственного языка запросов PromQL. Оповещения также настраиваются на основе выражений PromQL. Для визуализации Prometheus предоставляет встроенный веб-интерфейс. Для установки Prometheus достаточно скачать версию, совместимую с вашей операционной системой, с официального сайта (https://prometheus.io/download/) и распаковать архив. Общее руководство по установке и введение в Prometheus доступны по ссылке https://mng.bz/YD5z.
   Приложение Б. Настройка мониторинга 375 Перед запуском исполняемого файла Prometheus рекомендуется внести изменения в конфигурацию по умолчанию. Для этого необходимо отредактировать файл prometheus.yml (документация по настройке: https://mng.bz/GeKD). global : scrape_interval: 15s evaluation_interval: 15s alerting: alertmanagers: - static_configs: - targets: ["localhost:9095"] rule_files: - "rules.yml" scrape_configs: - job_name: "kafka" static_configs: - targets: ["localhost:9101","localhost:9102","localhost:9103"] - job_name: "prometheus" static_configs: - targets: ["localhost:9090"] Глобальная конфигурация в разделе global содержит такие настройки, как частота сбора метрик и частота оценки оповещений. Адрес Prometheus Alertma­ nager, который мы рассмотрим в разделе Б.3 далее, указан в секции alerting (localhost:9095). Кроме того, мы определяем два задания в scrape_config: задание kafka собирает метрики Java Management Extensions (JMX) с конечных точек брокеров Kafka (localhost:9101, localhost:9102, localhost:9103), а задание prometheus собирает метрики самого Prometheus (localhost:9090). В разделе rule_files можно указать список файлов с правилами оповещений. В нашем примере мы определяем простое оповещение в файле rules.yml: groups: - name: Kafka rules: - alert: Kafka Metrics Not Reachable expr: up{job="kafka"} == 0 for: 1m Оповещения определяются внутри группы оповещений, где можно задавать интервалы проверки, отличающиеся от глобального значения по умолчанию. Это бывает полезно, например, если запрос требует много ресурсов и поэтому должен выполняться реже. Список оповещений определяется в разделе rules. У оповещений всегда есть имя, заданное в поле alert (например, Kafka Metrics Not Reachable). Выражение для оценки указывается в поле expr (up{job="kafka"} == 0), а продолжительность,
   376 Приложения в течение которой выражение должно быть истинным перед срабатыванием оповещения, задается с помощью параметра for (1m). В примере мы создали оповещение, которое срабатывает, когда одна из конечных точек метрик Kafka недоступна в течение минуты. После создания конфигурации можно запустить исполняемый файл Prometheus. Затем можно открыть веб-интерфейс в браузере на порте 9090. Первые метрики должны отобразиться сразу же. Кроме того, Prometheus предоставляет автодополнение для запросов через PromQL. Два снимка экрана веб-интерфейса показаны на рис. Б.1 и Б.2. Рис. Б.1. В веб-интерфейсе Prometheus (по умолчанию на порте 9090) можно выполнять запросы и визуализировать данные с помощью PromQL Б.2. Экспортер для Prometheus Не каждый сервис или сервер нативно предоставляет метрики Prometheus через HTTP. К счастью, существуют многочисленные официальные и неофициальные экспортеры Prometheus, которые преобразуют метрики из существующих форматов в формат Prometheus и предоставляют их через HTTP. Список экспортеров см. на сайте https://prometheus.io/docs/instrumenting/exporters/.
   Приложение Б. Настройка мониторинга 377 Рис. Б.2. В разделе Alerts представлен список всех оповещений. Они могут находиться в одном из трех состояний: Inactive (неактивно: выражение ложно), Pending (в ожидании: выражение истинно, но минимальная длительность еще не достигнута) или Firing (сработало: выражение истинно и остается таковым как минимум указанное время) Kafka, например, использует Java Management Extensions (JMX) для передачи различных параметров инструментам мониторинга. Мы не будем углубляться в эту тему, поскольку в Сети или на наших тренингах вы найдете более актуальную информацию. Большинство известных нам инструментов мониторинга в той или иной степени поддерживают JMX без дополнительной настройки. Для мониторинга с Prometheus мы предпочитаем Prometheus JMX Exporter (https:// prometheus.io/docs/instrumenting/exporters/) с рекомендуемой конфигурацией для Kafka. Названия метрик зависят от конфигурации, а некоторые метрики могут вообще отсутствовать. Настроим тестовое окружение Kafka из приложения A так, чтобы можно было собирать метрики через экспортер Prometheus. JMX Exporter работает как агент Java. Сначала скачаем JMX Exporter и файл конфигурации: $ cd ~/kafka $ wget https://github.com/prometheus/jmx_exporter/releases/download/1.1.0/\ jmx_prometheus_javaagent-1.1.0.jar Загрузка JMX Exporter $ wget https://raw.githubusercontent.com/prometheus/jmx_exporter/refs/\ heads/main/examples/kafka-kraft-3_0_0.yml Загрузка конфигурации для Kafka
   378 Приложения Остановим брокеры Kafka: $ kafka-server-stop.sh Для добавления агента Java экспортируем переменную окружения KAFKA_OPTS и запустим Kafka. В переменной мы указываем файл .jar в качестве агента и задаем путь к файлу конфигурации JMX. Необходимо также прописать порт для JMX Exporter. Согласно файлу конфигурации Prometheus, порт для брокера 1 — 9101, для брокера 2 — 9102, для брокера 3 — 9103. После экспорта переменной окружения мы запускаем брокер. Повторим эти действия для всех трех брокеров: Настройка файла .jar и порта Экспорт переменной $ export KAFKA_OPTS=" \ Предоставление окружения -javaagent:$HOME/kafka/\ файла конфигурации jmx_prometheus_javaagent-1.1.0.jar=9101:\ JMX Exporter $HOME/kafka/kafka-kraft-3_0_0.yml" $ ~/kafka/bin/kafka-server-start.sh -daemon ~/kafka/config/kafka1.properties $ export KAFKA_OPTS=" \ То же для брокера 2 -javaagent:$HOME/kafka/jmx_prometheus_javaagent-1.1.0.jar=9102:\ $HOME/kafka/kafka-kraft-3_0_0.yml" $ ~/kafka/bin/kafka-server-start.sh -daemon ~/kafka/config/kafka2.properties $ export KAFKA_OPTS=" \ И для брокера 3 -javaagent:$HOME/kafka/jmx_prometheus_javaagent-1.1.0.jar=9103:\ $HOME/kafka/kafka-kraft-3_0_0.yml" $ ~/kafka/bin/kafka-server-start.sh -daemon ~/kafka/config/kafka3.properties Убедимся, что все работает, запросив конечную точку метрик через curl: $ # $ # $ # curl localhost:9101 Длинный список метрик curl localhost:9102 Длинный список метрик curl localhost:9103 Длинный список метрик Через несколько секунд метрики начнут отображаться в Prometheus. Еще один важный инструмент для мониторинга Kafka — Prometheus Blackbox Exporter (https://github.com/prometheus/blackbox_exporter). Он позволяет тестировать (зондировать) конечные точки с использованием различных протоколов — от простых проверок TCP-соединений до сложных HTTPS-запросов — для контроля доступности таких сервисов, как брокеры Kafka. Blackbox Exporter генерирует метрику probe_success, указывающую на успешную проверку. В зависимости от типа проверки создаются дополнительные метрики, например длительность HTTP-запроса, включая этапы DNS-запросов или установления TCP-соединения.
   Приложение Б. Настройка мониторинга 379 Это позволяет в деталях изучить производительность сервиса, которая не все­ гда объясняется только нагрузкой на центральный процессор, а часто связана со сложным взаимодействием множества компонентов. Еще один полезный инструмент — Prometheus Node Exporter (https://github. com/prometheus/node_exporter), собирающий метрики о текущей хост-системе. Он использует различные коллекторы для считывания и преобразования соответствующих метрик. В основном он собирает данные, связанные с использованием центрального процессора, памяти, сети и файловых систем. Например, Textfile Collector позволяет создавать произвольные метрики на основе текстовых файлов. Б.3. Prometheus Alertmanager Alertmanager — часть проекта Prometheus, отвечающая за управление оповещениями. Он группирует оповещения и определяет получателей уведомлений. Оповещения и уведомления можно временно отключить, например на период планового технического обслуживания. Кроме того, можно определить правила, разрешающие подавлять уведомления для определенных оповещений на основе других оповещений. СОВЕТ Когда возникает фундаментальная проблема, например недоступность сервера или базы данных, может сработать множество оповещений, поскольку связанные сервисы перестают функционировать. Если правила запрета хорошо настроены, то второстепенные оповещения не будут затмевать основную проблему, поскольку будут подавляться. Убедитесь, что правила настроены верно и не подавляют важные оповещения. Alertmanager тоже можно скачать с сайта Prometheus. Конфигурация Alert­ manager определяется в файле alertmanager.yml. Чтобы Alertmanager отправлял уведомления, обычно необходимо настроить действительного получателя (а если требуется отправка электронных писем, то и SMTP-конфигурацию), но здесь мы опустим эту часть. Подробная документация по параметрам конфигурации Alertmanager доступна на сайте Prometheus: https://mng.bz/9Y4j. Запускаем Alertmanager с помощью исполняемого файла alertmanager. Переопределим адреса прослушивания, поскольку порты по умолчанию, 9093 и 9094, уже заняты брокерами Kafka: $ ./alertmanager --web.listen-address=:9095 \ --cluster.listen-address=<address> На рис. Б.3 показан снимок экрана веб-интерфейса, доступного на порте 9095, с активными оповещениями.
   380 Приложения Рис. Б.3. Веб-интерфейс Alertmanager доступен на порте 9095 (по умолчанию — 9093) и отображает список активных оповещений. Кроме того, в веб-интерфейсе можно временно отключать оповещения или уведомления Б.4. Grafana Grafana (https://grafana.com/), как и Prometheus, поставляется с открытым исходным кодом. В отличие от интерфейса Prometheus, поддерживающего только простые запросы, Grafana позволяет создавать сложные дашборды из нескольких источников данных (в том числе из Prometheus). Мы можем просматривать несколько метрик на одном дашборде, изучая их сложные взаимосвязи, чтобы эффективно устранять неполадки. Дашборды могут иметь динамические переменные для быстрого переключения между разными ресурсами. Вдобавок Grafana поддерживает различные методы аутентификации и предоставляет детальные разрешения для дашбордов и источников данных. Кроме того, Grafana позволяет создавать оповещения и управлять ими, а также использовать всевозможные плагины с дополнительными функциями (https:// grafana.com/grafana/plugins/). Grafana можно скачать на странице https://grafana.com/grafana/download/ — либо напрямую в виде исполняемого файла, либо через установщик или пакетный менеджер. Способ запуска Grafana зависит от метода установки. В официальной
   Приложение Б. Настройка мониторинга 381 документации Grafana на https://mng.bz/jpza содержится подробная информация о настройке аутентификации, плагинов и многом другом. СОВЕТ Качественные дашборды с хорошей визуализацией помогают создавать осмысленные оповещения. Они часто становятся первым источником информации при диагностике проблем, поэтому важно тщательно продумывать дашборды, ведь при сбое счет порой идет на секунды. На рис. Б.4 показан дашборд Kafka от команды Strimzi с комплексной информацией о состоянии кластера Kafka. Рис. Б.4. Пример дашборда Kafka на основе Strimzi СОВЕТ Сообщество Strimzi предлагает одни из лучших дашбордов для Kafka (https:// mng.bz/W2Rg). Однако их не стоит слепо копировать, поскольку все зависит от сценария. В любом случае необходимо изучить дашборд заранее, чтобы не знакомиться с ним во время инцидента.
Комьюнити рецензентов и переводчиков ИТ-литературы Миссия участников клуба — обеспечить высокое качество профессиональной переводной литературы в России. «Книжные дебагеры» проверяют корректность терминологии и подписей на схемах и иллюстрациях, чтобы сделать книги более понятными русскоязычному читателю. Стать участником Read IT Club может любой ИТ-специалист, готовый поделиться опытом с сообществом. присоединиться к нам
ВОСПОЛЬЗУЙТЕСЬ В ОЗ М ОЖ Н ОСТЬЮ ПРИОБРЕСТИ КНИГИ НА САЙТЕ ИЗДАТЕЛЬСТВА piter.com со скидкой по промокоду 20% PITER
Анатолий Зеленин, Александр Кропп Apache Kafka в действии. От базовых концепций до продакшена Перевела с английского М. Попович Руководитель дивизиона Ю. Сергиенко Руководитель проекта А. Питиримов Ведущий редактор Н. Гринчик Литературный редактор Н. Хлебина Художественный редактор В. Мостипан Корректоры Т. Никифорова, Е. Павлович Верстка Г. Блинов Изготовлено в России. Изготовитель: ООО «Прогресс книга». Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург, Б. Сампсониевский пр., д. 29А, пом. 52. Тел.: +78127037373. Дата изготовления: 04.2026. Наименование: книжная продукция. Срок годности: не ограничен. Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12 — Книги печатные профессиональные, технические и научные. Импортер в Беларусь: ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80 01. Подписано в печать 25.03.26. Формат 70×100/16. Бумага офсетная. Усл. п. л. 30,960. Тираж 700. Заказ 0000.