Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

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

Лекция



Привет, Вы узнаете о том , что такое Redis Паттерны взаимодействия, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое Redis Паттерны взаимодействия , настоятельно рекомендую прочитать все из категории Redis DB Nosql.

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

  • очередь событий;
  • блокировка с Redlock;
  • Pub/Sub;
  • распределенные события.

Очередь событий


Списки в Redis – это упорядоченные списки строк, очень похожие на связные списки, с которыми вы можете быть знакомымы. Добавление значения в список (push) и удаление значения из списка (pop) очень легковесные операции. Как вы можете представить, это очень хорошая структура для управления очередью: добавлять элементы в начало и считывать их с конца (FIFO). Redis также предоставляет дополнительные возможности, которые делают этот паттерн более эффективным, надежным и легким в использовании.

У списков есть подмножество команд, которые позволяют исполнять «блокирующее» поведение. Под термином «блокирующее» понимается соединение только с одним клиентом. В действительности эти команды не дают клиенту выполнять что-либо до тех пор, пока в списке не появится какое-то значение или пока не истечет время ожидания. Это устраняет необходимость опрашивать Redis, ожидая результат. Поскольку клиент не может ничего выполнять пока ожидает значение, нам понадобится два открытых клиента, чтобы проиллюстрировать это:

# Клиент 1 Клиент 2
1
> BRPOP my-q 0
[ожидает значение]
2
> LPUSH my-q hello
(integer) 1
1) "my-q"
2) "hello"
[клиент разблокирован, готов принимать команды]
3
> BRPOP my-q 0
[ожидает значение]


В этом примере на шаге 1 мы видим, что заблокированный клиент ничего сразу не возвращает, так как в нем ничего не содержится. Последний аргумент – время ожидания. Здесь 0 означает вечное ожидание. На второй строке в my-q заносится значение, и первый клиент сразу же выходит из состояния блокировки. На третьей строке снова вызывается BRPOP (в приложении можно выполнять это в цикле), и клиент также ждет очередное значение. Нажатием «Ctrl + C» можно прервать блокировку и выйти из клиента.

Давайте развернем пример в обратную сторону и посмотрим, как BRPOP работает с непустым списком:

# Клиент 1 Клиент 2
1
> LPUSH my-q hello
(integer) 1
2
> LPUSH my-q hej
(integer) 2
3
> LPUSH my-q bonjour
(integer) 3
4
> BRPOP my-q 0
1) "my-q"
2) "hello"
5
> BRPOP my-q 0
1) "my-q"
2) "hej"
6
> BRPOP my-q 0
1) "my-q"
2) "bonjour"
7
> BRPOP my-q 0
[ожидает значение]


На шагах 1-3 мы заносим 3 значения в список и видим, что ответ растет, указывая количество элементов в списке. Шаг 4, несмотря на вызов BRPOP, возвращает значение немедленно. Все потому, что блокирующее поведение возникает только когда в очереди нет значений. Мы можем видеть такой же мгновенный ответ на шагах 5-6, потому что это выполняется по каждому элементу в очереди. На шаге 7 BRPOP не находит ничего в очереди и блокирует клиента, пока что-то не будет добавлено.

Часто очереди представляют некоторую работу, которую необходимо выполнить в другом процессе (воркере). В таком типе рабочей нагрузки важно то, чтобы работа не пропала, если воркер по какой-то причине упадет во время исполнения. Redis поддерживает такой тип очередей. Для этого вместо BRPOP используйте команду BRPOPLPUSH. Она ожидает значение в одном списке, и как только оно там появляется, заносит его в другой список. Это выполняется атомарно, поэтому невозможно двум воркерам изменить одно и то же значение. Посмотрим, как это работает:

# Клиент 1 Клиент 2
1
> LINDEX worker-q 0
(nil)
2 [Если результат не nil, как-то обработать его и перейти на шаг 4]
3
> LREM worker-q -1 [значение из шага 1]
(integer) 1
[вернуться к шагу 1]
4
> BRPOPLPUSH my-q worker-q 0
[ожидает значение]
5
> LPUSH my-q hello
"hello"
[клиент разблокирован, готов принимать команды]
6 [обработать «hello»]
7
> LREM worker-q -1 hello
(integer) 1
8 [вернуться к шагу 1]


На шагах 1-2 мы ничего не делаем, так как worker-q пуст. Об этом говорит сайт https://intellect.icu . Если же что-то вернулось, то обрабатываем это и удаляем, и снова возвращаемся к шагу 1, чтобы проверить, не попало ли чего-нибудь в очередь. Тем самым мы сначала очищаем очередь воркера и выполняем существующую работу. На шаге 4 мы ждем, пока значение не появится в my-q, и когда появляется, оно атомарно переносится в worker-q. Затем мы как-то обрабатываем «hello», после этого удаляем его из worker-q и возвращаемся к шагу 1. Если процесс умирает на шаге 6, значение все еще остается в worker-q. После перезапуска процесса мы немедленно удалим все, что не были удалено на шаге 7.

Этот паттерн сильно снижает вероятность потери работы, но только если воркер умирает между шагами 2 и 3 или 5 и 6, что маловероятно, но best practice будет учесть это обстоятельство в логике воркера.

Блокировка с Redlock


Иногда в системе необходимо заблокировать какой-нибудь ресурс. Это может понадобиться для того, чтобы применить важные изменения, которые не могут быть решены в условиях конкурентности. Цели блокировок:

  • позволить захватить ресурс одному и только одному воркеру;
  • иметь возможность надежно освобождать объект блокировки;
  • не блокировать ресурс намертво (должен быть разблокирован через определенный промежуток времени).


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

Во-первых, нужно понимать, что Redlock спроектирован для работы как минимум на 3 машинах с независимыми экземплярами Redis. Это избавляет от единой точки отказа в вашем механизме блокировки, которая может привести к взаимной блокировке всех ресурсов. Другой момент для понимания, что, хотя часы на машинах не должны быть синхронизированы на 100%, функционировать они должны одинаково – время движется с одинаковой скоростью: одна секунда на машине А то же самое, что одна секунда на машине Б.

Установка объекта блокировки с Redlock начинается с получения метки времени с миллисекундной точностью. Также вы должны заранее указать время блокировки. Затем объект блокировки задается установкой (SET) ключа со случайным значением (только если этот ключ еще не существует) и установкой времени ожидания на ключ. Это повторяется для каждого независимого экземпляра. Если экземпляр упал, то он немедленно пропускается. Если объект блокировки успешно установлен на большинстве экземпляров до истечения времени ожидания, тогда он считается захваченным. Время на установку или обновление объекта блокировки – это количество времени, необходимое для достижения состояния блокировки, минус предустановленное время блокирования. В случае ошибки или истечения времени ожидания разблокируйте все экземпляры и повторите снова.

Чтобы освободить объект блокировки, лучше использовать скрипт на Lua, который проверит, имеется ли во множестве ключей ожидаемое случайное значение. Если имеется, тогда можно удалить, иначе лучше оставить ключи, так как это могут быть более новые объекты блокировки.

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

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

Pub/Sub


Помимо хранилища данных Redis можно использовать как платформу «Pub/Sub» (издатель/подписчик). В этом паттерне издатель может выпустить сообщения любому числу подписчиков канала. Это сообщения по принципу «выстрелил и забыл», то бишь если сообщение выпущено, а подписчика не существует, то сообщение улетучивается без возможности восстановления.
Подписавшись на канал, клиент переводится в режим подписчика и больше не может вызывать команды – становится readonly. У издателя нет таких ограничений.

Можно подписаться более чем на один канал. Начнем с подписки на два канала «погода» и «спорт», используя команду SUBSCRIBE:

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


В отдельном клиенте (другом окне терминала, например) мы можем публиковать сообщения в любом из этих каналов с помощью команды PUBLISH: Redis Паттерны взаимодействия


Первый аргумент – название канала, второй – сообщение. Сообщение может быть любым, в данном случае это закодированный счет в игре. Команда возвращает количество клиентов, которым сообщение будет доставлено. В клиенте-подписчике мы сразу же видим сообщение:

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

Ответ содержит три элемента: указание, что это сообщение, канал подписки и, собственно, сообщение. Клиент сразу же после получения возвращается к прослушиванию канала.

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

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


В подписчике мы увидим тот же формат, но с другим каналом с сообщением:

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


Давайте выпустим сообщение в канал, где нет подписчиков:

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


Так как никто не слушает канал currency, ответ будет 0. Это сообщение ушло, и клиенты, которые после подпишутся на этот канал, не получат уведомление об этом сообщении – оно было отправлено и забыто.

Кроме подписки на один канал, Redis позволяет подписку на каналы по маске. glob-style-маска передается в команду PSUBSCRIBE:

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


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

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


Обратите внимание, что первые две команды возвращают 1, в то время как последняя возвращает 0. И хоть мы напрямую не подписаны на sports:hockey или sports:basketball, клиент получает сообщения благодаря подписке по маске. В окне клиента-подписчика мы можем увидеть, что там результаты только для каналов, соответствующих маске.

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


Этот вывод немного отличается от вывода команды SUBSCRIBE, поскольку он содержит саму маску, а также реальное имя канала.

Распределенные события


Схема обмена сообщениями Redis «Pub/Sub» может быть расширена, чтобы создать интересные распределенные события. Скажем, у нас есть структура, которая хранится в хэш-таблице, но мы хотим обновить клиентов, только когда отдельное поле превышает числовое значение, заданное подписчиком. Мы будем слушать каналы по маске и извлекать хэш в status. В этом примере нас интересует update_status со значениями 5-9.

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

Чтобы изменить значение status/error_level, нам понадобятся две команды, которые можно выполнить последовательно или в блоке MULTI/EXEC. Первая команда устанавливает уровень, а вторая публикует уведомление со значением, закодированным в самом канале.

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

В первом окне видим, что сообщение получено, и после этого можно переключиться на другого клиента и вызвать команду HGETALL:

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


Мы также можем использовать этот способ, чтобы обновлять локальную переменную какого-нибудь длительного процесса. Это может позволить нескольким экземплярам одного и того же процесса обмениваться данными в реальном времени.

Чем этот паттерн лучше использования «Pub/Sub»? Когда процесс перезапускается, он может просто получить все состояние и начать прослушивание. Изменения будут синхронизированы между любым количеством процессов.

Паттерны хранения данных


Существует несколько паттернов хранения структурированных данных в Redis. В этой главе мы рассмотрим следующие:

  • хранение данных в JSON;
  • хранение объектов.

Хранение данных в JSON


Существуют несколько вариантов хранения данных в формате JSON в Redis. Наиболее общая форма – сериализовать объект предварительно и сохранить под специальным ключом:

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


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

  • сериализация занимает клиентские вычислительные ресурсы на чтение и запись;
  • JSON-формат увеличивает размер данных;
  • Redis имеет только косвенный способ обращения с данными в JSON.


Первая пара пунктов может быть незначительна на небольших объемах данных, но затраты будут увеличиваться по мере роста данных. Однако, третий пункт наиболее критичен.

До Redis 4.0 единственным методом работы с JSON внутри Redis было использование Lua-скрипта в модуле cjson. Это частично решало проблему, хотя все еще оставалось узким местом и создавало дополнительную мороку с изучением Lua. Кроме того, многие приложения просто получали всю JSON-строку, десериализовали ее, работали с данными, сериализовали обратно и снова сохраняли. Это антипаттерн. Есть большой риск потерять данные таким способом.

# Экземпляр приложения #1 Экземпляр приложения #2
1
> GET my-car
2 [десериализовать, изменить цвет машины и сериализовать снова]
> GET my-car
3
> SET my-car

[новое значение из экземпляра #1]
[десериализовать, изменить модель машины и сериализовать снова]
4
> SET my-car

[новое значение из экземпляра #2]
5
> GET my-car


Результат на строке 5 покажет изменения только экземпляра 2, и изменение цвета экземпляром 1 будет утеряно.

Redis версий 4.0 и выше имеет возможность использовать модули. ReJSON – это модуль, который предоставляет специальный тип данных и команды прямого взаимодействия с ним. ReJSON сохраняет данные в двоичном формате, что снижает размер хранимых данных, предоставляет более быстрый доступ к элементам без затрат времени на де-/сериализацию.

Чтобы использовать ReJSON, нужно установить его на Redis-сервер или включить в Redis Enterprise.

Предыдущий пример с использованием ReJSON будет выглядеть так:

# Экземпляр приложения #1 Экземпляр приложения #2
1
> JSON.SET car2 . '{"colour": "blue",  "make":"saab", "model":93,  "features": ["powerlocks",  "moonroof"]}‘
OK
2
> JSON.SET car2 colour '"red"'
OK
3
  Redis Паттерны взаимодействия


ReJSON предоставляет более безопасный, более быстрый и более интуитивный способ работы с данными в формате JSON в Redis, особенно в тех случаях, где необходимы атомарные изменения вложенных элементов.

Хранение объектов


Стандартный тип данных Redis «хэш-таблица» на первый взгляд может казаться очень похожим на JSON-объект или другой тип. Намного проще делать поля либо строкой, либо числом и не допускать вложенных структур. Однако, предварительно вычислив «путь» до ка ждого поля, можно «сплющить» объект и сохранить его в хэш-таблицу Redis.

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


Используя JSONPath (XPath для JSON), мы можем представить каждый элемент на одном уровне хэш-таблицы:

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


Для ясности команды приведены по отдельности, но в HSET можно передавать множество параметров.

Теперь можно запросить весь объект или отдельное его поле:

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


И хотя это предоставляет быстрый и полезный способ получения хранимого объекта в Redis, это имеет свои недостатки:

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


Этот паттерн в значительной степени совпадает с ReJSON. Если ReJSON доступен, то в большинстве случаев лучше использовать его. Однако, сохранение объектов способом выше имеет одно преимущество перед ReJSON: интеграция с командой Redis SORT. Однако, эта команда вычислительно сложна и является отдельной сложной темой за рамками этого паттерна.

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

Данная статья про Redis Паттерны взаимодействия подтверждают значимость применения современных методик для изучения данных проблем. Надеюсь, что теперь ты понял что такое Redis Паттерны взаимодействия и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Redis DB Nosql

создано: 2021-03-14
обновлено: 2021-03-14
132265



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Redis DB Nosql

Термины: Redis DB Nosql