Как работают блокировки в High Load системах.

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

Особенность такой системы в том, что данные из финансовой системы поступают с некоторой сетевой задержкой, и в то время, когда выполняется запрос от клиента А, может прийти точно такой же запрос от клиента В, который еще не поступил в кэш. И в нашей системе по логике, раз нет данных в кэше, то придется повторить такой же запрос, который нам может стоить денег. Чтоб этого не произошло, необходимо как-то уведомить нашу систему, не повторять запрос, пока такой же запрос выполняется. Если система распределенная, то это должно быть какое-то централизованное устройство. Таким устройством и выступает сервер блокировок.

Обработка запроса

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

Введение в Apache ZooKeeper

В качестве сервера блокировок рассмотрим проект Apache ZooKeeper, который был изначально разработан компанией Yahoo как субпроект Hadoop. Что он собой представляет? Это иерархическое хранилище, в основу которого положена концепция, аналогичная файловой системе, доступ к узлам по пути. Такой узел называется znode (очевидно, сокращение от Zookeeper Node). Каждая znode может содержать данные и дочерние узлы. Существует корневая znode, от которой происходит ветвление других узлов.

ZooKeeper гарантирует:
> Последовательную консистентность – изменение данных от разных клиентов обрабатывается в соответствии с очередностью их поступления.
> Атомарность – изменение данных происходит в один момент времени только от одного клиента.
> Единый образ данных – клиент будет иметь доступ к одним и тем же данным, вне зависимости от сервера, к которому он подключен.
> Надежность – после обновления данных оно будет доступно с того времени, когда оно произошло.

Главная особенность ZooKeeper в том, что он в отличие от других систем хранения поддерживает концепцию обработчиков событий: watches. Каждый клиент может установить обработчик на события в любой znode (в соответствии с ограничениями ACL, но это тема отдельной статьи): удаление, добавление или изменение данных дочерних элементов. Когда наступает событие, клиент получает уведомление о том, что znode была изменена. Если соединение между клиентом и сервером было разорвано, то клиент также получает локальное уведомление. Механизм уведомлений реализуется на клиентской стороне.

Представление znode

На рис представлена схема двух дочерних узлов n-1 и n-2 узла con. Тогда путь до узла n-1 будет /con/n-1, а узла n-2 – /con/n-2. Для доступа к данным znode используются основные команды API:

  • getData – извлечение данных из znode;
  • setData – запись данных в znode;
  • getChildren – получение списка дочерних узлов;
  • create – создание дочернего узла;
  • delete – удаление дочернего узла;
  • exists – проверка наличия узла;
  • sync – ожидание, пока данные не изменятся (data to be propagated).

Существует два вида узлов:

  • персистентные (постоянные);
  • эфимерные, временные, которые существуют только в период пользовательской сессии.

Также узел может иметь свойство последовательности. Это аналогично автоинкрементному полю в MySQL. Нами задается префикс, например «n-», а далее при создании нового узла у него будет меняться суффикс, с увеличением на единицу, который представляет 16-битное целое число: 00000001, 00000002…

Начало работы

Прежде чем перейти к экспериментам, необходимо развернуть ZooKeeper. Обычно для экспериментов я использую docker-образ:

В случае если вы действительно собираетесь использовать ZooKeeper, то его можно проинсталлировать на сервере:

Для Ubuntu:

Для RHEL/CentOS/Oracle Linux:

Или просто скачать пакет ZooKeeper с сайта проекта и запустить:

Запуск проекта:

Проверить можно через консольную утилиту zkCli:

Серверная консоль. API

Консольный клиент в docker находится в /opt/zookeeper/bin, а при инсталляции ZooKeeper на сервере в /usr/share/zookeper. Запускаем zkCli.sh и команда help покажет нам доступные команды. Рассмотрим наиболее используемые:

  • ls path – аналог команды ls в операционной системе, показывает список дочерних узлов;
  • get path – показывает данные узла по указанному пути;
  • create path data – создает узел по указанному пути и записывает туда данные;
  • set path data – изменяет данные существующего узла;
  • sync path – проверка события;
  • delete path – удаляет указанный узел.

Создадим узел /con и в нем пару узлов n-1 и n-2 (см. рис. 2):

Created /con

Теперь посмотрим, что создали:

Первая строка – это те данные, которые мы записали, строки ниже – служебная информация, которая нам тоже может пригодиться. Мы также можем сделать команды get для /con/n-1 или /con/n-2. Начнем следить за узлом /con:

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

На практике это нам пока ничего не дает, но это нужно для понимания того, что происходит на сервере координации.

Программные интерфейсы

Существует множество клиентских библиотек для популярных языков программирования, из которых мы рассмотрим только РНР (pecl.php.net/zookeper)  и Python (kazoo). Эти библиотеки требуют инсталляции Cи библиотеки libzookeeper.

Для инсталляции python-библиотек достаточно сделать:

Для инсталляции РНР-библиотеки:

Посмотрим список дочерних узлов по пути /con (ex1.py):

Посмотрим данные узла /con:

Мы можем создавать узлы (zk.create), изменять данные узла (zk.set), проверять их наличие (zk.ensure_path) и удалять (zk.delete). Но самое интересное и зачем это все задумывалось – это отслеживать события. Создадим watcher (ex2.py):

Когда мы с помощью консольного клиента изменяем дочерние узлы в /con, например удаляем, то запущенная нами программа выводит следующее сообщение:

Необходимо заметить, обработчик срабатывает только один раз, потом его необходимо перезапустить, т.е. добавить в процедуру обработки строку, которая перезапускает обработчик:

Эфемерные узлы

Как уже упоминалось, есть узлы постоянные, которые остаются после окончания пользовательской сессии и даже после перезапуска ZooKeeper. А есть так называемые эфемерные узлы, которые существуют только на протяжении пользовательской сессии. Создание такого узла в консольном клиенте осуществляется с опцией -e:

Теперь запустим нашу программу ex2.py, а потом сделаем отсоединение:

В том терминале, где была запущена программа ex2.py, мы наблюдаем вызов обработчика. Если мы теперь снова приконнектимся и просмотрим список узлов, то увидим, что узел e1 отсутствует:

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

Последовательные узлы

Кроме того, у узлов может быть установлено свойство: sequence (последовательность).

Зачем нам могут понадобиться последовательные (автоинкрементные) узлы? Мы можем использовать узлы как, например, элемент очереди.

Примеры взаимодействия

Рассмотрим следующую архитектуру: наш веб-скрипт обращается за данными к одному из демонов, работающему на удаленном хосте. Каждый из запущенных демонов будет хранить в ZooKeeper элементы конфигурации, например IPадрес хоста, на котором он был запущен.

Ниже представлен элемент Python-программы, которая записывает свою конфигурацию в /config (ex3.py):

Далее представлен элемент программы на РНР, которая считывает конфигурацию и выводит один из IP-адресов хоста, на которых запущены Python-программы (ex4.php):

Также наш скрипт должен отслеживать, не изменилась ли конфигурация, это делается при выборе всех дочерних узлов из /config.

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

Как еще могут использоваться блокировки?

Пусть Процесс 1 должен через внешний ресурс, например кэш, передать информацию. Процесс 2 также должен использовать этот ресурс. Для разделения ресурса используется блокировка. Когда процесс осуществил захват ресурса r1, он создает эфемерный последовательный узел /resource/r1/n-, указывая в качестве данных свой pid. Другой процесс читает список дочерних узлов /resource/r1/*, и если этот список пустой, то данный ресурс (r1) свободен и готов для использования.

В данной статье представлено обозрение лишь небольшой части того, как можно засинхронизировать исполнение процессов в распределенных системах. Более полную информацию можно подчерпнуть на сайте проекта.

Если ваш интернет проект требует обработки больших обьемов информации и вы не знаете с чего начать, мы с радостью вам поможем. Наша команда имеет огромный опыт в работы с High Load & Big Data решениями.

Рейтинг материала
[Голосов: 1 Рейтинг: 5]
21 февраля 2017, 05:48

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *