Система кэширования zero-cache

в 18:48, , рубрики: c++, nosql, Песочница, я пиарюсь, метки: , ,

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

Задача

Дано:
Обычный сервер с x86-64 совместимым процессором подключенный по каналам RS-485 (через USB преобразователи) к большому числу контроллеров. Контроллеры занимаются приемом и выдачей разовых команд, вращают шаговые двигатели, оцифровывают аналоговые сигналы (АЦП). Кроме того, к серверу могут быть подключены различные PCI платы (аналоговые, ARINC, MIL-STD и пр.), которые также выдают и принимают данные с внешних устройств.

На сервер по Ethernet каналу каждые 20 мс приходит пакет с текущими значениями параметров для выдачи их на контроллеры и платы. От сервера каждые 20 мс должен уходить пакет со считанными контроллерами и платами входными параметрами. Задержка выдачи новых параметров на все контроллеры должна составлять не более 20 мс.

Кроме основного Ethernet канала, есть дополнительный канал по которому сервер выдает свое состояние (работает или выключен) и получает команды на перезагрузку и выключение.

На сервер ставится не ОС реального времени, а Linux. Выдерживание заданных временных задержек обеспечивается высокой производительностью сервера.

Задача:
Реализовать серверное ПО для обработки всех информационных потоков — для всех контроллеров, PCI плат и обоих Ethernet каналов. Общее число таких информационных потоков порядка 16 штук.

Решение средствами C++

Самое простое решение поставленной задачи (которое и было сначала реализовано) представляет собой многопоточное C++ приложение. Каждый поток обрабатывает конкретный канал RS-485, PCI плату или Ethernet порт. Информация между потоками передается через синглетон, инкапсулирующий STL map контейнер с ключами и объектами, представляющими параметры. Синхронизация между потоками осуществляется с помощью мьютексов, на уровне каждого отдельного параметра.

Достоинства:

  1. Быстрый доступ к параметрам из любого потока благодаря единому адресному пространству.

Недостатки:

  1. Сложная архитектура приложения — если меняется интерфейс общего для всех потоков класса (например класса «параметр» из контейнера map), это влечет за собой исправления по всему проекту.
  2. Данные из общего контейнера недоступны для внешних приложений — со временем встал вопрос диагностики работы системы и получения всех текущих значений параметров по дополнительному Ethernet каналу.
  3. Низкая надежность системы — при ошибке в одном из потоков падает все приложение.

Решение на основе БД

Для устранения существующих недостатков появилась идея хранить все общие параметры в БД MySQL вместо глобального синглетона. Такое решение позволило бы вместо одного процесса со множеством потоков реализовать отдельные независимые процессы для каждого информационного канала. Кроме того данные из БД стали бы легко доступны любому внешнему приложению, в том числе удаленно через дополнительный Ethernet канал. Но задержки на запись и чтение в БД MySQL оказались неприемлемо велики (в среднем порядка 100 мс на используемом оборудовании).

С целью ускорить доступ к данным была попытка использовать БД SQLite, файл которой размещается в разделе RAM. Задержки на операции чтения-записи сократились и стали порядка 20 мс. К сожалению, существовали пики в длительности задержек, связанные с попыткой одновременного доступа к данным из нескольких процессов (при работе одного процесса таких пиков не наблюдалось).

Решение на основе memcached

Дальнейшие поиски более быстрой системы хранения данных привели к системе кэширования memcached. Эта система показала практически такие же результаты как БД SQLite размещенная в разделе RAM — порядка 20 мс и пики до 50 мс.

Специализированное решение

Узким местом всех использованных ранее решений был единственный сокет системы БД или кэширования в который писали и читали данные сразу несколько процессов. В результате блокировок оказывалось, что операции доступа к данным происходят слишком медленно.

Чтобы устранить это узкое место появилась идея разнести параметры по разным сокетам. Каждый сокет должен обслуживаться отдельным потоком серверного приложения системы кэширования. Получаемые от клиентов запросы формируют очередь для каждого сокета, которая разбирается последовательно. Таким образом в каждый момент времени к одним и тем же данным в кэше имеет доступ единственный поток (который отвечает за данный ключ), обрабатывающий очередь запросов в порядке FIFO.

Для распределения параметров по разным сокетам используется служебный сокет сервера. Каждый клиент в начале сеанса связи высылает на служебный сокет имя ключа, котрый надо записать в кэш, а также данные для обратной связи (свой порт и хост). Сервер получив такой запрос проверяет существует ли указанный ключ в кэше. Если нет — ключ приписывается либо к существующему сокету, либо создается новый сокет, если лимит ключей у всех существующих сокетов достигнут. После этого сервер через служебный сокет сообщает клиенту новый сокет по которому следует вести весь дальнейший обмен. В результате каждый клиент общается только с сокетом, который обслуживает его ключи. При попытке чтения ключа, сервер ищет сокет к которому приписан требуемый ключ и выдает клиенту номер этого сокета.

image
Схема взаимодействия клиентов с системой кэширования.

  • Server — процесс системы кэширования на стороне сервера.
  • Client — процесс на стороне клиента. Клентские процессы могут работать на одном и том же хосте, либо на различных хостах.
  • Registrar — серверный объект для регистрации новых ключей и выдачи клиентам номеров сокетов для работы с контейнерами. Также объект Registrar создает новые контейнеры по мере необходимости.
  • Reactor #1, Reactor #2 — серверные объекты представляющие собой контейнеры хранящие определенные наборы ключей. Каждый контейнер доступен через отдельный сокет.
  • RegistrarClient — клиентский объект для отправки запросов регистрации ключей на сервер и получения номеров сокетов соответствующих контейнеров.
  • Client #1, Client #2 — клиентские объекты для обеспечения доступа к контейнерам Reactor. Каждый объект Client используется для доступа только к одному контейнеру.

Достоинства:

  1. Устранено узкое место в системе хранения, благодаря чему сократились задержки на доступ к данным.
  2. Гибкая настройка под конкретную задачу и оборудование — лимит ключей приписанных к каждому сокету настраивается.
  3. Поддержка как Unix сокетов, так и TCP сокетов. Выбор зависит от требований по производительности и задачи.

Недостатки:

  1. Задействуется большое количество файловых дескрипторов в системе (при использовании Unix сокетов) или большое количество портов (для TCP сокетов).
  2. Дополнительная нагрузка на процессор, связанная с работой нескольких потоков, обрабатывающих разные контейнеры.

Заключение

В результате использования специализированной системы кэширования удалось добиться средней задержки на операции чтения и записи порядка 1 мс при пиковых значениях до 10 мс.

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

Ссылки

www.zero-cache.org — сайт пректа описанной специализированной системы кэширования
www.zeromq.org — библиотека на базе которой реализована эта система кэширования

Автор: MegaPeter

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js