Lua на «краю»: неожиданные трюки в Nginx и Redis

Как превратить Nginx и Redis в умные точки автоматизации с помощью Lua: edge-feature flags, умный роутинг и A/B, токен-бакеты и коалесинг запросов, микро-WAF и атомарные операции в Redis. Когда логику выносить на «край», а когда — оставлять в бэкенде.

Разработка

6 мин

Представьте: 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; используйте его как умный фильтр.

Какую задачу вы бы вынесли на «край» следующей? Напишите, разберём в продолжении. 💬

Комментарии