Архитектура CEX — одна сделка, 10 подсистем
Во втором разделе мы разобрались, что CEX — это быстрая централизованная прослойка между пользователем и блокчейном, которая позволяет торговать off-chain и взаимодействует с блокчейном только при депозите и выводе. Теперь пора разобраться, из каких именно частей эта прослойка состоит — не абстрактно, а так, чтобы в конце раздела вы могли пальцем показать на схему и сказать: вот здесь живёт стакан, вот здесь ждут подтверждения депозитов, вот здесь рассылаются цены наружу.
Схема из этого раздела — центральная для всего навыка. К ней мы будем возвращаться в каждом последующем разделе: matching engine в четвёртом, жизненный цикл ордера в пятом, леджер и кастоди в шестом-седьмом. Каждый из них — крупный план одного из блоков на этой схеме.
Вид сверху
Биржа — это около десятка отдельных подсистем, каждая со своими требованиями к задержке, надёжности и безопасности. Почти все инженерные решения в этом навыке — следствие того, как эти подсистемы взаимодействуют.
- Держит стакан в памяти — price levels + FIFO очереди
- Один поток на символ: никакой многопоточности внутри матчинга, чтобы порядок исполнения был детерминированным
- Принимает команды (new/cancel/modify) и выдаёт события (trade, order update)
- Бюджет задержки — микросекунды; любой внешний вызов из этого потока запрещён
Пробегусь быстро по каждому блоку — подробное объяснение каждого ждёт в соответствующем разделе.
- API Gateway — вход снаружи, принимает REST, WebSocket и FIX.
- Auth — проверяет, кто прислал запрос и имеет ли право на эту операцию.
- Rate Limit — защита от клиента, который заваливает API запросами.
- Risk Engine — до того как ордер доходит до стакана, проверяет, что у пользователя есть средства, и резервирует их в леджере.
- Matching Engine — центр вселенной, стакан в памяти, сводит ордера по цене и времени.
- Ledger — двойная запись, внутренний источник истины по балансам.
- Wallet Service — мост в блокчейн, следит за депозитами и подписывает выводы.
- Market Data — рассылает стакан и трейды наружу.
- Settlement — сверка после торгов.
- Notifications — асинхронные письма и пуши.
Первое, что стоит заметить глядя на карту: это не микросервисная архитектура в привычном смысле. На горячем пути — от gateway до matching engine и обратно — почти нет обращений между сервисами через сеть. Причина простая, и мы разберём её прямо сейчас.
Горячий путь и холодный путь
Главный водораздел в архитектуре биржи — это не «фронтенд/бэкенд» и даже не «сервис А / сервис Б». Это горячий путь против холодного.
Горячий путь — это всё, что участвует в обработке одного ордера от момента его получения до подтверждения клиенту. Бюджет — единицы-десятки микросекунд на матчинг и сотни микросекунд на всё остальное вокруг. В этот бюджет не помещается ни одно сетевое обращение через интернет, ни один запрос в дисковую базу данных, ни один вызов к внешнему сервису. Всё, что лежит на горячем пути, обязано быть в памяти одного процесса — или нескольких, объединённых быстрой внутренней сетью.
Холодный путь — это всё остальное: рассылка рыночных данных, уведомления, сверка сделок после торгов, обработка депозитов и выводов. Здесь уже можно позволить себе очереди, повторные попытки, группировку операций, асинхронные обработчики и даже многодневные задержки — подтверждения блокчейна живут именно здесь, и они легко занимают минуты.
Откуда мы знаем, что это разделение — не теоретическое упражнение? Из практики. В 2011 году команда LMAX, британской финансовой биржи, строила торговую систему и попробовала стандартный подход — больше серверов, очереди сообщений, микросервисы. Получилось медленно: каждый сетевой переход между сервисами добавлял задержку, и итоговое время обработки ордера уходило в миллисекунды. Тогда они пошли в обратную сторону: убрали все сетевые переходы с горячего пути, убрали очереди между сервисами и написали один процесс, который делал всё в памяти, на одном потоке, через кольцевой буфер. Результат: шесть миллионов транзакций в секунду на одном сервере. Мартин Фаулер написал про это статью, которую в индустрии читают по сей день.
Именно из этих идей выросли matching engines всех крупных криптобирж. Разделение на горячий и холодный пути — это не стилевой выбор, а следствие физики: матчинг обязан быть быстрым до предела и потому собран как один тесно связанный процесс, а рассылка рыночных данных и wallet service допускают очереди, повторные попытки и асинхронную обработку — и потому живут как отдельные сервисы.
Но если горячий путь живёт в памяти — где тогда хранится всё остальное?
Где что хранится
Источников истины несколько, и у каждого своя роль.
Стакан живёт в памяти matching engine. Это не кэш базы данных — это единственное место, где актуальный стакан существует в принципе. Если процесс падает, стакан восстанавливается из журнала событий, который matching engine пишет параллельно с обработкой ордеров. Резервный экземпляр matching engine читает тот же журнал и поддерживает собственную копию стакана — в четвёртом разделе мы посмотрим, как именно.
Балансы пользователей живут в транзакционной базе данных леджера — обычно Postgres или что-то похожее с полноценными ACID-гарантиями. Каждое движение средств — это транзакция в модели двойной записи. Это единственная подсистема, где важнее корректность, чем скорость: потерять миллисекунду тут не страшно, а вот допустить дубль или рассинхронизацию — катастрофа. Шестой раздел целиком про это.
Рыночные данные живут как поток сообщений — обычно через Kafka или специализированную систему вроде Aeron. Потребители (клиенты через WebSocket, внутренние аналитические системы, архив) подписываются на этот поток независимо друг от друга. Хранить стакан в базе данных и читать его оттуда для клиентов никто не будет — это медленно и бессмысленно.
Исторические данные — сделки за прошлые периоды, журналы аудита, снимки состояния системы — уходят в объектное хранилище типа S3 или в аналитическое хранилище. К ним обращаются регуляторы, внутренний аудит, аналитическая команда. На горячий путь это не влияет никак.
Ключи от кошельков — отдельная история, к которой мы вернёмся в седьмом разделе. Если коротко: приватные ключи от горячего кошелька живут в аппаратном модуле безопасности (HSM) или в системе вычислений с разделёнными секретами (MPC), а от холодного — в железке, которую нужно физически принести в комнату с камерами и двумя охранниками, чтобы что-то ею подписать.
У каждой части системы — своё хранилище с нужными ей гарантиями. Но как масштабировать matching engine, если торговых пар становится сотня?
Шардирование по символам
Как масштабировать matching engine, если один процесс на одном потоке упирается в процессор? Решение, к которому пришли практически все биржи: не масштабировать один процесс, а запускать много — по одному на символ или группу символов.
Что такое символ (или торговая пара)? BTC/USDT — это торговая пара, в которой первый актив (BTC — Bitcoin) покупается и продаётся за второй (USDT — стейблкоин, привязанный к доллару). Это отдельный рынок со своим стаканом, своей ценой и своим потоком ордеров — точно так же, как на фондовой бирже акции Apple и акции Google торгуются на разных рынках независимо друг от друга. На крупной криптобирже таких пар сотни: BTC/USDT, ETH/USDT, SOL/USDT и так далее. Когда дальше в тексте мы говорим «символ», мы имеем в виду именно это — один рынок одного актива против другого.
Это работает, потому что сделки по BTC/USDT и ETH/USDT независимы: одна не может повлиять на другую, и нет смысла пытаться обработать их в общем потоке. У каждого символа — свой поток обработки, свой журнал событий, своё детерминированное поведение. Соседние символы в соседних процессах — совершенно отдельные системы, которые объединяет только общий сервер.
Шард — это не динамически перебалансируемая сущность. Переместить символ между шардами — это заранее запланированная операция, которая требует остановки торговой пары на несколько секунд или минут, записи последнего состояния стакана в журнал и подъёма этого стакана на новом экземпляре. Делать это в прямом эфире никто не умеет, и большинство бирж делает это максимум раз в несколько месяцев, когда нагрузка действительно этого требует.
Шардирование даёт горизонтальное масштабирование по символам. Но каждый из этих экземпляров — единая точка отказа для своей торговой пары. Как биржа выживает, когда один из них падает?
Развёртывание
Каждый значимый компонент биржи существует минимум в двух экземплярах, а рыночные данные раздаются из многих точек по всему миру.
- Активный matching engine — единственный источник правды по стакану
- Транзакционная БД леджера (primary)
- Risk engine в той же сети для минимальных задержек
- Низкоуровневая сеть оптимизирована под стабильную p99 задержку
- Пассивная реплика matching engine, догоняет primary по журналу
- Реплика БД леджера в синхронном или полусинхронном режиме
- Во время failover становится новым primary — это заранее отрепетированная процедура
- Всегда в другом здании, часто в другом городе
Принципиальная асимметрия: экземпляров матчинга всегда мало, и они физически рядом друг с другом. Задержка между основным и резервным экземплярами определяет, сколько сделок может потеряться при отказе основного — а значит, резерв должен быть или в том же здании, или в соседнем, или хотя бы в том же городе. Межконтинентальная репликация для matching engine — это не запасной план, а гарантия больших потерь при переключении.
А вот рыночные данные — наоборот, чем больше точек раздачи, тем лучше. Клиенту в Токио безразлично, что матчинг крутится в условном Франкфурте, — он подписывается на WebSocket ближайшего граничного сервера и получает обновления стакана с минимальной задержкой. Граничные серверы не участвуют в принятии решений по сделкам, они просто ретранслируют события. В инженерных блогах Kraken и Coinbase есть несколько постов о том, как эта часть устроена у них.
Переключение между основным и резервным экземплярами matching engine — это не «автоматически за миллисекунды». Это заранее отрепетированная процедура, которая занимает минуты, и в хорошо построенной бирже она происходит под контролем дежурной смены, а не сама собой. Цена ошибки высока: если переключиться слишком рано, можно получить две параллельные версии реальности на одном символе, и часть сделок окажется в одном журнале, часть — в другом. Девятый раздел разбирает, как этот класс задач решают на практике.
Всё, о чём шла речь выше, — внутреннее устройство биржи. Но есть один блок на карте, который живёт на границе двух миров.
Wallet service встречается с блокчейном
Пользователь нажимает «внести» и отправляет Bitcoin-транзакцию на депозитный адрес биржи. На экране — счётчик подтверждений. 0/3. 1/3. 2/3. Только на «3/3» деньги появятся в балансе. За этим счётчиком стоит wallet service — единственный блок на карте, который напрямую работает с блокчейном.
Когда клиент отправляет депозит, транзакция появляется в очереди ещё не подтверждённых транзакций сети. Wallet service следит за этой транзакцией через подключение к узлу сети (Bitcoin Core, Ethereum execution client, Solana RPC — в зависимости от сети) и ждёт, пока глубина подтверждения достигнет порога, на котором биржа считает депозит окончательным. Для Bitcoin это обычно 3–6 блоков, для Ethereum — несколько десятков секунд после финализации, для Solana — единицы секунд. Именно после этого момента wallet service даёт команду леджеру начислить средства на счёт пользователя — и только с этого момента клиент видит деньги у себя в балансе.
На выводе всё работает в обратную сторону: клиент нажимает «вывести», проходит проверки risk engine и auth, леджер блокирует средства в ожидании исхода, wallet service строит транзакцию, подписывает её через HSM или MPC, рассылает в сеть и снова ждёт подтверждения. Пока подтверждения нет, средства считаются «в пути»; после — списываются с баланса окончательно. Эта асимметрия между «мгновенно внутри биржи» и «минуты-часы на стыке с блокчейном» — прямое следствие того, о чём мы говорили в первом разделе: блокчейн медленный, и всё, что можно сделать без него, делается без него.
Всё остальное — группировка выводов, горячие и холодные кошельки, HSM и MPC — тема седьмого раздела. Здесь достаточно увидеть, где именно wallet service сидит на карте и почему он, в отличие от matching engine, совершенно спокойно может занять несколько секунд на одну операцию: он на холодном пути, никто от него не ждёт мгновенной реакции.
Почему биржи выглядят именно так
Принципы архитектуры CEX — это не стиль и не мода, а прямые следствия нескольких жёстких ограничений:
- Сделка должна исполниться за микросекунды. Отсюда — матчинг в одном процессе, в памяти, без обращений между сервисами на горячем пути.
- Баланс пользователя должен быть корректным всегда. Отсюда — леджер в ACID-базе, двойная запись, жёсткие инварианты.
- Блокчейн медленный, и ничего с этим не поделать. Отсюда — wallet service как отдельный сервис с собственными подтверждениями и повторными попытками, вынесенный с горячего пути.
- Клиенты хотят видеть стакан в реальном времени со всех концов земного шара. Отсюда — многорегиональная раздача рыночных данных через граничные серверы.
- Что-то обязательно сломается в три часа ночи в воскресенье. Отсюда — резервный экземпляр matching engine, отработанные процедуры переключения, изолированный доступ для администраторов — и девятый раздел, целиком посвящённый этому классу задач.
Для нас как архитекторов здесь важен конкретный урок: за каждым компонентом на карте стоит одно физическое ограничение, которое нельзя убрать — только учесть. Это не академическая конструкция: именно из-за таких ограничений Binance, Coinbase и Kraken выглядят изнутри примерно одинаково, хотя строили их разные команды в разные годы. Карта из раздела выше — не единственно возможное устройство биржи, а единственное устройство, которое отвечает на все эти ограничения одновременно. Если хочется спроектировать что-то принципиально другое — нужно честно сказать, от какого из них вы готовы отказаться.
Куда идём дальше
Мы построили карту. В следующем разделе мы разберём самый важный блок на карте — matching engine — изнутри: стакан, ценовые уровни, очереди по времени поступления, алгоритм сведения, почему один поток на символ и почему этот кусок — самый интересный с инженерной точки зрения во всей бирже.
Все остальные блоки на карте тоже дождутся своей очереди. Жизненный цикл ордера в пятом разделе проведёт один ордер через всю схему по стрелкам. Шестой и седьмой — подробный разбор леджера и wallet service. Восьмой раздел — про gateway, API и рыночные данные. Девятый — про то, что происходит с этой архитектурой, когда что-то идёт не так.
Дополнительное чтение
- Martin Fowler — The LMAX Architecture — martinfowler.com/articles/lmax.html. Канонический текст 2011 года про архитектуру, в которой осознанный отказ от микросервисов на горячем пути дал результат, недостижимый обычными средствами. Прочесть целиком — лучший комментарий к этому разделу.
- LMAX Disruptor — technical paper — lmax-exchange.github.io/disruptor. Подробный разбор кольцевого буфера, который лежит в основе подхода LMAX. Читается как учебник по тому, как симпатия к железу побеждает абстрактный «хороший дизайн».
- LMAX Disruptor — GitHub — github.com/LMAX-Exchange/disruptor. Реальная реализация с тестами и бенчмарками — можно подержать в руках и посмотреть, как она выглядит в коде.
- Kraken Engineering Blog — blog.kraken.com/category/product/engineering. Материалы о том, как реальная крупная биржа рассказывает о своей инфраструктуре — масштабирование, переход на Rust, операционные истории.
- Coinbase Engineering Blog — coinbase.com/blog/landing/engineering. Корпоративный инженерный блог публичной криптобиржи; здесь появляются посты про matching engine и ledger («Bookkeeper»). На момент написания доступ к конкретным постам бывает капризным, поэтому стоит искать конкретные темы через поиск.
- Real Logic / Aeron — github.com/real-logic/aeron. Современный транспортный фреймворк от авторов LMAX Disruptor. Используется в биржах и высокочастотных торговых системах как основной транспорт для обмена событиями между процессами с минимальной задержкой.