← К содержанию навыка

Архитектура CEX — одна сделка, 10 подсистем

Во втором разделе мы разобрались, что CEX — это быстрая централизованная прослойка между пользователем и блокчейном, которая позволяет торговать off-chain и взаимодействует с блокчейном только при депозите и выводе. Теперь пора разобраться, из каких именно частей эта прослойка состоит — не абстрактно, а так, чтобы в конце раздела вы могли пальцем показать на схему и сказать: вот здесь живёт стакан, вот здесь ждут подтверждения депозитов, вот здесь рассылаются цены наружу.

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

Вид сверху

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

Архитектура CEX
Кликните на подсистему, чтобы увидеть её обязанности и раздел, где она разбирается
Matching Engine
hot path
Стакан и сопоставление ордеров
Обязанности
  • Держит стакан в памяти — price levels + FIFO очереди
  • Один поток на символ: никакой многопоточности внутри матчинга, чтобы порядок исполнения был детерминированным
  • Принимает команды (new/cancel/modify) и выдаёт события (trade, order update)
  • Бюджет задержки — микросекунды; любой внешний вызов из этого потока запрещён
Где разбирается: Раздел 4 — Стакан и matching engine
Карта архитектуры CEX — кликните на подсистему, чтобы увидеть её обязанности и раздел, где она разбирается

Пробегусь быстро по каждому блоку — подробное объяснение каждого ждёт в соответствующем разделе.

  • API Gateway — вход снаружи, принимает REST, WebSocket и FIX.
  • Auth — проверяет, кто прислал запрос и имеет ли право на эту операцию.
  • Rate Limit — защита от клиента, который заваливает API запросами.
  • Risk Engine — до того как ордер доходит до стакана, проверяет, что у пользователя есть средства, и резервирует их в леджере.
  • Matching Engine — центр вселенной, стакан в памяти, сводит ордера по цене и времени.
  • Ledger — двойная запись, внутренний источник истины по балансам.
  • Wallet Service — мост в блокчейн, следит за депозитами и подписывает выводы.
  • Market Data — рассылает стакан и трейды наружу.
  • Settlement — сверка после торгов.
  • Notifications — асинхронные письма и пуши.

Первое, что стоит заметить глядя на карту: это не микросервисная архитектура в привычном смысле. На горячем пути — от gateway до matching engine и обратно — почти нет обращений между сервисами через сеть. Причина простая, и мы разберём её прямо сейчас.

Горячий путь и холодный путь

Главный водораздел в архитектуре биржи — это не «фронтенд/бэкенд» и даже не «сервис А / сервис Б». Это горячий путь против холодного.

Горячий путь — это всё, что участвует в обработке одного ордера от момента его получения до подтверждения клиенту. Бюджет — единицы-десятки микросекунд на матчинг и сотни микросекунд на всё остальное вокруг. В этот бюджет не помещается ни одно сетевое обращение через интернет, ни один запрос в дисковую базу данных, ни один вызов к внешнему сервису. Всё, что лежит на горячем пути, обязано быть в памяти одного процесса — или нескольких, объединённых быстрой внутренней сетью.

Холодный путь — это всё остальное: рассылка рыночных данных, уведомления, сверка сделок после торгов, обработка депозитов и выводов. Здесь уже можно позволить себе очереди, повторные попытки, группировку операций, асинхронные обработчики и даже многодневные задержки — подтверждения блокчейна живут именно здесь, и они легко занимают минуты.

Горячий путь против холодного
Бюджеты задержек — порядки величин, не замеры конкретной биржи
Горячий путь
Микросекунды и миллисекунды — никаких сетевых хопов, никакой БД на критическом участке
API Gateway
TLS + маршрутизация
< 1 мс
Auth
Проверка подписи из кэша
< 100 мкс
Rate Limit
Token bucket в памяти
< 50 мкс
Risk Engine
Резерв средств в памяти
< 100 мкс
Matching Engine
Сведение ордера
10–50 мкс
Ledger (commit)
Транзакция в БД
< 1 мс
Холодный путь
Миллисекунды, секунды, минуты — можно ждать подтверждения, батчить, ретраить
Market Data fan-out
Широковещание подписчикам
1–10 мс
Wallet Service
Подтверждения блокчейна
секунды–минуты
Settlement
Сверка после сделки
секунды
Notifications
Email / push
секунды
Почему это разделение критично: горячий путь — это часть системы, где один сетевой хоп или одна случайная блокировка мьютекса съедает весь бюджет задержки. Всё, что здесь лежит, обязано быть в памяти одного процесса. Холодный путь — всё остальное: там можно позволить себе очереди, ретраи, батчинг и асинхронные воркеры. Эта граница — главный принцип, который определяет, почему matching engine не распиливают на микросервисы, а wallet service — наоборот, совершенно спокойно живёт как отдельный сервис.
Бюджеты задержек для горячего и холодного путей — порядки величин, не замеры конкретной биржи

Откуда мы знаем, что это разделение — не теоретическое упражнение? Из практики. В 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 и так далее. Когда дальше в тексте мы говорим «символ», мы имеем в виду именно это — один рынок одного актива против другого.

Один поток на символ, много символов на шард
Как биржа масштабирует matching engine: разные символы — разные процессы
Matching Engine #1
Самая загруженная пара — выделенный инстанс, чтобы буря по BTC не мешала другим
BTC/USDT
BTC/USD
BTC/EUR
Matching Engine #2
Второй по объёму актив — тоже отдельный шард с собственным процессом
ETH/USDT
ETH/USD
ETH/BTC
Matching Engine #3
Несколько десятков менее ликвидных символов живут вместе на одном инстансе
SOL/USDT
DOGE/USDT
ADA/USDT
LINK/USDT
AVAX/USDT
MATIC/USDT
...
Matching engine обрабатывает каждый символ в одном потоке — это даёт полностью детерминированный порядок сделок и снимает необходимость в блокировках внутри стакана. Биржа масштабируется не по потокам, а по символам: горячие пары получают собственные инстансы, менее активные символы собираются в общие шарды. Перетасовка символов между шардами — это заранее спланированная операция с остановкой пары, а не динамический балансировщик, и происходит она максимум раз в недели-месяцы.
Шардирование matching engine по торговым парам: горячие пары получают собственные экземпляры, менее активные объединяются в общие шарды

Это работает, потому что сделки по BTC/USDT и ETH/USDT независимы: одна не может повлиять на другую, и нет смысла пытаться обработать их в общем потоке. У каждого символа — свой поток обработки, свой журнал событий, своё детерминированное поведение. Соседние символы в соседних процессах — совершенно отдельные системы, которые объединяет только общий сервер.

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

Шардирование даёт горизонтальное масштабирование по символам. Но каждый из этих экземпляров — единая точка отказа для своей торговой пары. Как биржа выживает, когда один из них падает?

Развёртывание

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

Топология развёртывания: один матчинг, много точек раздачи данных
Primary + replica для matching, многорегиональный fan-out для market data
Primary data center
Здесь живёт matching engine и ledger
  • Активный matching engine — единственный источник правды по стакану
  • Транзакционная БД леджера (primary)
  • Risk engine в той же сети для минимальных задержек
  • Низкоуровневая сеть оптимизирована под стабильную p99 задержку
Replica / Failover DC
Горячий резерв, переключение — минуты, не часы
  • Пассивная реплика matching engine, догоняет primary по журналу
  • Реплика БД леджера в синхронном или полусинхронном режиме
  • Во время failover становится новым primary — это заранее отрепетированная процедура
  • Всегда в другом здании, часто в другом городе
Журнал сделок и ledger replication
Edge regions — fan-out рыночных данных
WebSocket-подписки терминируются ближе к клиенту, матчинг остаётся в primary
EU (Frankfurt)
US-East (Ashburn)
APAC (Tokyo)
APAC (Singapore)
Принципиальная асимметрия: самих matching-инстансов всегда мало и они сидят близко друг к другу, потому что задержка между primary и replica определяет, сколько сделок может потеряться при падении. А раздача market data — наоборот, чем больше точек терминации, тем лучше: для клиента в Токио не должно иметь значения, что матчинг крутится в условном Франкфурте, потому что дельты стакана он получает с ближайшего edge-узла.
Топология развёртывания CEX: основной экземпляр и резерв для матчинга, многорегиональная раздача рыночных данных

Принципиальная асимметрия: экземпляров матчинга всегда мало, и они физически рядом друг с другом. Задержка между основным и резервным экземплярами определяет, сколько сделок может потеряться при отказе основного — а значит, резерв должен быть или в том же здании, или в соседнем, или хотя бы в том же городе. Межконтинентальная репликация для 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 Architecturemartinfowler.com/articles/lmax.html. Канонический текст 2011 года про архитектуру, в которой осознанный отказ от микросервисов на горячем пути дал результат, недостижимый обычными средствами. Прочесть целиком — лучший комментарий к этому разделу.
  • LMAX Disruptor — technical paperlmax-exchange.github.io/disruptor. Подробный разбор кольцевого буфера, который лежит в основе подхода LMAX. Читается как учебник по тому, как симпатия к железу побеждает абстрактный «хороший дизайн».
  • LMAX Disruptor — GitHubgithub.com/LMAX-Exchange/disruptor. Реальная реализация с тестами и бенчмарками — можно подержать в руках и посмотреть, как она выглядит в коде.
  • Kraken Engineering Blogblog.kraken.com/category/product/engineering. Материалы о том, как реальная крупная биржа рассказывает о своей инфраструктуре — масштабирование, переход на Rust, операционные истории.
  • Coinbase Engineering Blogcoinbase.com/blog/landing/engineering. Корпоративный инженерный блог публичной криптобиржи; здесь появляются посты про matching engine и ledger («Bookkeeper»). На момент написания доступ к конкретным постам бывает капризным, поэтому стоит искать конкретные темы через поиск.
  • Real Logic / Aerongithub.com/real-logic/aeron. Современный транспортный фреймворк от авторов LMAX Disruptor. Используется в биржах и высокочастотных торговых системах как основной транспорт для обмена событиями между процессами с минимальной задержкой.