Скрипты Redis Lua изолированно выполняют на сервере несколько команд Redis вместе с условиями. Таким образом, между чтением, проверкой и записью не возникает противоречивых промежуточных результатов из-за действий других клиентов. В данном контексте «Atomar» не означает автоматический откат: Ввод данных и пути обработки ошибок необходимо тщательно продумать, прежде всего перед выполнением операций записи. Решающее значение имеют четко объявленные ключи, стабильные возвращаемые значения, короткое время выполнения и подходящая модель — от нативной команды до функции Redis.
Классификация атомарных скриптов Redis на Lua
Скрипты Redis Lua выполняют бизнес-логику обработки данных непосредственно на сервере Redis. Пока скрипт выполняется, Redis не обрабатывает другие серверные операции; поэтому содержащиеся в нем команды изолированы от других клиентов. Это позволяет объединить несколько простых команд в одну атомная операция объединять, например, проверку лимита с последующим обновлением счетчика или списание средств только при наличии достаточного остатка.
Без скрипта клиент сначала может с помощью запроса GET прочитать значение счетчика, проверить предельное значение в коде приложения, а затем отправить запрос INCR. Однако между этими шагами другой клиент может изменить значение того же счетчика. Скрипт же считывает, проверяет и увеличивает значение без этого заметного промежуточного состояния. Это устраняет условие гонки в составном правиле, но не решает автоматически такие вопросы, как подходящие предельные значения, сроки выполнения или форматы возвращаемых данных.
«Атомарный» и «изолированный» не означает, что Скрипты Redis на Lua Транзакции базы данных с автоматическим откатом. Если после уже выполненной операции записи возникает ошибка выполнения, предыдущие изменения не отменяются в полном объеме. Поэтому скрипты должны проверять входные данные, типы данных и бизнес-условия перед первой записью; пути обработки ошибок после внесения изменений требуют тщательно продуманного подхода.
Типичные правила: разрешать доступ только в пределах установленного лимита или уменьшать количество элементов в наборе только при наличии достаточного количества. Сначала проверьте, не отражает ли уже существующая отдельная команда Redis все условия правила. Использование скрипта целесообразно в том случае, если несколько операций Redis, включая их условия, должны выполняться атомарно.
Шаблон Lua «сравнение и удаление» сравнивает сохраненное значение с переданным токеном владения и удаляет его только в случае совпадения. Таким образом, задержавшийся процесс не сможет удалить ключ, который к тому моменту уже был занят, только из-за наличия у него старого токена.
Данный пример сравнительного анализа описывает исключительно безопасную последовательность действий для одного ключа Redis. Он не решает более сложные вопросы, связанные с распределёнными блокировками, такие как подходящие сроки действия аренды (lease), приостановки процессов, сбои или координация нескольких экземпляров Redis. Кроме того, атомарность команды или скрипта распространяется только на задействованные данные Redis, а не на платежи, базу данных, электронную почту или внешние API.
Lua Sandbox и четкие границы
Redis Open Source использует Lua 5.1 для выполнения скриптов. Эту среду выполнения не следует приравнивать к локально установленной или актуальной основной версии Lua: набор доступных языковых функций и правила безопасности определяются Redis. Поэтому разработчикам скриптов Redis Lua следует тестировать их на совместимость с фактически используемой версией Redis и не полагаться на особенности какой-либо внешней среды Lua.
Выполнение осуществляется в одной Песочница с намеренно узкими ограничениями. Скрипт должен обрабатывать данные Redis и переданные аргументы, но не должен использовать ни файловую систему, ни сеть, ни службы операционной системы. Поэтому внешние HTTP-вызовы, отправка сообщений или доступ к локальным файлам должны реализовываться в коде приложения или в специально предназначенной для этого службе, а не в рамках «cache scripting».
Redis предоставляет KEYS и ARGV в качестве глобальных переменных области видимости. Для собственных промежуточных значений и вспомогательных функций, напротив, используй локальные переменные с помощью local. Таким образом, становится понятно, какие значения применимы только к данному вызову, и логика скрипта не создает ненужных зависимостей. Команды Redis вызываются целенаправленно с помощью redis.call или redis.pcall на.
Песочница не заменяет планирование ресурсов. Во время обычного выполнения скрипт блокирует доступ других клиентов к серверу, поэтому длинные циклы, неограниченные объемы данных и вычислительно-емкие операции не подходят. Ограничьте работу несколькими заранее известными ключами и небольшими вычислениями. Обширные анализы, подсчёт общих запасов на основе SCAN или взаимодействие со сторонними системами повысили бы операционные риски, не обеспечивая при этом значимого расширения атомарности.
Понимание EVAL, KEYS и ARGV
Для непосредственного вызова скрипта используется следующая форма EVAL script numkeys [key …] [arg …]. Согласно исходному коду, параметр `numkeys` определяет, сколько из последующих параметров являются ключами. Скрипт получает их через KEYS с индексацией, начинающейся с единицы; все остальные значения находятся в ARGV. Это разделение имеет важное значение: ключи описывают данные Redis, а аргументы — бизнес-данные, такие как пороговое значение, сумма или ожидаемый токен.
Например, скрипт ограничения получает счетчик в виде KEYS[1], а максимальное значение — в виде ARGV[1]. Он считывает текущее значение, преобразует пороговое значение с помощью tonumber(ARGV[1]) преобразует его в число и сравнивает оба значения перед увеличением. Такое преобразование явно отражает задуманное числовое правило, вместо того чтобы полагаться на неявную обработку значений аргументов. Если счетчик отсутствует, скрипт может целенаправленно рассматривать прочитанное значение как ноль.
Каждый ключ, который скрипт считывает или записывает, должен быть заранее указан в качестве аргумента «key». Составление имен ключей в скрипте из префиксов или их вывод из сохраненных данных не является надёжным подходом. В частности, в случае использования Redis Open Source с включенным кластером Redis не может определить перед выполнением, какие данные потребуются скрипту. Поэтому передавайте известные ключи полностью через KEYS, а переменные значения — исключительно через ARGV.
В случае Redis с открытым исходным кодом и включенным кластером ключи, передаваемые в скрипт, должны, кроме того, находиться в одном и том же хеш-слоте. Предыдущее объявление позволяет выполнить эту проверку, но не заменяет её. Для связанных между собой данных может помочь специально выбранный хеш-тег, например account:{4711}:balance и account:{4711}:reservations. Часть в фигурных скобках определяет здесь распределение слотов; ключи, определяемые динамически, нарушили бы эту схему.
Атомарное обновление счетчика с фиксированным окном
Следующий пример представляет собой атомарный Счетчик с фиксированным окном для локальной тестовой инстанции. Он проверяет значение счетчика и пороговое значение за один цикл сервера и устанавливает время истечения только при первом успешном доступе в пределах временного окна. Таким образом, устраняется временной промежуток между вызовом GET в коде приложения и последующим вызовом INCR, в течение которого другой клиент мог бы изменить значение счетчика.
При вызове передаются ключ счетчика, предел и продолжительность окна в секундах. Статус 1 означает «разрешено», статус 0 — «лимит достигнут». Статус 2 сигнализирует о недействительном вводе, обнаруженном в ходе предварительной проверки, о заверенном значении строкового счетчика или о наличии строкового счетчика без TTL. Если ключ содержит другой тип данных Redis, то уже вызов GET завершится с технической ошибкой типа; в этом случае скрипт не возвращает статус 2. Другие ошибки выполнения Redis также следует отличать от функционального статуса возврата. Данный пример не является шаблоном для данных доступа, производственных лимитов или нагрузочных тестов.
Перед каждой операцией записи скрипт проверяет все числа на то, что они являются конечными положительными целыми числами, не превышающими намеренно установленный небольшой верхний предел. Это больше, чем просто проверка с помощью tonumber: Такие показатели, как 1.5 или 1e3 отклоняются. Кроме того, ограничение в один миллион не позволяет использовать точность чисел Lua или INCR ожидаемый целочисленный строковый код, выходящий за пределы примера, может оказаться актуальным. Максимальная продолжительность окна, равная 86 400 секунд, также ограничивает EXPIRE переданное количество секунд.
Регулярное выражение допускает только десятичные цифры; после этого вспомогательная функция проверяет числовое значение, целочисленность и верхний предел. Уже существующий счетчик должен иметь только неотрицательное целое значение в том же ограниченном диапазоне. Благодаря этому отрицательное, дробное или превышающее предельное значение не сможет незаметно изменить семантику пределов. Только после этих проверок следует INCR.
Если ключ отсутствует, скрипт начинает отсчёт с 0. Если уже существует действительный строковый счётчик без срока действия, он возвращает статус 2 и ничего не записывает. После первого INCR устанавливает EXPIRE TTL, который был ранее полностью проверен. При последующих совпадениях он остаётся неизменным, так что окно не удлиняется непрерывно.
Der Договор о возврате является частью интерфейса: первый элемент массива описывает статус, второй — в зависимости от статуса — возвращает значение счетчика или код ошибки. Вызывающий код должен по-разному обрабатывать функциональный отказ со статусом 0 и статус 2, который указывает на нарушение предварительных условий. Дополнительную информацию о выборе и мониторинге времени выполнения см. в статье Анализ и оптимизация срока действия ключей Redis дополнительная основа.
Здесь TTL намеренно устанавливается только при первом попадании. Шаблон, который обновлялся бы при каждом доступе, имел бы иную временную семантику и уже не был бы фиксированным окном. Атомарность в Lua устраняет лишь ситуацию конкуренции. То, подходит ли метод «фиксированного окна», «скользящего окна» или «токена-бакета» для обеспечения требуемой справедливости и распределения нагрузки, определяет выбранный алгоритм, а не язык сценариев.
Выбрать подходящую модель атомизации
Не каждое составное требование требует использования скрипта. Если существует отдельная команда Redis, которая уже полностью отражает бизнес-правило, то в большинстве случаев её проще использовать и проверять. В случае многоуровневых правил, напротив, необходимо учитывать условия, типы данных и контракт на возвращаемое значение в совокупности.
Начиная с версии Redis Open Source 8.4 доступны встроенные операции «Compare-and-Set» и «Compare-and-Delete» для отдельных строковых ключей: SET поддерживает функции сравнения IFEQ/IFNE/IFDEQ/IFDNE; DELEX осуществляет условное удаление. Таким образом, для соответствующих случаев с отдельными ключами не требуется отдельный скрипт сравнения. В Redis 8.2, 8.0 и 7.x эти новые опции SET и DELEX недоступны; в этих версиях по-прежнему актуальны соответствующие шаблоны WATCH или Lua.
Для оптимистичного алгоритма «Compare-and-Set» команда WATCH может быть целесообразной перед командами MULTI и EXEC: если наблюдаемый ключ изменится до выполнения EXEC, транзакция прерывается, и клиент принимает решение о повторной попытке. Кроме того, транзакции не предусматривают общего отката в случае ошибок во время выполнения EXEC. Поэтому WATCH остаётся одним из вариантов, если необходимое условие не может быть реализовано с помощью одной нативной команды.
| Модель | Подходящие условия эксплуатации | Код и вызов | После перезапуска или переключения на резервный сервер | Поведение клиента и ограничения |
|---|---|---|---|---|
| Встроенная команда | Существующая отдельная операция отображает правило | Не программный код; прямая команда | Кэш скриптов не затронут | Отсутствие повторной загрузки скриптов; ограничение существующей семантикой |
| Нативный CAS/CAD, начиная с Redis Open Source 8.4 | Установка или удаление отдельного строкового ключа в зависимости от значения | SET с IFEQ/IFNE/IFDEQ/IFDNE; DELEX с условием сравнения | Кэш скриптов не затронут | Проверить ограничение по версии и условие сравнения; не использовать составное правило с несколькими ключами |
| MULTI/EXEC с WATCH | Оптимистическое чтение, проверка и письмо | WATCH, MULTI, EXEC | Отсутствие программной памяти | В случае изменений перед выполнением EXEC необходимо повторно прочитать данные и принять решение; откат при ошибках EXEC не производится |
| EVAL | Небольшой скрипт, запускаемый напрямую | Исходный код для каждого EVAL | Кэш скриптов не является постоянным | Перезагрузка дайджеста не производится; исходный код передается повторно |
| SCRIPT LOAD plus EVALSHA | Повторно используемый скрипт с известным дайджестом | Загрузить, затем выполнить вызов с использованием SHA1-дайджеста | Кэш может отсутствовать | Обработать NOSCRIPT и перезагрузить страницу; тщательно спланировать резервный вариант для конвейера |
| Функции Redis, начиная с версии 7.0 | Именованая многократно используемая логика обработки данных | FUNCTION LOAD, затем FCALL | Библиотеки реплицируются и сохраняются | Требуется процесс управления версиями и развертыванием; не следует путать с EVAL |
Скрипты EVAL привязаны к кэшу скриптов и получают свои входные данные через KEYS и ARGV. Функции Redis Начиная с Redis 7.0 они доступны в виде именованных библиотек: их регистрируют с помощью FUNCTION LOAD, вызывают с помощью FCALL, а также сохраняют и реплицируют вместе с базой данных. Их ключи и аргументы передаются в функцию в качестве параметров; из этого следует, что модель предоставления и вызова отличается от той, что используется в EVAL.
Поэтому EVAL — это прямой путь к созданию небольших логических модулей, ориентированных на конкретные задачи. Наличие нескольких клиентов и логика обработки данных, требующая долгосрочного сопровождения, часто говорят в пользу использования функций, при условии, что используемая версия Redis с открытым исходным кодом их поддерживает. При принятии решения следует также учитывать вопросы развертывания, прав доступа, обработки ошибок и чётко задокументированного возвращаемого значения, а не только количество команд Redis.
Кластеры, ошибки и контракты возврата
В Redis с открытым исходным кодом и включенным кластером передаваемые ключи в скрипте с несколькими ключами должны находиться в одном и том же хеш-слоте. Хеш-теги позволяют контролировать этот процесс: в случае account:{4711}:balance и account:{4711}:reservations Содержимое в фигурных скобках определяет слот. Поэтому к обоим ключам можно обращаться одновременно. Требование «одного и того же слота» действует в данном случае также для рассматриваемых здесь операций с несколькими ключами и транзакций MULTI/EXEC. В других конфигурациях продуктов и кластеров отдельные команды могут отличаться. Из этого не следует, что для Lua существует общее разрешение на межслотовые операции: в документации по многоключевым операциям EVAL/EVALSHA классифицируются как однослотовые операции даже в Redis Software с активированным кластером, как с OSS Cluster API, так и без него.
Все используемые ключи должны быть объявлены в качестве аргументов ключа перед вызовом. Скрипт не должен выводить имена ключей из сохраненных значений или формировать их динамически. Это правило позволяет Redis корректно проверять слоты перед выполнением и предотвращает скрытые зависимости, которые остаются незаметными в автономном экземпляре, но приводят к сбоям в Redis Open Source с включенным кластером.
С redis.call() ошибка вызванной команды Redis передается клиенту как ошибка скрипта. redis.pcall() напротив, возвращает его в Lua, чтобы скрипт мог целенаправленно обработать его. Использование pcall имеет смысл только в том случае, если определена соответствующая реакция — например, четко структурированный ответ об ошибке или альтернативный допустимый ход выполнения. Тихое игнорирование ошибок скрывает проблемы с данными и целостностью.
A Недействительный договор разграничивает технические ошибки и функциональные результаты. Например, WRONGTYPE означает, что сохраненный тип данных Redis не соответствует ожидаемому параметру команды и требует проверки. Отклоненное бронирование из-за отсутствия товара на складе, напротив, является ожидаемым результатом и может, например, возвращать статус и остаток товара. Приложения не должны одинаково обрабатывать эти категории или повторять обе без разбора.
Обеспечение надежной работы скриптов
EVAL подходит для прямого вызова: клиент передает полный исходный код Lua вместе со значениями ключей и аргументов. В случае часто используемого скрипта, который не изменяется, приложение может вместо этого использовать SCRIPT LOAD загрузить в кэш скриптов. Для этого Redis возвращает дайджест SHA1; EVALSHA затем выполняет именно соответствующий исходный код. Это позволяет избежать повторной передачи данных, но не влияет ни на атомарность, ни на функциональную ответственность скрипта.
Der Кэш скриптов не является постоянным. После перезапуска, переключения на резервный сервер или SCRIPT FLUSH вызов можно выполнить с помощью дайджеста с помощью NOSCRIPT произойдет сбой. Приложение должно обрабатывать такой случай в стандартном порядке: перезагрузить скрипт и повторить вызов, гарантирующий правильную работу, если это допускает собственная логика повторных попыток. Поэтому дайджест не следует рассматривать как подтверждение того, что скрипт уже присутствует на каждом целевом сервере.
В случае конвейеров эта функция резервного перехода имеет ограничения. Если несколько команд уже были отправлены вместе, приложение может столкнуться с NOSCRIPT-Не заменяйте ошибки задним числом путем загрузки и повторного выполнения в том же месте. Redis рекомендует в таких случаях использовать параметризированный EVAL в качестве резервной стратегии. Тем, кто планирует репликацию и отработку отказа, следует также понимать, какую роль играет буфер репликации при повторном подключении реплики: Понимание отставания репликации Redis.
Значения переменных не должны указываться в исходном коде Lua, а должны находиться в ARGV. В противном случае каждое пороговое значение будет генерировать свой скрипт, что приведёт к ненужному увеличению объёма кэша. Начиная с Redis 7.4, с помощью EVAL или EVAL_RO загруженные скрипты удаляются при достижении предела кэша по принципу LRU; это не заменяет ни параметризацию, ни обработку NOSCRIPT.
Умение справляться с длинными скриптами и орфографическими ошибками
Скрипт Lua во время своего обычного выполнения блокирует другие действия сервера. Это обеспечивает изоляцию, однако при длительном выполнении становится Операционный риск. Если скрипт превышает настроенное busy-reply-threshold, Redis отвечает на обычные команды следующим образом: BUSY; он не завершает работу скрипта автоматически. Поэтому ограничьте скрипты несколькими известными ключами и небольшими, ограниченными вычислениями.
Операции записи, выполняемые перед возникновением ошибки или бесконечного цикла, являются особенно критичными. Если скрипт уже изменил данные, то может SCRIPT KILL не завершить его надежно. Поэтому проверяй вводные данные перед первым записыванием и избегай бесконечных циклов, а также SCAN общих запасов. Тесты должны отражать объемы данных и пути возникновения ошибок в условиях планируемого использования.
| Дело | Очевидный ответ | Типичная причина | Безошибочная последовательность |
|---|---|---|---|
| NOSCRIPT | Сообщение об ошибке NOSCRIPT | В кэше временных скриптов отсутствует дайджест | Загрузить скрипт или использовать EVAL с параметрами; повторить только в соответствии с собственным правилом повторных попыток. |
| CROSSSLOT | CROSSSLOT в Redis Open Source с включенным кластером | Переданные скриптом ключи находятся в разных слотах хеша | Измените структуру ключей и объявите все необходимые ключи. |
| WRONGTYPE | Ошибка Redis WRONGTYPE | У Key есть неожиданный тип данных | Исправьте модель данных или условия сценария; не рассматривайте это как отказ по содержанию. |
| Давление в памяти через maxmemory | Операция записи может привести к прерыванию работы скрипта | При запуске Redis уже превышает лимит памяти | Не следует слепо повторять; в случае использования redis.pcall необходимо предусмотреть надёжный и задокументированный механизм обработки ошибок. |
| ЗАНЯТО | Ответ об ошибке «BUSY» при выполнении других команд | Скрипт превышает пороговое значение busy-reply-threshold | Снизить нагрузку и уменьшить размер скрипта; после операций записи не полагаться на функцию «kill». |
| Отказ по профессиональным соображениям | Задокументированное значение состояния | Например, достигнут лимит или остаток слишком низкий | Проанализировать статус и отклонить деловую операцию в установленном порядке. |
На сайте максимальный объем памяти процесс зависит от первой операции записи. Если Redis уже превысил лимит, команда, требующая большого объема памяти, может привести к redis.call прервать выполнение скрипта; redis.pcall возвращает ошибку в Lua и требует наличия специально разработанного механизма обработки ошибок. Уже внесенные изменения при этом не исправляются.
Первая операция, не требующая дополнительной памяти, например, DEL или LREM, то скрипт можно оставить работать; последующие операции записи могут увеличить потребление через maxmemory увеличить. Технические ошибки, такие как WRONGTYPE или CROSSSLOT В случае Redis Open Source с включенным кластером исправления в модели данных или структуре ключей требуют согласования, тогда как только сам скрипт может определять отклонение как стабильный статус.
Осознанно выбирать подходящие области применения
В случае условного бронирования скрипт может проверить наличие товара, отклонить запрос, если количество недостаточно, и в случае успеха вернуть оставшееся количество. атомное резервирование однако включает только Redis. Оплата, реляционная база данных, электронная почта и внешние API требуют отдельной настройки и, при необходимости, логики компенсации.
Выбор зависит от версии Redis и модели данных. Начиная с Redis Open Source 8.4, параметры сравнения можно задать с помощью SET условная постановка и DELEX выполнить сравнение и удаление отдельного ключа-строки. До версии Redis 8.4 или в случае более сложного условия необходимо WATCH с MULTI/EXEC Альтернативный вариант: если наблюдаемый ключ изменяется до выполнения команды EXEC, транзакция прерывается, и клиент принимает решение о повторном чтении и повторении операции. Небольшой скрипт на Lua подойдет в тех случаях, когда несколько команд или структур данных, включая соответствующие бизнес-правила, должны взаимодействовать на стороне сервера.
Для распределенных блокировок ни отдельная команда, ни шаблон Lua не подходят в качестве общей концепции. Срок аренды, паузы в работе процессов, сбои, повторения, переключение на резервный сервер и сценарии с несколькими экземплярами необходимо оценивать отдельно. Отдавайте предпочтение нативной команде, если используемая версия и её семантика охватывают всё правило. В противном случае WATCH и взвесить все «за» и «против» использования короткого скрипта в зависимости от соглашения об ошибках и места размещения бизнес-логики. Для многократно используемой серверной логики может подойти функция Redis. Скрипты, доступные только для чтения начиная с Redis 7.0 можно использовать через EVAL_RO или EVALSHA_RO работать, но только при условии, что логика гарантированно не предусматривает записи.
Источники и современное состояние исследований
Состояние поиска:
Дата исследования и версия: 23 сентября 2026 года. В статье рассматривается Redis с открытым исходным кодом и проводится разграничение между скриптами EVAL и функциями Redis, начиная с версии Redis 7.0. Перед использованием необходимо проверить совместимость версий и доступные команды с конкретной версией Redis, используемой в вашей среде.
https://redis.io/docs/latest/develop/programmability/eval-intro/
https://redis.io/docs/latest/develop/programmability/
https://redis.io/docs/latest/commands/eval/
https://redis.io/docs/latest/develop/using-commands/multi-key-operations/
https://redis.io/docs/latest/develop/using-commands/transactions/
https://redis.io/docs/latest/develop/programmability/functions-intro/
https://redis.io/docs/latest/commands/evalsha_ro/




