Kafka - действительно крутая и мощная платформа, и, поскольку у нас в Wirecard Brazil много замечательных инженеров-программистов, я хочу поговорить о том, как мы здесь используем Kafka, о проблемах, которые мы стремимся решить, и о проблемах, которые возникли во время разработки.

В этом посте вы не найдете никакого кода, я не собираюсь показывать шаг за шагом, как его реализовать. Вы найдете реальный пример Kafka, показывающий: почему мы решили его реализовать, как мы это сделали, с какими проблемами мы столкнулись при этом, с чем мы сталкиваемся сейчас и так далее!

Без лишних слов, приступим!

Эта проблема:

У нас возникли проблемы при синхронизации данных с эластичным поиском, и исправить это было нашей основной целью. Раньше у нас был единый эластичный поисковый кластер для всех наших данных из всех API, хотя это действительно плохо, это было очень удобно вначале.

У нас есть приложение под названием reports-api, которое преобразует URL-запрос в параметры эластичного поиска и выполняет поиск документов в этом едином кластере эластичного поиска. В основном, учитывая следующий URL:

https: //sandbox.moip.com.br/v2/orders?q=caue.ferreira
& filters = status :: in (PAID, WAITING) | paymentMethod :: in (CREDIT_CARD, BOLETO) | value :: bt (5000,10000) & limit = 3 & offset = 0

reports-api будет преобразован в следующие параметры поиска:

  • статус должен быть ПЛАТИТ или ОЖИДАНИЕ
  • paymentMethod должен иметь значение CREDIT_CARD или BOLETO.
  • значение должно быть от 5000 до 10000.

Хотя reports-api будет выполнять поиск, ему по-прежнему нужны эти данные. reports-sync - это приложение, которое отвечало за получение всех данных из всех хранилищ данных приложения и вставку их в эластичный поиск. Давайте посмотрим на проблемы.

reports-sync извлекал данные из каждой базы данных каждого приложения API, например: у нас есть приложение под названием invoice-api. reports-sync необходимо получить эти данные в базе данных invoice-api с заранее определенным запросом, и они вставят их в эластичный поиск, кроме того, ему также необходимо преобразовать все необработанные данные, полученные из базы данных в бизнес-данные, также известные как документ json. У нас также была другая проблема: мы извлекали данные из базы данных по частям, что в конечном итоге приводило к отсутствию данных.

Теперь давайте разберемся с этими проблемами. Для начала отчеты - синхронизация знала некоторые бизнес-правила, которых не следовало делать, она знала, как преобразовать необработанные данные в бизнес-данные означало, что он получал данные из базы данных и преобразовывал их в json, не только знал, чего не следует, у нас также была проблема при изменении json; необходимо было изменить как invoice-api, так и reports-sync, поскольку им обоим нужно было знать, как предоставлять один и тот же ресурс json. Эта природа «швейцарского армейского ножа» сделала reports api приложением без реального владельца. Его управление было беспорядочным, ни одна команда не отвечала за его работу, и не было четко определенной области.

Увы, еще одна проблема, связанная с счетом, заключается в том, насколько он зависит от другой службы, заказа. Я не буду вдаваться в подробности, но в основном order содержит всю платежную информацию, и поэтому, когда мы хотим получить счет, нам может потребоваться также получить платеж, который включает зависимость от другого заявление.

Наконец, существует проблема того, как мы сохраняем данные в эластичном поиске. Тот факт, что reports-sync получал данные через выборку, вызывал несоответствия. У нас было действительно проблемное решение для синхронизации этих данных, наш запрос должен был идентифицировать смещение текущей синхронизации, учитывая это, когда у нас были какие-либо проблемы в эластичном поисковом кластере, который мы использовали для вычисления времени вручную. определение момента, когда мы должны начать повторную синхронизацию. Снова и снова нам не хватало некоторых обновлений статуса, а иногда и целого ресурса. Нельзя сказать, что если бы у нас был простой в reports-api или reports-sync, , это повлияло бы на все API одновременно. !

В любом случае, хотя вначале это работало довольно хорошо, поскольку у нас появляется все больше и больше сервисов, которые производят все больше и больше данных, которые необходимо предоставлять в рамках эластичного поиска, мы начали задаваться вопросом, был ли это лучший подход.

Первое решение до того, как мы узнали Кафку:

Еще до того, как мы подумали о Kafka, мы решили, что наше решение не в лучшем виде, поэтому начали работу над новыми архитектурами. Первое, что мы сделали с invoice-api, - это удалили его из среды reports.

Мы создали отдельный эластичный поисковый кластер, и теперь invoice-api отвечает за предоставление данных для эластичного поиска, а также invoice-api содержит правила для преобразования любого параметра запроса и извлечения данных из эластичного поиска. При создании любого invoice, поскольку invoice-api уже получил ответ json на метод POST, мы также вставляли его в эластичный поиск ; нам не нужно было копировать логику, преобразующую необработанные данные в бизнес-данные. У нас также был метод PUT, это была внутренняя конечная точка, которую приложение invoice-sync после прослушивания определенной очереди RabbitMQ обновляло документ счета с соответствующим статусом, и они вставляли данные в также эластичный поиск.

Речь шла о том, чтобы изолировать право собственности на счет, теперь у нас есть четко определенная область действия и домен. Команда, ответственная за счет, обладала всеми знаниями, необходимыми для управления всем процессом.

Ресурс invoice больше не зависел от reports-api, поэтому, если он не работает, мы не затрагиваем ресурс invoice; Кроме того, мы нигде не реплицируем логику создания счета, и у нас был другой кластер для его эластичного поиска, поэтому, если бы мы хотели обновить версию эластичного поиска, мы не повлияли бы на другие ресурсы!

Опять же, хотя это сработало, мы все еще не очень довольны этим решением. На invoice-api теперь было слишком много ответственности, он отвечал за создание счетов, вставку их в эластичный поиск и поиск по ним; мы могли бы также добавить к нему ответственность invoice-sync!

Решение Kafka:

Мы открыли Кафку! Мы были очень взволнованы его реализацией, идея была проста: мы добавили бы Kafka connect для получения данных из базы данных, а затем вставили бы эти данные в эластичный поиск! Просто как тот!

Что ж, если бы это было так просто, как кажется ... Я хотел бы добавить раскрытие:

Заставить Кафку работать было действительно непросто. Это было действительно захватывающе, но ни в коем случае не было легким. Это было жестко, заняло много времени, и не все понимают каждый шаг, даже сегодня.

У нас есть команда по инфраструктуре, и вначале они ей управляли, и, как и большинство людей два года назад, у нас не было опыта работы с ней.

Я не собираюсь слишком углубляться в конфигурацию kafka; но имейте в виду, что у нас была некоторая настройка и что в каждой из наших сред есть 3 Kafka Brokers и 3 Z ookeepers.

Мы начали с одного kafka connect для управления всеми соединителями во всех приложениях. Нам не потребовалось много времени, чтобы понять, что мы вернулись к проблеме reports-api, мы должны создать отдельные экземпляры kafka connect, по одному для каждого приложения. Итак, мы сделали это, каждое приложение имело свой kafka connect для каждой среды в отдельном контейнере.

Первой нашей идеей было реализовать паттерн CDC, извлекающий данные из базы данных. Для этого нам нужно было создать соединение, и мы решили использовать debezium в качестве нашего исходного соединения или для получения всех данных из базы данных. binlog и вставьте их в тему kafka connect. Лучшей таблицей, которую мы могли прослушивать на предмет вставок и обновлений, была таблица, в которой хранится история состояния счетов, поэтому в любое время, когда у нас было какое-либо обновление и / или вставка в эту таблицу, мы отправляли данные в исходный соединитель. Хотя наше приложение никогда не будет обновлять эту таблицу, имейте это в виду, она вернется позже.

invoice-sync также претерпел несколько изменений, теперь у него есть потоковый клиент Kafka. исходный соединитель будет генерировать события, эти события будут преобразованы в ресурсы json с помощью invoice-sync, а затем созданы в приемном соединителе. Давайте разберем это:

Чтобы преобразовать необработанные данные в ресурс json, у нас возникла проблема; помните, когда я сказал, что счет зависит от заказа? Что ж, чтобы создать ресурс json, запросы invoice-sync могут по-прежнему запрашивать данные из order-api, получающего платежную информацию для создания ресурса счетов.

Имея в руках ресурс json, мы отправляем его в тему в другом kafka connect; соединитель раковины. Напоминаем, что прежде чем мы отправим ресурс json в это соединение, у нас есть реестр схем со схемами avro, чтобы гарантировать, что он соответствует ожидаемому ресурсу.

Вот и все! Как только мы отправим данные в коннектор приемника, он предоставит их эластичному поиску, и все готово!

Вскрытие

Теперь, когда я объяснил, как мы реализовали это решение и почему, давайте разберемся, что сейчас происходит.

Во-первых, теперь у нас действительно стабильная и надежная среда, около двадцати проектов уже используют потоки Kafka в Wirecard Brazil, не все из них используют его для создания документов для эластичного поиска, на самом деле большая часть то не являются проектами, целью которых является синхронизация данных с эластичным поиском. У нас есть проекты, которые отслеживают потоки kafka, и некоторые приложения, которые прослушивают определенные потоки для запуска определенных действий, но это уже история для другого раза.

Возвращаясь к архитектуре kafka, которую мы обсуждали здесь, все приложения, которые начали ее использовать, стали значительно более стабильными, мы значительно сократили количество билетов, открываемых из-за отсутствующих или устаревших ресурсов. В invoice api была утечка памяти, которая время от времени приводила к завершению работы приложения, когда мы удалили большую часть дорогостоящих вызовов, у приложения почти не было никаких проблем, позже мы сосредоточимся на том, как исправить эту проблему .

Хотя это правда, что invoice api использует kafka исключительно для создания документов для своего эластичного поиска, мы уже создаем события invoices , поэтому любое приложение, которое хочет работать с этими ресурсами, может получить их, не запрашивая данные из invoice api, не перегружая его.

Конечно, не все - цветы, и во время реализации этой архитектуры у нас тоже было несколько проблем, и я хотел бы поделиться ими.

Обратите внимание на переменные Kafka connect

Это легко испортить, переменные соединения важны, но если вы что-то забудете или добавите другое значение, вы можете в конечном итоге, что все не работает. Вначале у нас были некоторые проблемы с этим, поэтому не торопитесь и убедитесь, что он соответствует вашим потребностям. Сначала мы добавляли тысячу разделов в некоторые темы Kafka.

Загрузка ретроактивных данных

Одна из проблем, с которыми мы столкнулись во время его разработки, заключалась в том, чтобы получить все ретроактивные данные. Хотя все новые счета добавлялись безупречно, нам нужно было получить старые данные.

Сначала мы создали сценарий для имитации вставки бинлога, он выбирал фрагменты данных из базы данных и подделывал вставку; в конечном итоге это сработало, однако процесс оказался чрезвычайно медленным. Загрузка всех данных в binlog и в тему исходного соединителя может занять несколько часов. Проблема, с которой мы столкнулись, также связана с параллелизмом: сценарий binlog faker мог получить старые данные из счет, который только что обновлялся, он заменит фактический статус старым статусом. Учитывая обе проблемы, мы отказались от нее и начали думать о новом решении.

С проблеском магии мы подумали о лучшем решении; чисто, быстро, просто. Помните, я говорил об обновлении операции в определенном существе таблицы? Мы понимаем, что в этой таблице есть столбец updated_at. Мы его просто обновили. Сначала мы обновили все строки, уменьшив их на 1 секунду, затем добавили вторую обратно. Что он сделал, так это то, что debezium немедленно получил все эти данные и отправил их в тему подключения к базе данных за считанные секунды.

Avro и плохо разобранный ресурс

Об этом нечего сказать, если у вас плохо проанализированный объект, вы получите исключение, и kafka connect прекратит синхронизацию. У нас была эта проблема несколько раз на ранней стадии разработки, но вначале, поскольку мы еще не использовали новый эластичный поисковый индекс, мы могли просто удалить все из коннектора kafka, исправить то, что мы сделали неправильно и снова отправим все данные.

У нас также несколько раз возникала эта проблема в производстве, и вот здесь есть несколько проблем. По какой-то причине у нас были неправильные данные, вставленные в базу данных, и тогда полученные данные были бы неправильными, даже если мы исправим базу данных, данные уже были отправлены debezium. Есть несколько способов решить эту проблему, мы решили ее, удалив все данные из подключения к базе данных, а затем снова отправив последние 7 дней. Обратите внимание, что мы решили использовать этот подход только потому, что у нас было время хранения 7 дней, и мы находимся в сценарии, когда мы точно знали результат.

Avro важен, поскольку вы можете получать данные из нескольких проектов на разных языках, он гарантирует, что вы будете создавать события только с желаемой структурой.

Подключение Kafka для каждого приложения

Изначально мы использовали одно соединение kafka для каждой среды, но это оказалось действительно проблематичным. Я бы хотел, чтобы у вас был Kafka для каждого приложения в каждой среде, чтобы у вас не было нескольких проблем, которые у нас были. Когда мы впервые начали с одного kafka connect, мы подготовили invoice-sync для работы с avro, однако эластичное поисковое соединение сериализовало ресурсы, отличные от Avro, и мы не могли изменить его, потому что одно из приложений, которое также использовало это соединение kafka, не было готово к работе с Avro.

Работа с одним kafka connect для каждого приложения в среде действительно увеличивает сложность работы, если это так, немного больше, поскольку мы используем AWS ECS для них (как почти все наши сервисы), а иногда, когда мы хотим работать напрямую с коннектором, в автомате, в контейнере заходить скучновато и только тогда можно с ним работать; но оно того стоит.

Убедитесь, что вы правильно настроили удержание

Напомните себе, что настойчивость важна. У нас был проект, который потреблял поток событий от kafka, и недавно у нас возникла проблема с одним из коннекторов kafka, он больше не мог правильно формировать данные для эластичного поиска. Хотя мы решили эту проблему, только позже мы заметили, что у нас было хранение по умолчанию в семь дней, из-за чего мы не могли легко восстановить потерянные данные, нам пришлось повторно отправить все эти данные в коннектор kafka .

Кафка Менеджер

Нам нужно перезапустить контейнер Kafka Manager, чтобы он заработал; время от времени. Мы все еще пытаемся понять, как лучше его использовать или как исправить.

Сложность и кривая обучения

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

Прежде всего спросите себя:

Нужно ли нам работать с потоками Kafka или мы используем это только для ажиотажа?

Хотя я полностью осваиваю новые технологии и работаю с каждым новым классным фреймворком, я понимаю, что иногда вы можете переборщить с его разработкой.

Куда мы идем?

Мы знаем, что нам нужно решить проблемы. Для начала у нас есть зависимость от order-api, и хотя мы не можем решить всю зависимость, мы можем решить ее часть, отправив только идентификатор order. в эластичный поиск invoice, а затем - при извлечении - объединить его с документом из эластичного поиска order.

Мы могли бы добавить логику создания счета из необработанных данных в invoice-sync, но тогда мы вернемся к проблеме нескольких реализаций одного и того же правила; мы все еще думаем о том, как лучше всего это сделать. Возможно, нам стоит поработать над асинхронным подходом, при котором invoice-sync будет генерировать события непреобразованных invoices, а invoice-api будет потреблять он и создаст преобразованный счет-фактуру, при таком подходе мы все равно будем зависеть от invoice-api, но у нас не будет запроса api.

У нас есть некоторые приложения, которые все еще используют reports-api, для тех, которые мы создали, мы создали приложение, которое отвечает за управление соединителями всех из них. Хотя это довольно удобное и действительно классное приложение, в идеале мы должны следовать шаблону различных экземпляров kafka connect и эластичного поиска для каждого приложения.

Часть проектов не использует Avro, и у нас есть некоторые приложения, которые до сих пор не используют Kafka, так как Kafka здесь в тренде, у нас все еще есть несколько проектов, которые нужно обновить.

Мы должны стремиться к более автоматизированному подходу, который, безусловно, уменьшит утомительную ручную задачу, которую приносят дополнительные экземпляры подключения. В идеале инфраструктура как неизменяемый код. Вся наша среда должна быть создана с помощью скриптов ansible и / / или terraform, без участия человека.

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

Cloud-Native Experience для Kafka только что выпущен, мы его читаем, может быть, это может быть нашим следующим подходом.

Думаю, пока это все, если у вас возникнут вопросы, дайте мне знать!

Оставайся классным :)