Статьи

Рассекая Disruptor: Соединение зависимостей

Итак, теперь я покрыл сам кольцевой буфер , читая и записывая его.

По логике вещей, следующая вещь — это соединить все вместе.

Я говорил о нескольких производителях — у них есть барьер производителей, чтобы держать их в порядке и под контролем. Я говорил о потребителях в простой ситуации. Несколько потребителей могут стать немного более вовлеченными.   Мы сделали несколько хитрых вещей, чтобы потребители могли зависеть друг от друга и от кольцевого буфера. Как и во многих приложениях, у нас есть ряд вещей, которые должны произойти, прежде чем мы сможем по-настоящему освоить бизнес-логику — например, нам нужно убедиться, что сообщения записаны на диск, прежде чем мы сможем что-либо делать.

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

Алмазная конфигурация
DiamondPath1P3CPerfTest иллюстрирует конфигурацию, которая не является чем-то необычным — один производитель с тремя потребителями. Хитрость в том, что третий потребитель зависит от того, чтобы два предыдущих потребителя закончили, прежде чем он сможет что-либо сделать.

Потребитель 3 может быть вашей бизнес-логикой, потребитель 1 может выполнять резервное копирование полученных данных, а потребитель 2 может готовить данные или что-то в этом роде.

Конфигурация Diamond с использованием очередей

В
архитектуре в стиле SEDA каждый этап будет разделен очередью:

(Почему в очереди должно быть так много «е»? Это письмо, с которым у меня больше всего проблем на этих рисунках).

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

Конфигурация Diamond с использованием Disruptor

В
мире
Disruptor все это управляется в одном кольцевом буфере:

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

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

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

Как работают потребительские зависимости в Disruptor

Hmm. Я вижу, мне нужен пример.

Мы вступаем в партию на полпути истории: продюсер заполнил кольцевой буфер до порядкового номера 22; потребитель прочитал и обработал все до 21; потребитель два обработал все до последовательности 18; Третий потребитель, который зависит от других потребителей, только достиг 15.

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

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

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

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

Возникает вопрос: если все идет прямо из кольцевого буфера, как потребитель три узнает обо всем, что сделали первые два потребителя? Если все три потребителя беспокоятся о том, что более ранние потребители выполнили свою работу (например, реплицировали данные в другое место), то все в порядке — когда третьему потребителю говорят, что работа выполнена, он счастлив. Однако если потребителю три нужны результаты более ранней обработки, откуда он это получает?

Изменение записей

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

Вы можете увидеть это в
DiamondPath1P3CPerfTest
FizzBuzzEntry имеет два поля, а также значение: fizz и buzz. Если потребитель является потребителем Fizz, он пишет в Fizz. Если это потребитель Buzz, он пишет в Buzz. Третий потребитель, FizzBuzz, прочтет оба этих поля, но не запишет ни в одно из них, поскольку чтение в порядке и не вызовет разногласий.

Некоторый настоящий Java-код.

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

ConsumerBarrier consumerBarrier1 = ringBuffer.createConsumerBarrier();

BatchConsumer consumer1 = new BatchConsumer(consumerBarrier1, handler1);
BatchConsumer consumer2 = new BatchConsumer(consumerBarrier1, handler2);

ConsumerBarrier consumerBarrier2 = 
    ringBuffer.createConsumerBarrier(consumer1, consumer2);

BatchConsumer consumer3 = new BatchConsumer(consumerBarrier1, handler3);

ProducerBarrier producerBarrier = 
    ringBuffer.createProducerBarrier(consumer3);

Таким образом

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

  • Используйте множественные потребительские барьеры для управления зависимостями между потребителями.
  • Пусть барьер производителя наблюдает за последним потребителем на графике.
  • Разрешить только одному потребителю писать в отдельное поле в записи.

РЕДАКТИРОВАТЬ: Адриан
написал хороший DSL для облегчения подключения Disruptor.

 

От http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-wiring-up.html