Lua на «краю»: неожиданные трюки в Nginx и Redis
Как превратить Nginx и Redis в умные точки автоматизации с помощью Lua: edge-feature flags, умный роутинг и A/B, токен-бакеты и коалесинг запросов, микро-WAF и атомарные операции в Redis. Когда логику выносить на «край», а когда — оставлять в бэкенде.
Представьте: 02:17, ночной пик. Один апстрим дышит на ладан, очередь растёт, аналитика просит включить новую фичу только для части трафика, а маркетинг — не потерять скорость страниц. Решение не в бэкенде. Решение — на «краю»: Nginx с Lua, плюс быстрый «блокнот состояний» — Redis. За миллисекунды вы включаете фичфлаг, перенаправляете запросы, ограничиваете «болтливых» клиентов и возвращаете кэш там, где это безопасно. ⚡
Lua в Nginx — это мини-мозг прямо у входной двери вашего сервиса: он видит заголовки, куки, странные паттерны, разговаривает с Redis и принимает маленькие, но критичные решения до того, как запрос дойдёт до приложения. Redis же даёт атомарность и скорость: счётчики, квоты, флаги, идемпотентность — без гонок и тяжелых транзакций.

🧠 Почему именно Lua на краю
⚡ Скорость и неблокирующий I/O: OpenResty/ngx_lua работают в событийной модели, без блокировок.
🧩 Встраиваемость: скрипты правят поведение прямо в фазах Nginx (rewrite/access/header/body/log).
🔒 Атомарность в Redis: Lua-скрипты выполняются как единая операция (никаких гонок данных).
Ключевая идея: «маленькая логика у входа» экономит ресурсы бэкенда и даёт тонкую управляемость.
🌐 Неожиданные применения в Nginx + Lua
🎛️ Edge-feature flags: включаем фичи по сегментам (страна, версия клиента) до захода в приложение.
🧭 Умный роутинг: направляем трафик по абонентским планам/AB-вариантам, читаем правила из Redis.
🛡️ Микро-WAF: «мягкие» блокировки по поведенческим сигнатурам, не мешая легальным пользователям.
🧯 Circuit breaker на входе: временно отрезаем больной апстрим, даём кэш/заглушку.
🧹 Нормализация URL: каноникализация, сжатие мусорных query, борьба с дубликатами для кэша.
🔗 Как это связывается с Redis
🏷️ Флаги/правила живут в Redis с TTL → мгновенное распространение без рестарта Nginx.
🧪 A/B: вариант пользователя хранится в Redis → консистентность между запросами.
🚦 Rate limiting: токен-бакеты/лейки на ключи
IP+endpoint
илиuser_id
.🔁 Request coalescing: один запрос к бэку, остальные ждут (заглушаем «стадный эффект»).
🧩 Пример: валидация JWT и динамический лимит
1) Валидируем токен прямо в access_by_lua
access_by_lua_block {
local jwt = require "resty.jwt"
local validators = require "resty.jwt-validators"
local auth = ngx.var.http_authorization or ""
local token = auth:match("Bearer%s+(.+)")
if not token then return ngx.exit(ngx.HTTP_UNAUTHORIZED) end
local ok = jwt:verify("shared-secret", token, {
validators.set_system_leeway(60),
validators.require_aud("my-api"),
})
if not ok.valid then return ngx.exit(ngx.HTTP_FORBIDDEN) end
ngx.ctx.user_id = ok.payload.sub
}
2) Токен-бакет в Redis через Lua (атомарно)
-- KEYS[1]=ключ ведра; ARGV: now(ms), rate(ток/сек), burst
local key = KEYS[1]
local now = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local burst= tonumber(ARGV[3])
local data = redis.call("HMGET", key, "tokens", "ts")
local tokens = tonumber(data[1]) or burst
local ts = tonumber(data[2]) or now
local delta = math.max(0, now - ts) / 1000 * rate
tokens = math.min(burst, tokens + delta)
local allowed = tokens >= 1
if allowed then tokens = tokens - 1 end
redis.call("HMSET", key, "tokens", tokens, "ts", now)
redis.call("PEXPIRE", key, math.floor((burst/rate)*1000))
return allowed and 1 or 0
3) Вызов из Nginx-Lua
local redis = require "resty.redis"
local r = redis:new()
r:set_timeout(50)
assert(r:connect("redis", 6379))
local key = "tb:" .. (ngx.ctx.user_id or ngx.var.remote_addr) .. ":" .. ngx.var.uri
local allowed = r:eval(token_bucket_script, 1, key, ngx.now()*1000, 5, 10)
r:set_keepalive(1_000, 100) -- пул коннекшенов
if allowed == 0 then return ngx.exit(429) end
💡 Логику лимита можно подмешивать: VIP получают больший burst
из Redis.
🧰 Ещё трюки с Redis+Lua
🔐 Идемпотентность API: ключ вида
idem:<hash>
с TTL — отсекаем повторы.📦 Уникальная выдача задач: атомарно перемещаем элементы из набора в список «в работе».
🔒 Лёгкие локи:
SET key val NX PX ttl
; для распределённых — аккуратно с Redlock (оцените риски сети/часов).🧮 Квоты/лимиты: счётчики на пользователя/организацию, сброс по расписанию.

📏 Когда «край» лучше, а когда — нет
Сценарий | Lua на Nginx/Redis | Лучше в приложении |
---|---|---|
Маршрутизация, фичи, лимиты | ✅ Моментальное решение на входе | — |
Тяжёлые вычисления, ML, рендер | ❌ Не место для CPU-жора | ✅ В бэкенде/worker |
Атомарные операции данных | ✅ Redis Lua — идеально | — |
Бизнес-правила и сложные трансформации | ⚠️ Можно, но аккуратно | ✅ Читаемость в кодовой базе |
В Кодике мы делаем обучение программированию увлекательным и понятным: у нас есть интересные курсы с заданиями, которые помогают прокачивать навыки шаг за шагом.
А ещё у нас есть активный telegram-канал, где мы обсуждаем крутые идеи, делимся опытом и вместе разбираем задачи — учиться становится не только полезно, но и весело.
🧵 Итоги
Lua на «краю» даёт мгновенные решения: флаги, лимиты, роутинг, коалесинг.
Redis-скрипты обеспечивают атомарность и высокую пропускную способность.
Главное — не переносить тяжёлую бизнес-логику на Nginx; используйте его как умный фильтр.
Какую задачу вы бы вынесли на «край» следующей? Напишите, разберём в продолжении. 💬