- PVSM.RU - https://www.pvsm.ru -

Всем привет! Меня зовут Сергей Костанбаев, на Бирже я занимаюсь разработкой ядра торговой системы.
Когда в голливудских фильмах показывают Нью-Йоркскую фондовую биржу, это всегда выглядит так: толпы людей, все что-то орут, машут бумажками, творится полный хаос. У нас на Московской бирже такого никогда не было, потому что торги с самого начала ведутся электронно и базируются на двух основных платформах — Spectra (срочный рынок) и ASTS (валютный, фондовый и денежный рынок). И сегодня хочу рассказать об эволюции архитектуры торгово-клиринговой системы ASTS, о различных решениях и находках. Рассказ будет длинный, так что пришлось разбить его на две части.
Мы одна из немногих бирж мира, на которых проводятся торги активами всех классов и предоставляется полный спектр биржевых услуг. К примеру, в прошлом году мы занимали второе место в мире по объёму торгов облигациями, 25 место среди всех фондовых бирж, 13 место по капитализации среди публичных бирж.

Для профессиональных участников торгов критичны такие параметры, как время отклика, стабильность распределения времён (джиттер) и надёжность всего комплекса. На текущий момент мы обрабатываем десятки миллионов транзакций в день. Обработка каждой транзакции ядром системы занимает десятки микросекунд. Конечно, у сотовых операторов под Новый год или у поисковиков нагруженность сама по себе выше нашей, но вот по нагруженности вкупе с вышеупомянутыми характеристиками с нами мало кто может сравниться, как мне кажется. При этом нам важно, чтобы система ни на секунду не подтормаживала, работала абсолютно стабильно, и все пользователи находились в равных условиях.
В 1994 году на Московской межбанковской валютной бирже (ММВБ) была запущена австралийская система ASTS, и с этого момента можно отсчитывать российскую историю электронных торгов. В 1998 году архитектуру биржи модернизировали ради внедрения интернет-трейдинга. С тех пор скорость внедрения новых решений и архитектурных изменений во всех системах и подсистемах только набирает обороты.
В те годы биржевая система работала на hi-end железе — сверхнадёжных серверах HP Superdome 9000 (построенных на архитектуре PA-RISC [1]), у которых дублировалось абсолютно всё: подсистемы ввода-вывода, сеть, оперативная память (фактически, был RAID-массив из RAM), процессоры (поддерживалась горячая замена). Можно было поменять любой компонент сервера без остановки машины. Мы полагались на эти устройства, считали их фактически безотказными. В роли операционной системы выступала Unix-подобная система HP UX.
Но примерно с 2010 года возникло такое явление, как high-frequency trading (HFT), или высокочастотная торговля — попросту говоря, биржевые роботы. Всего за 2,5 года нагрузка на наши серверы увеличилась в 140 раз.

Выдерживать такую нагрузку со старой архитектурой и оборудованием было невозможно. Нужно было как-то адаптироваться.
Запросы к биржевой системе можно разделить на два типа:

Схематично ядро системы можно разделить на три уровня:
Ядро торговой системы представляет собой хитрую in-memory базу данных, в которой все транзакции — это биржевые транзакции. База была написана на С, из внешних зависимостей имелась только библиотека libc и полностью отсутствовало динамическое выделение памяти. Чтобы уменьшить время обработки, система запускается со статическим набором массивов и со статической релокацией данных: сначала все данные на текущий день загружаются в память, и дальше обращений к диску не выполняется, вся работа ведётся только в памяти. При запуске системы все справочные данные уже отсортированы, поэтому поиск работает очень эффективно и занимают мало времени в runtime. Все таблицы сделаны с интрузивными списками и деревьями для динамических структур данных, чтобы они не требовали выделения памяти в runtime.
Давайте вкратце пробежимся по истории развития нашей торгово-клиринговой системы.
Первая версия архитектуры торгово-клиринговой системы была построена на так называемом Unix-взаимодействии: применялись разделяемая память, семафоры и очереди, а каждый процесс состоял из одного потока. Этот подход был широко распространён в начале 1990-х.
Первая версия системы содержала два уровня Gateway и центральный сервер торговой системы. Схема работы была такая:
Фактически, здесь описана репликационная модель, в которой Gateway полностью повторял действия, выполняемые в торговой системе. Отдельный канал репликации обеспечивал один и тот же порядок исполнения транзакций на множестве узлов доступа.
Поскольку код был однопоточным, для обслуживания множества клиентов использовалась классическая схема с fork-ами процессов. Однако делать fork для всей базы данных было очень накладно, поэтому применялись легковесные процессы-сервисы, которые собирали пакеты из TCP-сессий и перекладывали их в одну очередь (SystemV Message Queue). Gateway и Trade Engine работали только с этой очередью, забирая оттуда транзакции на исполнение. Отправить в неё ответ уже было нельзя, потому что непонятно, какой сервис-процесс должен его прочитать. Так что мы прибегли к уловке: каждый fork-нутый процесс создавал для себя очередь ответов, и когда во входящую очередь приходил запрос, к нему сразу добавлялся тег для очереди ответов.
Постоянное копирование из очереди в очередь больших объёмов данных создавало проблемы, особенно характерные для информационных запросов. Поэтому мы воспользовались ещё одним трюком: кроме очереди ответов каждый процесс создавал также и общую память (SystemV Shared Memory). В неё помещались сами пакеты, а в очереди сохранялся только тег, позволяющий найти исходный пакет. Это помогло сохранять данные в кэш-памяти процессора.
SystemV IPC включает в себя утилиты для просмотра состояния объектов очередей, памяти и семафоров. Мы активно этим пользовались, чтобы понимать, что происходит в системе в конкретный момент, где скапливаются пакеты, что находится в блокировке и т. П.
В первую очередь мы избавились от однопроцессового Gateway. Его существенным недостатком было то, что он мог обрабатывать либо одну репликационную транзакцию, либо один информационный запрос от клиента. И с ростом нагрузки Gateway будет всё дольше обрабатывать запросы и не сможет обрабатывать репликационный поток. К тому же, если клиент отправил транзакцию, то нужно только проверить её валидность и переадресовать дальше. Поэтому мы заменили один процесс Gateway на множество компонентов, которые могут работать параллельно: многопоточные информационные и транзакционные процессы, работающие независимо друг от друга с общей областью памяти с применением RW-блокировки. И заодно внедрили процессы диспетчеризации и репликации.
Вышеописанная версия архитектуры просуществовала вплоть до 2010 года. Тем временем нас уже перестала удовлетворять производительность серверов HP Superdome. К тому же архитектура PA-RISC фактически умерла, вендор не предлагал никаких существенных обновлений. В результате мы стали переходить с HP UX/PA RISC на Linux/x86. Переход начался с адаптации серверов доступа.
Почему нам снова пришлось менять архитектуру? Дело в том, что высокочастотная торговля значительно изменила профиль нагрузки на ядро системы.
Допустим, у нас есть небольшая транзакция, которая вызвала значительное изменение цены — кто-то купил полмиллиарда долларов. Спустя пару миллисекунд все участники рынка это замечают и начинают давать коррекцию. Естественно, запросы выстраиваются в огромную очередь, которую система будет долго разгребать.

На этом интервале в 50 мс средняя скорость составляет около 16 тыс. транзакций в секунду. Если уменьшить окно до 20 мс, то получим среднюю скорость уже 90 тыс. транзакций в секунду, причём на пике будет 200 тыс. транзакций. Иными словами, нагрузка непостоянная, с резкими всплесками. А очередь запросов нужно всегда обрабатывать быстро.
Но почему вообще возникает очередь? Итак, в нашем примере множество пользователей заметили изменение цены и отправляют соответствующие транзакции. Те приходят в Gateway, он их сериализует, задаёт некий порядок и отправляет в сеть. Маршрутизаторы перемешивают пакеты и отправляют их дальше. Чей пакет пришёл раньше, та транзакция и «выиграла». В результате клиенты биржи стали замечать, что если одну и ту же транзакцию отправлять с нескольких Gateway, то шансы на её быструю обработку возрастают. Вскоре биржевые роботы начали забрасывать Gateway запросами, и возникла лавина транзакций.

После длительного тестирования и исследований мы перешли на real-time ядро операционной системы. Для этого выбрали RedHat Enterprise MRG Linux, где MRG расшифровывается как messaging real-time grid. Преимущество real-time-патчей в том, что они оптимизируют систему под максимально быстрое исполнение: все процессы выстраиваются в FIFO-очередь, можно изолировать ядра, никаких выбрасываний, все транзакции обрабатываются в строгой последовательности.

Красный — работа с очередью в обычном ядре, зеленый — работа в real-time ядре.
Но достичь низкого уровня задержки на обычных серверах не так просто:
Надо сказать, тема настройки железа и ядра Linux под realtime-обработку заслуживает отдельной статьи. Мы много времени потратили на эксперименты и исследования, прежде чем достигли хорошего результата.
При переходе с PA-RISC-серверов на x86 нам практически не пришлось сильно изменять код системы, мы лишь адаптировали и перенастроили её. Заодно поправили несколько багов. Например, быстро всплыли последствия того, что PA RISC являлась Big endian-системой, а x86 — Little endian: например, неправильно считывались данные. Более хитрый баг заключался в том, что PA RISC использует последовательно консистентный [2] (Sequential consistent [3]) доступ к памяти, тогда как x86 может переупорядочивать операции на чтение, поэтому код, абсолютно валидный на одной платформе, стал нерабочим на другой.
После перехода на х86 производительность выросла почти в три раза, средняя длительность обработки транзакции снизилась до 60 мс.
Давайте теперь подробнее рассмотрим, какие ключевые изменения были внесены в архитектуру системы.
Переходя на commodity-серверы мы отдавали себе отчёт, что они менее надёжны. Поэтому при создании новой архитектуры мы априори предполагали возможность выхода из строя одного или нескольких узлов. Поэтому нужна была система горячего резервирования, способная очень быстро переключиться на резервные машины.
Кроме того, были и другие требования:
При создании системы горячего резервирования мы не рассматривали такие сценарии, как двойные отказы (например, перестала работать сеть на одном сервере и завис основной сервер); не рассматривали возможность ошибок в ПО, потому что они выявляются в ходе тестирования; и не рассматривали неправильную работу железа.
В результате мы пришли к следующей схеме:



Схема работала следующим образом.
Допустим, главный сервер перестал отвечать, но Gateway продолжают взаимодействовать. На резервном сервере срабатывает таймаут, он обращается к Governor, а тот назначает ему роль главного сервера, и все Gateway переключаются на новый главный сервер.
Если главный сервер снова входит в строй, на нём тоже срабатывает внутренний таймаут, потому что в течение определённого времени к серверу не было обращений от Gateway. Тогда он тоже обращается к Governor, и тот исключает его из схемы. В результате биржа до конца торгового периода работает с одним сервером. Поскольку вероятность выхода сервера из строя достаточно низкая, такая схема считалась вполне приемлемой, она не содержала сложной логики и легко тестировалась.
Продолжение следует.
Автор: Московская Биржа
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/algoritmy/317547
Ссылки в тексте:
[1] PA-RISC: https://en.wikipedia.org/wiki/PA-RISC
[2] последовательно консистентный: https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%BA%D0%BE%D0%BD%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D0%BE%D1%81%D1%82%D1%8C
[3] Sequential consistent: https://en.wikipedia.org/wiki/Sequential_consistency
[4] Источник: https://habr.com/ru/post/444300/?utm_source=habrahabr&utm_medium=rss&utm_campaign=444300
Нажмите здесь для печати.