← Вернуться к списку статей

Как разработать высоконагруженную финансовую систему: архитектура, безопасность и защита от сбоев

Как разработать высоконагруженную финансовую систему: архитектура, безопасность и защита от сбоев

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

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

Сначала — денежная модель и границы ответственности

Центром финансовой платформы обычно становится не таблица платежей, а учетный журнал операций. Для денег надежнее использовать двустороннюю модель проводок: каждая операция отражается по дебету и кредиту, а сумма по связанным счетам сходится. Баланс в таком случае можно пересчитать из журнала, а не считать единственным источником истины поле balance, которое легко повредить из-за ошибки или гонки запросов.

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

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

Как построить обработку нагрузки

Синхронный API нужен для короткого пользовательского сценария: принять команду, проверить базовые условия и вернуть идентификатор операции. Долгие действия — обращение к внешнему банку, антифрод-проверка, формирование выписки или уведомление — лучше выполнять асинхронно. Очередь снижает связанность компонентов и сглаживает пики, но не делает систему надежной автоматически: сообщения должны иметь идентификатор, срок жизни, число попыток и маршрут в карантинную очередь.

Ключевая техника для финансовых операций — идемпотентность. Клиент или платежный провайдер может повторить запрос из-за тайм-аута, хотя первая попытка уже завершилась. Сервер принимает идемпотентный ключ, связывает его с параметрами команды и сохраненным результатом. Повтор с теми же данными возвращает прежний результат, а повтор с изменившейся суммой или получателем отклоняется.

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

Для чтения применяют реплики, кэш и заранее подготовленные представления, однако кэш не должен быть источником финансовой истины. После записи клиенту нужно явно сообщать, подтверждена ли операция, принята ли в обработку или только зарегистрирована. Нельзя маскировать задержку красивым статусом «успешно», если окончательный результат еще неизвестен.

Отказоустойчивость без ложных гарантий

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

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

Для оценки плана восстановления используют два показателя: RPO — допустимый объем потерянных данных, и RTO — время возврата сервиса в рабочее состояние. Их нельзя назначать одинаковыми для всей платформы. Для учетного журнала RPO обычно стремится к нулю, а витрина аналитики может восстановиться позже. Требования проверяют не на бумаге, а во время учений с фиксацией фактического времени и обнаруженных разрывов.

Полезны тайм-ауты, ограничители повторов, circuit breaker и bulkhead-изоляция, разделяющая ресурсы разных потоков. Повторный запрос должен иметь задержку с увеличением интервала и случайным разбросом. Если внешний банк не отвечает, система не должна бесконечно занимать рабочие потоки и создавать лавину повторных списаний. Для спорных операций нужен отдельный процесс сверки с внешним провайдером.

Безопасность: защита не только периметра

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

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

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

Наблюдаемость и проверка качества

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

Трассировка проходит через API, очередь, сервис проводок и интеграцию с банком. В нее нельзя включать секреты и полные платежные реквизиты. Оповещения должны быть привязаны к действию: исчерпался запас соединений — ограничить нагрузку, растет очередь — увеличить обработчики или остановить второстепенный поток, обнаружено расхождение — запустить сверку и уведомить ответственную команду.

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

Хорошая финансовая архитектура не обещает невозможного и не прячет неопределенность за статусом «успешно». Она точно разделяет принятое решение, проведенную операцию и результат внешней сверки; хранит историю так, чтобы ее можно было проверить; ограничивает ущерб при отказе и дает команде инструменты для восстановления. Именно эти свойства, а не количество сервисов, определяют надежность платформы под реальной нагрузкой.