предисловие
Redis
Он предоставляет множество важных расширенных функций, таких как публикация и подписка,Lua
сценарий и т. д.Redis
Он также предоставляет атомарные команды с автоинкрементом, но что, если нам нужно выполнить несколько команд одновременно и сохранить эти команды атомарными? В это время вы можете использоватьLua
скрипт для достижения.
Опубликовать и подписаться
Теоретически функция публикации и подписки может быть реализована напрямую с помощью двустороннего связанного списка, однако функция публикации и подписки, реализованная с помощью обычного двустороннего связанного списка, имеет два ограничения:
- Если скорость, с которой производитель создает сообщения, намного выше, чем скорость, с которой потребитель потребляет сообщения, неиспользованные сообщения в связанном списке будут накапливаться в больших количествах, что приведет к большому объему памяти.
- Очередь сообщений, реализованная на основе связанного списка, не поддерживает рассылку сообщений «один ко многим».
Поскольку обычный двусторонний связанный список для реализации функции публикации и подписки имеет эти два ограничения, поэтомуRedis
Прямой реализации двустороннего списка нет. существуетRedis
Публикацию и подписку в можно разделить на два типа:канал на основеа такжешаблон на основе.
реализация на основе каналов
Реализация на основе каналов в основном осуществляется с помощью следующих трех команд:
- подписаться на канал-1 канал-2: Подпишитесь на один или несколько каналов.
- отписаться от канала-1: отменить подписку на канал (нельзя отказаться от подписки в командном интерфейсе).
- опубликовать сообщение канала-1: на канал
channel-1
отправлять сообщенияmessage
.
Откройте клиентскую, введите команду подпискиsubscribe music movie
, указывая, что текущий клиент подписываетсяmusic
а такжеmovie
Сообщения с двух каналов:
Затем откройте другой клиент 2 и выполните следующую команду, чтобы опубликовать сообщение:
publish movie myCountry //发布消息 myCountry 到 movie 频道
publish music love //发布消息 love 到 music 频道
publish tv myHome //发布消息 myHome 到 tv 频道
Возврат после публикации первых двух каналов1
означает, что в настоящее время1
Клиенты подписались на канал и этому клиенту были отправлены сообщения.
В это время, когда мы вернемся к предыдущему клиенту, мы обнаружим, что клиент получил сообщение.myCountry
а такжеlove
два сообщения иmyHome
Это сообщение принадлежит каналуtv
, клиент не подписан, поэтому не получит:
Также имеются следующие2
Эта команда может просмотреть информацию о канале, на который подписан текущий клиент:
- каналы punsub [имя_канала] : просмотр каналов, на которые подписан текущий сервер. Возвращает все каналы без параметров, а для последующих параметров можно использовать подстановочные знаки.
?
или*
. - pubsub numsub имя_канала [имя_канала]: просмотреть количество подписок на указанный канал (одновременно можно просмотреть несколько).
Анализ принципа реализации
Информация о клиентах и их подписанных каналах хранится вredisServer
в объектеpubsub_channels
в свойствах.
struct redisServer {
dict *pubsub_channels;//保存了客户端及其订阅的频道信息
//... 省略其他信息
};
pubsub_channels
атрибут представляет собой словарь, чейkey
Сохраняемое значение — это имя канала,value
Это связанный список, и связанный список хранит информацию о каждом клиенте.id
, на следующем рисунке схематически представлена структура хранилища на основе подписки на канал:
- подписка
При подписке сначала проверит, существует ли канал в словаре: если его нет, то нужно создать словарь для текущего канала, а связный список создать как
value
и текущий клиентid
Поместите его в связанный список; если он существует, прямо поместите текущий клиентid
Поместите его в конец списка. - отписаться
При отписке клиент должен быть
id
Удалить из соответствующего связанного списка.Если после удаления связанный список пуст, необходимо одновременно удалить канал из словаря. - отправлять сообщения
будет идти первым при отправке сообщения
pubsub_channels
Поиск ключей в словаре.Если найден ключ, который может быть сопоставлен, будет найден соответствующий связанный список, и сообщение будет пройдено.
реализация на основе шаблонов
Публикация и подписка на основе схемы реализуются в основном с помощью следующих трех команд:
- psubscribe pattern-1 pattern-2: Подпишитесь на один или несколько шаблонов, шаблоны могут передаваться подстановочными знаками
?
а также*
Представлять. - punsubscribe pattern-1 pattern-1: отменить подписку на шаблон (на основе командных операций, нельзя отменить подписку на интерфейсе)
- опубликовать сообщение канала-1: на канал
channel-1
отправлять сообщенияmessage
. Это то же самое, что и приведенная выше команда на основе канала.
Откройте клиентскую, введите команду подпискиpsubscribe m*
, указывая, что текущий клиент подписывается на всеm
Каналы, которые начинаются с:
Затем откройте другой клиент 2 и выполните команду публикации сообщения:
publish movie myCountry //发布消息 myCountry 到 movie 频道
publish music love //发布消息 love 到 music 频道
publish tv myHome //发布消息 myHome 到 tv 频道
Возврат после публикации первых двух каналов1
означает, что в настоящее время1
Клиент подписался на канал (вышеупомянутый клиент на основе подписки на канал автоматически отпишется после его закрытия), и сообщение было отправлено этому клиенту.
В это время, когда мы вернемся к предыдущему клиенту, мы обнаружим, что клиент получилmyCountry
а такжеlove
два сообщения, так как оба канала начинаются сm
начало, иmyHome
Это сообщение принадлежит каналуtv
, не соm
В начале клиент не подписан, поэтому не получит:
Аналогично, подписки на основе схемы также предоставляют команду запроса:
- pubsub numpat: запрос количества подписанных схем на текущем сервере.
Анализ принципа реализации
Информация о схеме для клиентов и их подписок хранится вredisServer
в объектеpubsub_patterns
в свойствах.
struct redisServer {
list pubsub_patterns;//保存了客户端及其订阅的模式信息
//...省略其他信息
};
pubsub_patterns
Свойство — это список, структура которого внутри списка (исходныйserer.h
внутри) определяется следующим образом:
typedef struct pubsubPattern {
client *client;//订阅模式的客户端
robj *pattern;//被订阅的模式
} pubsubPattern;
- подписка
создать новый
pubsubPattern
структура данных добавлена в связанный списокpubsub_patterns
конец. - отписаться
Отписаться от списка отписавшихся на данный момент клиентов
pubsubPattern
из связанного спискаpubsub_patterns
удалено в - отправлять сообщения На этом этапе необходимо пройти весь связанный список, чтобы найти соответствующий шаблон. Причина, по которой связанный список используется на основе сценария шаблона, заключается в том, что шаблон поддерживает подстановочные знаки, поэтому его невозможно реализовать напрямую со словарем.
PS: когда одновременно существуют подписки на основе каналов и на основе схемы,Redis
Сначала он будет искать словарь каналов, а затем просматривать список шаблонов для отправки сообщений.
Lua-скрипт
Redis
от2.6
Версия начинает поддерживатьLua
сценарий, для поддержкиLua
сценарий,Redis
встроенный в серверLua
окружающая обстановка.
использоватьLua
Самым большим преимуществом сценариев является то, чтоRedis
Весь скрипт будет выполняться как единое целое и не будет прерываться другими запросами, сохраняя атомарность и снижая нагрузку на сеть.
Вызов Lua-скрипта
Lua
Синтаксис выполнения скрипта следующий:
eval lua-script numkeys key [key ...] arg [arg ...]
- оценка: выполнить
Lua
команда скрипта. - луа-скрипт:
Lua
содержание скрипта. - numkeys: указывает, что
Lua
Сколько нужно в сценарииkey
, если не используется, напишите0
. - клавиша [клавиша...]: будет
key
передается в качестве аргументов, чтобыLua
сценарий,numkeys
да0
можно опустить. - аргумент:
Lua
Параметры, используемые в скрипте, можно опустить, если их нет.
Далее мы выполняем простой без каких-либо аргументовLua
Команда скрипта:
eval "return 'Hello Redis'" 0
Выполнение команд Redis в сценариях Lua
существуетLua
выполнить в скриптеRedis
Для команды требуется следующий синтаксис:
redis.call(command, key [key ...] argv [argv…])
- команда:
Redis
в команде, напримерset
,get
Ждать. - ключ: операция
Redis
серединаkey
значение, эквивалентное формальному параметру при вызове метода. - param: представляет параметр, эквивалентный фактическому параметру при вызове метода.
Предположим, мы хотим выполнить командуset name lonely_wolf
, затем используйтеLua
Скрипт должен выполняться так:
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 name lonely_wolf
нужно знать, это:KEYS
а такжеARGV
Должен быть в верхнем регистре, нижний индекс параметра начинается с1
Начинать. в приведенной выше команде1
Указывает, что текущий необходимо пройти1
индивидуальныйkey
Сводка Lua-скрипта
Иногда, если мы выполняемLua
Если скрипт очень длинный, вызовите его прямо такLua
Скрипт очень неудобный, поэтомуRedis
который дает командуscript load
вручную дать каждомуLua
Скрипт генерирует сводку,Причина, по которой мы говорим здесь manual, заключается в том, что даже если мы не используем эту команду, каждый раз, когда мы вызываемLua
При написании сценарияRedis
также для каждогоLua
Скрипт формирует сводку.
Другие связанные команды:
-
script exists 摘要
: определяет, существует ли дайджест.0
значит не существует,1
Указывает на существование. -
script flush
: очистить всеLua
Кэширование скрипта.
Далее, давайте проверим это, последовательно выполнив следующие команды:
script load "return redis.call('set',KEYS[1],ARGV[1])" //给当前 Lua脚本生成摘要,这时候会返回一个摘要
evalsha "c686f316aaf1eb01d5a4de1b0b63cd233010e63d" 1 address china //相当于执行命令 set address china
get address //获取 adress,确认上面的脚本是否执行成功
script exists "c686f316aaf1eb01d5a4de1b0b63cd233010e63d" //判断当前摘要的 Lua脚本是否存在
script flush //清除所有 Lua脚本缓存
script exists "c686f316aaf1eb01d5a4de1b0b63cd233010e63d" //清除之后这里就不存在了
После выполнения получаются следующие результаты:
Файл Lua-скрипта
когда нашLua
Когда сценарий очень длинный, писать сценарий непосредственно в командном окне не интуитивно понятно, и сложно найти проблемы с синтаксисом, поэтомуRedis
Это также позволяет нам сначала напрямую записать скрипт в файл, а затем напрямую вызвать файл.
Например, мы создаем новыйtest.lua
сценарий:
redis.call('set',KEYS[1],ARGV[1])
return redis.call('get',KEYS[1])
После загрузки файла в указанный каталог выполните следующую команду:
redis-cli --eval test.lua 1 age , 18 //注意 key 和 arg 参数之间要以逗号隔开,且逗号两边的空格不能省略
Затем вы можете вернуться в обычном режиме18
:
Исключение скрипта
мы знаем,Redis
инструкции являются однопоточными, и теперь используйтеLua
сценарий, мы можем пройтиLua
скрипт для реализации некоторой бизнес-логики, то еслиLua
Время выполнения скрипта истекает или попадает в бесконечный цикл, в это время другие инструкции будут заблокированы, что приведет кRedis
нельзя использовать нормально. Как мы должны относиться к этому сейчас?
Время ожидания сценария истекло
чтобы решитьLua
Проблема таймаута скрипта,Redis
Предоставляет параметр тайм-аутаlua-time-limit
контролироватьLua
Время ожидания выполнения скрипта в миллисекундах, по умолчанию5000
(который5
секунд), по истечении периода тайм-аутаLua
автоматически сломает сценарий.
Скрипт застрял в бесконечном цикле
Если скрипт попадет в бесконечный цикл, таймаут в это время не сработает, смоделируем это:
Сначала откройте клиент 1 и выполните бесконечный циклlua
сценарий:
eval 'while(true) do end' 0
Затем откройте еще один клиент 2 и выполните любую команду:
get name
вернусьbusy
, указывающее, что команда не может быть выполнена в данный момент:
намекатьbusy
После этого в то же времяRedis
Решение также дано, мы можем только использоватьscript kill
илиshutdown nosave
Команда, для чего эти две команды?
- Убить скрипт: когда скрипт находится в бесконечном цикле, выполнение этой команды может принудительно
Lua
Выполнение скрипта прерывается. Ограничение этого скрипта в том, что он застрял в бесконечном цикле.Lua
Сценарий не должен успешно выполнить команду. - выключение nosave: принудительный выход
Lua
скрипт, может решитьscript kill
Ограничения команд.
Далее давайте выполним команду на втором клиентеscript kill
, а затем посмотрите на эффект зависания клиента 1 в бесконечном цикле:
Видно, что клиентLua
Скрипт завершился.По следующим подсказкам можно узнать, что это из-за выполнения.script kill
вызвано командойLua
Прерывание сценария.
Теперь мы повторно используем клиент для выполнения следующегоLua
Скрипт, разница между этим скриптом и вышеприведенным скриптом в том, что выполнение здесь проходит успешно.Redis
Бесконечный цикл запускается только после команды:
eval "redis.call('set','age','28') while true do end" 0
В это время переходим ко второму клиенту для выполненияscript kill
команда, найденная неспособной прерватьLua
Вышел сценарий:
Прямое прерывание здесь не допускаетсяLua
Сценарий потому, что перед ним уже есть бесконечный цикл.Redis
Команда успешно выполнена, если она будет прервана напрямую, это вызовет несогласованность данных.
В этом случае только путем выполненияshutdown nosave
команда принудительно прерватьLua
Скрипт, вот потому что добавилnosave
не сработает послеRedis
постоянство, поэтому при перезапускеRedis
После обслуживания можно гарантировать согласованность данных.На следующем рисунке показано выполнениеshutdown nosave
Отрисовка клиента 1 после команды:
Почему команда скрипта kill может быть выполнена
Redis
Выполнение команды однопоточное, так почемуLua
После того, как сценарий находится в бесконечном цикле, другие клиенты все еще могут его выполнять.script kill
А команды?
Это потому чтоLua
Механизм сценариев предоставляет функцию ловушки, которая позволяет запускать код ловушки, когда внутренняя виртуальная машина выполняет инструкции, поэтомуRedis
Именно этот принцип используется для реализацииLua
Хук устанавливается перед скриптом, т.е.script kill
Команды выполняются через хуки-функции.
Суммировать
Эта статья в основном знакомитRedis
Функциональность публикации-подписки в иLua
использование сценария, использованиеLua
Скрипты могут позволить выполнять несколько команд атомарно, уменьшая нагрузку на сеть, но в то же время уделяя вниманиеLua
Проблема с бесконечным циклом, вызванная скриптом.