Всем привет! Меня зовут Николай Висков, я лидер направления разработки протокола доступа Биржи, работаю в блоке развития торгово‑клиринговых систем, занимаюсь разработкой высоконагруженных систем.

Лирическое отступление

Существует два фундаментальных подхода к доставке данных: уникаст — передача «точка‑точка», и мультикаст — передача «один‑ко‑многим». Подавляющее большинство данных в локальных и глобальных сетях ходит по TCP, уникасту. У большинства программистов сложилась четкая ассоциация: если нужно устроить сетевое взаимодействие, то используем TCP. Но в финансовой сфере на этот счёт мы привыкли думать по‑другому. Если требуется построить систему с минимальным временем отклика, но при этом с высокой пропускной способностью и надежной доставкой, то мультикаст годится лучше.

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

Может показаться, что мультикаст должен работать медленнее уникаста. Транслировать несколько сообщений вместо одного, это, наверное, не быстро? Однако это не так. На физическом уровне, поток фотонов, как и электрический сигнал, могут бесплатно «раздваиваться». Когда в солнечный день на стене у нас висит пятно солнечного зайчика, мы можем поставить призму и сделать два из одного. Или три... При этом распространяться «зайцы» будут по‑прежнему: (почти) со скоростью света.

Где мультикаст используется на Бирже: 4 способа

Итак, зачем мультикаст нужен нам на Бирже? Попробую описать несколько случаев, где мы его используем:

1. Самое очевидное — это распространение публичной рыночной информации.

Биржа раздает такие данные посредством IP/UDP мультикаст по протоколам SIMBA (от SIMpleBinAry) и FAST (от FIX Adapted for STreaming). Это биржевой стандарт, так делают все и это наиболее честный способ уведомлять о заявках и сделках на бирже. Благодаря мультикасту все участники в зоне колокации получают данные одновременно. Как правило, это два фида обычного IP/UDP мультикаста с возможными потерями и переупорядочиванием.

2. Быстрое и надежное резервирование сервисов.

Предположим, в упрощенной схеме ТКС есть несколько компонентов: Шлюз → Диспатчер → Риски → Матчинг → Снова Шлюз. Если компонент детерминирован, то надежный мультикаст в такой схеме позволяет резервировать сервис просто добавив его копию и сделав дедупликацию исходящих сообщений.

3. Мониторинг потоков данных.

Биржа — это среда (очень) строгой отчетности. Поэтому хорошей практикой считается захватывать весь трафик на входе и выходе каждого из компонентов ТКС. Трафик захватывается на уровне сети в виде Packet Capture там, где это возможно. Также он подслушивается специальными сервисами, которые подписываются на мультикаст группы и сохраняют все данные в виде текстовых или бинарных файлов.

4. Раздача ExecLog микросервисам.

Хорошим подходом к архитектуре считается разнести бизнес‑логику на некоторое количество слабо связанных сервисов. На бирже основным источником данных для таких сервисов является поток заявок и сделок, распространяемый из ядра торговой системы. Такой поток содержит всю необходимую информацию для генерации всех сообщений ExecutionReport по стандарту протокола FIX и потому называется ExecLog. Таким образом, мы имеем один высоконагруженный поток данных и много потребителей для него. Мультикаст в таком случае — естественный выбор, очень хорошо ложится в нашу схему. При этом от источника до получателя будет всего один транзитный участок (hop). Никакой мессаджинг с брокером сообщений не сможет предложить более короткое время отклика.

Я вижу у способа распространения данных один‑ко‑многим без потерь пакетов такое количество преимуществ, что мне кажется странным то, что надежный мультикаст так слабо распространен за пределами финансовой сферы. На собеседованиях в крупные IT компании на солидную должность, как правило, просят пройти этап по архитектуре ПО. Там наверняка предложат спроектировать архитектуру надежного сервиса, который будет обрабатывать запросы клиентов. Скорее всего, как правильный ответ примут архитектуру, которая будет включать использование очереди сообщений с брокером: Kafka, RabbitMQ или SQS. Вспоминают ли в таких случаях про архитектуру с использованием раздачи сообщений по мультикасту? Я сомневаюсь.

Нигде, ни на HighLoad, ни здесь на Хабре, я не сталкивался с тем, чтобы за пределами финансовой сферы решили использовать месседжинг без брокера, на основе мультикаста.

Нужен ещё один способ!

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

От транспорта мы ожидали получить:

1. Низкое и что еще важнее предсказуемое, ровное время отклика.

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

3. Возможность отправить сообщение один‑ко‑многим, не переплачивая за каждого дополнительного слушателя.

При этом внутренняя сеть Биржи построена на основе RDMA сети, а значит нам нужна поддержка RDMA UD на нативном уровне. RDMA — это прямой доступ к оперативке и тотальная минимизация задержек, что для обработки транзакций на бирже необходимо как воздух.

Первым делом мы провели RFI, тендер на разработку ПО, но желающих сделать нам такой софт не нашлось вообще. Слишком сложно?

Тогда мы решили поэкспериментировать с OSS. Так как нам требовался надежный мультикаст, то мы смотрели на реализации протокола PGM.

Мне особенно был близок PGM, так как когда‑то именно благодаря статье о этом протоколе я получил свой инвайт на Хабр.

Мы нашли несколько подходящих OSS решений, а после тестирования и сравнения остановились на OpenPGM. В нем нас устраивало все, кроме отсутствия поддержки RDMA. Но добавить RDMA, как оказалось, задача решаемая.

PGM был спроектирован под IP‑мультикаст, это прямо прописано спеке. То есть, если мы любым способом настроим PGM поверх RDMA UD, мы отступим от канонов, заложенных в протоколе. В спецификации PGM есть пункты, которые плохо совместимы с технологией RDMA. Например, приведу выдержку из спеки про использование портов:

Data‑Source Port:

A random port number generated by the source. This port number

MUST be unique within the source. Source Port together with

Global Source ID forms the TSI.

Data‑Destination Port:

A globally well‑known port number assigned to the given PGM

application.

Здесь дело в том, что технология RDMA использование портов не предполагает, хотя это реализовано в надстройке RDMA_CM. При этом, нам очень хотелось избежать применения RDMA_CM, так как это будет вносить дополнительные задержек при сопоставлении адресов с сетевыми устройствами.

RDMA_CM — это слой, который связывает привычную нам адресацию сети и интерфейс IBverbs. Вызовы RDMA_CM API как правило начинаются с префикса rdma, ibverbs начинаются с ibv. При этом IBverbs ничего не знает о RDMA_CM. Получается, что IBverbs — это базовая часть RDMA, а RDMA_CM — это более высокий уровень абстракции.

В нашей реализации Data‑Destination Port игнорируется, а Data‑Source Port по‑прежнему является частью TSI. Надо оговориться, что это просто номер, который не указывает на реальный порт или сокет.

В общем, пришлось отступать от спеки там, где иначе переписать транспорт на RDMA было невозможно.

IP адресов, кстати, в RDMA тоже нет, но это меньшая проблема, так как есть прямой аналог — GID.

Посмотрим на результаты

На данный момент запустить OpenPGM по RDMA сетям нам удалось. Получившиеся результаты — одного порядка с показателями транспортного слоя, который реализован в торгово‑клиринговых системах сейчас. Получившееся решение удовлетворяет нас и по функциональности, и по оценкам производительности.

Для тестирования мы написали несколько бенчмарков, которые эмулируют трафик работы торговой системы.

Локальный бенчмарк

Локальный бенчмарк без сетевого оверхэда обрабатывает 8 сообщений по 100 байт за 10 микросекунд для RDMA транспорта, против почти 20 микросекунд для UDP мультикаста.

В бенчмарке замеряется время каждой итерации (Time) и отдельно время затраченного процессорного времени (CPU). Разница между Time и CPU для Posix составляет почти четверть всего времени, а для RDMA эти два значения почти совпадают.

Это наглядно демонстрирует то, насколько RDMA интерфейс меньше времени проводит в ожидании и более эффективен.

Скрытый текст

Running rapid/mq.bench.perf.t

Run on (56 X 3500 MHz CPU s)

CPU Caches:

L1 Data 48 KiB (x56)

L1 Instruction 32 KiB (x56)

L2 Unified 1280 KiB (x56)

L3 Unified 43 008 KiB (x2)

Load Average: 5.36, 6.53, 7.13

‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑-

Benchmark Time CPU Iterations UserCounters...

‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑--‑-

MQBench/posix/pgm/100/8 19 891 ns 14 952 ns 46 245 bytes_per_second=51.0272M/s items_per_second=535.059k/s

MQBench/posix/pgm/100/64 148 981 ns 110 035 ns 6330 bytes_per_second=55.469M/s items_per_second=581.635k/s

MQBench/posix/pgm/500/8 21 334 ns 16 285 ns 42 363 bytes_per_second=234.248M/s items_per_second=491.253k/s

MQBench/posix/pgm/500/64 158 560 ns 119 189 ns 5841 bytes_per_second=256.043M/s items_per_second=536.962k/s

MQBench/rdma/pgm/100/8 10 022 ns 9998 ns 68 569 bytes_per_second=76.3116M/s items_per_second=800.185k/s

MQBench/rdma/pgm/100/64 57 812 ns 57 657 ns 11 689 bytes_per_second=105.858M/s items_per_second=1.11M/s

MQBench/rdma/pgm/500/8 11 044 ns 11 018 ns 61 158 bytes_per_second=346.237M/s items_per_second=726.111k/s

MQBench/rdma/pgm/500/64 65 155 ns 64 991 ns 7730 bytes_per_second=469.568M/s items_per_second=984.756k/s

Пинг‑понг

Еще один бенчмарк — это пинг‑понг по RDMA‑сети. Нам удалось достичь пропускной способности транспорта 500 000 сообщений в секунду с RTT менее 5 микросекунд. То есть на сетевой hop приходится около 2.5 микросекунд времени отклика.

2.5 микросекунды — это все еще существенно больше, чем показывают штатные утилиты RDMA из rpm пакета perftest. Оверхэд тестируемого решения на основе OpenPGM получается около 1 мкс на hop, а значит, нам есть еще что оптимизировать.

Скрытый текст

mq_ping_lenovo1_pgmib 10 000 events 9911 RTT 5016ns

mq_ping_lenovo1_pgmib 50 000 events 48 064 RTT 4992ns

mq_ping_lenovo1_pgmib 100 000 events 92 173 RTT 5004ns

mq_ping_lenovo1_pgmib 200 000 events 170 993 RTT 4941ns

mq_ping_lenovo1_pgmib 400 000 events 306 109 RTT 4910ns

mq_ping_lenovo1_pgmib 500 000 events 360 313 RTT 4849ns

mq_ping_lenovo1_pgmib 600 000 events 403 607 RTT 5008ns

mq_ping_lenovo1_pgmib 700 000 events 435 860 RTT 4939ns

mq_ping_lenovo1_pgmib 800 000 events 495 852 RTT 4858ns

Вместо заключения

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

OpenPGM — это проект под лицензией GPL, а значит при его использовании и распространении нам придется выложить все внесенные изменения в OSS. Так что вполне возможно, что однажды у Биржи появится собственная открытая реализация ultra‑low‑latency messaging поверх RDMA. И, возможно, именно провалившийся тендер оказался лучшим вариантом развития событий.