← К содержанию гайда

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

Во втором разделе мы разобрались, что 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 мс
Холодный путь
Миллисекунды, секунды, минуты — допустимы ожидание подтверждений, batching и retry
Market Data fan-out
Широковещание подписчикам
1–10 мс
Wallet Service
Подтверждения блокчейна
секунды–минуты
Settlement
Сверка после сделки
секунды
Notifications
Email / push
секунды
На горячем пути один сетевой переход или случайная блокировка mutex может съесть весь бюджет задержки. Всё, что здесь лежит, обязано быть в памяти одного процесса. Холодный путь — всё остальное: там можно позволить себе очереди, retry, batching и асинхронные воркеры. Эта граница — главный принцип, который определяет, почему 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 независимы: одна не может повлиять на другую, и нет смысла пытаться обработать их в общем потоке. У каждого символа — свой поток обработки, свой журнал событий, своё детерминированное поведение. Соседние символы в соседних процессах — совершенно отдельные системы, которые объединяет только общий сервер.

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

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

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

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

Топология развёртывания: один matching engine, много точек раздачи данных
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 engine всегда мало, и они расположены близко друг к другу, потому что задержка между primary и replica определяет, сколько сделок может потеряться при падении. А раздача market data — наоборот, чем больше точек присутствия, тем лучше: для клиента в Токио не должно иметь значения, что matching engine крутится в условном Франкфурте, потому что дельты стакана он получает с ближайшего 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 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. Используется в биржах и высокочастотных торговых системах как основной транспорт для обмена событиями между процессами с минимальной задержкой.