Привет, Хабр! Я Алексей Рыбак, предприниматель и основатель R&D-лаборатории DevHands, автор телеграм-канала про System Design и Highload. В прошлом — СТО и руководитель московского офиса Badoo. Также работал CIO во втором по размеру такси-сервисе «Везёт», который мы после продажи интегрировали с Яндекс.Такси. Сейчас наша компания разрабатывает образовательные программы по хайлоаду, перформансу, архитектуре, базам данных и другим направлениям. На прошлой неделе мы провели стрим по очередям с Владимиром Перепелицей, на котором обсудили:
выбор брокера и системы очередей 2025м году, что поменялось?
NATS, его особенности, перспективы, кого он “подвинет” в первую очередь, Kafka или RabbitMQ?
что нового в свежей Apache Kafka 4?
насколько популярны архитектуры, где, например, Kafka основной storage (IoT, сбор метрик и тд)
Стрим получился длинным, но очень интересным. Хотим поделиться с вами текстовой расшифровкой этой встречи.
Алексей Рыбак: Всем привет, ребят. Сегодня у нас очередная открытая встреча Devhands Open Sessions. И у нас в гостях Владимир Перепелица, Solution Architect в Exness, известный эксперт по Tarantool, выступавший многократно на конференции HighLoad и эксперт по очередям. И тема у нас сегодня «очереди в 2025 году». Владимир, привет!
Владимир Перепелица: Да, всем привет.
Алексей Рыбак: Супер. Слушай, хорошо помню наш прошлогодний стрим. Мы обсуждали с тобой тему, с которой, в общем, я бы сказал, с кондачка не разберется даже эксперт. Каким образом можно было бы все-все-все системы очередей классифицировать, как на них смотреть? Хотел бы вернуться к этой теме, потому что у меня как будто бы стало все укладываться сильно лучше сначала, а потом все опять, скажем так, запуталось. И вот хотел бы, чтобы ты мне помог это распутать.
В прошлый раз у тебя была идея смотреть на то, что из себя представляют данные сообщения, какая у них природа, и что мы хотим с ними делать. И это был достаточно такой хитрый ход с твоей стороны: абсолютно правильный, но, с другой стороны, он не давал какого-то чёткого алгоритма выбора.
Как ты относишься к тому, чтобы классифицировать все-таки на три следующие группы. (...) Первая группа – это все, что стримит лог, просто надежный лог-стриминг. Вторая – это именно месседж-брокеридж в том смысле, что вот что-то куда-то выплюнулось, должно куда-то уйти, где-то есть вот эти все правила, что куда как должно уйти. Ну и вообще говоря, гарантии доставки, они уже как-то приплетаются, но уже как будто бы сбоку. Главное сейчас, real-time, прокачать прямо сейчас запросы. И, в общем, можно сказать, что это брокеры.
И тут стоят еще отдельно где-то Pub/Sub, паттерн, который похож, с одной стороны, на брокера, но не совсем, потому что для Pub/Sub важны не просто гарантии, но еще определенные фишечки, рюшечки, сохранения, откладывания, может быть, даже реприоритизации. Поставить плюс один, попробовал обработать, и у меня не получилось. Возьмем в следующий раз через какое-то время. Вот это все...
Владимир Перепелица: Но это ты уже откатываешься к очередям. Начинаешь из Pub/Sub делать очередь.
Алексей Рыбак: Ну да, но с другой стороны, если мы забудем про какие-то архитектурные терминологии, как будто бы, если я делаю какую-то систему, вот мне нужно подписаться на что-то и с этим чем-то как-то работать. И когда я подписался, я не гарантирую, что мне это понадобится прямо сейчас. А когда я начинаю это обрабатывать, вдруг оказывается, что прямо сейчас вот это обработать не могу. Потому что это зависит от какого-то кластера, который лежит, например. И как будто бы вот эта вот вещь, связанная с очередями, то, что ты сказал, что как будто бы микс двух архитектур, она, в общем, возникает, естественно.
Long story short, вот такое разделение на три группы имеет смысл или нет?
Владимир Перепелица: Ну, в целом, скажем так, всегда можно делить любое пространство на разное количество частей, и границы будут более четкими или более мелкими.
(00:05:03)
То есть предложенное тобой разделение, да, вполне валидно. То есть до появления Kafka у нас был классический месседжинг, то есть это MQP, все вокруг него, то есть RabbitMQ, ActiveMQ, еще какой-то набор MQ. В целом ZeroMQ тут даже, но уже с вариациями. Есть Pub/Sub, в том или ином виде реализуемый, либо stateless, то есть когда мы ничего никуда не сохраняем, либо stateful, как, например, в том же Rabbit, когда поверх очередей реализуется Pub/Sub, то есть меняя доставку от одного к одному в один ко многим.
И стриминг, который тоже по-своему является трансформацией Pub/Sub. То есть это Pub/Sub, доведенный, скажем так, до абсолюта по durability, но со своими проблемами и со строгой упорядоченностью, когда уже архитектура диктует механизмы обработки. То есть в целом, да, вполне валидно. Именно появление Kafka, я бы сказал, сформировало вообще весь этот рынок стриминга и event-sourcing. То есть сначала Kafka взяла одну из гипотез, реализовала ее хорошо, не Kafka, LinkedIn в Kafka, но мы имеем в виду на примере конкретного продукта. А дальше оказалось, что этот паттерн, он удачный с точки зрения эксплуатации, он хороший, простой, понятный. И дальше его стали тиражировать.
Так же, как и в базах данных. То есть у нас есть аналитические, транзакционные, но там грань везде смазанная. То есть, есть гибридные, одни паттерны у них лучше, у других хуже. Здесь то же самое. На самом деле на текущий момент, мы сегодня можем об этом поговорить, Kafka добавила очереди, очереди добавили стриминг. И в общем-то, все плавненько смешивается, что должен быть продукт, который тебе дает разные сценарии использования.
Алексей Рыбак: Да, и вот это, кстати, заметно и на рынке баз данных. И я хотел еще добавить. А еще в какой-то момент обязательно чуваки из Postgres'ов тащат в какой-то там extension определенный паттерн работы с данными. Ладно, хорошо. До Postgres, кстати, мы еще дойдем, потому что был у одного из подписчиков был вопрос про очереди на PostgreSQL. Хорошо, итак. Есть два, как мне казалось всегда, два таких постоянно упоминаемых продукта. Kafka и Rabbit. И у меня всегда в голове была приблизительно такая модель. Если вам не нужен супербыстрый обмен сообщениями, если ваша история больше прокачивать большой объем, держать его долго и реализовывать разного рода с хорошими гарантиями. И в основном ваш паттерн – это Pub/Sub. Я сейчас не говорю про event sourcing, потому что как будто бы это вообще отдельная история. И там можно потеряться и несколько часов в этих архитектурных паттернах провести. Давай просто стриминг какого-то лога, стриминг каких-то событий - это Kafka.
А если, значит, тебе нужно обмениваться сообщениями достаточно быстро, с какими-то гарантиями, немножко другими, но в первую очередь сделать это как можно быстрее, скажем так, бери Rabbit. Вот эта модель, она правильная или нет?
Владимир Перепелица: Ну, so-so. То есть в целом, если данные представляют из себя, скажем так, некоторую упорядоченную последовательность. Упорядоченность – важный момент, потому что когда мы говорим про брокеры очередей, такие как Rabbit, основная их возможность и функция – это переупорядочивать сообщения. То есть брать, откладывать, кидать новые. В Kafka, вот как мы их записали, в каком порядке они идут, как бы все, вот они так лежат. Если ты не можешь его законсьюмить, ты тормозишь всю очередь. Ну, пока ограничимся таким утверждением. Там партицию на самом деле, но неважно. То есть ты тормозишь очередь.
В целом, скажем так, Rabbit – не самое удачное решение. У него было, скажем так, много взлетов и падений. Больше, наверное, падений.
(00:10:00)
При том, что, в общем-то, он неплохая система, довольно производительная. То есть его можно использовать для передачи сообщений. Он очень сложен в эксплуатации. Плюс он не масштабируется горизонтально. То есть у него нет в коробке хорошего инструмента, который позволил бы его масштабировать горизонтально. То есть по надежности они дотянули. А вот по масштабированию нет. И получается такая ситуация, что ты можешь настроить очень сложный Dispatch сообщений, но ты не можешь масштабироваться. Условные 50 тысяч сообщений в секунду, и все, твой предел, предел твоей системы.
Алексей Рыбак: Извини, это на ядро или на типичную ноду?
Владимир Перепелица: На кластер. Мы говорим про durability. Нет, может быть, ты чуть можешь больше вытащить, если докинешь ядер. Но все равно, скажем так, система имеет предел масштабирования, и у нее нет функционально-горизонтального масштабирования, какое мы имеем, например, в той же Kafka. Поэтому если нас интересует какой-нибудь трансфер сообщений с роутингом, то лучше посмотреть на тех же свежих игроков. То есть если сравнивать впрямую, то по функциональности, возможностям масштабирования производительности, опять же, тот же NATS, свежий, новый, молодой, неотягощенный проблемами протокола, выигрывает по возможностям эксплуатации у Rabbit.
То есть смысла брать Rabbit на сегодняшний день как будто бы нет, если только нет жесткой потребности либо взять его очень сложный роутинг, либо взять четко конкретный интерфейс AMQP. То есть у вас может быть продукт, который требует AMQP, и вот под AMQP лучше решения не осталось. Новое вряд ли кто-то будет делать, в этом нет смысла, а среди старых Rabbit самый лучший.
Алексей Рыбак: Окей, то есть фактически ты говоришь приблизительно следующее. Да, было такое дело, да. Мы продолжаем выбирать Kafka в случае, если нам нужно в определенном порядке все записать и потом, грубо говоря, как-то проигрывать, неважно, в каком паттерне. А если у вас есть более-менее сложный Dispatching, быстрый, и вам нужно связать между собой несколько ваших сервисов, микросервисов, то был Rabbit, а теперь как будто бы… Вот ты фактически сказал, что NATS является в первую очередь, это один из наших следующих вопросов, в первую очередь заменой Rabbit. Правильно будет так сказать?
Владимир Перепелица: Да, с одним исключением. То есть это если мы взвешенно подходим к выбору. Вот если мы посмотрели, подумали, повыбирали. Есть еще интуитивный выбор, то есть это когда разработчик просто что-то делает, делает, делает и такой: «А мне бы надо что-то про обмен сообщениями, про очереди». В этом случае большинство берет Redis. Redis у тебя просто под рукой, он у тебя уже есть, он быстро находит функциональную возможность реализовать очередь через него, будь то там Celery питоновская, или простые листы, или Pub/Sub. Но потом, конечно, он уже узнает про проблемы потери сообщений, про ненадежность доставки. Но на первом этапе это прекрасный инструмент. То есть он просто сразу есть. Он сразу есть, сразу работает и закрывает задачу пользователя.
Поэтому до выбора существует скорее так, если стримы – это Kafka, а если не стримы, а вот что-то другое, – это Redis, он у всех под рукой.
Алексей Рыбак: Ну вот видишь, у некоторых под рукой PostgreSQL, и там есть каналы Notify и то же самое. Мы сейчас про это тоже поговорим. Мне очень понравилось, как ты в прошлый раз вспомнил про известный вопрос, какой дистрибутив Linux выбрать. Надо выбрать тот, который использует ваш знакомый системный администратор. Если в контуре есть Redis или Postgres, и они что-то похожее там умеют, то с большой долей вероятности сначала попробуем их.
Можем шаг назад, прежде чем пойдем вперед? Это очень интересно. Поскольку мы много обсуждаем в разных ипостасях дизайн, разработку сложных систем, надежных систем, масштабируемых систем, необязательно систем очередей. Где “накосяпорили” разработчики Rabbit, что скейлинг настолько неудобный? В двух словах это можно каким-то образом описать или нет?
Владимир Перепелица: Смотри, мое личное впечатление от Rabbit, где накосячили его разработчики.
(00:15:02)
Взяв Erlang, они не сделали архитектуру масштабируемой, при том, что Erlang вроде бы под это заточен, он должен уметь раскидывать все по разным хостам, но они под это не заточили всю систему. Они не сделали систему, как в Kafka, докинул брокеров, раскидал на них задачи и система тащит больше. То есть они предусматривают, что у тебя есть узел, вот он брокер, все, у тебя все задачи проходят через этот брокер. Ты можешь поставить запасной, ты можешь поставить реплики, но они тоже будут запасными.
Алексей Рыбак: Я не могу сделать proxy-брокер?
Владимир Перепелица: Ну, можешь.
Алексей Рыбак: Взять и сказать, типа, раунд-робином давай кидай на этот апстрим.
Владимир Перепелица: Это все сделано костылями. Там есть федерация, через которую можно это все раскидывать. Я пока в курсе был Rabbit, то есть хотел показать, как бы это можно масштабировать так, чтобы красиво. Красиво не получилось. А показывать гору костылей как-то не очень. То есть, в общем, из коробки, вот прям поставил кластер, и он у тебя скейлится. Этого нет. Да, сделать это можно, но это, опять же, уже что-то добавленное сбоку, расширениями, не всегда просто устанавливаемыми. И странно, при том, что сама платформа языковая, кажется, это могла бы дать.
Ну и вторая ошибка – это, собственно, да, выбор платформы. Дефицит разработчиков всегда сказывается на популярности инструмента. То есть найти Erlang-разработчика – это такой, скажем так, челлендж, который еще захочет поинвестировать свое личное время, потому что это open-source. Гораздо проще сейчас найти GO'шный или RUST'овый open-source, или Java'вый, собственно, Kafka – тому пример. Java у нас много. Ее можно недолюбливать, у нее есть куча проблем, но она чертовски популярна. Поэтому выбор Erlang как инструмента, скажем так, скорее затормозил его развитие. И сейчас, если человек приходит, смотрит, возможно, пытается что-то улучшить, такой: «Ой, там что-то, что сейчас не популярно», человек уходит.
Алексей Рыбак: Хорошо, скажи, пожалуйста, значит, Rabbit и Kafka – это просто стандарт. NATS уже такой же стандарт или нет?
Владимир Перепелица: Я бы не сказал, что он стандарт. NATS – это на сегодняшний день популярный инструмент. Я бы использовал слово emerging. Он появляется, он становится популярным, у него хорошие функциональные возможности, очень удобное управление. Как инструмент он хорошо заточен под сегодняшний ландшафт. Он cloud-native, он очень простой, одновременно с тем функциональный. То есть он взял лучшее из стримингового подхода и подхода по передаче сообщений. То есть так же, как Kafka при своем создании выбрала удачную архитектуру, NATS тоже выбрал удачную архитектуру, которая хорошо…
Вот как Lego. Lego – это хороший конструктор, потому что у него все детали очень хорошо друг к другу подходят. Вот в NATS, скажем так, их концепт позволяет им хорошо собирать очень разнообразные кластера. И, скажем так, в их исполнении кластер не выглядит как набор костылей. То есть он выглядит как нечто цельное, единое, хорошо собранное.
Алексей Рыбак: Они являются резидентом или частью того самого списка cloud-native foundation, который поддерживает специальный, авторизованный…
Владимир Перепелица: Слушай, я не слежу за списком CNCF, он слишком огромный. То есть, возможно, да.
Алексей Рыбак: Окей, окей. Просто интересно, что с их точки зрения является сейчас самым правильным cloud-native, потому что не всегда самое удобное и популярное.
Владимир Перепелица: Они же не предлагают тебе, не говорят, что это самое правильное. Они просто говорят: «Вот есть варианты», и этих вариантов всегда огромное количество.
Алексей Рыбак: Окей, хорошо. Итак, можешь рассказать в двух словах? Как будто бы, когда мы в прошлый раз обсуждали, что вообще есть, какой есть ландшафт, звучал CNCF, звучал, естественно, Redis, звучал Rabbit, звучали какие-то родные очереди cloud-провайдеров, Redpanda, кстати, кто-то из подписчиков тоже вспоминал.
(00:20:01)
Каждый раз начинаешь перечислять, кто-то обязательно поднимет руку и напишет в комментариях «а вот что забыли вот это, забыли вот это». Значит, их много. NATS, как будто бы ты про него, я так со стороны, как будто бы ты про него знал, а потом как познал, как разобрался, как будто бы ты про него стал все более восторженно говорить, настолько, что в результате он стал частью твоего курса. Можешь вот это рассказать, твое погружение и твое осознание того, что это действительно классный “emerging” инструмент, как оно вообще сформировалось, вокруг каких сценариев такое, соответственно, убеждение ты получил?
Владимир Перепелица: У меня был, скажем так, первичный опыт с NATS, назовем его старым, еще до того, как его весь переписали на Go, до того, как в него добавили стриминг. То есть мое первое знакомство с NATS – это были stateless-брокеры, то есть они все также собирались в некоторый грид, через который можно было передавать сообщения, но я на него всегда смотрел как на систему, которая передает ненадежно сообщения, быстро, хорошо, с классной маршрутизацией, но ненадежно. И, собственно, анализируя ландшафт, я как раз и посмотрел, что NATS сейчас, то есть NATS реализованный на Go, NATS со всеми текущими инструментами, с теми механизмами персистенса, которые они у себя заимплементили, это совершенно другой NATS.
Они очень органично встроили внутрь себя систему стриминга, то есть позаимствовав идеологию у Kafka, но, скажем так, тщательно ее доработав. То есть то, где мы в Kafka, скажем так, страдаем, или там, где неудобно, например, выполнять какую-нибудь фильтрацию сообщений, разделение их в рамках одного топика, то есть они это организовали немного по-другому, за счет этого они покрывают и стандартный сценарий, и появляются дополнительные новые. При этом персистенс у них сделан надежно, то есть если мы говорим про какой-нибудь Redis, у него персистенс, ну такой, костыльненький, то есть асинхронная репликация, данные можно потерять, то есть они его делали, что называется, по-взрослому, сразу с использованием кворума, с использованием Raft, это уже на сегодняшний день им было доступно, поэтому они сделали это как положено.
То есть если ты включаешь персистенс, ты можешь полагаться, что твои сообщения не потеряются, то есть это очень долгое время была боль для систем передачи сообщений, что они сообщения передают, но, в общем-то, иногда теряют. И это был один из поинтов, за счет которого в свое время выехала Kafka, то есть она не теряла сообщения, она тоже использовала, скажем так, механизмы, похожие на Raft, то есть поскольку это реплицированный лог, то есть у нас есть фолловеры, то есть фолловеры тоже коммитят, то есть Kafka смогла доставлять сообщение надежно. Вот NATS можно доставлять сообщение тоже надежно. Это очень важный момент.
Алексей Рыбак: Слушай, я, знаешь, хотел спросить быстро, такой вопрос, ты немножечко тут упомянул Redis. У Redis забыл, как называется эта опция, причем это, по-моему, чуть ли не опция клиента, такая, что тебе какое-то количество реплик real-time должны ответить, что они тоже сохранили, приняли соответствующие данные.
Владимир Перепелица: Есть.
Алексей Рыбак: Там есть такое. Но это для обычных ключей. Для очередей такая штука есть?
Владимир Перепелица: В Redis нет очередей. В Redis есть две функции. В Redis есть списки, и да, для списка ты, в общем-то, тоже это можешь сделать.
Алексей Рыбак: То есть как-то все-таки повысить гарантию немножечко можно?
Владимир Перепелица: Нет, повысить, конечно. Более того, в рамках курса я показывал, как на Redis делать максимально хорошую передачу сообщений, максимально надежную. У Redis есть кластер, у Redis есть даже черновик Raft. То есть когда-нибудь мы увидим Redis, который абсолютно хорошо и консистентно сохраняет сообщения, в том числе очереди. Но либо из бета собирайте отдельными патчами, либо не сегодня.
Алексей Рыбак: Да, но то, что есть сейчас в кластере, ты можешь обновить данные и дождаться не только от своей ноды, которая является обработчиком, вылетает из головы, все по-разному это называют. У них хэш-слоты, по-моему, это называется. Вот эти хэш-слоты разбили на этот кластер, и как будто бы при обновлении можно сказать – я хочу, чтобы мне какое-то количество нод, вот не знаю, является ли это параметром или нет. Оно, конечно, все совсем не кворумное, то есть там нет такого алгоритма, но там есть какая-то гарантия.
(00:25:13)
Ну ладно, хорошо. Давай назад теперь к NATS. Итак, NATS проект достаточно новый, относительно остальных. Rabbit сколько лет, Kafka сколько лет. Ну, Rabbit, я так понимаю, прям даже сильно больше, чем Kafka. А Kafka, кажется, уже лет 10, если не больше тоже. С точки зрения удобства использования в разных системах. Ну, не знаю, понятно, что когда на go что-то пишется, то гoшникам сам бог велел, потому что, скорее всего, гoшник для гошника API сделает хороший. А вот API в другие языки, в другие экосистемы. Насколько это все, как можно было бы оценить зрелость, удобство с этой точки зрения?
Владимир Перепелица: А вот это как раз следующий момент, который я хотел подсветить. Поскольку я знаю очень хороший, но очень неудобный проект, я могу оценивать первое погружение, когда я с чем-то новым сталкиваюсь. То есть у NATS сделано все правильно. Он максимально легко запускается в минимальной конфигурации. Он довольно легко конфигурируется в сложных конфигурациях, в кластерных. У него хорошая observability стандартными инструментами. То есть вообще без использования каких-то там дополнительных языков. То есть ты просто создаешь кластер, обзервишь кластер, каким-либо образом модифицируешь при помощи стандартных утилит, стандартного CLI. И они очень простые, понятные, человеколюбивые, скажем так. То есть вот у них этот дизайн был сделан сразу хорошо. Плюс они подумали про то, что не только гошники есть. У них хороший набор внешних библиотек, которые они супервайзят при разработке NATS. За счет этого у него получается очень хорошая такая вот экосистема, своя собственная, которая прорастает в другие языки.
Алексей Рыбак: То есть они сами… вот это на самом деле очень важная штука. И я в свое время обращал внимание на то, действительно ли люди супервайзят набор каких-то API и (следят за) adoption в других языках, контролируя это и контрибьютя в это. Или они просто говорят: «Ребята, вот там у нас есть стандартная документация, вот по образу и подобию делайте и имплементируйте свои языки». То есть грубо говоря, если ты пишешь какой-то код, например, на Python, я знаю несколько языков, но, например, на Python, то реализацию API к NATS на Python написали чуваки из NATS. Правильно я тебя понимаю?
Владимир Перепелица: Да.
Алексей Рыбак: Да, это круто. Хорошо. Ладно. Тогда смотри, еще такой момент, про который ты упомянул. Это то, что у NATS все-таки есть какой-то стриминг. Этот стриминг может быть даже чем-то лучше, новее, по-другому сделан, нежели в Kafka. Такой вопрос, может быть, немножко нубский. А может, ну её теперь, эту Kafka? Может быть, теперь берем NATS и любой паттерн, который можно реализовать, у нас теперь есть супер инструмент, такая прям золотая пуля?
Владимир Перепелица: А вот тут мы приходим к интересной дилемме. Причем нет хорошего ответа, как правильно. Время покажет, и это очень долгий процесс всегда. Когда мы разрабатываем что-то новое, хорошее, у нас всегда есть выбор. Взять своего ближайшего конкурента и повторить его протокол, имплементацию, чтобы где-то стать и подменять его, либо спроектировать что-то свое, что будет хорошо отвечать твоим требованиям, но будет несовместимым.
Еще у нас часто упоминается пульсар. И пульсар упоминается как потенциальный дроп-ин для Kafka. Или есть Redpanda, которая просто говорит: «Мы Kafka, но переписанная на C++». Вот в случае NATS, они не повторяют протокол Kafka, и не пытаются повторять, и не пытаются быть дроп-ин-заменой. То есть они могут быть функциональной заменой. И это значит, что они теряют всю существующую экосистему. У тебя есть Kafka Connect, у NATS Kafka Connect нет. То есть у тебя есть большое количество существующих каких-то дата-пайплайнов, которые умеют в Kafka. Это значит, что NATS в этой точке стать не может. Какие-то приложения могут поддержать и NATS тоже, но это не происходит автоматом.
(00:30:05)
И там, где мы можем прийти и вместо Kafka поставить Redpanda, просто потому что она использует тот же самый протокол, те же самые подходы. То есть это просто дроп-ин-замена. Мы не можем заменить NATS. Это потребует определенных усилий со стороны команды разработки. Команда разработки должна… Теперь мы используем не Kafka, не кафковские библиотеки, а мы используем NATS. В итоге они могут выиграть. Они могут выиграть в производительности, в функциональности, но они потеряют какие-то стандартные инструменты дата-аналитиков.
Алексей Рыбак: Смотри, фактически ты говоришь о том, что это не дроп-ин-реплейс. Хорошо, это не дроп-ин-реплейс, но все-таки я стримлю, грубо говоря, события так же надежно? Я могу прокачивать такое же количество десятков, сотен мегабайт в секунду этих событий? Наборы, не знаю, фишечек, рюшечек, которые у меня есть… То есть я, в принципе, не могу на Rabbit, как я понимаю, реализовать нормальный стриминг. Потому что он для этого не предназначен. Кейс, при котором я на Rabbit сохраняю действительно в течение долгого времени поток событий, потом его проигрываю, на Rabbit он в принципе невозможен.
Владимир Перепелица: Смотри, вот здесь у меня даже мемасик на эту тему нарисован, где Человеки-пауки друг на друга указывают. Вот Rabbit добавляет стримы, Radis добавляет стримы, Kafka добавляет очереди. То есть все системы копируют друг у друга то, чего им не хватает, и то, что есть у других. То есть стриминговый подход, он всегда лежал рядом с очередями. То есть он заимствовал из него какие-то идеи. Поэтому системы очередей, теряя свою аудиторию в пользу Kafka, пытались хотя бы часть удержать. Поэтому они в том или ином виде добавляют этот сценарий.
У стрима есть определенные свойства. Это Immutable log, который ты можешь перепроигрывать. Вот основное определение. Это не сообщение, которое полетело, добавилось, уничтожилось. Это что-то неизменное, что ты можешь реплеить.
Алексей Рыбак: Хорошо, но фактически ты так очень аккуратно сказал, что да, Алексей, это не дроп-ин реплейсмент. Заменить просто так нельзя, надо почитать, как это делать, надо с этим поработать. Но в целом да, это добавили в NATS и добавили достаточно удачно.
Владимир Перепелица: Да.
Алексей Рыбак: Оно быстро работает, оно с хорошими гарантиями и так далее. Хорошо, окей. Потому что если мне кто-то скажет, что, условно говоря, Redis добавил стримы, они такие же надежные, как в Kafka, я так скажу, что нет, вряд ли. Или тем более в Rabbit. Если мне в какой-то момент разработчики YDB говорят, что у нас есть и такие данные, и еще надежное хранение для стриминга, я так уже почешу репу, подумаю: «Ну, эти, наверное, может и сделают». Как будто бы, не знаю, немножко интуитивно, может быть, немножко поломана в этом смысле логика. Но если ты говоришь, что NATS сделали достаточно хорошо и стриминг, то как будто бы, все-таки, через какое-то время может же оказаться, что у людей NATS, и они вообще любой паттерн реализуют через NATS.
Владимир Перепелица: Смотри, там, скажем так, функционально есть возможность горизонтального масштабирования, но она отличается от таковой в Kafka. То есть Kafka говорит, вот у тебя есть топик, он за тебя там как-то делится на партиции, ты просто знаешь про существование партиций, настраиваешь их количество, а дальше ты просто масштабируешься партициями. В NATS подход тот же самый. Ты просто строишь кластер, раскидывая по нему эти самые стримы, но ты управляешь, то есть для тебя единица разделения, ты ее можешь самостоятельно указывать. То есть это, скажем так, одновременно и плюс, и минус.
(00:35:00)
Важный момент, я не могу просто поднять NATS, накидать в него серваков и сказать: «Вот тебе поток сообщений, кушай и масштабируй». То есть в дефолтном варианте он может у меня приземлиться просто на один сервер кластера. Ну и на его реплики. И никакого масштабирования. То есть функционально оно… То есть он дает возможность более тонкого контроля, за счет этого у меня есть больше возможности по работе с данными, но, опять же, за счет этого мне над этим надо подумать.
То есть он как Tarantool, возможно, поэтому он мне и нравится. То есть он заставляет чуть больше подумать, как работать со своими данными, но дает больше возможностей, чем Kafka.
Алексей Рыбак: Когда ты сказал про то, что он приземлит все на один сервер, у меня как будто бы создалось впечатление, что нельзя… Знаешь этот мем (с Боромиром)... Нельзя просто так взять и поскейлить NATS. Ты только что говорил обратное, что, наоборот, NATS удобная классная штука. Значит, ещё раз, легко там поскейлить именно стриминг или нет?
Владимир Перепелица: Легко, но ты должен об этом подумать.
Алексей Рыбак: Нельзя просто сказать – пожалуйста, раскидай вот этот поток на вот эти N? Для меня легко – это вот так.
Владимир Перепелица: Вот скажи ему, что возьми по этому признаку, и признак укажи, по какому признаку раскидывать.
Алексей Рыбак: Ага, надо указать какой-то признак. Хорошо, ладно, понятно. То есть надо, грубо говоря, то есть он… Это как если бы я на Reverse Proxy, значит, не просто бы upstream с весами бы указывал, а еще писал бы маленький Low-code, который применялся бы к каждому…
Владимир Перепелица: И высчитывал консистентную функцию, да.
Алексей Рыбак: Ну, понятно, хорошо. Ладно. Был один вопрос. Вопрос такой, что… А можно ли сравнить это все… Ну, про Rabbit уже поговорили. А можно ли это все сравнить со старым и добрым Notify в Postgres? Я напомню, там такая есть штука удобная, по-моему, кстати говоря, в экосистеме MySQL такого нет. Ты объявляешь некий канал, потом ты подписываешься на этот канал. Ну, фактически как подписаться на Topik, если угодно. Ну, просто канал. Канал просто – это хорошее слово, потому что, как я понимаю, он существует здесь и сейчас. Насчет персистентности, гарантии и durability вообще сейчас обсудим.
И сейчас просто те, кто не знаком с этой историей, она с какого-то древнего Postgres есть. Ты объявляешь, что есть определенный канал. В этот канал как будто бы можно что-то модифицировать. И тогда все, кто, значит, сказал, что мы подписались на этот канал, получат соответствующую нотификацию. Вот, грубо говоря, такой есть стандартный механизм, который очень похож на брокера. Ну, фактически, в этом смысле он брокером и является. При этом сложные какие-то, ну, понятно, что сложные какие-то цепочки строить нельзя, и непонятно, что там с надежностью. Вот подписчик спрашивает, вот что с этим плохого, можно продолжать пользоваться? Или надо на ваши эти новые модные инструменты переходить?
Владимир Перепелица: Вопрос, для чего? Опять же, то есть у нас существует огромное количество кейсов. То есть начнем с того, что Pub/Sub внутри процесса Postgres позволяет нам сделать одну вещь, которая традиционно в базах данных отсутствует. Она позволяет событийно связать между собой двух клиентов. То есть ты можешь из одного клиента прийти, вызвать PL/pgSQL функцию, которая будет говорить: «А дай-ка мне задачу». А другой клиент придет, и когда он вставит эту задачу, тоже PL/pgSQL функции, он может в этот канал плюнуть сообщение, что «а у меня тут есть новая задача». И таким образом, вместо такого SQL-поллинга, когда ты приходишь, что-то селектишь, а там ничего нет, ты реализовываешь событийную очередь.
И если мы пойдем посмотрим на какие-нибудь кастомные очереди на Tarantool, то они именно так и организованы. То есть Tarantool – это база данных, в ней данные о самих задачах, о том, кто их взял, хранятся в табличках с индексами. Но для того, чтобы она вела себя как очередь, реализуется набор функций, и клиенты связываются между собой при помощи абсолютно аналогичного примитива. Кто знает Golang, там есть каналы. Вот в Tarantool есть каналы, и, собственно, в Postgres это, по своей сути, тоже канал, позволяющий связать между собой клиента.
(00:40:03)
Свойства у него абсолютно такие же. И, кстати, в Redis и Pub/Sub это тоже такой же канал. Он абсолютно эфемерен, то есть он ничего никуда не сохраняет. Но ты его можешь использовать в своем прикладном коде на стороне базы. То есть ты можешь на стороне базы написать функцию, которая куда-то что-то сохранит и положит это в канал. И таким образом ты можешь связать между собой нескольких клиентов.
Рассматривать этот сценарий как средство передачи сообщений – он ненадежный. Более того, если тебя устраивает ненадежно уведомлять других, возьми Redis. Он проще, быстрее и намного удобнее в этом плане для передачи уведомлений. Если тебе все-таки нужно надежно, значит тебе еще и сохранить данные нужно. Поэтому делаешь функцию, сохраняешь. Если у тебя есть только Postgres, если тебе производительности Postgres хватит, в целом Postgres на сегодняшний день достаточно хорош, то в общем-то можно.
Мы всегда могли реализовать очередь на Postgres без этой функциональности. Приход за задачами подразумевал, что ты будешь периодически опрашивать. То есть у тебя есть лаг равный таймауту, с каким таймаутом ты ходишь за этими задачами. Ну и, собственно, есть паразитная нагрузка. Ты периодически опрашиваешь табличку. Каналы позволяют избавиться от этих двух проблем. У тебя передача задачи становится почти мгновенная, и ты не мучаешь базу этой нагрузкой. Вот и все.
Алексей Рыбак: Если моргнула сеть, или еще что-то произошло, все потерялось?
Владимир Перепелица: Когда канал несет функцию нотификации, ты не теряешь события, ты теряешь нотификацию. Но после восстановления соединения ты идешь в таблицу. Как это можно сделать в двух словах? У тебя есть продюсер. Продюсер приходит, вставляет что-то в таблицу. А потом такой в канал. У меня тут новый таск, новый месседж. У тебя есть клиент. Он приходит, смотрит в таблицу, тасков нет. Он такой – посижу-ка я на канале. Если, пока он сидит на канале, ему кто-то прислал события, он такой в таблице канал. Дальше уже транзакции, кто заберет, это конкуренция между клиентами. Но в общем случае, если ты пришел заново, то есть порвалось соединение, то тебе не важно, что там было в канале, ты просто прочитаешь таблицу.
Алексей Рыбак: Да, то есть через нотификации мы экономим в этом паразитном регулярном опросе и получаем более дешевое оповещение. Хорошо, окей. Кстати говоря, ребят, вот это уже был вопрос одного из подписчиков. И если у вас есть еще какие-то вопросы, то вы можете сделать следующие вещи. Во-первых, члены наших курсов находятся в Zoom-комнате. Вы можете включаться, задавать эти вопросы, либо писать в чат. Все, кто смотрит через YouTube, в YouTube тоже есть чат, туда что-то пишут. Но правда сейчас пока как будто бы пишут не вопросы. Сейчас быстро гляну. Сейчас, Саша, я вижу в твою руку. Да, тут интересно, на самом деле есть один вопрос, с которого я хотел бы на самом деле начать, потому что его Андрей задал достаточно давно. Смотри, какой вопрос. Звучит он так. Мы говорим про NATS в его open-source исполнением? Кстати говоря, NATS немножко не так пишется, но неважно.
Значит, я так понимаю, что есть какой-то еще и не open-source NATS, и я как раз про это хотел поговорить. Чем отличается, условно говоря, NATS тот, который… Я так понимаю, что это все-таки коммерческая организация, которая работает по принципу OpenCore. Какая-то есть Enterprise-версия, чем-то она отличается, при этом OpenSource-версия достаточно крута. Судя по тому, что я слышал. Твой vision, чем они отличаются? И все, что ты говоришь, это относится, я понимаю, к OpenSource-версии или все-таки уже нет?
Владимир Перепелица: Я даже не знаю, что есть в коммерческой версии NATS. Это все OpenSource.
Алексей Рыбак: Андрей, это все про OpenSource. Еще один вопрос, который у меня тоже был, такой же, как у Андрея. Значит, вопрос такой. Я когда последний раз открыл NATS, удивился тому, насколько все модно-молодежно там сделано, как в стиле современных получивших инвестиции стартапов, которые пилят очередную базу данных, K-value и так далее. Все клево. И, честно говоря, вот если заранее не знать, что такое NATS, ты немножечко теряешься, потому что там написано, типа, все в одном, K-value, еще там что-то.
(00:45:03)
Ну, то есть, грубо говоря, вот открываешь NATS.io, и там создается впечатление, что это просто какой-то комбайн, кухонный комбайн, который умеет дофига всего. И как-то в одном из чатов мы с тобой как раз это стали обсуждать. Я такой говорю: «Слушай, если они говорят, что у них внутри K-value, может, нам и Radis уже теперь не нужен, да? Давай в NATS и стримы, и Pub/Sub, и брокер, и key-value. Ты мне скептически ответил - мржеш развернуть свой ответ? Вот Андрей (в комментариях) про key-value тоже спросил.
Владимир Перепелица: Так же, как мы из базы данных делаем очередь, так мы и из стриминговых систем. То есть, если из обычной очереди базу данных сделать сложно, там сообщения имеют время жизни, то есть сообщение влетает, и пока его не заберет какой-нибудь консюьмер, то в стриминговых системах сообщения остаются. Стрим можно реплеить. Если мы можем стрим реплеить, то мы можем восстанавливать какой-то стейт.
И если мы посмотрим, например, на подход Kafka Streams, там есть понятие создания базы данных. Что такое база данных вообще в концепции Event Sourcing? У нас есть поток событий, которые изменяют какой-то стейт. Например, наши события — это insert, delete, update и так далее. У нас всегда, на любой момент времени в этом логе есть результирующий стейт. И вот этот результирующий стейт можно рассматривать как текущий снимок базы данных. В общем-то, базы данных так и работают. У них есть текущий стейт и лог, при помощи которого они к этому стейту пришли.
Алексей Рыбак: Да, но только это уезжает логикой на клиента теперь, и это нетривиально.
Владимир Перепелица: Нет, потому что в Kafka Streams это есть в стандартной Kafka. То есть у тебя есть инструменты, которые тебе позволяют создавать примитив в таблицах поверх твоей Kafka.
Алексей Рыбак: То есть она все применяет и рассчитывает на лету?
Владимир Перепелица: Да.
Алексей Рыбак: Или нет? Программист должен будет правильно реализовать, нигде не ошибиться? Меня всегда именно поэтому обработка ивентов и переход к этой концепции смущал.
Владимир Перепелица: Прелесть этой системы, что если программист ошибся, он может откатиться во времени на начало лога и проиграть все заново.
Алексей Рыбак: Да, но дебажить все равно… Ладно, слушай...
Владимир Перепелица: Так вот, собственно, и у Kafka, в Kafka Streams, и у NATS, скажем так, есть какие-то механизмы, которые позволяют в том или ином виде воссоздавать вот эти примитивы. То есть примитив K-value, какого-то хранилища. То есть NATS использует, NATS JetStream, под капотом Raft. Raft — это реплицированный лог, который умеет что-то хранить. То есть изначально он создавался под хранение как раз K-value данных. Поскольку он есть, его можно использовать для этого, для этих задач. То есть он сохраняет приходящее сообщение, то есть он сохраняет приходящие стримы, но при этом его же можно использовать для хранения K-value данных.
Да, ключевое отличие NATS от Kafka, одно из ключевых, это наличие индекса к тому, что у нас сохранено в стриме. То есть если в Kafka ты не можешь просто сказать: «А дай-ка мне вот какое-то вон то сообщение или отфильтруй мне сообщение по какому-то признаку, то есть ты будешь фильтровать весь поток», то NATS к своему стриму строит индекс, поэтому ты можешь индексным поиском делать некоторую выборку.
И я не заглядывал в исходники, не могу ответить на 100%, но насколько я понимаю, они используют этот же подход для организации key-value хранилища. То есть у них есть индекс к тому, что они сохраняют.
Алексей Рыбак: Но как будто бы key-value – это вообще О1, хеш-таблица, причем тут индекс…
Владимир Перепелица: Это почти ни у кого не хеш-таблица, то есть это обычно все-таки дерево и логарифм.
Алексей Рыбак: У memcached хеш-таблица.
Владимир Перепелица: Ну, да. Хорошо. Но memcached не сохранял это на диск, поэтому…
Алексей Рыбак: Да-да-да. Хотя что-то там в последнее время какая-то персистентная движуха есть. И там был memcached-плагин, с протоколом memcache и проброс всего в InnoDB внутри MySQL.
Владимир Перепелица: А, это да, это было.
(00:50:01)
Алексей Рыбак: Я посмотрю, скину, интересно. Там что-то они продолжают потихонечку подпиливать, как ни странно. Хорошо. Александр тянет руку уже достаточно давно. Александр, твой вопрос.
Александр: Да, привет, Володь, привет, Леш. Я не успел его забыть. Вопрос такой, пока мы далеко не ушли от нотификации в Postgres, три вопроса, суперблиц, три вопроса коротких по нотификациям. А как они работают? То есть он блочится на соединении, пока нотификация не придет, или он пулит постоянно новые события? Это первое. Второе, как это работает с какими-нибудь пуллерами? Ну, в первую очередь, встроенными, например, в Java какой-нибудь Hikari, но и PgBouncer тоже. Ну, если пулит, тогда понятно, вопросов, наверное, нет. И третье, эти нотификации, они широковещательные, или если один клиент подключился, то он все и заберет?
Владимир Перепелица: Начну с конца. Нотификации широковещательные. Вот что касается первых двух, быстро не отвечу. Я достаточно давно смотрел на этот механизм. Я знаю, что он есть. То есть я знаю, что на нем можно сделать подобный подход. То есть я использовал его аналог в Tarantool, но в самом Postgres я не могу сказать, скажем так, каким образом он работает.
Алексей Рыбак: Окей, спасибо. Вот еще один вопрос в чате YouTube. Максим спрашивает: «Является ли фишка Request-Response уникальной в мире NATS, уникальной в мире очередей? Что это такое и является ли это уникальным?».
Владимир Перепелица: Нет, уникальным не является. Они успешно, скажем так, сперли этот подход из RabbitMQ. Тому, как оно реализовано, то есть оно было срисовано с того, как это делает RabbitMQ. Сценарий Request-Response, кстати, он очень удобный, то есть мне кажется, вот такой механизм балансировки нагрузки гораздо лучше, чем наши классические Reverse Proxy, но, тем не менее, его принципы — это просто очередь в одну сторону, из которой разгребают консьюмеры, и с каждым сообщением связана обратная очередь, которую слушают продюсеры.
То есть, во-первых, такое можно реализовать самому на любой очереди, которая предоставляет динамическое создание топиков или каналов, или еще чего-нибудь. То есть, например, я могу на Kafka вам предложить, как такое сделать, если вы будете создавать топики динамически, то есть если у вас это не запрещено. То есть это есть в Rabbit, это можно сделать на чем-то другом.
Алексей Рыбак: Как будто это больше организация каких-то… ну, то есть ты сказал, что это для масштабирования, а я так понял, что это для более удобного связывания продюсера и консьюмера. Все-таки, поясни, пожалуйста, паттерн. В какой момент это надо?
Владимир Перепелица: Паттерн заключается в том, что мы не знаем, то есть, когда у нас задачи неравномерные, предположим, у нас идет входящий поток запросов, при этом мы не знаем вычислительную стоимость каждого запроса.
Алексей Рыбак: Запрос – это при этом что? Постановка в очередь какого-то события?
Владимир Перепелица: Нет, предположим, ты просишь рассчитать тебе, не знаю, математическую функцию, сделать GraphQL-запрос, еще что-то, то есть что-то, чью стоимость ты заранее не знаешь. То есть оно может выполняться быстро, или оно может выполняться долго, потребляя много CPU.
Алексей Рыбак: Прости, я опять прерву, но это фактически сродни ивенту. Такому-то чуваку нужен вот такой расчет.
Владимир Перепелица: Ну, можно так сказать, но лучше на это смотреть в модели именно Request-Response. То есть тебе на Nginx пришел запрос от пользователя прямо сейчас, он тебе нужен сейчас, не потом. То есть это синхронный запрос. В классической модели ты его передаешь на какой-то upstream, то есть ты просто вот его выбираешь, у тебя обратная связь редко присутствует, поэтому ты просто отдаешь его какому-то upstream. И в случае неравномерных запросов, когда у тебя одни дорогие, другие дешевые, возможно попадание нескольких дорогих запросов, а по закону подлости именно так и будет, они попадут в один upstream, этот upstream перегрузит, при том, что другие простаивают.
Это как раз очень легко решается с помощью NATS-овского Request-Response или аналогичного подхода. Получив такой запрос, получив любой запрос, ты кидаешь его в очередь. У тебя консьюмеры на вот этих upstream, эти запросы выгребают. То есть если консьюмер сейчас занят, он не возьмет задачу и значит ее сможет взять кто-то, кто сейчас свободен.
(00:55:28)
То есть у тебя выполняется равномерное использование ресурсов пропорционально вычислительным мощностям. И когда ты эту задачу выполнил, ты обратно отвечаешь тому, кто эту задачу поставил. Поскольку это, скажем так, система связанная через очереди, тебе нужна очередь, куда положить это сообщение обратно. И вот именно так это организовывается. Вместе с каждой задачей идет типа очередь маленькая под одно сообщение. Вот сюда положи результат. Все.
Алексей Рыбак: То есть это некий такой специальный API, который сразу уже за программиста подумал о том, что здесь неплохо реализовать вот такой паттерн. И вот тебе, пожалуйста, вот сюда положи, вот отсюда прочитай, а внутри оно развернулось в очередь. Соответственно, тот, кто затормозил или вдруг помер, он не затормозит общую обработку. Это шикарно.
Владимир Перепелица: Я могу даже рассказать конкретный юзкейс. То есть я этот паттерн применял, когда он еще не был популярен. То есть еще не было такого широкого распространения NATS. То есть мы использовали этот паттерн с помощью Tarantool. Там его тоже можно написать. У тебя с одной стороны интерфейс Request-Response. Ты просто вызываешь функцию и ждешь результат. Внутри функции мы кидаем это в виде задачи в исходящую очередь. У нас есть консьюмеры, которые забирают задачи, обрабатывают, кладут обратно, и к сообщению прибавляется ответ. И делалось это для мобильной игры с дублированным состоянием. То есть у нас есть движок, который способен рассчитывать стейт игры из предыдущего стейта и действий пользователя.
Это позволяет снизить Latency. То есть ты отдаешь этот движок на клиенты. Поэтому клиенты выполняются очень быстро. У них все моментально рассчитывается. И они вот этот стрим событий передают на сервер. Но сервер должен проверить, что клиент правда эти действия выполнил. Чтобы читеры не читерили. Вот эти входящие запросы нужно было рассчитывать. То есть клиент присылает свой запрос. Я нажал вот такую кнопку. Сделал вот это. Это падает в виде задачи. Но мобильные клиенты, у них ненадежная сеть. У них может все порваться. Они могут прийти и сказать еще раз это.
И вот эта задача, она имеет идентификатор. То есть он приходит еще раз, говорит: «Так вот, да, я вот эту задачу сделал». И он ждет ответа. То есть если сервер ее к этому моменту запроцессил быстро. Там несколько сценариев. Он пришел, он не отвалился. Бэкенд отпроцессил. Говорит: «Вот твой ответ». Мы отдали ему в форме ответа. Если он порвался и пришел заново, то он продолжает ждать ответа. Либо он пришел позже, уже после того, как запрос обработан. И ему сразу отдается ответ.
То есть в этом случае сильно минимизируется работа бэкендов. То есть бэкенд отрабатывает один раз на каждую задачу. В этом случае нам не нужно менять никакой пайплайн в случае изменения нагрузки. То есть если нам нужно процессить больше задач, мы просто докидываем железо и подключаем консьюмерам к этим очередям. То есть оно делается максимально просто. И, собственно, у нас юзер-экспириенс со стороны клиента, он может ретраить запрос. И запросы получаются идемпотентные.
Алексей Рыбак: Отлично. Ты знаешь, в свое время у нас очень часто возникала такая задача. И я, правда, не помню какой это был год. Это было достаточно давно. Я думаю, что Redis еще тогда не было. И приходилось вот эту штуку действительно писать руками. И, конечно, здорово, что сейчас есть готовые инструменты и не один, которые умеют такие вещи делать.
Окей, хорошо. Давай еще один. Была большая новость последнего месяца. Выход четвертой Kafka. Я помню, что этот тренд когда-то подсветили ребята, кстати говоря, из Picodata. Ну, ты знаешь, вы вместе работали над Tarantool. По крайней мере, с Костей уж точно. Вот они пришли к кому-то в гости. Интересную мысль сказал не то Костя, не то его коллега, что сейчас наблюдается переписывание кода распределенных решений, распределенных баз данных.
(01:00:12)
Потому что многие алгоритмы стали уже доступны, появились библиотеки.
Владимир Перепелица: Рафтизация. Назовем это рафтизацией.
Алексей Рыбак: Да, и такая идет плавная рафтизация. Вот, значит, у нас вышла Kafka, у которой теперь нет ZooKeeper. Теперь ZooKeeper не нужен. И как будто бы я вот так почитал changelog. Я такой, ну ладно, хорошо, а что еще? Для тебя что в четвертой Kafka самое интересное?
Владимир Перепелица: Окей, смотри. В четвертой Kafka есть Raft, он есть и сейчас. То есть в курсе, там вот в предыдущих курсах…
Алексей Рыбак: По дефолту его включить…
Владимир Перепелица: Да, они говорили, что все, мы отключим этот ZooKeeper, больше не надо его использовать. Мы будем по дефолту использовать Raft. Мы считаем, что мы его отладили достаточно хорошо. При этом в какой-нибудь 3.8 у нас все такой же хороший Raft, и его тоже уже много кто и давно поддерживает. Но тем не менее, это хороший важный шаг. Следующее – это Tiered Storage. То есть Tiered Storage – это, скажем так, важный инструмент для хранилищ. Точнее, для систем, где Kafka используют как неизменяемое хранилище, где старые данные не удаляют, где пытаются репроцессить то, что у тебя сохранялось долгое время. И если раньше тебе для этого приходилось ставить под Kafka очень большие диски, то теперь ты можешь старые данные, которые тебе регулярно не нужны, к которым ты, может быть, вообще не будешь обращаться, выгрузить куда-нибудь еще. Но если тебе они будут нужны, Kafka тебе их достанет, положит, просканирует и так далее.
Алексей Рыбак: Это делается через какую-то процедуру архивации, через определенный API, или это operations-вещь, которая прикладному программисту недоступна?
Владимир Перепелица: Kafka делает это сама. То есть ты просто пишешь в topic, ты настраиваешь для него retention, и Kafka будет это выбрасывать в какой-нибудь…
Алексей Рыбак: Ага, понятно. Она может как бы сгружать вот этот вот… Это на самом деле большая проблема для практически любого проекта, который начинает накапливать, что-то куда-то писать, а потом такой думает, блин, что же с этим делать, как-то бить на сегменты – нет, не получается. В архив...
Владимир Перепелица: Redpanda как раз пыталась на этом сильно продаваться, то есть типа вот, а у Kafka нет тиринга, а вот у нас есть, поэтому покупайте Redpanda. Что они будут делать сейчас? Ну, не знаю, наверное, придумают что-нибудь еще.
Алексей Рыбак: Понятно. То есть тиринг, ты считаешь, это прям… из того, что ты говоришь, как будто даже более важно, чем рафтизация?
Владимир Перепелица: Ну, скажем так, это, да, достаточно важный элемент, нельзя поспорить. И, скажем так, есть новая штука, абсолютно новая штука, это я уже упоминал. То есть раз очереди начали копировать стримы из Kafka, Kafka такая: «Ага, теперь у меня есть очереди». В целом, скажем так, нельзя сказать, что Kafka Queues закрывают все сценарии. Теперь с появлением Kafka Queues нам не нужны классические брокеры. То есть все равно, то есть ты не можешь сделать на Kafka advanced routing, какой-нибудь exchange, экспайрищиеся сообщения, TTL, retry, вот это все, что можно сделать на классическом брокере. Но, тем не менее, они ушли, то есть они создали новый тип консьюмеров, консьюмер группы, в которой задачи можно раскидывать на нескольких консьюмеров. То есть это было вот таким фундаментальным принципиальным ограничением Kafka, то есть одна партиция, один консьюмер. Вот Kafka Queues снимает этот блок.
То есть я предполагаю, что они будут дальше развивать эти подходы, но на текущий момент это довольно важный шаг для развития всей экосистемы.
Алексей Рыбак: Я правильно понимаю, что если раньше нужно было делать разные партиции, фактически это означало, что у тебя идет дублирование данных? По сути, если у тебя есть несколько потребителей, несколько партиций, то ты начинаешь, грубо говоря, дублировать эти логи. Правильно? А сейчас как будто бы…
Владимир Перепелица: Нет. Если у тебя есть несколько консьюмеров, которые должны процессить весь поток, то это разные консьюмер группы. То есть это ты создаешь как есть.
(01:05:13)
У тебя есть весь топик, он делится на партиции. Вот количество партиций и максимальная степень параллелизма. И вот одну партицию, то есть вот этот кусочек топика, в один момент времени может процессить только один консьюмер. И в рамках этой партиции у тебя гарантируется FIFO, то есть строгий порядок.
Алексей Рыбак: Я понял, то есть под партицией понимается какой-то чанк, не копия данных, а какой-то чанк данных?
Владимир Перепелица: В целом это шард, если мы говорим про базы данных. То есть это шарт, который туда попал по признаку, по шард-кею, partition ID. Так вот, раньше partition обрабатывался строго в заданном порядке. Новый тип консьюмеров позволяет обрабатывать данные из партиции в условно-рандомном порядке. То есть три консьюмера приходят и говорят: «Мы будем обрабатывать партицию № 8». Им раздают, берут из этой партиции три задачи и раскидывают всем трем. То есть ordering нарушается. Они процессятся параллельно. До того, как обработают вторую задачу, кто-то возьмет четвертую, потому что он закончил раньше.
Именно этот механизм отслеживания, то есть, что мы из одной партиции раздираем задачи и раздаем нескольким, вот это и есть Kafka Queues, если так грубо. На самом деле, это пока черновик. Они говорят: «Это нет, не для продакшена. Мы только это выпустили, еще, может быть, что-то сломаем, поменяем». Но просто стоит знать, что появилась такая вещь, на нее стоит обратить внимание. Если очень надо, так же, как с Raft. Если очень надо, можно пользоваться и раньше.
Алексей Рыбак: Скажи, пожалуйста, вещь, которую я не понимаю. Надеюсь, с твоей помощью разобраться. Все-таки достаточно частый паттерн, что нужно обрабатывать в определенном порядке какие-то события. И некоторые события мы по каким-то причинам обработать не можем. Мы не можем их обработать и не хотим, чтобы они нам заперли очередь. Вот в Kafka что делают для того, чтобы такую ситуацию разрешить?
Владимир Перепелица: В Kafka очередь обрабатывают. То есть ты не можешь не потребить сообщение и идти дальше. Поэтому ты сообщение взял, ты должен его забрать, условно говоря, себе. Единственный способ пойти дальше, это законфермить это сообщение. Раз ты его конфермишь, это значит, ты его должен куда-то отложить. Поэтому откладываешь ты его в какой-то новый топик, который имеет историческое название Dead Letter Queue. То есть, терминология Dead Letter Queue существовала и в Rabbit, и в других системах. То есть, это в принципе подход, который существовал еще, я так понимаю, в почтовых системах, иначе бы он так не назывался.
То есть, у тебя есть что-то, что ты не можешь обработать сейчас. Ты складываешь это в отдельную стопочку, которую очень часто разгребает человек, потому что система обработать это не смогла. Если это все-таки носит какой-то временный характер, например, нам нужно попробовать потом, то это может быть отдельный. У тебя есть топик, ты из него разгребаешь. Если ты прямо сейчас не можешь, ты кидаешь, например, в Delayed Topic, и у тебя есть консьюмер, который разгребает из него. То есть, он уже будет на этом Delayed Topic стоять. Если он не может отпроцессить, он будет ждать, пока не сможет.
То есть, можно сделать, собственно, Topic, Delayed Topic и Dead Topic. Если совсем не смогли с 15-й попытки, то ладно, уберем его совсем. Задачка мертвая. В общем, организация возможна.
Алексей Рыбак: Подожди, я просто пытаюсь вспомнить на своей практике, в какой момент возникали такие ситуации. Такие ситуации возникают, когда есть сложно распределенная система, там есть некоторые кусочки, которые не должны лежать совсем, и поэтому с надежностью достаточно высокой делается High-Availability Solution. То есть, грубо говоря, в случае, если какая-то база отключается, она достаточно быстро переключается и так далее. Поэтому, по сути, если что-то нужно обработать на этом реплика-сете, он всегда должен быть живой. А бывают ситуации, когда ты, в принципе, такой: ну и ничего, откажет тут какая-то часть, полежит какое-то время, не страшно, но мы сэкономим. Иногда, кстати говоря, очень много денег можно сэкономить, вот как-то комбинируя эти подходы.
И я вспоминаю, что было достаточно много ситуаций, когда были какие-то кусочки, которые вот сейчас на каком-то maintenance, они не нужны для какой-то real-time какой-то обработки. Юзеры не очень страдают. Условно говоря, у юзера какая-то часть функции недоступна, но вероятность того, что именно в этот момент этот юзер ей воспользуется, она не очень большая. Вот когда возникают такого рода проблемы, именно из-за этого и запираются очереди. Тогда я понимаю, что единственный способ – сделать вот этот промежуточный delay, и delay сделать таким, чтобы он делился по причине delay. Грубо говоря, если у меня есть такой тип отказа, я его отложу сюда. Если другой тип отказа, или отказ такой подсистемы, я его положу в другое место. Почему? Потому что иначе, если это все какие-то отказы, то вероятность того, что у меня через какое-то время все эти отказы ушли, и вся эта очередь будет обработана, она равна нулю. Если я их разделю по причинам, то есть большая вероятность, что когда эта система восстановится, то тот отдельный “заделейенный” поток, который “заделеился” именно из-за этого отказа, достаточно быстро будет обработан. Ну как будто бы это очень сложно писать. Нет у тебя такого впечатления?
Владимир Перепелица: Скажем так, если мы говорим про Kafka и говорим про такой сценарий, то да, его довольно сложно. Тебе это нужно все мейнтейнить руками. В случае условного Rabbit, кажется, это было бы сделать проще. Там есть различные exchange, Dead Letter Queue. Они более автоматические. С Dead Letter Queue можно забирать в другой exchange, перекладывать это в другие очереди. То есть со стороны консьюмера все выглядит просто. Ты настраиваешь это все внутри очереди. То есть если ты задачу взял, если ты ее вернул, можешь посмотреть на счетчик, сколько раз она возвращалась. Если не получается, переложить в другую, а там свой консьюмер.
То есть тут разница между Rabbit и Kafka будет: в случае Rabbit ты это все централизованно настраиваешь. Ты приходишь и настраиваешь это все внутри exchange, внутри Rabbit. В случае Kafka ты это отдашь все на клиентов. То есть клиенты будут брать задачи, класть в новые топики. Причем у этого паттерна есть очень противный минус с точки зрения экосистемы в принципе модели продюсер-консьюмер. Когда у тебя есть отдельно продюсер, он пишет. У него другие гарантии на availability. То есть для него важно записать хоть куда-нибудь. Поэтому если тебе нужно повышать доступность такой системы, ты просто можешь поставить два кластера с разными зонами.
А если мы говорим про консьюмер, у него требования к доступности брокера прямо сейчас ниже. То есть ему просто сказали: «Ты читаешь из этого брокера», он из него читает. Вот если у тебя консьюмер выполняет одновременно роль и консьюмера и продюсера, у тебя начинают смешиваться паттерны. То есть ты берешь задачу, ты ее процессишь, не получилось, ты превращаясь в продюсера и начинаешь ее продюсить.
Иногда у тебя бывают задачи, подразумевающие например какое-нибудь обновление, миграцию, еще что-то. В этом случае у тебя переключение делается по очереди. Сначала мы переключаем продюсеров, потом мы переключаем консьюмеров. Или мы сначала переключаем консьюмеров, потом переключаем продюсеров. Когда у тебя роль смешанная и она еще и через один и тот же connect, тебе сложно таким инструментом управлять.
Алексей Рыбак: Да, как-то мне пока не очень это все нравится, но, наверное, нужно просто поработать и посмотреть. Скажи, пожалуйста, если я хочу реализовать отложенную обработку, давай все остальное, а вот это сейчас не будем брать. Где удобнее всего с точки зрения прикладного программиста это делать?
Владимир Перепелица: На Tarantool (смеется). В целом, скажем так, хорошая коробочная, что называется, отложенная обработка поддерживается в Rabbit. Это классический брокер, у него есть под это прям параметры.
(01:15:08)
Ты передаешь задачу, ты говоришь, насколько ее отложить. Она станет доступна через этот интервал времени. Если у тебя задачи идут с постоянным delay, то в общем-то можно сделать на Kafka, приложив таймстемп, и у тебя консьюмер просто должен ждать до таймстемпа. Но это топик, сдвинутый во времени, по сути. Если же у тебя идут задачи с рандомным тайм-аутом, то на Kafka это очень сложно реализовывать. Прям запредельно сложно.
Алексей Рыбак: Ну хорошо, а на NATS я могу реализовать такую логику? Когда я беру задачу, я начинаю ее обрабатывать, где-то там в кишках какая-то подсистема недоступна. Я поэтому пытаюсь ее заретраить, через какое-то время я хочу это отложить. Причем, как часто это бывает, я откладываю сначала на одно время, потом х2, потом еще х2, то есть х4 и так далее. И потом, начиная с какого-то счетчика, будь то один, будь то четыре, я уже говорю – все. Но я четыре раза попытался. И вот я так понимаю, что на Rabbit все сделать можно. На Kafka очень сложно. На NATS, на остальных как?
Владимир Перепелица: У NATS есть инструменты для управления временем в том или ином виде. Когда мне нужен был сложный процессинг, связанный со временем, я брал Tarantool. Мы на Tarantool делали что-то очень сложное с отложенными задачами, с отложенными зависимыми задачами. Когда у тебя одна задача зависит от другой, а ту нужно через интервал. У нас это была система, связанная с биллингом и подписками. Когда ты их обновляешь, продлеваешь, одна зависит от другой. Реализовать это на внешней системе было довольно сложно.
Насколько мне известно, в NATS есть штуки, связанные со временем. Можно запланировать задачу, запаблишить ее через интервал времени. Так что, в какой-то мере, можно сказать, что delay там есть.
Алексей Рыбак: Хорошо, спасибо. И, наверное, последний вопрос, который был у меня. У нас много идет в других курсах обсуждений различных проектов, различных схем системного дизайна. Давайте спроектируем вот такую систему, такую. Я обратил внимание на два тренда. Один тренд, против него не попрешь. Практически все системные дизайны выглядят очень похоже. Если нужно нарисовать очередь, почти всегда рисуется какая-то “труба”, на которой написано Kafka. И с учетом того, что эту тему поднимали еще не так давно с Филом Дельгядо, что, в принципе, все эти схемы, они так или иначе, если их расписать, они все похожи друг на друга, и, по сути, это все такая трата времени – рисовать похожие вещи. Поэтому там у нас была дискуссия про то, как можно дифференцировать архитектора, архитекторские навыки на системном дизайне, и одна из проблем была в том, что рисуются похожие вещи, всё одно и то же.
Но, ладно, это некая такая объективная вещь, Kafka действительно очень популярна, поэтому она есть. А вот следующая вещь, которая стала появляться не так давно, если честно, но она тоже является для меня трендом, что для определенного класса приложений, я бы сказал, что я вижу таких класса два, но, может быть, ты что-то добавишь, Kafka становится вообще, если не Primary Storage, то я бы сказал так, First Storage. То есть в том смысле, что есть большое количество клиентов, которым гарантируют, что данные приняты, и тот, кто это “прогарантировал” – это Kafka. И после этого уже отправляется клиенту OK.
Потому что обычно немножко происходит иначе. Ты сначала приходишь в сервис, этот сервис сохраняет в базу. Сервис может бросить какой-то ивент, ему там уже все равно, что там и как, если надо, он там каким-то образом через Transactional Outbox уже свою вещь реализует. Но когда его база, база этого сервиса сказала OK, он клиенту отвечает OK. А тут наоборот - первична Kafka.
И класса я вижу два. Первый класс — это набор разного рода сбора метрик и статистических вещей, где кто находится, Internet of Things, вот всякие такие штуки.
(01:20:06)
Координаты, координаты человека, координаты автобуса и так далее. Многие люди говорят – ребята, вот у нас есть сервис, он сразу пишет в Kafka, Kafka как база данных, только лучше, как раз под это подходит. А там дальше все там, консьюмеры что-то разбирают и так далее. Вот такая рисуется архитектура. Немножко новая в том смысле, что обычно рисуется сначала где-то база, тот кто говорит первый ok – это обычно база данных, потому что мы привыкли, что то, что сохраняется, надёжно. Это первое.
Второе, есть ряд товарищей, кто очень любит Event Sourcing, и тоже строят все вокруг какого-то сначала надежного сохранения именно потока данных в Кафке.
Как ты думаешь, это похоже на правду, про что-то я забыл, насколько вообще ты считаешь это целесообразным, правильным вот так делать, чтобы Kafka являлась первым storage, который уже клиенту если сказал OK, то типа точно OK?
Владимир Перепелица: Смотри, ты говорил довольно долго, я уже накопил некоторый буфер. По поводу систем дизайна, рисуется труба и пишется Kafka, рисуется база данных и пишется Postgres, то есть это просто какой-то хороший популярный стандарт на текущий момент, то есть при том, что есть другие решения, они могут лучше подходить, но опять же, как в той истории, какой атрибутив Linux взять, у нас есть еще, назовем это стоимость сопровождения, стоимость поиска специалиста по данной технологии, и найти специалиста по Postgres и Kafka, то есть практически любой инженер с улицы, так или иначе поработавший в индустрии, сталкивался с Postgres и Kafka, а с Tarantool и NATS, он мог не сталкиваться, и вместо того, чтобы решать твою бизнес-задачу, он пойдет сначала разбираться с инструментом, который, может быть, и сильный, который, может быть, может больше, но не является таким прям индустриальным стандартом.
Так вот, возвращаясь к, собственно, Kafka и ее надежности. Учитывая, что под Kafka у нас лежат принципы, по сути, рафтовые, то есть мы коммитим на брокера, брокер дожидается синхронизации от реплик, и только после этого он отвечает клиенту, что он принял, здесь гарантия, как у синхронной репликации, при том, что в Postgres делать синхронную репликацию очень неудобно, она не динамическая, то есть тебе этим нужно управлять, поэтому к нему нужен еще Patroni какой-нибудь, в общем, зачастую у Kafka гарантия сохранности будет выше, чем у большинства сетапов баз данных. Так сложилось, то есть мы базы данных обычно пишем, как попало и реплицируем асинхронно. Если ты пройдешься по инсталляциям, где стоят стандартные базы данных, где народ этим не заморачивался, ну да, вот у нас Postgres, вот у него реплика, реплика какая? Ну, асинхронная, а то с синхронной медленно писать, а еще, не дай бог, она ляжет. Поэтому по умолчанию Kafka имеет лучшие характеристики по надежности и лучшие характеристики по отказоустойчивости, потому что из трех брокеров можно один потерять и все продолжит работать.
При этом, если пойти дальше, когда она подходит как такой основной storage? Она подходит тогда, где сами данные, где вот их природа, ну да, я не могу не говорить про природу данных, это поток событий, то есть это действительно какие-то метрики, это какой-то event sourcing. Кажется, что туда же должны попадать еще и логи, то есть они имеют такую же природу, они происходят от упорядоченного времени, но для систем логов она зачастую является транспортом, потому что логи нам важны не в виде потока, логи нам нужны индексированные. Поэтому мы их надежно принимаем, то есть Kafka хороший транспорт для того, чтобы эти логи из разных точек собрать, свести вместе, а дальше мы их сложим в систему хранения.
Собственно, с метриками система приблизительно такая же, то есть мы собираем метрики из разных источников с помощью Kafka и с помощью перепроигрывания, то есть это лучше подходит, потому что если нам нужно восстановить какие-то показатели метрик на определенный момент времени, мы можем для этого пользоваться event sourcing.
(01:25:00)
Обычные решения могут для этого хуже подходить, хотя в общем, если у нас time series база, то это мы уже приняли событие, разложили, и вот тебе возможность посмотреть в прошлое. Так что для метрик это тоже может быть зачастую только транспорт, а если у тебя, скажем так, сами данные, например, какие-нибудь транзакции, происходящие во времени, сделки, еще что-то и, в общем-то, скажем так, они все сами по себе ценные, то есть важен не только последний стейт, но и все, что происходило. То есть ты хочешь иметь историю, ты хочешь иметь возможность восстанавливать стейт на любой момент в прошлом. Вот тогда Kafka как primary storage – это прям, наверное, must, потому что с другими системами ты просто… то есть если мы, скажем, возьмем обычную базу данных, ты внутри базы данных начнешь делать себе инструмент история транзакций, которая, по сути, будет тоже писаться линейно на диски… лучше бы ты сразу сохранил это в виде вала, а из него реконструировал текущий стейт, при том, что это будет, скажем так, еще и лучше работать.
Один момент, если мы говорим про event sourcing, нам нужен Tiered Storage, потому что удалять старые данные вроде бы не очень хорошо. То есть если мы говорим, что все данные важны за всю историю. Ну вот, какое-то такое мое мнение.
Алексей Рыбак: Главное, что ты фактически подтвердил, что такие архитектуры все более и более популярны, что возможности Kafka достаточно высокие, надежность у Kafka достаточно высокая. Я бы, конечно, стал сравнивать в этом смысле надежность не с традиционными базами, а с какими-то современными кластерными СУБД, потому что они изначально пишутся похожим образом, так же, как и сделана Kafka. Но, с другой стороны, конечно, то количество человеко-часов, которое вложено в классические базы данных, оно очень часто не дает нам даже чисто психологически взять и сказать: «Ой, я доверю сейчас каким-то неклассическим, новым, модным базам данных свой primary storage». Нет, уж лучше я буду писать...
Владимир Перепелица: В Redis. Да, есть люди, которые вроде бы переживают за данные, а потом берут и пишут их в Redis.
Алексей Рыбак: Ладно, слушай, я на самом деле все свои вопросы задал, но не задал самый главный, вернее, даже не сделал правильный вовремя анонс, но исправляюсь. У тебя через неделю очередной стартует курс. И этот курс – это как раз интенсив по очередям, где ты взял отдельно Kafka, отдельно NATS, собрал разные сценарии, включающие не просто то, что принято говорить happy path, но еще сценарии развала, и всячески шатаешь эти инсталляции, показываешь, где что получается надежно, а где не очень.
Мог бы в двух словах рассказать про этот курс и, может быть, какие-то подробности про эти сценарии, которые могли быть интересны потенциальным слушателям, потому что у нас ещё есть несколько мест, на которые, как я надеюсь, часть ребят, послушав этот стрим, все-таки придет.
Владимир Перепелица: Ну, в целом, да, исторически так сложилось, то есть мы анализировали спрос на те или иные технологии, то есть первоначальная идея про очереди, про курс была «давайте включим все, что только существует», но опять же, скажем так, победителями остались именно Kafka как самый популярный на сегодня инструмент для реализации передачи сообщений, даже там, где она не очень хорошо подходит, ее все равно довольно часто используют. И NATS как такая хорошая, легковесная и удобная эволюция Kafka, вот я бы сказал так. То есть человеку, который работает с Kafka, стоит знать про NATS, то есть, да, вообще общее количество систем довольно большое, но именно по критериям, то есть Kafka – это must, NATS – это стоит обратить внимание.
По поводу рассматриваемых сценариев, если говорить про Kafka, то есть мы поговорим про важные параметры, при том, что упоминается Kafka хорошая, Kafka надежная, с ней можно сделать то, пятое, десятое, она не будет терять данные, но если вы по умолчанию просто с дефолтами поставите Kafka, вот прям ничего не настраивая, возьмете дефолтных клиентов, продюсеров, консьюмеров, вероятность того, что у вас будет потеря сообщений, она, в общем-то, не нулевая.
(01:30:22)
То есть где-то будут не те дефолты, где-то вы выберете не те параметры, где-то вы не совсем корректно напишите прикладной код, и поэтому мы разбираем, то есть основной упор в моем курсе на durability данных, то есть как передавать сообщения с потерями, мы знаем, то есть можно взять Redis и в него кидать, ну потеряется часть и неважно, то есть в общем, если вы можете терять сообщения, передавайте их как угодно. Мы будем говорить про то, как передавать сообщения надежно. И основной упор в сценариях будет именно на этом, то есть где сообщения можно потерять, как сделать так, чтобы они не терялись, само начало будет про то, какие вообще у нас есть гарантии, свойства, возможности. И в рамках этого всего один из таких самых популярных сценариев, то есть у нас есть очередь – хорошо, очередь хорошо принимает сообщения и передает, но нам часто очередь нужно стыковать с чем-то, то есть у нас данные лежат в базе, а как их в очередь положить?
Вот два популярных паттерна, Transactional Inbox и Transactional outbox, то есть разбираем на примере использования этого в связке, то есть вот база данных, вот Kafka, вот другая база данных, строим пайплайн передачи, ну и опять же, так, чтобы не терять, не дублировать и так далее, то есть в общем основной упор будет на этом.
Алексей Рыбак: Да, спасибо большое. Еще достаточно частый вопрос, следующий, люди приходят из разных систем, кто-то пишет на Python, кто-то на Java, кто-то пишет на GO, кто-то пишет на C-sharp, кто-то пишет на Rust, кто-то пишет на php, на JavaScript даже пишут тоже. Популярных языков, каждый раз я делаю опрос, их 10, как будто бы у некоторых людей возникает сомнение, что я из этой экосистемы, а я что смогу поделать с той инфраструктурой, которую вы даете, с теми примерами, которые вы даете, играет ли вообще это значение?
Владимир Перепелица: Я бы сказал так, если вы на сегодняшний день уже научились пользоваться одним из AI-шных инструментов, то есть если у вас есть возможность взять ChatGPT, DeepSeek, Claude, Cursor, что угодно, они прекрасно умеют переводить код, условно говоря, с Python на Golang, с Golang на JavaScript, с JavaScript на php, то есть максимум, с чем вам придется столкнуться, это подыскать подходящую библиотеку под свой язык, но я верю, что если вы занимаетесь программированием на каком-то языке, вы умеете искать библиотеки под свой язык.
Дальше вы говорите: «Смотри, ChatGPT, возьми вот этот код, перепиши его на вот этот, с использованием вот этой клиентской библиотеки», все. То есть, в общем, этого будет более чем достаточно. Примеры, в общем-то, довольно простые, то есть они скорее объясняют какие-то концепции, нежели делают какой-то большой сложный промышленный код. Поэтому я не думаю, что будет проблема в использовании какого-то другого языка. То есть из перечисленных тобой я сходу могу привести пример на любой из языков, в общем-то, так или иначе я сталкивался с ними. Единственное, я сходу не знаю, как запустить C# на Linux, но вроде бы там есть фреймворк Mono, это, наверное, то, на что я потрачу времени больше стандартного, но те, кто пишут на C-Sharp, я думаю, с этим справятся.
Алексей Рыбак: Там даже, знаешь, если вдруг это понадобится, скажи мне, я недавно разбирался, там даже Mono нигде, по-моему, не звучит, оно все звучит как dotnet. И действительно всё сделано достаточно удобно.
Владимир Перепелица: Более того, то есть в рамках курса, то есть в рамках того, что мы разбираем Kafka, как она устроена, Kafka – это Java, то есть Kafka вся написана на Java. Ее дефолтные клиенты написаны на Java, и мы рассматриваем такой инструмент, как Kafka Streams. Несмотря на то, что его популярность немножко проседает, то есть появились инструменты внешние по отношению Kafka, которые лучше, круче и так далее.
(01:35:04)
Kafka Streams как самостоятельный инструмент и как функциональная возможность все еще довольно важна, то есть вот для того, чтобы хорошо работать с Kafka, нужно понимать все, что в ней есть. И, собственно, Kafka Streams пишется на Java, и, собственно, наши примеры будут на Java, то есть это код на Java, подготовленный для не-джавистов. Джавистам будет, конечно, вообще кайф и просто, но я делал эти сценарии для тех, кто не знаком с экосистемой Java, кто не собирается в этом разбираться, ставить IDE и так далее. Просто я хочу вот здесь взять, туда положить, здесь трансформировать и не хочу учить Java, что мне написать. Про Kafka Streams будет подача приблизительно такая. Скопировал, вставил, перевел.
Алексей Рыбак: Спасибо. Супер. Саш, твой вопрос, пожалуйста, давай.
Александр: У меня, собственно, не вопрос, я хотел дополнить, как участник, как человек, который прошел этот курс, и я пришел на курс, имея уже опыт работы с Kafka, правда, со стороны, соответственно, разработчика, но, тем не менее, там понаступал на разные грабли, то есть не просто так там сообщение отправить. И могу сказать следующее, курс очень интересный, материал реально классный, подача материала отличная, понятная, и уронить какую-нибудь ноду, вот это вот все на инфраструктуре тоже здорово. Спасибо большое.
Алексей Рыбак: Супер, спасибо большое. Скажу заранее, мы не договаривались, Саша не подсадной человек, сам по своей инициативе сказал такое.
Владимир Перепелица: Мне понравилась фраза: «Нельзя просто так взять и отправить сообщение».
Алексей Рыбак: Окей, слушай, возник еще один вопрос, но, мне кажется, на него мы уже дали ответ. Пришел с некоторым опозданием Алекс в YouTube-трансляцию, спрашивает: «Извините, если уже обсуждали, принципиально в контексте системного дизайна выбор между NATS и Kafka, первый больше про мессенджинг, второй больше про ивенты?» Ну, я бы сказал: «Да, но», потому что вот это большое «но» про всё то, что в NATS было втащено… но давай ты.
Владимир Перепелица: В рамках системного дизайна можно просто нарисовать трубу и написать на ней что-нибудь, потому что это все про рисование исключительно. NATS можно использовать как стриминговую платформу вместо Kafka, или NATS можно использовать для доставки сообщений без персистенса. Например, Kafka для доставки сообщений без персистенса нельзя. То есть, NATS можно использовать как request-балансер со сценарием Request-Response, тоже Kafka так не используешь. То есть, у NATS для систем-дизайна, кажется, гораздо больше поля. То есть, так же, как у меня есть знакомый, который много лет работал с Tarantool, но не работал с другими базами данных. Когда он пришел на собеседование, он все написал на Tarantool, потому что мог. «А это на чем сделаете?», «На Tarantool». «А это на чем?», «Ну, и это на Tarantool». «А вот тут, что там, http-сервер?», «Да и это на Tarantool, я его знаю». Поэтому, с точки зрения инструмента, то есть для решения задач систем-дизайна, NATS подходит лучше. Но с точки зрения промышленной применимости, возможно, лучше показать, что вы делаете взвешенный выбор. Вы берете Kafka, потому что под нее больше инструментов, а еще есть дата-аналитики, которые умеют в Kafka. И они потом будут оттуда данные забирать в свои системы для какого-нибудь BI и так далее.
Алексей Рыбак: Спасибо за развернутый ответ. Вопросы кончились. Спасибо всем, кто пришел. Спасибо тебе, Володь. Удачи тебе с курсом. И что же тогда, друзья, на этом мы заканчиваем. Еще раз всем спасибо. До новых встреч. Надеемся, что в какой-то момент с Володей еще раз обсудим через полгода новости или, может быть, через год. Все. Спасибо еще раз. Всем пока.
Владимир Перепелица: Всем спасибо. Увидимся на курсе.
(01:39:22) (Конец записи)
Если вы дочитали до конца и считаете, что мы что-то забыли, пишите в комментариях.
Расшифровкой поделился Алексей Рыбак — автор телеграм-канала про System Design и Highload, основатель DevHands.io — проекта по продвинутому обучению программистов и со-основатель teamwork360.io — сервиса для автоматизации HR-процессов и оценки сотрудников. Наши ближайшие запуски: PostgreSQL 17: архитектура и тюнинг SQL, Highload-буткемп, Системный дизайн.