пятница, 28 ноября 2014 г.

21.1 ZeroMQ: рабочий пример. Межброкерная маршрутизация. Разбор задачи.

(Начало - здесь).


Постановка задачи.

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

Нам эту задачу решить - как два байта переслать. 

Разберем задачу, используя ZeroMQ.

Хотя, конечно, сначала не мешает разобраться.

Подробности


Вот что рассказал заказчик:
  • Рабочие процессы запускаются на оборудовании разного типа, но каждый из рабочих может выполнять любую задачу. В каждом кластере несколько сотен рабочих, а таких кластеров - несколько десятков.
  • Клиенты порождают задачи для рабочих. Каждая задача есть независимая единица работы. Каждый клиент хочет найти незанятого рабочего, чтобы как можно скорее отправить тому задание. Клиентов будет много, и они будут появляться и пропадать в произвольном порядке.
  • Реальную трудность представляет собой  реализация задачи добавления и удаления кластеров в любой момент времени. Кластер может уйти или присоединиться к облаку мгновенно, вместе со всеми своими клиентами и рабочими.
  • Если в своем кластере нет свободных рабочих, клиентские отправляются другим свободным рабочим облака.
  • Клиенты отправляют одно задание за раз и ждут ответа. Если они не получают ответ в течении X секунд, они просто посылают задачу снова. Это не наша забота, это уже делает API клиента.
  • Рабочие очень простые, они обрабатывают одновременно только одно задание. В случае краха они перезапускаются скриптом, который их стартовал.
На всякий случай, чтобы убедиться, что мы все правильно поняли, еще раз переспросим у клиента:
  • "Между кластерами будет какое-нибудь супер-пупер сеть, верно?" Клиент ответил: "Да, конечно. Мы же не идиоты"
  • "О каких объемах данных идет речь?" . Клиент ответил: "До тысячи клиентов на кластер, и каждый делает не менее десяти запросов в секунду. Запросы маленькие, ответы тоже, не более 1Kb каждый."
Посчитаем и посмотрим, будет ли все это работать поверх обычного TCP. 2500 клиентов x 10/сек x 1000 байт x 2 два направления = 50MB/сек или 400Mb/сек, т.е. не проблема для 1Gb сети.

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

Архитектура одного кластера.

Рабочие и клиенты синхронны. Для распределения задач между рабочими используем схему "Балансировка нагрузки". Рабочие все идентичны; мы не имеем ни малейшего понятия о различных услугах. Рабочие - анонимны. Клиенты никогда не обращаются к рабочим напрямую. Мы не реализуем гарантированную доставку, просто повтор, снова и снова...

По уже описанным причинам, клиенты и рабочие не будут общаться напрямую. Это сделает невозможным динамически добавлять у удалять узлы. Итак, базовая модель представляется собой уже знакомый нам брокер сообщений "Запрос-Ответ".

Архитектура кластера:


Масштабируем до нескольких кластеров.

Каждый кластер содержит набор клиентов, рабочих и брокера. Теперь соединим брокеров:

Множество кластеров:


Вопрос: как сделать, чтобы клиенты одного кластера общались с рабочими другого кластера? Есть несколько вариантов решений, у каждого свои плюсы и минусы:
  • Клиенты могут подключаться напрямую к обоим брокерам. Плюс в том, что нут нужды модифицировать ни  брокеров, ни рабочих. Но клиенты становятся более сложными и зависимыми от топологии. Если, к примеру, добавим третий или четвертый кластер, то пострадают все клиенты. По сути, мы перемещаем логику маршрутизации и отказоустойчивости на клиентов, что не очень хорошо.
  • Рабочие могут подключаться напрямую к обоим брокерам. Но рабочие с сокетами REQ не могут так делать, они могут ответить только на один запрос одного брокера. Мы могли бы использовать сокеты REP, но REP не дают возможности реализовать настраиваемое распределение брокер - рабочий так, как мы делаем для балансировки нагрузки, а лишь только использовать встроенную балансировку нагрузки. Не годится. Если мы хотим распределять работу среди простаивающих рабочих, нам точно нужна балансировка нагрузки. Одним из решений было бы использование сокетов ROUTER для узлов с рабочими. Пусть это будет идеей № 1.
  • Брокеры могут соединяться друг с другом. Это решение выглядит более опрятно, так как создает совсем немого дополнительных соединений. Мы не можем добавлять кластеры на лету, но, возможно, мы решим это позже. Теперь клиенты и рабочие остаются в неведении о реальной топологии  сети, а брокеры рассказывают друг другу об имеющихся у них свободных мощностях. Пусть это будет идеей № 2.
Рассмотрим идею №1. В этой модель рабочие соединяются с обоими брокерами и получают задания от любого из них.



 Выглядит вполне выполнимо.  Однако, не обеспечивается то, что мы хотели: клиенты должны получать возможность нагружать по возможности локальных рабочих, а удаленных - только тогда, когда это будет лучше, чем ожидание, когда локальные рабочие освободятся.
Также рабочие будут сообщать "готов" обоим брокерам, и получать два задания одновременно, тогда как другие будут простаивать. Похоже, идея не очень удачная, так как мы снова смещаем логику маршрутизации на края топологии.

Идея № 2.Соединяем между собой брокеров и не трогаем клиентов и рабочих, которые привычно используют сокеты REQ.

Брокеры общаются каждый с каждым.


Более привлекательная конструкция., так как проблема решена в одном месте и невидима для для всего остального мира. Брокеры как бы открыли секретные каналы каждый с каждым и общаются шепотом, как караванщики: "Эй, у меня есть некоторые свободные мощности. Если у тебя слишком много клиентов, крикни мне, и мы замутим сделку".

По сути, это просто более сложный алгоритм маршрутизации: брокеры становятся субподрядчиками друг друга. Есть и другие причины, чтобы этот вариант нравился, о которых стоит упомянуть, пока мы не начали кодировать:
  • По умолчанию загружены локальные компоненты (клиенты и рабочие одного кластера), а работой за пределами кластера является исключительным случаем (перетасовка  заданий между кластерами).
  • Можно использовать разные потоки сообщений для  различных типов работы. Имеется в виду, что мы можем обрабатывать их по-разному, например, использовать разные типы сетевых соединений.
  • Похоже на то, что масштабировать будет несложно. Межконнектинг трех или более брокеров не создаст большой сложности. А если это станет проблемой, её будет очень легко решить, добавив супер-брокера.
Отлично, теперь сделаем рабочий пример. Упакуем весь кластер в один процесс. Конечно, это будет не по-настоящему, мы это делаем просто для имитации, а модель потом можно будет достаточно точно масштабировать на реальные процессы. В этом и заключается красота ZeroMQ - вы разрабатываете на микро-уровне, и масштабируете все на макро-уровень. Нити становятся процессами, а затем - вычислительными установками, а шаблоны и логика остается той же самой. Каждый наш "кластер" процессов содержит нити клиентов, нити рабочих и нить брокера.

Мы хорошо умеем работать со следующей моделью:
  • Клиент REQ (нить клиента) создает задачу и передает её брокеру (ROUTER).
  • Рабочий REQ (нить рабочего) выполняет задачу и возвращает результат брокеру (ROUTER).
  • Брокер реализует очереди и распределяет нагрузку, используя схему "Балансировка нагрузки".
Интеграция или равноправный обмен?

Есть несколько вариантов соединений брокеров друг с другом. Все, что нужно - это иметь возможность сказать другому брокеру "у меня есть рабочие мощности", и принять несколько задач. Еще нужно уметь сообщать другим брокерам, "стоп, я переполнен". Не нужно ничего особенного, иногда мы сможем принятые задания обработать сразу, иногда - как только появится возможность.

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

Связь между брокерами типа "интереграция":


Такая схема позволяет обойтись довольно простой логикой брокеров: когда нет заданий, сообщаем другому брокеру "готов", и принимаем от него задания. Проблем в том, что это слишком просто для этой проблемы. Интегрированные брокеры  смогут одновременно обрабатывать только одну задачу. Если брокер имитирует  параллельно клиента и рабочего, он по определению также должен быть параллельным, и если больше нет свободных рабочих, он не должен использоваться. Наши брокеры должны подключаться полностью в асинхронном режиме.

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

Вместо интеграции посмотрим на равноправный обмен, при котором брокеры явно знают друг о друге и общаются по специальным каналам. Разберем этот случай, предположив, что нам нужно связать N брокеров. Каждый брокер имеет (N - 1) партнеров, и все брокеры используют один и тот же код и логику. Между брокерами будет два различных потока данных:

  • Каждому брокеру нужно сообщить своим партнерам, сколько рабочих сейчас у него свободно. Это может быть совсем простой информацией - просто количество, которое регулярно обновляется. Очевидно (и правильно) напрашивается схема pub-sub. Итак, каждый брокер открывает сокет PUB и публикует информацию об этом, и каждый брокер открывает сокет SUB и подключается к сокету PUB каждого другого брокера так, чтобы получать информацию о состоянии партнеров.
  • Каждому брокеру нужен способ делегировать задачи партнерам и асинхронно получать от них ответы. Сделаем это с помощью сокетов ROUTER: другой рабочей комбинации просто нет. Каждый брокер имеет два таких сокета: один для задач, который он принимает  и второй для задач, которые он делегирует. Если использовать не два сокета, потребуется больше работать, чтобы разобрать, что именно мы сейчас читаем - запрос или ответ. Это также будет означать добавление дополнительной информации в конверт сообщения.
А ведь еще есть обмен сообщениями с местными клиентами и рабочими.

 Церемония именования

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

Совершим семантическую церемонию присвоения имени сокетам. Есть три потока данных:
  • Локальный поток "запрос - ответ" между брокером и его клиентами и рабочими.
  • Облачный поток "запрос - ответ" между брокером и его брокерами - партнерами.
  • Поток состояния между брокером и его брокером - партнером.
Если подобрать осмысленные имена, имеющие сходную длину, то код будет выглядеть более опрятным. Это не такая уж важность, но внимание к деталям здорово помогает. Для каждого потока данных у брокера есть два совета, которые мы ортогонально назовем фронтэнд и бэкэнд. Эти имена использовались уже довольно часто. Фронтэнд принимает информацию о задаче. Бэкэнд отправляет её к другим узлам. Концептуально поток направлен спереди назад (ответы ведутся в обратном направлении - с зада на перед).

Итак, в коде данного примера будем использовать следующие имена сокетов:
  • localfe и localbe для локального потока данных.
  • cloudfe и cloudbe для облачного потока данных.
  • statefe и statebe для потока данных о состоянии.

Так как мы все имитируем в рамках одного компьютера, то в качестве транспорта потоков данных будем использовать tcp://localhost. А разные "кластеры" будут висеть на разных портах.
Так как наш мозг не идеальная машина, мы не станем использовать имен вроде s1, s2, s3, s4. Куда легче запомнить "три потока, два направления", чем "шесть разных сокетов".

Схема сокетов брокера:

Итак, коннектим сокет cloudbe каждого брокера к сокету cloudfe каждого другого брокера; точно так же коннектим (физически - "биндим") сокет statebe каждого брокера с statefe каждого другого брокера.

Дальше мы начнем шаг за шагом создавать прототип рабочей системы (Продолжение).

Комментариев нет :

Отправить комментарий