Практическое задание

Эта задача — шаг 13 курса «Redis в проде»

Распределённая блокировка для cron-скриптов

сложно~25 мин

Ночные задачи запускаются по cron на нескольких серверах, и иногда одна и та же задача выполняется дважды одновременно. Напиши обёртку /usr/local/bin/with-lock (bash, исполняемый файл), которая берёт блокировку в Redis:

with-lock <name> <command...>

Требования:

  • блокировка — ключ lock:<name>, значение — уникальный токен этого запуска; ключ ставится одной атомарной командой, только если его ещё нет, и с TTL 30 секунд (чтобы упавший процесс не держал блокировку вечно);
  • если блокировка занята — сразу выйти с ненулевым кодом, команду не запускать и не ждать;
  • иначе — выполнить команду и выйти с её кодом;
  • после команды снять блокировку, только если токен в ключе всё ещё твой (за время работы TTL мог истечь, и блокировку взял другой процесс). Сравнение и удаление — атомарно, одним EVAL (Lua-скрипт на сервере), а не парой GET + DEL.

Redis уже запущен на 127.0.0.1:6379.

Что потренируешь:
  • redis
  • SET NX EX
  • EVAL
  • Lua
  • распределённая блокировка
  • bash

Разбор темы

Простейшая распределённая блокировка в Redis — SET key token NX EX ttl: ключ создаётся атомарно и только если его нет, а TTL гарантирует, что упавший владелец не заблокирует всех навсегда. Цена TTL — блокировка может истечь, пока команда ещё работает, и её возьмёт другой процесс. Если после этого первый владелец сделает просто DEL, он удалит чужую блокировку, и в критическую секцию войдут сразу двое. Поэтому значение — уникальный токен, а снятие — «сравнить и удалить» на сервере одним Lua-скриптом через EVAL: скрипт выполняется атомарно, между GET и DEL никто не вклинится. Пара отдельных GET + DEL из клиента такой гарантии не даёт.

Что проверяется

  1. /usr/local/bin/with-lock существует и исполняемый
  2. команда выполняется, после неё блокировка снята
  3. код выхода команды передаётся наружу
  4. во время работы ключ есть, TTL до 30 с, токен уникален
  5. занятая блокировка: быстрый отказ без запуска команды
  6. чужая блокировка не удаляется при снятии
  7. захват и снятие атомарные — SET с NX/EX и EVAL

Как это выглядит