(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

На каких принципах постороены современные высокопроизводительные системы?
Не претендую на полный список, но попробую собрать основные архитектурные принципы с примерами реализующих их систем.

1) shared nothing - каждый запрос на обновление пользовательских данных обрабатывается одним (!) экземпляром сервиса. Пропадает необходимость в распределенных транзакциях или использовании паттерна "Сага", и т.об. повышается скорость и надежность. Технически это горизонтальное масштабирование сервиса\балансировщиков\проксей плюс шардирование хранилища и кэша Примеры: Kafka, Kafka Streams, Spark, Terradata, Hadoop, Solr, ElasticSearch... На примере Kafka: каждый брокер получает свою долю партиций - частей на которые делятся топики - и отвечает за чтение, запись из них, а также репликацию данных. Да, всему кластеру Kafka приходится шарить метаданные о расположении партиций на брокерах - в Zookepper в текущих версиях и в специальных топиках с метаданными в последней версии. И да, ответственный за патрицию может меняться. Но за запросы к пользовательским данным в партиции в каждый момент времени отвечает один брокер, на остальные брокеры эта информация только реплицируется. Репликация проходит асинхронно, без привязки к запросу клиента. Еще примеры: https://dimosr.github.io/shared-nothing-architectures/

2) data locality - данные хранятся на той же ноде, где проходят вычисления. Нет лишних сетевых запросов - быстрее обработка данных. Примеры: Kafka Streams, Spark, Hadoop. На примере Kafka Streams - любые методы, агрегирующие и трансформирующие данные стрима, работают только с данными из тех партиций Kafka, которые лежат на локальной машине. Только так получится добиться приемлемой производительности поточной обработки данных (streaming) в распределенной системе.

3) append-only или log-based storage - данные сохраняются добавлением записи в файл, никаких обновлений и удалений на уровне записей не происходит, файлы ротируются, устаревшие файлы удаляются целиком. Где-то рядом хранится указатель на текущую запись в файле. Т.к последовательная запись на порядок быстрее случайной, то append-only сильно ускоряет запись. Примеры: снова Kafka, Hadoop, Lucene, этот же принцип лежит в основе техник write-ahead logging (WAL) в журналах упреждающей записи СУБД и CQRS + Event Sourcing. Немного о последней: https://www.baeldung.com/cqrs-event-sourcing-java . И о том, как работает WAL https://habr.com/ru/company/postgrespro/blog/459250/ И о том, как Kafka сохраняет данные: https://mbukowicz.github.io/kafka/2020/05/31/how-kafka-stores-messages.html

4) zero-copy - в общем случае данные при чтении из диска и к примеру отправке по сети копируются в памяти несколько раз из буфера в буфер. Почему? Потому что буферы у файлового драйвера, у сетевого драйвера и у Java разные. Но этого можно избежать и работать с данными из буфера ОС, если они не меняются вашим сервисом или меняются, но используются одним процессом. Естественно это ускоряет работу с данными. zero copy должен поддерживаться на уровне ОС, Linux поддерживает. Примеры использования: опять Kafka. Как это работает в Kafka https://andriymz.github.io/kafka/kafka-disk-write-performance/ Про zero copy в Java я упоминал в https://t.me/javaKotlinDevOps/17, вот тут детальнее https://shawn-xu.medium.com/its-all-about-buffers-zero-copy-mmap-and-java-nio-50f2a1bfc05c

to be continued

P.S. Во всех 4 пунктах упоминается Kafka, и это не случайность)

#arch #Kafka #performance
Всем привет!

Может ли API с широким функционалом стать проблемой? Имея несколько реализаций от разных производителей ПО, являясь эталонной реализацией паттернов интеграции https://www.enterpriseintegrationpatterns.com/ при этом быть хуже конкретного продукта с ограниченым набором функционала?
Как можно догадаться из вопроса - ответ: да. Я про JMS vs Kafka.

JMS - это API из состава Java EE (сейчас Jakarta EE). Есть несколько реализаций: практически у каждого сервера приложений есть свой JMS - IBM, Oracle, JBoss, SAP, есть и Open source решения - ActiveMQ, Artemis MQ и другие.
Что есть в JMS? Стандарт широкий: есть очереди (точка-точка, ака P2P) и топики (подписка, ака PubSub), опциональная персистентность и транзационность, возможность настраиваемой маршрутизации и конвертации сообщений. И с security все хорошо.

У Kafka же только топики, нет продвинутой маршрутизации, трансформации, персистентность постоянная, транзакционности нету, гарантии однократной вычитки должен обеспечивать клиент. Да и вендор один. Но справедливости ради vendor lock нет, т.к продукт open source, за деньги только поддержка.

При этом Kafka успешно отвоевывает долю рынка. В чем же дело?

Секрет Kafka в том, что она выпустила достаточно простой, очень быстрый open source продукт, достаточный для большого числа клиентов. Этим нивелируется преимущество JMS в возможности выбора реализации. Причем Kafka быстрая с включённой по умолчанию персистентностью, а значит и высокой надёжностью. По скорости однозначно бьет все реализации JMS. Про то, как удалось добиться такого результата, я писал тут https://t.me/javaKotlinDevOps/91

В чем проблемы JMS:
попытка объять необъятное в API = переусложнение,
много вендорской специфики, которая может помешать смену реализации,
слишком большая роль брокера - все возможности по маршрутизации и трансформации не бесплатны по производительности, и кроме того ведут к тому, что обычная очередь превращается в Enterprise Service Bus, а у этой концепции есть свои минусы.

Я не хочу сказать, что JMS можно выкидывать, а лишь пишу почему Kafka удалось ее так сильно потеснить. Если вам нужно взаимодействие точка-точка и нет больших объёмов данных - JMS вполне подойдёт.

#kafka #jms #comparison
Всем привет!

Сегодняшний пост начну издалека. Распределенные системы обмениваются сообщениями. Каждое сообщение можно рассматривать как событие, требующее какой-то обработки и передачи дальше - в другую подобную систему или для хранения в БД. Т.об. мы получаем распределенную цепочку микросервисов, через которые проходит событие. Существуют т.наз. семантики доставки сообщений:
- at-most-once
- at-least-once
- exactly-once

at-most-once - максимум один раз, т.е. 0 или 1. Т.е. есть вероятность потерять и не обработать сообщение. Подходит для случаев, когда поток сообщений большой, используется для сбора статистики и потеря одного сообщения не критична. Например - статистика по кликам на сайте.

at-least-once - минимум один раз, т.е. 1 или более. Т.е. есть вероятность повторно отправить какое-то сообщение и, соответственно, обработать его дважды. Подходит для систем, где важно знать последнее значение. Пример: стоимость акции на сайте брокера. Или для систем, идемпотентно обрабатывающих входящие сообщения - https://habr.com/ru/companies/domclick/articles/779872/

exactly-once - строго один раз. Идеальный случай.

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

Самый интересный и сложный - это конечно exactly-once. Как с ним обстоят дела?
Например, его поддерживает Kafka - самая распространённая система потоковой передачи сообщений - https://docs.confluent.io/kafka/design/delivery-semantics.html
А также самые распространенные системы потоковой обработки данных:
Kafka Streams - https://kafka.apache.org/33/documentation/streams/core-concepts
Flink - https://flink.apache.org/2018/02/28/an-overview-of-end-to-end-exactly-once-processing-in-apache-flink-with-apache-kafka-too/
Spark - https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html

Кажется, что все хорошо. Но не совсем)

Если прочитать внимательнее, что пишут, например, создатели Kafka, то выяснится что exactly-once гарантируется на участке Producer - Kafka, но далее все зависит от того, как организована работа в Consumer-е. Вот неплохая переводная статья на эту тему от одного из создателей Kafka: https://habr.com/ru/companies/badoo/articles/333046/ // в статье detected american style самореклама, но все равно она неплохая)))

Создатели Flink тоже говорят, что
а) мы даем механизм для exactly-once - в случае сбоя мы откатим ваш обрабатывающий процесс на конкретное состояние (checkpoint), и вы четко будете знать, что это за состояние - его метку времени, что было до и после него, но
б) что делать уже обработанными записями, находящимися после восстановленного состояния - разбирайтесь сами
в) возможность вернуться к сообщениям, на которые указывает checkpoint - тоже на стороне разработчика. В случае Kafka это чаще всего элементарно - сообщения не удаляются сразу после чтения из топика, а если это MQ или сетевой сокет...
г) а еще можно рассмотреть кейс, если кто-то обновил хранилище, где хранится состояние
д) или если в функции потоковой обработки используются какие-то внешние вызовы, которые сломаются на повторах после отката...

Но по большому счету это частности. Основная проблема - системы типа Kafka или Flink могут обеспечить exactly-once на какой-то небольшой части вашей микросервисной системы. Как обеспечить ее на всей системе - в любом случае задача архитектора и разработчика.
Подсказка: наличие operationId (traceId), идемпотентность, транзакции там где это возможно, докаты и наконец админка для ручного разбора инцидентов если не помогли все предыдущие варианты.

#streaming #kafka #flink #arch #microservices #exactly_once
Транзакционность в Kafka.

Транзакцию легко реализовать в рамках БД. Еще есть распреденные транзакции: старый недобрый JTA - долго и дорого, паттерн Сага с eventually consistentcy - работает, при этом требует проработки архитектуры системы в целом.

А что Kafka?
Казалось бы - append only запись и чтение, какие транзакции? Транзакции внутри Kafka - запись на N брокеров, Zookeeper и возврат ответа в producer - выносим за скобки, не было бы тут транзакционности - кто бы ей пользовался)

Но транзакции могут быть на уровне бизнес-логики. Например, мы можем перекладывать сообщения из одного топика в другой попутно выполняя над ними преобразования. Запись - понятно, а чтение из Kafka - это тоже запись, точнее сдвиг текущей позиции для данного consumer в топике.
Так что с транзакциям внутри Kafka (внутри- это принципиально)? Они есть. С ACID, все как положено.
Детали тут https://www.confluent.io/blog/transactions-apache-kafka/
Интересно, что запись в топике появится сразу. Но это запись «почтальона Печкина», consumer вам ее не покажет, потому что у вас документов нету, тьфу, не то, потому что транзакция не зафиксирована) Регулируется это служебными заголовками. Данный лайфхак улучшает время чтения данных из транзакции, по сути это предварительное кэширование.

Полноценно функционал доступен начиная с версии 3.6. Последняя на данный момент 3.9

#kafka #transactions #streaming
Всем привет!

Окей, транзакции в Kafka и в БД по отдельности у нас есть. А можно объединить их в одной транзакции?
Во-первых у нас есть паттерн Сага.
А во-вторых - YDB (от Яндекса).
Вообще интересно развивалась данная СУБД. Вначале это было быстрое и горизонтально масштабируемое облачное noSQL хранилище с полноценными транзакциями. Потом разработчикам не понравилось, как работает Kafka в многопользовательском режиме, и они добавили в YDB топики. Ещё один плюс - не надо отдельно разворачивать Kafka. И наконец «финалочка» - появилась поддержка транзакций топики+таблицы. Паттерн Transaction outbox - давай, до свидания)
Вообще людям, которые могут себе позволить облако Яндекса по финансовым и идеологическим соображениям, не завязанных на существующий технологический стек - им можно только позавидовать)
Ложка дёгтя - транзакции в YDB пока работают медленнее Kafka. И медленнее, чем хотелось бы команде Яндекса. Команда работает над этим)

#rdbms #transactions #kafka #streaming
Всем привет!

Случайно наткнулся на старую статью - 2015 год - про переход с legacy на Service Oriented Architecture ака SOA.
И хочу сказать, что это хороший пример развития истории по спирали)

Что в статье актуально?
Заменяем слово SOA на микросервисы, и в целом все, что касается преимуществ микросервисной архитектуры и стратегии перехода на нее - актуально. Микросервисы = SOA 2.0 )))

REST оставляем, SOAP+XML заменяем на gRPC\GraphQL для тех случаев, когда требуется большая производительность и гибкость соответственно по сравнению с REST. К слову, недостаток производительности и гибкости - это основные проблемы SOAP. Ремарка - знаю места, где SOAP еще жив (интеграция с госорганами), но он в любом случае вымирает.

ESB, трудности реализации асинхронного взаимодействия - все эти задачи взяла на себя Kafka. Прорывной инструмент - быстрый, надежный (обеспечивает дешевую персистентность), opensource, простой с точки зрения разработчика. В т.ч. потому, что нет необходимости разрабатывать логику маппинга сообщений на брокере. Да, он реализует только одну из двух основных моделей асинхронного взаимодействия - Publisher-Subscriber - и не реализует Message Queue. Но понятно, что топиками можно пользоваться как заменой очередей, и в большинстве случаев проблем при этом не будет.

Облачные решения - за 10 лет из вызова превратились в новую реальность)

А вызов сейчас - внедрение AI. Как-то так)

#microservices #ai #cloud #kafka #rest
Зачем нужен MQ?

По работе передо мной периодически встает данный вопрос - зачем нужен MQ? Нужен ли он?

Поясню. Мы давно и успешно перешли с ESB поверх IBM WebSphere MQ на Kafka. Стало сильно лучше. В первую очередь за счет того, что получить топик Kafka, выпустить сертификаты и настроить ACL сильно проще, чем заказать доработку для на ESB. Да, это проблемы ESB, а не MQ, но их исчезновение все сразу заметили) Второй плюс: появились persistence и возможность повторной вычитки сообщения в случае сбоя - и сильно увеличилась надежность решения. И конечно же Kafka быстрее.

Как итог - Kafka используется везде и всегда. При этом есть понимание, что кроме Kafka есть другие инструменты, реализующие паттерн очередь (queue). Долгое время не мог сформулировать - в каких кейсах нужны очереди?
Ведь паттерн publisher-subscriber, реализованный в Kafka, является более общим, чем очередь. И т.об. очереди - асинхронное взаимодействие с одним producer и одним consumer - вроде бы можно делать в и Kafka. А возможность везде использовать один инструмент - это плюс.

Но если подумать - суть не в реализуемом паттерне. Ключевые отличия у Kafka и RabbitMQ (взял самую известную реализацию очередей для конкретики) другие. Покажу их на примере того, чего Kafka не умеет:

1) Kafka не удаляет сообщение после вычитки. Удаляет позднее, по TTL, вместе с партицией, но это другой алгоритм и другой use case. Т.е. первая потенциальная причина выбора очередей - необходимость гарантировать, что вычитанное одним потребителем, никогда не вычитает другой. Не скажу, что это частый кейс - возможно, требования безопасности.

2) Kafka имеет достаточно ограниченные возможности по маршрутизации. По сути на клиенте можно выбрать топик и партицию по определенному ключу. Вот и вся маршрутизация. Причем это было сделано сознательно, для достижения высокой производительности. Если нужна более сложная маршрутизация - это критерий для выбора очереди вместо Kafka. Тоже кейс не частый, и есть альтернатива в виде реализации маршрутизации на клиенте.

3) невозможно организовать конкурентную вычитку сообщений на consumer - конкуренция ограничена числом партицией. Меня число партицией после создания топика можно, но не нужно, т.к. это приводит к ребалансировке и плохо прогнозируемому падению производительности

4) у Kafka свой протокол. Поэтому если нужна поддержка существующего решения, где используется AMPQ или JMS - это не к Kafka. Особый случай, но упомянуть его надо.

Вот пожалуй и все варианты, когда Kafka не подходит.

P.S. Если знаете еще кейсы - напишите, плиз, в комментариях.

#mq #kafka
Kafka захватывает мир.

Я уже писал https://t.me/javaKotlinDevOps/411, что не умеет Kafka из-за заложенной в ней архитектуры. Если сформулировать одним словом - не реализует традиционную концепцию queue (а реализует - потоковой обработки данных, она же стриминг). Так вот, это уже не совсем так. В марте 2025 вышла Kafka 4.0 (пока даже без хотфиксов), где появилась новая фича - Kafka Queues https://cwiki.apache.org/confluence/display/KAFKA/KIP-932%3A+Queues+for+Kafka
Технически это сделано так: появились share groups - альтернатива обычным consumer groups, позволяющие всем членам группы вычитывать конкуренто сообщения из одной и той же партиции. С consumer groups это было невозможно, для независимой вычитки одной партиции нужны были разные consumer groups. С маршрутизацией все по старому, но первый шаг к очередям сделан. Где это можно применить - там где важно управлять конкурентной вычиткой на уровне consumer и не важен порядок вычитки сообщений. Причем у одного топика могут быть как consumer groups, так и share groups.

Возвращаясь к захвату мира. У БД есть такая концепция tiered storage. Переводится как многослойное хранение. Обычно слоев два - оперативная и архивная БД. Так вот, начиная с версии 3.6 Kafka умеет в tiered storage. Для хранения архивных данных можно подключать внешние хранилища, в частности облачные S3. Что важно - tiered storage включается настройками, т.е. не нужно писать никакого кода. Видится, что это хорошая заявка на использование Kafka как хранилища. Особенно, если у вас стриминговые данные, а-ля поток события, а-ля event sourcing.

P.S. Если говорить о других важных изменениях в последних версиях Kafka:
1) разработчики окончательно выпилили Zookeeper. Теперь за выборы лидера (master для каждой партиции) отвечает один из брокеров Kafka, имеющий роль Controller. И в случае его падения оставшиеся брокеры сами без внешней помощи могут выбрать новый Controller. Вот такое крафтовое (KRaft) решение. Это название протокола выбора лидера если что)

2) улучшен протокол ребалансировки - алгоритма сопоставления партиций топика и потребителей при изменении либо числа потребителей, либо партиций. https://cwiki.apache.org/confluence/display/KAFKA/KIP-848%3A+The+Next+Generation+of+the+Consumer+Rebalance+Protocol Как одна из целей заявлена "The protocol should support upgrading the consumers without downtime". Серьезная заявка, будем посмотреть.


#kafka #queue