Глава 3. Свойства данных
Содержание
В этой главе мы рассмотрим следующие рецепты:
-
Применение растровых изображений
-
Установка срока действия ключа
-
Применение SORT
-
Использование конвейеров
-
Знакомство с транзакциями Redis
-
Применение PubSub
-
Использование Lua
-
Отладка Lua
В Главе 2, Типы данных мы представили шесть типов данных , поддерживаемых Redis. Помимо этих существенно важных типов данных, которые обычно применяются при разработке, Redis также предоставляет определённые полезные свойства данных чтобы сделать вашу жизнь более простой, если вы разберётесь как их правильно применять.
В данной главе мы ознакомимся со следующими свойствами:
-
Побитовые изображения: В данном рецепте мы покажем как побитовые изображения могут применяться вместо строк для сбережения пространства памяти при определённых обстоятельствах.
-
Срок истечения: Так как Redis является неким хранилищем в оперативной памяти и обычно применяется в качестве определённого кэша, существует руководящее правило по установке срока истечения давности для недолговечных данных. В данном рецепте мы покажем как устанавливать срок действия для ключей Redis и что происходит при истечении срока действия ключей.
-
Сортировка: При выборке значений из списков, множеств и сортированных множеств Redis поддерживается сортировка. В данном реццепте мы покажем как применять имеющуюся команду
sort
. -
Конвейер: В этом рецепте мы рассмотрим как применять конвейеры Redis и почему они служат великолепным средством оптимизации производительности множества операций Redis/
-
Транзакции: Redis поддерживает транзакции подобно RDBMS, тем не менее, транзакции Redis иные, что показано в данном рецепте.
-
PUBSUB: Redis может применяться в качестве канала обмена сообщениями. В данном рецепте вы изучите имеющиеся в Redis команды свойства Публикации/ Подписки (Publish/Subscribe), а также их варианты применения.
-
Создание/ Отладка Lua в Redis: Lua является языком сценариев, разработанным для встраивания в прочие приложения. Lua может применяться в Redis для связывания операций в пакет и придания полной атомарности определённому исполнению в целом. В данном рецепте вы изучите как создавать, исполнять и отлаживать простые сценарии Lua в Redis.
Побитовые изображения (bitmaps, также именуемые массивами бит или вектором бит) являются неким битовым массивом. побитовое изображение Redis не является неким новым типом данных; в действительности лежащим в его основе типом данных является строка. Поскольку строка по существу является неким большим двоичным объектом (binary blob), она может рассматриваться как побитовое изображение.
В данном рецепте мы применяем побитовое изображение для хранения некоего флага, причём независимо от того применял ли пользователь некое свойство в Relp. Допустим, что в Relp все пользователи имеют уникальный и инкрементально изменяемый идентификатор, который может обозначаться смещением в данном битовом изображении.
После того как вы закончите установку своего Сервера Redis как мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Для применения побитоваого изображения воспользуйтесь следующими шагами:
-
Откройте некий Терминал и подключитесь к Redis при помощи
redis-cli
. -
Для установки некоторого битового значения по указанному смещению в битовом изображении воспользуйтесь
SETBIT
.Скажем, когда пользователь с идентификатором
100
пытается применить функцию резервирования ресторана в Relp, мы устанавливаем значение соответствующего бита:127.0.0.1:6379> SETBIT "users_tried_reservation" 100 1 (integer) 0
-
Для выборки определённого битового значения по указанному смещению в каком- то битовом изображении может быть применена команда
GETBIT
.К примеру, когда вы желаете знать, пробовал ли когда- либо пользователь с идентификатором
400
делать заказы в реальном времени:127.0.0.1:6379> GETBIT "users_tried_online_orders" 400 (integer) 0
-
Для возврата общего числа установленных в
1
бит в определённом битовом изображении воспользуйтесь командойBITCOUNT
.Допустим, мы бы хотели знать сколько пользователей попыталось воспользоваться некоторой определённой функцией. Здесь вам на помощь придёт
BITCOUNT
:127.0.0.1:6379> BITCOUNT "users_tried_reservation" (integer) 1
-
BITOP
является командой для выполнения побитовых операций между растровыми изображениями. Эта команда поддерживает четыре побитовые операции:AND
,OR
,XOR
иNOT
. Полученный результат будет сохраняться в ключе получателя. Например, если мы желаем знать идентификаторы тех пользователей, которые одновременно применяют функцию резервирования ресторана и функцию заказов в реальном времени:127.0.0.1:6379> BITOP AND "users_tried_both_reservation_and_online_orders" "users_tried_reservation" "users_tried_online_orders" (integer) 13 127.0.0.1:6379> BITCOUNT "users_tried_both_reservation_and_online_orders" (integer) 0
Структура битового изображения в Redis отображена на следующем рисунке:
В нашем предыдущем примере мы могли также воспользоваться набором Redis для подсчёта числа пользователей. Давайте рассмотрим различия между применением битового изображения и некоторого набора в данном примере с точки зрения использования памяти.
Как мы уже видели, для каждого пользователя требуется всего один бит в имеющемся растровом изображении. Предположим, в Relp имеется два миллиарда пользователей; нам требуется выделить два миллиарда бит, что примерно составляет 250 МБ в памяти. Если применим для реализации той же самой функциональности подсчёта общего числа в неком наборе Redis, на потребуется сохранять только тех пользователей, которые используют в данном наборе эту функцию Relp.
Допустим, мы сохраняем необходимые идентификаторы пользователей в виде 8-ми байтовых целых. Если некая функция Relp очень популярна и её применяют 80% пользователей Relp (1.8 миллиардов), нам требуется выделить пространство для 1.6 миллиардов 8-ми байтовых целых значений, что составит в памяти приблизительно 12.8 ГБ. Битовое изображение имеет преимущества над неким множеством в терминах сбережения пространства когда общее число элементов становится чрезвычайно большим.
Однако, если некое свойство Relp не столь популярно, применение какого- то набора может быть более привлекательным. К примеру, если только 1% от общего числа пользователей (20 миллионов) пользуются этой функциональностью, применение множества в данном случае потребует всего 160 МБ пространства, в то время как битовое изображение всё ещё нуждается в объёме 250 МБ. Данное битовое изображение в данном случае является чрезвычайно разряжённым, а сами установленные биты в данном растровом изображении распределены случайным образом.
Кроме того, установка битов в некотором разряженном растровом изображении может блокировать на какое- то время сам Сервер Redis. Это будет происходить, когда подлежащее установке значение бита имеет очень большое смещение, а имеющееся битовое изображение мало, поэтому Redis придётся немедленно выделять память для увеличения такого битовой карты.
Для более подробных инструкций по битовым изображениям Redis и их применению обратитесь к разделу bitmap в https://redis.io/topics/data-types-intro.
В нашем рецепте Ключи управления в
Главе 2, Типы данных, мы изучили, что ключи Redis могут удаляться при помощи команд
DEL
или UNLINK
. Помимо удаления ключей вручную, мы можем также запросить
у Redis удалять ключи в автоматическом режиме устанавливая значение таймаута на ключи. В данном рецепте мы проиллюстрируем как устанавливать значение
таймаута на ключи Redis и объясним такой механизм истечения срока давности в Redis.
Вам понадобится завершить установку своего Сервера Redis как мы это объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Для того чтобы показать как устанавливать срок истечения действия на ключи мы сохраним значения идентификаторов пяти ресторанов, которые являются ближайшими к текущему местоположению данного пользователя в списке Redis. так как местоположение пользователя может часто изменяться, нам следует устанавливать некий таймаут в таком перечне Redis. По истечению данного таймаута мы сделаем выборку своего перечня ресторанов вновь, воспользовавшись своим текущим местоположением:
-
Создайте список Redis идентификаторов ресторанов для ключа
closest_restaurant_ids
:127.0.0.1:6379> LPUSH "closest_restaurant_ids" 109 200 233 543 222 (integer) 5
-
При помощи команды
EXPIRE
установите значение таймаута для данной команды в300
секунд:127.0.0.1:6379> EXPIRE "closest_restaurant_ids" 300 (integer) 1
-
Для проверки остатка времени до истечения установленного срока действия мы можем воспользоваться командой
TTL
:127.0.0.1:6379> TTL "closest_restaurant_ids" (integer) 269
-
Подождите
300
секунд пока не истечёт срок действия нашего ключа; исполнениеEXISTS
по этому ключу возвратит0
.127.0.0.1:6379> EXISTS "closest_restaurant_ids" (integer) 0
После установки некоторого таймаута на некий ключ Redis, значение времени действия для данного ключа будет сохраняться в виде некоторого абсолютного временного штампа UNIX. Таким образом, даже если Сервер Redis будет отключён какое- то время, данный временной штамп срока действия не будет утрачен и значение ключа всё равно прекратит своё действие после того как сервер вновь вернётся в рабочее состояние, а часы пройдут значение этого временного штампа UNIX.
Когда срок действия некоторого ключа истечёт, а соотвтетствующий клиент попробует осуществить к нему доступ, Redis немедленно удалит этот ключ из
памяти. Такой способ удаления Redis ключей называется пассивным истечением срока действия.
Что если срок действия некоторого ключа истёк, но к нему больше никогда не осуществляется доступ? Redis также выполняет и
активное удаление ключей с истекшим сроком действия периодически запуская некий вероятностный алгоритм. В частности,
Redis случайным образом прихватывает 20 ключей, связанных с заданным таймаутом. Ключи с истекшим сроком действия будут немедленно удалены. Если среди выбранных ключей
истёк срок действия более 25% ключей и они были удалены, Redis случайным образом вновь опять выхватывает 20 ключей и повторяет данный процесс. По умолчанию
данный процесс выполняется 10 раз в секунду, однако это настраиваемый параметр со значением hz
в имеющемся
файле настроек.
Значение таймаута некоторого ключа может быть очищено в следующих ситуациях:
-
Использованием соответствующей команды
PERSIST
для превращения его в постоянный ключ. -
Установленное значение в данном ключе заменяется или удаляется. Команды, которые очистят значение таймаута включают
SET
,GETSET
и*STORE
. Изменение элементов в некотором перечне, наборе или хэше не очищает значение таймаута, так как эта операция не заменяет само значение объекта в таком ключе. -
Данный ключ был переименован из иного ключа, который не имел таймаута.
-
Команда
TTL
возвратит-1
если данный ключ имеется, но не имеет никакого связанного с ним срока действия и вернёт-2
если такой ключ не существует.
Команда EXPIREAT
аналогична команде EXPIRE
, но берёт некий абсолютный
временной штамп UNIX при котором заканчивается срок действия данного ключа.
Кроме того, после вступления в действие Redis 2.6, можно применять PEXIRE
и
PEXIREAT
для определения значения таймаута для ключей с степенью градуирования в миллисекундах.
Поскольку когда именно будет удалён Redis некий ключ с истекшим сроком действия активным образом невозможно
предсказать, имеется вероятность, что некоторые ключи с истекшим сроком действия никогда не будут удалены. Мы можем активным образом инициировать
пассивное истечение срока действия запуская команду SCAN
, когда имеется
слишком много ключей с истекшим сроком действия, которые не были вычищены.
Официальные справочные материалы по команде EXPIRE
можно отыскать на
https://redis.io/commands/expire.
В Главе 2, Типы данных, мы уже изучили, что элементы в списке или неупорядоченном наборе, а также
в сортированной последовательности Redis могут упорядочиваются по своим множествам. Порой нам может потребоваться получить некую сортированную копию,
упорядоченную иначе чем множества. Для этой цели Redis предоставляет удобную команду с {оригинальным} названием SORT
.
В данном рецепте мы рассмотрим саму команду и её примеры.
Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Следующие шаги служат применению SORT
:
-
Откройте Терминал и подключитесь к Redis при помощи
redis-cli
. -
Если все элементы являются численными, мы можем просто запустить
SORT
по ключу для сортировки всех элементов в возрастающем порядке. Предположим, в Relp мы храним некоторые идентификаторов любимых ресторанов пользователей в некотором наборе Redis:127.0.0.1:6379> SADD "user:123:favorite_restaurant_ids" 200 365 104 455 333 (integer) 5 127.0.0.1:6379> SORT "user:123:favorite_restaurant_ids" 1) "104" 2) "200" 3) "333" 4) "365" 5) "455"
-
Если имеются не числовые элементы и мы желаем их отсортировать в лексикографическом порядке, нам требуется добавить соответствующий модификатор
ALPHA
. Допустим, в этом наборе мы храним названия ресторанов:127.0.0.1:6379> SADD "user:123:favorite_restaurants" "Dunkin Donuts" "Subway" "KFC" "Burger King" "Wendy's" (integer) 5 127.0.0.1:6379> SORT "user:123:favorite_restaurants" ALPHA 1) "Burger King" 2) "Dunkin Donuts" 3) "KFC" 4) "Subway" 5) "Wendy's"
-
Добавление модификатора
DESC
в командуSORT
возвратит элементы в нисходящем порядке. По умолчанию данная командаSORT
выполнит сортировку и возвратит все элементы, но мы можем ограничить общее число возвращаемых элементов при помощи соответствующего модификатораLIMIT
применяя начальное смещение (число элементов, которое следует пропустить) и количество (число подлежащих возврату элементов). Например, нам требуется получить из списка любимых ресторанов пользователя три ресторана:127.0.0.1:6379> SORT "user:123:favorite_restaurants" ALPHA LIMIT 0 3 1) "Burger King" 2) "Dunkin Donuts" 3) "KFC"
Порой нам требуется сортировать элементы не по их значению, а по некоторому их весу, который определяется в каких- то внешних ключах. Например, нам нужно
отсортировать любимые рестораны клиента по рейтингу, который определяется в ключах, подобных restaurnat_rating_200
,
где 200
является идентификатором данного ресторана. Это также можно выполнить при помощи
SORT
:
127.0.0.1:6379> SET "restaurant_rating_200" 4.3
127.0.0.1:6379> SET "restaurant_rating_365" 4.0
127.0.0.1:6379> SET "restaurant_rating_104" 4.8
127.0.0.1:6379> SET "restaurant_rating_455" 4.7
127.0.0.1:6379> SET "restaurant_rating_333" 4.6
127.0.0.1:6379> SORT "user:123:favorite_restaurant_ids" BY restaurant_rating_* DESC
1) "104"
2) "455"
3) "333"
4) "200"
5) "365"
В этой ситуации более полезны значения из внешних ключей. В нашем предыдущем примере мы более заинтересованы в получении названий ресторанов, нежели
их идентификаторов. Предположим, что мы храним имена ресторанов в ключах наподобии
restaurant_name_200
, где 200
является идентификатором искомого ресторана;
мы можем воспользоваться параметром GET
в SORT
для получения
названий ресторанов:
127.0.0.1:6379> SET "restaurant_name_200" "Ruby Tuesday"
127.0.0.1:6379> SET "restaurant_name_365" "TGI Friday"
127.0.0.1:6379> SET "restaurant_name_104" "Applebee's"
127.0.0.1:6379> SET "restaurant_name_455" "Red Lobster"
127.0.0.1:6379> SET "restaurant_name_333" "Boiling Crab"
127.0.0.1:6379> SORT "user:123:favorite_restaurant_ids" BY restaurant_rating_* DESC GET restaurant_name_*
1) "Applebee's"
2) "Red Lobster"
3) "Boiling Crab"
4) "Ruby Tuesday"
5) "TGI Friday"
Параметр GET
может применяться несколько раз, а GET #
означает получение самого по себе элемента.
Операция SORT
также может иметь некий параметр STORE
, который
сохраняет получаемый результат как некий перечень с заданным ключом. Вот некий образец применения STORE
:
127.0.0.1:6379>SORT "user:123:favorite_restaurant_ids" BY restaurant_rating_* DESC GET restaurant_name_* STORE user:123:favorite_restaurant_names:sort_by_rating
(integer) 5
Наконец, мы бы хотели отметить, что значение временной сложности SORT
составляет
O(N+M*log(M))
, где N
является числом элементов в обрабатываемом списке или наборе, а M
общим числом числом возвращаемых элементов. Принимая во внимание значение временной сложности операции SORT
, производительность
Сервера Redis может снижаться при сортировке больших объёмов данных.
Официальные справочные материалы для команды SORT
можно найти по ссылке https://redis.io/commands/sort.
В нашем рецепте Общее представление о протоколе Redis из Главы 1, Приступая к Redis мы освоили, что клиенты и серверы Redis взаимодействуют при помощи протокола RESP. Некий типичный процесс взаимодействия между такими клиентом и сервером может рассматриваться следующим образом:
-
Определённый клиент отправляет команду в свой сервер.
-
Этот сервер получает такую команду и помещает её в очередь исполнения (так как Redis является моделью исполнения с единственным потоком).
-
Данная команда исполняется.
-
Сервер отправляет полученный результат исполнения своему клиенту.
Общее время данного процесса имеет название RTT (round-trip time - периода кругового обращения, времени на передачу и подтверждение приёма). Как мы можем видеть, значение времени для шага 2 и шага 3 зависит от самого Сервера Redis, в то время как величина времени для шага 1 и шага 2 полностью определяется сетевой латентностью между определённым клиентом и его сервером. Если нам требуется исполнить множество команд, сетевая доставка может занимать значительный промежуток времени при сопоставлении со временем исполнения команды в самом сервере, которое зачастую очень непродолжительно.
Данный процесс может исполняться при помощи конвейера Redis. Самая основная идея такого конвейера Redis состоит в том, что клиент группирует множество команд и отправляет их одним выстрелом, вместо того, чтобы ожидать результата исполнения каждой индивидуальной команды; он запрашивает свой сервер возвращать получаемые результаты по окончанию выполнения всех команд. Общее значение времени исполнения сокращается впечатляюшим образом, так как шаг 1 и шаг 4 происходят всего лишь один раз при выполнении множества команд.
В данном рецепте мы продемонстрируем некий простой пример использования конвейеров.
Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Для установки в Ubuntu инструментария dos2unix
воспользуйтесь следующей командой:
sudo apt-get install dos2unix
В macOS вы можете установить инструменты dos2unix
при помощи такой команды:
brew install dos2unix
Шаги применения pipeline
таковы:
-
Откройте Терминал и выполните следующее:
~$ cat pipeline.txt set mykey myvalue sadd myset value1 value2 get mykey scard myset
Каждая строка в этом текстовом файле должна завершаться неким
rn
, а неn
. Для выполнения этого можно воспользоваться командойunix2dos
:$ unix2dos pipeline.txt
-
Для отправки этих команд в соответствующий конвейер вы можете воспользоваться
redis-cli
с параметром--pipe
:~$ cat pipeline.txt | bin/redis-cli --pipe All data transferred. Waiting for the last reply... Last reply received from server. errors: 0, replies: 4
Параметр --pipe
в redis-cli
отправит все полученные команды из stdin
одним выстрелом, избегая значительной стоимости RTT.
Более того, все команды могут быть в формате сырого протокола RESP, через который данный клиент взаимодействует со своим Сервером Redis. Для нашего предыдущего примера приводимая ниже операция может осуществить то же самое задание:
$ cat datapipe.txt
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n*4\r\n$4\r\nSADD\r\n$5\r\nmyset\r\n$6\r\nva
$ echo -e "$(cat datapipe.txt)" | bin/redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 4
Кроме того, при помощи параметра --pipe
в redis-cli
вы можете отправить
все команды в сыром протоколе в свой сервер сообща. За пояснениями самого протокола RESP обратитесь, пожалуйста, к рецепту
Общее представление о протоколе Redis
в Главы 1, Приступая к Redis.
Мы показали как применять конвейеры для отправки множества команд в Сервер Redis в сыром RESP. Некий конвейер обычно применяется в Клиентах Redis некоторого особого языка программирования. Подробности мы покажем в Главе 4, Разработка с помощью Redis.
Хорошее пояснение конвейеров в официальной документации Redis можно найти на https://redis.io/topics/pipelining.
Некоторой транзакцией в реляционной базе данных является какая- то группа операций, выполняющихся внутри неё как некое атомарное действие. Это означает, что данная группа операций должна целиком выполниться, или быть отвергнутой. Однако, концепция транзакции в Redis имеет некую иную историю. В данном рецепте мы рассмотрим транзакцию Redis и выведем отличия в транзакциях между Redis и RDBMS.
Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Для понимания транзакций Redis имеется некий сценарий, в котором мы собираемся организовать некий моментально убиваемые коды купонов, быструю
продажу в реальном масштабе времени для некоторого определённого ресторана в Relp. Имеется всего лишь пять кодов купона для таких моментально
убиваемых операций и у нас есть некий ключ, counts:seckilling
в виде счётчика для подсчёта доступных кодов
купонов.
Вот некий псевдокод для реализации такого счётчика:
//Initiate the count of coupon codes:
SET("counts:seckilling",5);
Start decreasing the counter:
WATCH("counts:seckilling");
count = GET("counts:seckilling");
MULTI();
if count > 0 then
DECR("counts:seckilling",1);
EXEC();
if ret != null then
print "Succeed!"
else
print "Failed!"
else
DISCARD();
print "Seckilling Over!"
Избыточная продажа часто выступает в качестве головной боли при проектировании приложений с моментально убиваемыми операциями. Основной причиной того, почему происходят такие избыточные продажи является тот факт, что имелось условие состязательности на момент выборки отвечающего за это счётчика; установленное численное значение должно быть допустимым для вашего бизнес- сценария, тем не менее, прежде чем вы уменьшите значение данного счётчика, такой счётчик может подвергнуться изменению со стороны прочих запросов. В нашем примере мы используем некую транзакцию Redis чтобы избежать такого варианта.
Во- первых, мы вызываем команду WATCH
со значением необходимого ключа для установки флага, который указывает
на то, что данный ключ был изменён прежде чем выполнена команда EXEC
; если это так, мы отвергаем данную транзакцию
целиком. После этого мы получаем соответствующее значение данного счётчика.
Во- воторых, мы запускаем некую транзакцию, вызывая соответствующую команду MULTI
и, если значением счётчика
является некое недопустимое значение, данная транзакция напрямую останавливается командой DISCARD
. В противном случае мы
можем перейти к уменьшению значения счётчика.
После этого мы пытаемся выполнить данную транзакцию. На протяжении применённой до этого команды WATCH
Redis
проверит не было ли изменено имеющееся значение counts:seckilling
. Если данное значение изменено, текущая
транзакция прекращается. Такое прекращение рассматривается как некое вторичное убивающее отвержение.
Как мы можем видеть, мы успешно гарантировали что не может пройти ошибки избыточной продажи в случае когда мы применяем преимущества имеющейся в Redis функциональности транзакции.
Заслуживает внимания имеющееся различие в транзакциях Redis и RDBMS.
Основным ключевым отличием является то, что в транзакции Redis отсутствует свойство отката. Говоря в целом, при транзакции Redis могут происходить два вида ошибки при применении различных методов обработки:
-
Самой первой является синтаксическая ошибка некоторой команды. В таком случае, из- за того что данная ошибка определяется при постановке команд в очередь, вся транзакция целиком быстро отвергается и никакая из команд в этой транзакции не будет обработана:
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET FOO BAR QUEUED 127.0.0.1:6379> GOT FOO (error) ERR unknown command 'GOT' 127.0.0.1:6379> INCR MAS QUEUED 127.0.0.1:6379> EXEC (error) EXECABORT Transaction discarded because of previous errors.
-
Другим вариантом является случай, при котором все команды были успешно поставлены в очередь, причём ошибка произошла в процессе исполнения. Прочие команды после этого продолжат исполняться без обратного отката. В качестве примера мы воспользуемся такой транзакцией:
127.0.0.1:6379> MULTI OK 127.0.0.1:6379> SET foo bar QUEUED 127.0.0.1:6379> INCR foo QUEUED 127.0.0.1:6379> SET foo mas QUEUED 127.0.0.1:6379> GET foo QUEUED 127.0.0.1:6379> EXEC 1) OK 2) (error) ERR value is not an integer or out of range 3) OK 4) "mas"
За подробным обсуждением транзакций в Redis отправляем вас к материалам https://redis.io/topics/transactions.
Относительно команд транзакции воспользуйтесь https://redis.io/commands#transactions.
Публикация - подписка (PubSub, Publish- Subscribe) является неким классическим шаблоном обмена сообщения, который имеет долгую историю, согласно Wikipedia, ещё с 1987. чтобы объяснить это по- простому, издатель, который желает опубликовать данные события, отправляет сообщения в некий канал PubSub, который доставляет данные события всем тем подписчикам, которые представили свой интерес относительно данного канала. Многие решения программного обеспечения промежуточного уровня, например Kafka или ZeroMQ {Прим. пер.: или RabbitMQ}, пользуются преимуществами данного шаблона при построении системы доставки сообщений, и то же самое делает Redis. В этом рецепте мы изучим шаблон PubSub в Redis.
Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Чтобы показать как применять PubSub, воспользуемся в качестве примера рекомендуемой системой активной доставки (pushing) сообщений.
Для эмуляции двух подписчиков в консоли-A (SUBer-1
) и консоли-B (SUBer-2
)
и одного издателя в консоли-C (PUBer
) откройте три консоли:
-
Подпишитесь на канал китайских ресторанов в
SUBer-1
127.0.0.1:6379> SUBSCRIBE restaurants:Chinese Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "restaurants:Chinese" 3) (integer) 1
-
Подпишитесь на канал китайских и таиландских ресторанов в
SUBer-2
127.0.0.1:6379> SUBSCRIBE restaurants:Chinese restaurants:Thai Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "restaurants:Chinese" 3) (integer) 1 1) "subscribe" 2) "restaurants:Thai" 3) (integer) 2
-
В
PUBer
опубликуйте сообщение в канале китайских ресторанов:127.0.0.1:6379> PUBLISH restaurants:Chinese "Beijing roast duck discount tomorrow" (integer) 2
-
Это сообщение получат два подписчика:
1) "message" 2) "restaurants:Chinese" 3) "Beijing roast duck discount tomorrow"
-
В канале
PUBer
опубликуйте сообщения в таиландском канале:127.0.0.1:6379> PUBLISH restaurants:Thai "3$ for Tom yum soap in this weekend!" (integer) 1
-
Получит это сообщение только
SUBer-2
, который подписан на такой канал таиландских ресторанов:1) "message" 2) "restaurants:Thai" 3) "3$ for Tom yum soap in this weekend!"
Для мониторинга всех доступных сообщений в некотором конкретном канале применяется команда SUBSCRIBE
.
Один клиент может подписаться на множество каналов одновременно при помощи SUBSCRIBE
, а также подписаться на
каналы, которые соответствуют некоторому заданному шаблону при помощи PSUBSCRIBE
. Для отписки от каналов
применяется UNSUBSCRIBE
.
Команда PUBLISH
служит для отправки сообщения в некий определённый канал. Это сообщение получат все подписчики
данного канала.
Другой важной командой является PUBSUB
, которая делает задание по управлению данным каналом. Например,
можно выбрать активные в настоящее время каналы воспользовавшись командой PUBSUB CHANNELS
следующим
образом:
127.0.0.1:6379> PUBSUB CHANNELS
1) "restaurants:Chinese"
2) "restaurants:Thai"
Для самого жизненного цикла некоторого канала SUBSCRIBE
создаст необходимый канал автоматически если на такой
канал до сих пор не было подписки. И, раз на определённом канале отсутствуют активные подписчики, этот канал со временем будет удалён.
Особое внимание следует уделять тому, что в Redis PubSub нет ничего удерживаемого. Это означает, что никакие сообщения, каналы или взаимоотношения PubSub не будут сохраняться на диск. Если по какой- то причине имеется данный сервер, все эти объекты будут продолжать своё действие.
Более того, в вариантах доставки и обработки сообщения, если дя некоторого канала нет подписчиков, подлежащее публикации сообщение будет отбрасываться. Кроме того, всякое помещаемое в канал издателем сообщение в Redis обрабатывается только одним подписчиком.
В качестве завершения, функциональность PubSub в Redis не подходит для вариантов доставки важных сообщений, хотя для тривиальных сообщений можно пользоваться его быстрой скоростью.
Помимо этого, уведомления пространства ключей Redis, некоторой основанной на PubSub функциональности, позволяет клиентам подписываться на некий канал Redis и получать публикуемые события в командах Redis или изменениях данных. Если у вас имеется интерес к данной функциональности, вы можете обратиться к той документации, которая перечисляется в следующем разделе.
Для подробного обсуждения PubSub в Redis обратитесь к https://redis.io/topics/pubsub.
За списком команд транзакции отсылаем к https://redis.io/commands#pubsub.
Функциональность уведомления пространства ключей описывается в https://redis.io/topics/notifications.
Lua является языком сценариев с малым весом, который был введён в Redis начиная с версии 2.6. Аналогично описанным в рецепте Общее понимание транзакций Redis транзакциям, некий сценарий Lua исполняется атомарно, в то время как в Lua могут реализовываться более мощные функции и логика программирования, нежели в языке сценариев на стороне сервера. В данном рецепте мы рассмотрим как создавать и исполнять сценарий Lua в Redis.
Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Этапы применения Lua таковы:
-
Мы воспользуемся Lua для обновления некоторого строкового объекта JSON в Redis.
-
Откройте некую консоль и создайте следующий сценарий Lua:
$ mkdir /redis/coding/lua; cd /redis/coding/lua $ cat updatejson.lua local id = KEYS[1] local data = ARGV[1] local dataSource = cjson.decode(data) local retJson = redis.call('get', id) if retJson == false then retJson = {} else retJson = cjson.decode(retJson) end for k,v in pairs(dataSource) do retJson[k] = v end redis.call('set', id, cjson.encode(retJson)) return redis.call('get',id)
-
Для вычисления соответствующего файла сценария вы можете применить
redis-cli
чтобы исполнить данный сценарий Lua:bin/redis-cli --eval updatejson.lua users:id:992452 , '{"name": "Tina", "sex": "female", "grade": "A"}' "{"grade":"A","name":"Tina","sex":"female"}"
-
Если вы желаете вызывать данный сценарий и далее, вы можете зарегистрировать его в своём Севрере Redis:
bin/redis-cli SCRIPT LOAD "`cat updatejson.lua`" "45a40b129ea0655db7e7be992f344468559f3dbd"
-
После осуществления регистрации вы можете исполнять данный сценарий Lua определяя тот уникальный идентификатор SHA-1, который возвращается при регистрации данного сценария. В нашем случае мы обновим значение степени на
"C"
для пользователяusers:id:992452
:bin/redis-cli EVALSHA 45a40b129ea0655db7e7be992f344468559f3dbd 1 users:id:992452 '{"grade": "C"}' "{"grade":"C","name":"tina","sex":"female"}"
Во- первых, мы создаём некий сценарий Lua updatejson.lua
. В этом сценарии Lua
KEY
и ARGV
являются аргументами для применяемой команды
EVAL
. В самом начале данного сценария мы получаем один KEY
в
качестве значения ключа, который мы желаем обрабатывать и один ARGV
в качестве того содержимого, которым мы
хотим изменить свою строку JSON. После этого преобразовывается в последовательный вид само передаваемое в данный сценарий содержимое JSON.
Далее мы выполняем выборку смого значения определённого ключа вызывая команду GET
при помощи функции
redis.call()
, а затем мы просматриваем то значение, которое возвращается командой
GET
. Если данным значением является false
, что указывает на факт
отсутствия данного ключа, мы устанавливаем его как некую пустую таблицу. В противном случае мы можем декодировать полученное значение как
какую- то строку JSON.
После этого мы начинаем итеративно изымать данные для установки величин ключа и значения в своей переменной
retJson
. В самом конце данного сценария мы устанавливаем обратно это значение в виде некоторой строки
JSON и возвращаем окончательное значение.
Для исполнения некоторого сценария Lua можно применять redis-cli
с соответствующим параметром
--eval
. В данном примере мы передаём в redis-cli
для обновления
значение идентификатора пользователя и информацию о нём:
users:id:992452 , '{"name": "Tina", "sex": "female", "grade": "A"}'
Данный идентификатор пользователя применяется в нашем сценарии Lua как значение элемента KEYS[]
, а
информация о пользователе рассматривается как соответствующий элемент ARGV[]
. К тому же запятая отделяет
KEYS[]
от элементов ARGV[]
. В данном сценарии Lua значение индекса
и для массива KEYS[]
, и для массива ARGV[]
начинается с 1. Вместо
применения redis-cli
для исполнения некоторого сценария Lua вы также можете воспользоваться в своей программе
командой EVAL
.
На самом деле нам нет необходимости определять данный сценарий Lua всякий раз, когда мы желаем его исполнить. В этом случае команда
SCRIPT LOAD
поможет нам кэшировать данный сценарий в основном процессе Сервера Redis. Возвращается некая строка
SHA-1, которая и будет применяться в качестве некоторого уникального идентификатора данного сценария в соответствующей команде
EVALSHA
всякий раз как вы пожелаете исполнит его.
Вы можете задуматься как делать выбор между транзакциями Lua и Redis в Redis, так как оба способны исполнять некую группу операций в целом. Говоря в целом, когда вам требуется некая сложная логика рассмотрения, или же обработка некоего цикла в вашем бизнес сценарии, лучше выбрать Lua нежели транзакцию.
Так же как и для транзакции Redis, следует уделять внимание времени исполнения вашего сценария Lua. Ваш Сервер Redis не пожелает обраьатывать
никакие команды при исполнении какой- то программы Lua. Поэтому нам следует быть уверенным, что такой сценарий Lua будет исполнен настолько быстро,
насколько это возможно. Как только время исполнения вашего сценария превышает установленные по умолчанию 5 секунд (для изменения этого установите
значение lua-time-limit
в файле настроек Redis), для его уничтожения вы можете вызвать
SCRIPT KILL
. Если в вашем сценарии уже были вызваны какие- то команды записи, вам придётся отправить команду
SHUTDOWN NOSAVE
для остановки своего Сервера Redis без выполнения удержания. Если значение времени исполнения
ниже 5 секунд, ваша команда SCRIPT KILL
будет также заблокирована пока не будет достигнут предел установленного
таймаута.
Относительно управления сценарием Lua, вы можете сообщать что некий сценарий существует выполняя вызов SCRIPT EXISTS
с соответствующим идентификатором SHA-1.
Самый последний подлежащий указанию момент состоит в том, что поскольку данный сценарий сохраняется в кэше сценариев процесса Сервера Redis, который исчезает при перезагрузке, вам придётся перезагружать такие сценарии Lua при повторных запусках вашего Сервера Redis.
Полное руководство по Lua 5.1 можно получить на https://www.lua.org/manual/5.1/.
Команды Lua в Redis представлены на https://redis.io/commands#scripting.
Отладка позволяет вам выявлять, диагностировать и прекращать ошибки в некоторой программе. Говоря в общем, отладка программы включает в себя проверку логики и просмотр значений переменных. Для Lua в Redis некий инструмент отладки был введён в версии 3.2 чтобы сделать такой процесс отладки намного более простым и существует множество функций, которые помогут вам выводить зарегистрированные записи для отладки. В данном рецепте мы рассмотрим как получать эти этапы отладки в Redis.
Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.
Для регистрации в redis вам придётся установить следующие настройки в redis.conf
:
logfile "/redis/log/redis.log"
loglevel debug
Затем вы можете запустить свой Сервер Redis с данным файлом настроек, как мы это показывали в рецепте Запуск и останов Redis в Главе 1, Приступая к Redis.
Давайте возьмём сценарий Lua, представленный нами в предыдущем рецепте чтобы показать в качестве примера как отлаживать некий сценарий Lua в Redis.
-
Откройте консоль и измените данный сценарий следующим образом:
$ mkdir /redis/coding/lua; cd /redis/coding/lua $ cat updatejsondebug.lua local id = KEYS[1] local data = ARGV[1] redis.debug(id) redis.debug(data) local retJson = redis.call('get', id) local dataSource = cjson.decode(data) if retJson == false then retJson = {} else retJson = cjson.decode(retJson) end redis.breakpoint() for k,v in pairs(dataSource) do retJson[k] = v end redis.log(redis.LOG_WARNING,cjson.encode(retJson)) redis.call('set', id, cjson.encode(retJson)) return redis.call('get',id)
-
При помощи
redis-cli
настройте профиль пользователя с идентификационным номером992398
:$ bin/redis-cli set users:id:992398 " {"grade":"C","name":"Mike","sex":"male"}"
-
Для проверки того что мы можем изменять необходимую схему в соответствующем JSON чтобы записать степень изменений этим сценарием Lua мы приступим к отладке с применением параметра
--ldb
вredis-cli
:$ bin/redis-cli --ldb --eval updatejsondebug.lua users:id:992398 , '{"grade": {"init":"C","now":"A"}}' Lua debugging session started, please use: quit -- End the session. restart -- Restart the script in debug mode again. help -- Show Lua script debugging commands. * Stopped at 1, stop reason = step over -> 1 local id = KEYS[1]
-
Введите с клавиатуры
s
для пошагового прохождения нашего сценария, а отладочная информация будет выводиться соответствующей функциейredis.debug()
:lua debugger> s * Stopped at 3, stop reason = step over -> 3 redis.debug(id) lua debugger> s <debug> line 3: "users:id:992398" * Stopped at 4, stop reason = step over -> 4 redis.debug(data) lua debugger> s <debug> line 4: "{"grade": {"init":"C","now":"A"}}" * Stopped at 6, stop reason = step over -> 6 local retJson = redis.call('get', id)
-
Для продвижения введите
s
.Когда в сценарии Lua вызывается некая команда Redis, наш отладчик отобразит эту команду целиком, а также её результат для вывода соответствующего значения конкретной переменной в сценарии Lua мы можем воспользоваться
p VARIABLE
:lua debugger> s <redis> get users:id:993298 <reply> "{"grade":"C","name":"Mike","sex":"male"}" * Stopped at 7, stop reason = step over -> 7 local dataSource = cjson.decode(data) lua debugger> p retJson <value> "{"grade":"C","name":"Mike","sex":"male"}"
-
Далее продолжайте исполнение этого сценария. Наше исполнение будет остановлено в следующей точке прерывания, которую мы устанавливаем в своём сценарии с помощью функции
redis.breakpoint()
:lua debugger> c * Stopped at 16, stop reason = redis.breakpoint() called -> 16 for k,v in pairs(dataSource) do
-
Для просмотра полного отлаживаемого вами сценария введите с клавиатуры
w
. После просмотра всего сценария мы установим точку прерывания при помощиb LINENUMBER
для проверки полученного результата прежде чем установить её обратно. Затем, воспользуйтесьc
для продолжения (continue) исполнения вплоть до точки останова в нашем предыдущем шаге:lua debugger> b 21 20 redis.log(redis.LOG_WARNING,cjson.encode(retJson)) #21 redis.call('set', id, cjson.encode(retJson)) 22 lua debugger> c * Stopped at 21, stop reason = break point ->#21 redis.call('set', id, cjson.encode(retJson))
function redis.log()
выведет соответствующую зарегистрированную информацию в файл протокола вашего Сервера Redis, которым в данном примере являетсяredis.log
из каталога/redis/log
:15549:M 26 Sep 09:20:52.442 # {"grade":"C","name":"Mike","sex":"male"}
-
Затем мы продолжим исполнение этого сценария вплоть до следующей точки прерывания:
lua debugger> c "{"grade":{"now":"A","init":"C"},"name":"Mike","sex":"male"}" (Lua debugging session ended -- dataset changes rolled back)
Весь процесс отладки достаточно хорошо сам себя поясняет и нет необходимости запоминать все имеющиеся в ldb
команды; соответствующая команда help
в ldb
выдаст справочную
информацию.
Важно знать, что за счёт ответвления некоторого процесса для общения с вашим сеансом отладки сам Сервер Redis не блокируется в процессе отладки.
Кроме того, данный механизм означает, что все внесённые в режиме отладки изменения, по умолчанию, будут отыграны обратно. Ещё один параметр,
--ldb-sync-mode
, сделает ваш сервер недоступным при вашей отладке. Поэтому убедитесь что вам на самом деле это
требуется, когда вы устанавливаете режим отладки для синхронизации.
Мы подробно рассмотрим протоколирование Redis в рецепте Ведение журналов из Главы 8, Развёртывание промышленной среды
Когда вы пытаетесь просмотреть значение переменной в сценарии Lua в Redis, ваша первая мысль может состоять в том чтобы вывести значение переменной
напрямую при помощи функции print()
в Lua. Хотя это достаточно просто и прекрасно работает, вам следует заметить,
что вы не сможете наблюдать за журналом если ваш сервер работает в режиме демона, так как вырабатываемое print()
сообщение будет отображено в stdout
вашего процесса redis-server
. Итак,
если ваш сервер запущен в режиме демона, stdout
будет отбрасываться.
Для получения полного руководства по LDB (Redis Lua debugger) обратитесь к https://redis.io/topics/ldb.
Относительно команд Lua отладки сценария в Redis, вы можете обращаться к https://redis.io/commands/script-debug.
Если вас интересуют дополнительные техники отладки, вы можете отыскать их в https://redislabs.com/blog/5-6-7-methods-for-tracing-and-debugging-redis-lua-scripts/.