Apache Kafka - это платформа распределенной потоковой передачи данных
Apache Kafka позволяет:
- Публиковать и подписываться на потоки записей, подобно очереди сообщений или корпоративной системе обмена сообщениями.
- Хранить потоки записей в отказоустойчивом долговечном виде.
- Обрабатывать потоки записей по мере их возникновения.
Если вы еще не использовали Kafka, вы можете перейти сюда, чтобы быстро начать и вернуться к этой статье, когда вы ознакомитесь с примером использования.
Kafka поддерживает высокопроизводительную, высокораспределенную, отказоустойчивую платформу с низкой задержкой доставки сообщений. Здесь мы сосредоточимся на аспекте доставки с низкой задержкой.
Низкая задержка при вводе/выводе = файловая система?
Большинство традиционных систем обработки данных используют память с произвольным доступом (RAM) в качестве хранилища данных, поскольку RAM обеспечивает чрезвычайно низкие задержки.
Хотя такой подход делает их быстрыми, стоимость оперативной памяти намного выше, чем стоимость диска. Такие системы обычно дороже в эксплуатации, когда через систему проходят данные со скоростью 100 ГБ/с.
Kafka полагается на файловую систему для хранения и кэширования. Проблема в том, что диски медленнее, чем оперативная память. Это связано с тем, что время поиска на диске велико по сравнению со временем, необходимым для фактического чтения данных.
Но если вы можете избежать поиска, то в некоторых случаях можно добиться задержек, не уступающих оперативной памяти. В Kafka это делается с помощью последовательного ввода-вывода.
Одним из преимуществ последовательного ввода-вывода является то, что вы получаете кэш без написания какой-либо логики в вашем приложении для этого. Современные операционные системы выделяют большую часть свободной памяти под дисковое кэширование. Поэтому, если вы читаете в упорядоченном порядке, ОС всегда может опережать чтение и сохранять данные в кэше при каждом чтении с диска.
Это гораздо лучше, чем поддерживать кэш в приложении JVM. Это связано с тем, что объекты JVM "тяжелые" и могут привести к большому объему сборки мусора, что становится хуже по мере увеличения размера данных.
Не используйте деревья
Большинство современных баз данных используют ту или иную форму древовидной структуры данных для постоянного хранения данных. Например, MongoDB использует BTree, а Cassandra - LSM tree.
Эти структуры обеспечивают производительность поиска O(log N).
Для системы обмена сообщениями, которая требует одновременного выполнения множества операций чтения и записи, использование деревьев может привести к большому количеству случайных операций ввода-вывода. Это приводит к большому количеству обращений к диску, что катастрофически сказывается на производительности.
Очередь - это гораздо лучшая структура данных для системы обмена сообщениями. Большую часть времени данные добавляются в систему, а чтение является простым. Все такие операции имеют значение O(1) - что гораздо более эффективно.
Не копируйте!
Одной из основных неэффективностей систем обработки данных является сериализация и десериализация (перевод в форматы, пригодные для хранения и передачи) данных при передаче.
Это можно сделать быстрее, если вместо JSON использовать более качественные двоичные форматы данных, такие как буферы протоколов или Flat-буферы.
Но как можно полностью избежать сериализации/десериализации?
Kafka решает эту проблему двумя способами:
- Использовать стандартизированный формат двоичных данных для производителей, брокеров и потребителей (чтобы данные можно было передавать без модификации).
- Не копировать данные в приложении ("zero-copy").
Первый вариант объясняется сам собой. Второе требует внимания.
Обычная передача данных из файла в сокет может происходить следующим образом:
- ОС считывает данные с диска в pagecache в пространстве ядра.
- Приложение считывает данные из пространства ядра в буфер пространства пользователя
- Приложение записывает данные обратно в пространство ядра в буфер сокета
- ОС копирует данные из буфера сокета в буфер сетевой карты, откуда они отправляются по сети.
Однако если у нас есть один и тот же стандартизированный формат данных, который не требует модификации, то нам не нужен шаг 2 (копирование данных из пространства ядра в пространство пользователя).
Если мы храним данные в том же формате, в котором они будут передаваться по сети, то мы можем напрямую копировать данные из pagecache в буфер сетевой карты. Это можно сделать с помощью системного вызова OS sendfile.
Что еще?
Kafka использует множество других методов, помимо упомянутых выше, чтобы сделать системы намного быстрее и эффективнее:
Пакетирование данных для уменьшения количества обращений к сети, а также преобразование большого количества случайных записей в последовательные.
Сжатие пакетов (а не отдельных сообщений) с помощью кодеков LZ4, SNAPPY или GZIP. Большая часть данных в сообщениях внутри пакета (например, поля сообщений и информация метаданных) совпадает. Это может привести к лучшему коэффициенту сжатия.
Важно отметить, что все вышеперечисленные техники могут быть применены в большинстве систем для достижения низких задержек. Они не требуют вмешательства в ядро, настройки сборки мусора, использования нативных приложений или экстремальных структур данных.
Однако один недостаток заключается в том, что некоторые из этих методов специфичны для случаев, похожих на платформу обмена сообщениями. Они не подходят для более общих распределенных баз данных.