Хроники архитектурного дизайна. Часть 2: использование шаблонов гарантированной доставки

Системный аналитик ГК Юзтех
Привет, мир!
Меня зовут Роман Ремизов. Я — системный аналитик ГК Юзтех. В рамках цикла статей «Хроники архитектурного дизайна» я делюсь своей экспертизой о разных автоматизированных банковских системах (АБС) и рассказываю, что нужно знать перед тем, как приступить к архитектурному дизайну.
Данная статья, как и первая из этого цикла, написана с допустимым уровнем конкретики. И ещё, стоит помнить, что все банки разные и на других проектах могут преобладать иные архитектурные решения.
В этой статье мы обсудим такую интересную тему, как использование шаблонов гарантированной доставки.
Невероятно, но ещё раз о Kafka. «Прямо-таки, вечная очередь?»
Следствием нарастающей популярности брокера сообщений Kafka, выступает достаточное множество материалов, связанных с описанием как общей функциональности данного программного продукта, так и её «подкапотного пространства». С этими материалами можно ознакомиться в официальной спецификации, а также в статьях авторов на различных ресурсах. Мы же рассмотрим чуть больше, чем то, что можно «достать из коробки», чтобы использовать расширенные возможности этого брокера, которые используются для построения архитектуры высоконагруженных банковских систем.
В контексте финтех, и, в частности, банковских проектов, ключевым требованием является надёжность данных. Речь идёт не только и не столько про использование Kafka в качестве персистентного хранилища, которым она, как показывает практика, в реализации высоконагруженных систем является крайне редко. А, учитывая особенности инфраструктуры и объём данных, есть как минимум две причины, почему на практике складывается именно так, а не иначе:
Аппаратная часть имеет особенность «моргать». Это происходит редко, но всё же, происходит. Согласитесь, будет обидно, если, например, финансовая операция не пройдёт, потому что где-то в дата-центре был скачок напряжения. Да, есть инструменты, которые позволяют парировать такие инциденты, но на практике, всё-таки, используются сначала штатные, а уже затем аварийные средства. Можно сказать, что я утрирую и не описываю все возможные кейсы, чтобы использовать их в качестве аргументов, но сейчас моя цель заключается в том, чтобы обозначить риски, а не убедить читателя.
Глубина хранения большого объёма данных в реальности составляет всего 30 минут. И, несмотря на возможности Kafka, при таких ограничениях персистентным хранилищем всё ещё является база данных. Есть, конечно, нереляционные БД, которые работают в оперативной памяти, но сейчас речь идёт не про них. И, к слову, при импортозамещении они не являются целевыми.
Для участников комьюнити системного анализа ГК Юзтех в первом квартале 2025 года готовится отдельный пул материалов ведущим системным аналитиком ГК Елизаветой Акмановой. Это делается для погружения в практическое использование Kafka, её возможностей, и знакомства с её «подкапотным пространством». Мы же, в ожидании материалов моей коллеги, коротко вспомним теорию.
Теория надёжности данных
KafkaProducer обеспечивает надежность данных через конфигурационный параметр acks. Этот параметр определяет, сколько подтверждений необходимо получить продюсеру, чтобы считать запись успешно доставленной брокеру. Вот возможные значения:
none — продюсер считает записи успешно доставленными сразу после их отправки на брокер, не ожидая подтверждений;
one — продюсер ждет подтверждения от лидер-брокера о том, что запись была добавлена в лог;
all — продюсер ожидает подтверждений как от лидера, так и от реплик брокера.
Разные банковские продукты имеют свои требования к надёжности данных, и здесь необходимо найти баланс: высокая пропускная способность может привести к риску потерь, в то время как высокая надежность может снизить пропускную способность.
Рассмотрим подробнее сценарий с параметром acks=all. Если для записи установлен параметр acks=all в кластере из трех брокеров Kafka, в идеальных условиях Kafka будет иметь три реплики данных — одну на лидере и две на фолловерах. Когда все три реплики имеют одинаковое смещение записи в логах, они считаются синхронизированными. Это означает, что синхронизированные реплики содержат идентичный контент для одной конкретной партиции топика.
Просто бизнес виноват или «пришла беда, — оттуда и ждали»
Несмотря на то, что использование acks=all гарантирует, что все реплики партиции топика получили сообщение, с точки зрения бизнес-логики это не всегда бывает достаточно для гарантированной доставки. Прежде всего это происходит потому, что ack не является адекватным критерием контроля над целостностью выполнения бизнес-процесса. Для бизнеса бывает мало, что технически надёжность данных была обеспечена брокером. Как и в любом заданном вопросе нужна обратная связь, тем более, если перед нами старая амбарная книга или журнал ведомостей, переосмысленные сначала в нормативных документах современного бухгалтерского учёта, а затем в компонентах архитектуры информационных систем, обеспечивающих выполнение процессов.
Представим себе такое решение, отражением которого является реальный случай из моей практики, когда в одном из топиков внешней Kafka от нескольких продюсеров собираются входящие события от АБС (автоматизированных банковских систем) продуктового слоя.
Брокер, на попытку записать в него сообщение, может отвечать ack, когда все реплики партиции получили сообщение (при использовании acks=all) или отвечать ошибкой. Любой ошибкой из предусмотренного Kafka перечня ошибок. Однако, ни одна из этих ошибок не будет иметь бизнес-смысл.
При получении ошибки произойдут как минимум два события:
Продуктовая АБС, учитывая требования к быстродействию (SLA) принимающей системы, согласно своей логике, через определённый временной промежуток (тайм-аут) будет пытаться повторно отправить сообщение в топик. После небольшого количества попыток (retry) она откинет метрику в Grafana через Prometheus (условно, возьмём такие инструменты мониторинга), которую увидит сотрудник сопровождения.
Так как в продуктовой АБС процессы часто обеспечены UI, то пользователь увидит ошибку на экране. Однако, данная ошибка будет ожидать от пользователя только два вероятных варианта поведения: либо пользователь должен обратиться к сотруднику сопровождения для устранения причины, либо попробовать повторить отправку сообщения принимающей системе. И логично, что сотрудник отделения банка не должен и не сможет в полной мере проанализировать такой инцидент без сотрудника сопровождения, особенно если повторная отправка сообщения не решит проблему.
Мы рассмотрели кейс, когда количество источников сообщений равно количеству АБС продуктового слоя, подключенных к одной принимающей системе.
Вторым практическим кейсом из моей практики является случай, когда принимающая система обработала сообщение и передала его потребителю, а в качестве потребителя результата этой обработки имеется группа равнозначных, с точки зрения бизнес-логики, банковских систем. Например, ассортимент главных книг, оперативные и постоянные хранилища, системы транзакционного мониторинга.
Если системам транзакционного мониторинга достаточно стриминга, где базовых инструментов обеспечения надёжности данных Kafka более чем достаточно (там даже не всегда acks=all), то для остальных систем мало ack`ов или ошибок Kafka. Причиной этому является то, что разного рода генераторы (или фермы), которые я ранее обернул под псевдоним «принимающая система», фактически не владеют всеми состояниями той сущности, которую создают. Забавно, но это не входит в зону их ответственности.
У них на входе есть сообщения от АБС продуктового слоя, а на выходе счёт или бухгалтерская проводка в статусах «Создан» или «Агрегирована». Но для целостности выполнения бизнес-процесса эти сущности должны быть «Переданы», «Получены», «Проведены» и, например, «Аннулированы», а данные состояния появляются только в потребителях.
При этом, если счёт может быть «Создан» в генераторе, то генератор является владельцем этого состояния счёта. А если счёт был «Передан» всем потребителям, и был получен только ack от Kafka, как понять в каком состоянии он находится сейчас, в настоящее время?
Непосредственно шаблоны. «Ваш звонок очень важен для Вас»
Для организации гарантированной доставки на банковских проектах применяются соответствующие шаблоны. Они описывают минимум требований в решении, согласно которым сообщение считается гарантировано переданным как источниками, так и потребителями. Подчеркну, гарантировано переданным. Это нужно, в том числе, для:
Организации рабочего пространства ответственных лиц и ситуационных центров (например, уже упомянутых сотрудников сопровождения). Под организацией в этом контексте понимается как предоставление доступов, так и сопутствующего инструментария. Prometheus и Grafana с набором конкретных метрик, доступ к конкретным топикам Kafka, доступ к конкретным БД и их схемам на конкретных стендах, — речь идёт про всё это. А также про сменность персонала и графики его работы.
Разработки и внедрения специализированных инструментов. Проще сказать — «контроллеров» (ударение на второе «о»), если вспомнить паттерн MVC. Ведь по причинам, описанным в первой статье цикла, у генераторов, как и у всех общих сервисов, нет своего UI. Он им и не нужен.
Для примера возьмём два шаблона:
Первый шаблон: много источников сообщений относятся к одной принимающей системе.
Второй шаблон: одна система имеет много потребителей своих сообщений.
В рамках как первого, так и второго шаблона, кроме «основного» топика внешней Kafka, должны быть добавлены также топики, в которых размещаются квитки ответа от принимающей системы. По одному для успехов и ошибок. Данное архитектурное решение позволяет:
Поддержать бизнес-логику согласно фактическому состоянию обработки сообщения на стороне принимающей системы.
Расширить информационную составляющую ответа необходимым атрибутным составом квитка ответа.
Вывести для системы источника конкретный набор точек, используемых в качестве критериев для дальнейших действий и событий.
Организовать зонтичную систему мониторинга для всех систем, участвующих в выполнении бизнес-процессов финтех учреждения, а в нашем конкретном случае — банка.
Контролировать состояние трафика сообщений между АБС бесшовно, но с конкретикой: в какой системе и в результате чего произошла ошибка или какой конкретно успех был достигнут. В этом пункте важно, что кроме анализа должны быть и инструменты воздействия, в которых и заключается функция контроля.
Предварительное заключение 2
Учитывая сказанное выше, в процессе архитектурного дизайна нам, как системным аналитикам, следует определиться с тем, какой шаблон гарантированной доставки будет полноценно обеспечивать требования к надёжности данных.
Если мы видим, что принимающая система обладает ресурсами, способными действительно обеспечить Kafka ролью персистентного хранилища данных, или мы всего лишь стримим в принимающую систему сообщениями, то, вполне вероятно, будет достаточно того, что у Kafka есть в «коробке». Выбор остаётся лишь между настройкой acks. Чем больше контроля, тем ниже быстродействие, и наоборот. Это решение, на мой взгляд, подойдёт для слабонагруженных систем или для систем мониторинга, где одна метрика не так существенна, и обычно анализируются целые всплески событий, состоящих из, порой, десятков или сотен однородных метрик.
Когда каждое сообщение — это репутационная единица, нужно гораздо больше критериев оценки добротности обработки сообщений. Нужна полноценная, исчерпывающая обратная связь, содержащая полную информацию о его состоянии. Важно помнить, что не любое сообщение можно приравнять к значимой сущности, но каждая значимая сущность, так или иначе, может быть обёрнута в сообщение для своего путешествия по микро-сервисной архитектуре.