Skip to content

Latest commit

 

History

History
73 lines (49 loc) · 15.7 KB

File metadata and controls

73 lines (49 loc) · 15.7 KB

Тестовое задание на знание С++, IQ Option

Данный проект является результатом моей работы над тестовым заданием, полученным в компании IQ Option. Суть проекта - разработка микросервиса для формирования и выдачи рейтинга успешных торговых операций на базе получаемых от центральной системы сообщений.

Структура файлов проекта

Файлы разбиты на несколько основных каталогов по функциональному признаку:

  • ipc - модули, отвечающие за взаимодействие с клиентом сервиса (уровень протокола и транспорта)
  • service - файлы непосредственно самого сервиса, содержащие модули с его бизнес-логикой
  • utils - небольшие вспомогательные модули-утилиты (работа с датами/временем, сериализация и т.п.)
  • lib - файлы сторонних библиотек. В проекте используется единственная внешняя библиотека ASIO, с помощью которой в мультиплатформенном стиле осуществляется работа с транспортным уровнем (сокетами)
  • test - отдельное приложение, предназначенное для тестирования сервиса. В отличие от самого сервиса, имеет ряд явных недоработок и по умолчанию создавалось с меньшим вниманием к качеству. Пользоваться этим приложением можно, но не обязательно.

Архитектурные решения

Мультиплатформенность

Целевой платформой для сервиса по условиям задания был Linux, но я писал код в максимально общем стиле, чтобы избежать привязки к платформе. Весь код, за исключением работы с транспортным уровнем, написан с использованием лишь стандартных средств С++1z, а для работы с сокетами используется популярная мультиплатформенная библиотека ASIO.

Транспорт

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

Протокол

Протокол взаимодействия с сервисом построен на обмене сообщениями в бинарном формате. Структура сообщений подразумевает максимальную компактность и минимум операций на сериализацию/десериализацию.

Ядро

По условиям задания, требовалось обеспечить функционирование сервиса в рамках конкретной рабочей сессии, персистентное хранение сессии реализовывать не требовалось. Поэтому ядро сервиса представляет собой набор стандартных контейнеров С++, а также методов, позволяющих добавлять и извлекать из этих контейнеров данные.

Производительность

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

Ядро работает на базе четырёх потоков, причём синхронизация между ними сведена к необходимому минимуму и реализована с помощью атомарных операций. Потоки обмениваются сообщениями через де-факто неблокирующую очередь. При работе с памятью использован подход, обеспечивающий минимизацию избыточных реаллокаций памяти за счёт переиспользования уже ранее выделенных буферов.

Целевые метрики загрузки

Так как конкретных условий, с которыми данный сервис должен был бы справляться, озвучено не было, в качестве ориентиров по загрузке были взяты цифры о количестве пользователей и совершаемых сделок, опубликованные на сайте IQ Option. Так как последние имевшиеся на момент проектирования цифры относились к 2016 году, я экстраполировал их с примерным сохранением трендов и пропорций. Итого, сервис рассчитан на:

  • 30 миллионов зарегистрированных пользователей
  • ..., из которых 1.5 миллиона постоянно подключены к сервису
  • ... и генерируют 5000 выигранных сделок в минуту. Данные цифры не являются абсолютным пределом и не прошиты в коде сервиса. Но они использовались для принятия архитектурных решений и выделения возможных узких мест с целью оптимизации оных.

Компромиссы

С целью обеспечения требуемой производительности и пользуясь данным мне карт-бланшем в выборе архитектурных решений, мною были внедрены следующие компромиссы реализации:

  • Пользовательский рейтинг обновляется раз в календарную минуту - в рамках одной минуты информация о новых сделках будет агрегироваться сервисом, но не будет учитываться в рейтинге до наступления следующей минуты.
  • Привязка сделок ко времени выигрыша осуществляется не на основе приходящей от центральной системы метки, а по факту получения сообщений сервисом. Точность привязки - одна минута. Это может внести неточность в абсолютные цифры пользовательских выигрышей (выигрыш, полученный в последнюю минуту календарной недели, может частично быть учтён в рамках следующей недели), но существенной роли не играет и не приводит к потере какой-либо информации о торговых операциях.
  • В силу существенного объёма трафика, который предполагается получать от сервиса (даже если за неделю хоть одну сделку совершат всего 3 миллиона человек, то с учётом формата рейтинга "топ-10 + 10 до тебя + 10 после тебя" мы имеем 90 миллионов сообщений с пользовательским рейтингом в минуту) было принято решение отказаться от передачи имён пользователей вместе с рейтингом и предположить, что база пар id/имя уже присутствует в ядре системы. Тем не менее, на случай если данный функционал абсолютно критичен и не может быть исключён, код сервиса может быть перекомпилирован с опцией препроцессора PASS_NAMES_AROUND, включающей поддержку имён пользователей на уровне протокола и ядра.

Возможности для дальнейшей оптимизации

Целью работы над данным заданием было продемонстрировать умения владения языком С++ и стандартной библиотекой, а также способность принимать грамотные архитектурные решения. Тем не менее, в силу ограниченности времени и ресурсов данный код не претендует на промышленный уровень качества. В качестве аспектов, где могли бы быть внедрены улучшения, вижу следующие моменты:

  • Внедрение асинхронности работы с транспортом. Сейчас чтение и запись в сокеты - блокирующие, что сделано для упрощения структуры всей системы (с учётом того, что главной целью задания было построение архитектуры ядра, вопрос транспорта данных был реализован максимально незатейливо, но с возможностью дальнейшей модернизации минимальными усилиями), из-за чего задержки на стороне клиента приводят к задержкам внутри микросервиса (правда, в "боевых" условиях с миллионами сообщений в минуту задержкам неоткуда взяться).
  • Отказ от стандартных STL-контейнеров и переход на их более оптимизированные аналоги, предоставляемые сторонними библиотеками.
  • Замена уровня транспорта с TCP сокетов на UNIX domain сокеты. Тем не менее, для работы над тестовым заданием были выбраны именно обычные TCP сокеты как дающие возможность тестировать этот код на любой платформе, не только на UNIX-совместимой.
  • Профилировка приложения и определение оптимального количества потоков обработки сообщений и генерации ответов. Как следствие, может потребоваться изменение механизма синхронизации между потоками.
  • Более точная работа с системным временем. На данный момент стандартные средства работы со временем, предложенные С++1z, приводят к быстрой рассинхронизации между клиентом и сервером (если предположить, что клиент также ведёт свою копию рейтинга с секундной точностью).
  • Углублённая проработка протокола, добавляющая в него дополнительные возможности (синхронизация по времени между клиентом и сервисом, поддержка нескольких клиентов, контроль доступа к данным сервиса и т.д.).
  • Использование более функциональных механизмов логгирования и контроля за внутренним состоянием, чтобы отслеживать возможные ошибки или параметры нагрузки.

Инструменты для сборки

Для сборки проекта мной использовалась связка IDE CLion, встроенной в него среды сборки CMake 3.9 и toolchain MinGW-64 7.2.0. За отсутствием альтернатив сервис собирался и тестировался на домашней Windows-машине, но его код и библиотеки никак не завязаны на Windows, поэтому собрать сервис можно под любую платформу, для которой существует связка asio+gcc+cmake.

Запуск и использование

Чтобы запустить сервис, нужно передать ему через параметр командной строки номер порта, который он будет слушать. В качестве клиента можно воспользоваться приложением test или написать свой клиент на базе классов для клиентских сообщений, реализованных в рамках протокола. Однако ещё раз напоминаю, что, в отличие от самого сервиса, за код приложения test я ответственность нести не готов, т.к. данная программа предназначалась сугубо для внутреннего пользования, и уж никак не в режиме production.


Спасибо за внимание! Надеюсь, вы оцените мои усилия по достоинству.