Вы находитесь на странице: 1из 18

Apache Kafka

https://www.youtube.com/watch?v=-AZOi3kP9Js&ab_channel=%D0%92%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC
%D0%B8%D1%80%D0%91%D0%BE%D0%B3%D0%B4%D0%B0%D0%BD%D0%BE%D0%B2%D1%81%D0%BA
%D0%B8%D0%B9

https://www.youtube.com/watch?v=PtFTGQG2LwA&ab_channel=InterviewDone%21

Kafka – распределенный брокер сообщений (событий) в стримминговом режиме.


Свойства Kafka:
- распределенность
Множество объединенных и слаженно работающих серверов
- отказоустойчивость
- высокая доступность данных
Выход из строя какого-то узла не нарушает доступа к данным
- данные согласованны и надежны
- высокая производительность (пропускная способность)
До 1 млн сообщений в секунду
- горизонтальное масштабирование
Ставятся простые машины, которые легко добавлять/удалять.
Перебалансировка автоматическая.
- интегрируемость

Какую задачу решаем с помощью Kafka?


Есть данные (Events, события) (однотипные/разнородные). Мы хотим эти данные куда-то
отправить. Для это есть поставщики данных (Producers). Те, кому нужны эти данные, называются
потребителями (Consumers). Разным потребителям могут быть нужны одни и те же данные, а могут
быть нужны и непересекающиеся данные.
Сложности:
- надежность и гарантия доставки
- подключение новых получателей
- отправители знают получателей
- техническая поддержка
- интеграция разных стеков
Producers должны все знать о consumers; что делать если consumer не доступен; при появлении
нового consumer’а необходимо как-то их регистрировать у producer’ов.

Возможное решение:

Нужно взять посредника (broker). Он примет на себя функцию получения данных от producer’ов и
предоставление данных consumer’ам. В брокере есть набор ящиков под каждый тип событий.
Продьюсеры складывают в эти ящики сообщения, а консьюмеры потом забирают нужные им
события из разных ящиков.
Удобства:
- надежность и гарантия доставки
Гарантию обеспечивает единый узел – брокер.
- подключение новых пользователей
- отправители не знают получателей
- техническая поддержка
- интеграция разных стеков происходит просто
Продьюсерам и консьюмерам нужно знать только API брокера

Брокером является Kafka.


Основные сущности Kafka:
 Broker
 Zookeeper
 Massage (Record)
 Ящик (Topic) с разделением на партиции (Partition)
 Producer (отправитель)
 Consumer (получатель)

Kafka Broker (Kafka Server | Kafka Node)


- Прием сообщений
- Хранение сообщений
- Выдача сообщений

Делается несколько брокеров, чтобы данные не терялись и увеличивалась производительность. Эти


брокеры между собой общаются, образуя Kafka Cluster

- Масштабирование
- Репликация (для надежности)

Требуется координатор, в котором хранится конфигурация и состояние кластера – Zookeeper.


Zookeeper – это хранилице (небольшая БД). БД быстро работает на операции чтения, не очень
быстро на операции записи, поэтому для хранения мета данных кластера или состояний, которые не
сильно быстро изменяются очень хорошо подходит.
- Состояние кластера
- Конфигурация
- Адресная книга (data) (Сами данные, где расположены (ящики-топики, партиции, лидер-партиция,
фоловверы)
- Выбор Kafka Controller

Среди брокеров выделяется особенный брокер – Kafka Controller. Обеспечивает консистентность


данных.

Kafka Message

Topic/Partition
Topic – стрим данных. Внутри очередь, в которую складываются все входящие сообщения.
В очередь сообщения попадают в том же порядке, в котором отправляются producer’ом. И
считываем в том же порядке. Фича Kafka – обеспечение FIFO (ordering support).
При чтении (консьюмером) сообщения/события из топика не пропадают, данные не удаляются =>
широковещательный режим: может быть несколько консьюмеров, которые потребляют одни и те
же даные. Здесь показана линейная работа, в один поток, не очень работает при больших нагрузках.

Данные в топике по умолчанию хранятся 1 неделю. Можно выставить бесконечность, так и


параметр – не хранить.

Partitions

Разделение единой очереди на партиции. Это конфигурируемый параметр (мы его задаем). Можем
отправлять не одним продьюсером, а сразу несколькими, и также получать – можем не одним
последовательным консьюмером, а сразу в паралелль из разных партиций получать однотипные
события.
Консьюмер потребляет не в том порядке, в котором изначально были заложены события у
продьюсера, но они в том порядке, в котором лежат в партиции. Упорядочивание – на уровне
партиций.

Когда необходимо события от одного объекта (пользователя) обрабатывать упорядоченно, можно


события одного пользователя отправлять в одну партицию.

Topic – труба
Partition – трубочки
Broker – место, где проходят трубочки

В проде лучше не делать число брокеров меньше, чем 3 (для устойчивости и надежности системы)

Размещение топика по кластеру (несколько брокеров) и партиции распределяются по брокерам.


Нумерация партиций от 0. Но возможна несбалансированность.
Почему происходит такая несбалансированность?
У Кафки заложен следующий принцип сбалансированности – «Учитывается количество всех
партиций всех топиков на брокер».
Не важно, чтобы на каждом брокере лежало одинаковое количество партиций от любых
топиков.
Не важно из какого топика партиция на каком брокере.
Берет все топики, которые есть, все их партиции и раскидывает равномерно. Эта настройка
автоматическая.

Можем получить проблему, если полагаться на автоматическую балансировку. Если имеем


«жирный» топик (много-много партиций), то может оказаться, что все партиции одного топика
будут лежать на одном. А другие все маленькие топики и их партиции будут лежать на других
брокерах => брокеры будут недозагружены.
Иногда задачу балансировки приходится решать вручную, понимая какой топик «жирный», чтобы
разнести топики.
Это можно делать с помощью кофигурации.

Где хранятся данные топика?


Данные хранятся в обычных log файлах.
Папка logs. В этой папке есть подпапки для каждой партиции нашего топика. Имя подпапки
<name_of_topic>-<num_of_partition>.

Рассмотрим подпапку ./А-1 (партиция 1 топика А):

- файл 000000000000.log

Содержит сами данные (сообщения), которые мы отправляем + некоторая дополнительная


информация.
o offset (нумерация от 0) – номер сообщения в партиции
o position (в байтах) – смещение в файле
o timestamp
o message

- файл 000000000000.index
Это маппинг offset на position.

- файл 000000000000.timeindex

Это маппинг timestamp на offset.

Бывает полезно, когда хотим, к примеру,


прочитать сообщение, начиная со вторника. Мы
не знаем какойtimestapm был во вторник, но
можем указать какой-то примерный, и получим
offset.

В итоге: по timestamp получаем offset в *.timeindex file => в файле *.index по offset получаем
position => по position в *.log file получаем наше сообщение.

Message Key (Key – часть сообщения, может быть null; в качестве key можно использовать id
пользователя)

- Если null, то выбирается partition по round robin (по очереди, по кругу; все партиции равномерно
заполняются)
- Если не null, то все сообщения с этим ключом пишутся всегдюа в один partition (случай, когда
события от одного пользователя нужно обрабатывать по очереди – events одного клиента, лог
одного сервиса, изменения из одного источника…)

Если топик поработает чуть дольше, то увидим следующую картину:


Появились еще файлы с такой же
структурой. У файла *.log. есть
лимит на размер 1Gb. Поэтому и
появляются другие файлы.
Появляются сегменты (обозначения
кафки). Активный сегмент – с
наибольшим именем.

000002.log – 2 это номер оффсета, с


которого начинается файл.

- Ручное удаление данных из топика не поддерживатеся


- Поддерживается автоматическое удаление данных по TTL (time-to-live)
o удаляются целиком сегменты партиций, а не отдельные сообщения
o segment timestamp expired => to delete

Подводный камень – timestamp сегмента (может быть кем-то установлен очень большим (далеким)
и сегмент не будет удаляться).

Data Replication – надежность данных и отказоустойчивость.

Если бы на каждом брокере было только по одной партиции из топика, то при «падении» брокера,
данные бы пропадали.

Настройка при создании топиков – replication-factor

Важно условие – реплики одной партиции НЕ МОГУТ находиться на одном брокере.

Здесь для каждой партиции replication-


factor = 2.

Теперь, если один брокер «упадет», то


данные не потеряются. Плюс кафка
автоматически добавит новые реплики, которые пропали. Таким образом у нас опять для каждой
партиции две реплики.

Нюансы:
- Отставание данных в репликах от ГЛАВНОЙ (Leader) реплики

Master-Slave система – гарантия согласованности данных.

Одна из реплик назначается Leader репликой. Все остальные реплики – Follower.


За начначение Leader-реплики отвечает кафка контроллер (Zookeeper). Операции чтения и записи
производятся только с Leader-репликой.

Иногда случается расбалансированность и все Leader-реплики располагаются на одном брокере.


Тогда одна нода сильно перегружена.
Нужно смотреть по «жирным»
топикам. Если такое
наблюдается, то необходимо
перебалансировать. Указать в
конфигах какая leader-реплика
на каком брокере.

Отставание данных
!!! Всегда пишется в Leader-реплику.
Каким образом данные попадают в follower-реплики?
Фолловеры опрашивают лидера – «Что нового есть? Дай нам.» Так как это действие
периодическое, то может быть отставание по событиям.
Что произойдет, если Leader «выпал»? Кто теперь Leader? И все ли данные у него есть? =>
Ненадежно! (может оказаться, что ни у одного фоловера нету всех данных)

Решение:
В Кафке есть in-sync реплики – ISR (min.insync.replicas = 3, ставят обычно на единицу меньше, чем
кол-во всех реплик).
Когда пишем события в Leader, после записи, лидер реплика обращается к ISR фоловерам и
синхронно записывает события в эти фоловеры. Оставшиеся не ISR фоловеры работают в прежнем
режиме и данные могут отставать.

О том, как отправить сообщения в Kafka (Producer)


Kafka Producer – высокопроизводительный отправитель сообщений.

Producer пишет только в Leader-


реплику.

У producer есть функция send:


acks = 0
Producer не ждет подтверждения отправки сообщений
(самый не надежный режим – могут теряться сообщения)
acks = 1 (Часто используемый вариант)
Producer ждет подтверждения отправки сообщений только от Leader-реплики
(компромиссный режим в некоторых случаях – могут теряться сообщения, если брокер с лидер-
репликой упал до реплицирования сообщения, но вероятность такого события крайне мала)
acks = -1 (asc = all)
Producer ждет подтвержение отправки сообщений от всех ISR-реплик, включая Leader.
(надежный режим – сообщения не теряются)

Delivery semantic support (семантика доставки)


Продьюсер может гарантировать, что отправляется…
at most once (…не более одного сообщения)
at least once (…хотя бы одно сообщение, то есть может отправить еще дубль)
exactly once (…каждое сообщение только один раз) – реализутеся отделньо и сложнее
o idempotence

Чем слабее уровень, тем производительнее решение.

Подробнее об операции SEND

1. Операция fetch metadata из Zookeeper (в новых версиях Kafka – producer идет в broker, а broker в
Zookeepr)

Producer нужно знать из чего состоит кластер. Дальше ему нужно отправлять сообщения в топик,
здесь нужно знать, где находятся leader реплики.

Нюанс:
Сама операция send заявлена как асинхронная. Но fetch metadata блокирующая операция, значит
если, например, с Zookeper что-то случилось и мы зависли, и этот таймаут может длиться 60 сек
(настраиваемый параметр). Это тяжелая операция.

Таким образом, не нужно на каждое событие создавать нового продьюсера. Нужно создать одного
продьюсера и с ним работать. Он один раз подгрузит мета данные, закэширует, будет с ними
работать и периодически (или по изменениям) обновлять.

2. serialize message
Полученные сообщения (байты) сериализуем в нужный формат.
Указываем:
- key.serializer
- value.serilizer
(например, StringSerializer)

3. define partition
Выбор партиции, в которую пойдет сообщение.
Варианты:
 explicit partition (указать номер партиции)
 round-robin («разберись сам»)
 key-defined (key_hash % n (остаток от деления)) – очеь часто используемый

Эту фичу задействуем, когда хотим обеспечить строгую согласованность между операциями
обновления записи (update) и чтения (get) по пользователю. Все события одного пользователя
кладем в одну партицию с помощью того, что в качестве ключа передаем идентификатор
пользователя (key). А в партиции Kafka гарантируем упорядоченность – в каком порядке положили,
в таком порядке потом обработали.

4. compress message
Используем настройки compression.codec

5. accumulate batch
Аккумулируем батч для повышения производительности
 batch.size – копим батч до достижения batch.size (16 килобайт по-умолчанию)
Пример, накопили 10 сообщений. Если они привысили 16 килобайт, и батч может быть
отправлен в брокер.
 linger.ms – если не можем накопить батч, но хотим отправить сообщения, то действуем
по тайм-ауту.

Нюанс: может быть такое, что не batch.size, ни linger.ms не превышен, но в тоже время батч
отправляем в брокер. Допусти видим, что у брокера номер 2 две партиции 1 и 2, и создаем два batch
для 1-ой партиции и для 2-ой. Если суммарный оьббъем двух батчей превышает batch.size, то эти
бати отправляются, несмотря на то, что они не заверщены. Отправляются, так как идут на один
брокер.

Consumer
Сообщения из partition читаются в порядке добавления, FIFO.
Данные из разных partition могут перемешиваться любым способом.

poll message – тащим сообщения пачками из партиции

Рассмотрим функцию POLL изнутри:

1. fetch metadata
get:
- cluster state
- topic placement
Где находятся топики, какие реплики являются leader, на каких они лежат брокерах, состояние
кластера?

Подключение к Leader-репликам
всех партиций топика.

Если у нас consumer один, то он


сразу подключается ко всем leader-
партициям.
!!! Но, в один поток может быть
медленно.

Из одного топика читают много консьюмеров, объединенные в одну группу – параллелизация


чтения.

Можем распараллелить с помощью consumer группы.

Параметр <group.id> - для


каждого консьюмера указываем
один и тот же group.id

Теперь каждый консьюмер


читает определенные партиции.

Один из наиболее важных


критериев в распределнных системах – сбалансированность.

Kafka Consumer Offset


Проблема – читаем из партиции сообщения. Пусть первый консьюмер получает сообщения из
нулевой партиции. Брокер предоставил консьюмеру пачкой сообщения (допустим три сообщения -
0, 1, 2). Мы эти сообщения получили и обработали. И после этого консьюмер «упал». Что
происходит далее? В группе есть другой консьюмер, который на себя берет обработку этой
партиции. И заново считывает 0,1,2 сообщения. Но зачем? Мы уже обработали эти сообщения.

Как решить?
В Kafka есть фича – хранение оффсетов и коммиты. Есть специальный системный topic -
__consumer_offsets.

В топике __consumer_offsets хранятся оффсеты для каждой группы, которые уже прочитали.
Теперь если после обработки первых 3-х сообщений консьюмер упал, то происходит переключение
обработки текущей партиции на другого консьюмера. Который в свою очередь из оффсет-топика
считывает информацию, где он видит какая партиция обрабатывалась и какой был последний
коммит (offset = 2). Теперь запрашивает у партиции сообщения с оффсетом 3. Таким образом
обработка сообщений не дублируется.

Kafka сохраняет offsert для каждого consumer group для каждого partition – указатель на то, какое
сообщение читать дальше.

Delivery semantics – гарантия доставки

Какие бывают коммиты?


 Auto commit (не всегда хорошо) – коммит сразу после получения
=> at most once – хотя бы один раз обработаем, но можем не обработать (miss message)
Как только консьюмер получил батч данных от брокера, происходит авто коммит и кафка считает,
что для этой группы консьюмеров эти сообщения уже обработаны. Хотя консьюмер их только
получил, и если он упадет, не успев обработать, то сообщения потеряются. При следующем
подключении считывание будет со следующего оффсета.
 Manual commit – коммит сразу после обработки
=> at least once (duplicate messages)
Брокер предоставляет данные, коммит не происходит, обрабатываем сообщения, и после этого
отправляем коммит.

Нужен идемпотентный consumer. При повторном получении сообщения состояние consumer не


изменится, потому что он это сообщение уже обработал.

Если И не хотим терять сообщения, И не хотим получать дубли?


 Custom offset managment
=> exactly once (not missed, no duplicates)
Свое хранилище оффсетов (храним в бд, файл или еще где-то), то есть не полагаемся на топик
consumer_offsets.

Перформанс кафки. Почему такая быстрая?


- масштабируемая архитектура
- последовательное чтение и запись
- no random read
- консьюмеры кэшируют то, что последнее вычитали (кэширование страниц в памяти)
- нулевая копия (zero-copy)
- существует огромное количество настроек для разных кейсов

Kafka broker discovery


Если хотим подключться к Kafka cluster, нужно знать адрес и порт одного брокера. Далее адреса
остальных брокеров запрашиваются автоматически.

Каждый брокер – bootstrap сервер. Каждыц брокер знает обо всех других брокерах, их топиках и
партициях.
Подводя итоги:

Пока количество partitions остается постоянным, один и тот же ключ пишется в один и тот же
partition. При удалении, а затем добавлении партиций, происходит перебалансировка по свои
алгоритмам и key уже станет другим.

Вам также может понравиться