Глава 3. Свойства данных

Содержание

Глава 3. Свойства данных
Введение
Применение растровых изображений
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Установка срока истечения на ключи
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Применение SORT
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Применение конвейеров
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Общее понимание транзакций Redis
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Применение PubSub
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Применение Lua
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно
Отладка Lua
Подготовка...
Как это сделать...
Как это работает...
Также ознакомьтесь...
Дополнительно

В этой главе мы рассмотрим следующие рецепты:

  • Применение растровых изображений

  • Установка срока действия ключа

  • Применение 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.

Как это сделать...

Для применения побитоваого изображения воспользуйтесь следующими шагами:

  1. Откройте некий Терминал и подключитесь к Redis при помощи redis-cli.

  2. Для установки некоторого битового значения по указанному смещению в битовом изображении воспользуйтесь SETBIT.

    Скажем, когда пользователь с идентификатором 100 пытается применить функцию резервирования ресторана в Relp, мы устанавливаем значение соответствующего бита:

    
    127.0.0.1:6379> SETBIT "users_tried_reservation" 100 1
    (integer) 0
     	   
  3. Для выборки определённого битового значения по указанному смещению в каком- то битовом изображении может быть применена команда GETBIT.

    К примеру, когда вы желаете знать, пробовал ли когда- либо пользователь с идентификатором 400 делать заказы в реальном времени:

    
    127.0.0.1:6379> GETBIT "users_tried_online_orders" 400
    (integer) 0
     	   
  4. Для возврата общего числа установленных в 1 бит в определённом битовом изображении воспользуйтесь командой BITCOUNT.

    Допустим, мы бы хотели знать сколько пользователей попыталось воспользоваться некоторой определённой функцией. Здесь вам на помощь придёт BITCOUNT:

    
    127.0.0.1:6379> BITCOUNT "users_tried_reservation"
    (integer) 1
     	   
  5. 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 отображена на следующем рисунке:

 

Рисунок 3-1



Также ознакомьтесь...

В нашем предыдущем примере мы могли также воспользоваться набором 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. По истечению данного таймаута мы сделаем выборку своего перечня ресторанов вновь, воспользовавшись своим текущим местоположением:

  1. Создайте список Redis идентификаторов ресторанов для ключа closest_restaurant_ids:

    
    127.0.0.1:6379> LPUSH "closest_restaurant_ids" 109 200 233 543 222
    (integer) 5
     	   
  2. При помощи команды EXPIRE установите значение таймаута для данной команды в 300 секунд:

    
    127.0.0.1:6379> EXPIRE "closest_restaurant_ids" 300
    (integer) 1
     	   
  3. Для проверки остатка времени до истечения установленного срока действия мы можем воспользоваться командой TTL:

    
    127.0.0.1:6379> TTL "closest_restaurant_ids"
    (integer) 269
     	   
  4. Подождите 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.

Применение SORT

В Главе 2, Типы данных, мы уже изучили, что элементы в списке или неупорядоченном наборе, а также в сортированной последовательности Redis могут упорядочиваются по своим множествам. Порой нам может потребоваться получить некую сортированную копию, упорядоченную иначе чем множества. Для этой цели Redis предоставляет удобную команду с {оригинальным} названием SORT. В данном рецепте мы рассмотрим саму команду и её примеры.

Подготовка...

Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.

Как это сделать...

Следующие шаги служат применению SORT:

  1. Откройте Терминал и подключитесь к Redis при помощи redis-cli.

  2. Если все элементы являются численными, мы можем просто запустить 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"
     	   
  3. Если имеются не числовые элементы и мы желаем их отсортировать в лексикографическом порядке, нам требуется добавить соответствующий модификатор 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"
     	   
  4. Добавление модификатора 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. Некий типичный процесс взаимодействия между такими клиентом и сервером может рассматриваться следующим образом:

  1. Определённый клиент отправляет команду в свой сервер.

  2. Этот сервер получает такую команду и помещает её в очередь исполнения (так как Redis является моделью исполнения с единственным потоком).

  3. Данная команда исполняется.

  4. Сервер отправляет полученный результат исполнения своему клиенту.

Общее время данного процесса имеет название  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 таковы:

  1. Откройте Терминал и выполните следующее:

    
    ~$ cat pipeline.txt
    set mykey myvalue
    sadd myset value1 value2
    get mykey
    scard myset
     	   

    Каждая строка в этом текстовом файле должна завершаться неким rn, а не n. Для выполнения этого можно воспользоваться командой unix2dos:

    
    $ unix2dos pipeline.txt
     	   
  2. Для отправки этих команд в соответствующий конвейер вы можете воспользоваться 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 и выведем отличия в транзакциях между 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 могут происходить два вида ошибки при применении различных методов обработки:

  1. Самой первой является синтаксическая ошибка некоторой команды. В таком случае, из- за того что данная ошибка определяется при постановке команд в очередь, вся транзакция целиком быстро отвергается и никакая из команд в этой транзакции не будет обработана:

    
    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.
    		
  2. Другим вариантом является случай, при котором все команды были успешно поставлены в очередь, причём ошибка произошла в процессе исполнения. Прочие команды после этого продолжат исполняться без обратного отката. В качестве примера мы воспользуемся такой транзакцией:

    
    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

Публикация - подписка (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) откройте три консоли:

  1. Подпишитесь на канал китайских ресторанов в 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
     	   
  2. Подпишитесь на канал китайских и таиландских ресторанов в 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
     	   
  3. В PUBer опубликуйте сообщение в канале китайских ресторанов:

    
    127.0.0.1:6379> PUBLISH restaurants:Chinese "Beijing roast duck discount tomorrow"
    (integer) 2
     	   
  4. Это сообщение получат два подписчика:

    
    1) "message"
    2) "restaurants:Chinese"
    3) "Beijing roast duck discount tomorrow"
     	   
  5. В канале PUBer опубликуйте сообщения в таиландском канале:

    
    127.0.0.1:6379> PUBLISH restaurants:Thai "3$ for Tom yum soap in this
    weekend!"
    (integer) 1
     	   
  6. Получит это сообщение только 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

Lua является языком сценариев с малым весом, который был введён в Redis начиная с версии 2.6. Аналогично описанным в рецепте Общее понимание транзакций Redis транзакциям, некий сценарий Lua исполняется атомарно, в то время как в Lua могут реализовываться более мощные функции и логика программирования, нежели в языке сценариев на стороне сервера. В данном рецепте мы рассмотрим как создавать и исполнять сценарий Lua в Redis.

Подготовка...

Вам требуется завершить установку своего Сервера Redis которую мы объясняли в своём рецепте Загрузка и установка Redis в Главе 1, Приступая к Redis.

Как это сделать...

Этапы применения Lua таковы:

  1. Мы воспользуемся Lua для обновления некоторого строкового объекта JSON в Redis.

  2. Откройте некую консоль и создайте следующий сценарий 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)
     	   
  3. Для вычисления соответствующего файла сценария вы можете применить redis-cli чтобы исполнить данный сценарий Lua:

    
    bin/redis-cli --eval updatejson.lua users:id:992452 , '{"name": "Tina", "sex": "female", "grade": "A"}'
    "{"grade":"A","name":"Tina","sex":"female"}"
     	   
  4. Если вы желаете вызывать данный сценарий и далее, вы можете зарегистрировать его в своём Севрере Redis:

    
    bin/redis-cli SCRIPT LOAD "`cat updatejson.lua`"
    "45a40b129ea0655db7e7be992f344468559f3dbd"
     	   
  5. После осуществления регистрации вы можете исполнять данный сценарий 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

Отладка позволяет вам выявлять, диагностировать и прекращать ошибки в некоторой программе. Говоря в общем, отладка программы включает в себя проверку логики и просмотр значений переменных. Для 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.

  1. Откройте консоль и измените данный сценарий следующим образом:

    
    $ 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)
     	   
  2. При помощи redis-cli настройте профиль пользователя с идентификационным номером 992398:

    
    $ bin/redis-cli set users:id:992398 "
    {"grade":"C","name":"Mike","sex":"male"}"
     	   
  3. Для проверки того что мы можем изменять необходимую схему в соответствующем 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]
     	   
  4. Введите с клавиатуры 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)
     	   
  5. Для продвижения введите 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"}"
     	   
  6. Далее продолжайте исполнение этого сценария. Наше исполнение будет остановлено в следующей точке прерывания, которую мы устанавливаем в своём сценарии с помощью функции redis.breakpoint():

    
    lua debugger> c
    * Stopped at 16, stop reason = redis.breakpoint() called
    -> 16 for k,v in pairs(dataSource) do
     	   
  7. Для просмотра полного отлаживаемого вами сценария введите с клавиатуры 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"}
     	   
  8. Затем мы продолжим исполнение этого сценария вплоть до следующей точки прерывания:

    
    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/.