中文翻译暂不可用,显示俄语原文。

Реализовать distributed lock для обновления памяти с использованием Redis Redlock

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать distributed lock для обновления памяти с использованием Redis Redlock

1. Цель задачи

Научиться проектировать и реализовывать распределённую блокировку (distributed lock) с помощью алгоритма Redlock на базе Redis для предотвращения race-conditions при параллельных обновлениях общей памяти мультиагентной системы. В результате должен получиться модуль, который гарантирует, что только один агент в каждый момент времени может модифицировать критическую секцию памяти, и исключает состояния гонки.

Ключевой результат Рабочая реализация distributed lock на Python с использованием Redis Redlock, интегрированная в симуляцию обновления памяти агентами, и набор тестов, подтверждающих отсутствие конфликтов.

2. Исходные данные

Перед началом необходимо иметь:

Что нужноОткуда взять
Запущенный экземпляр Redis (локально или в Docker)Docker образ redis:7-alpine или установленный Redis 6+
Python 3.10+ с возможностью установки пакетовСреда разработки
Базовое понимание алгоритма RedlockДокументация Redis, статья «Distributed locks with Redis»
Шаблон мультиагентного взаимодействия (опционально)Может быть взят из предыдущих задач (например, протокол распространения сообщений)

Если нет реального инструмента — симулируем:

  1. Redis – запустить в Docker: docker run -d --name redis-lock -p 6379:6379 redis:7-alpine
  2. Мультиагентная среда – написать простую симуляцию: три воркера (threading или asyncio), которые периодически обновляют общий словарь (память). Для каждого обновления – захват блокировки.
  3. Контроль race-condition – без блокировки запустить симуляцию и убедиться, что происходит потеря данных (инкремент счётчика неверен). Это будет baseline.

3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияPython 3.10+Основная реализация
Redis клиентredis-py (>=5.0)Взаимодействие с Redis
Redlock реализацияredlock-py (библиотека) или собственная реализацияАлгоритм распределённой блокировки
Тестированиеpytest, pytest-asyncio (если async)Модульные и интеграционные тесты
Контейнеризация (опционально)Docker ComposeДля нескольких Redis инстансов (если требуется истинный Redlock)
Логированиеstructlog или loggingОтслеживание захвата/освобождения блокировок

4. Этапы выполнения

Этап 1: Подготовка окружения и baseline (40 минут)

Действия

  1. Запустите Redis локально (Docker или native). Проверьте доступность: redis-cli pingPONG.
  2. Создайте виртуальное окружение Python и установите зависимости:
    python -m venv venv
    source venv/bin/activate
    pip install redis redlock-py pytest structlog
    
  3. Напишите простую симуляцию без блокировки (файл simulation_no_lock.py):
    • Общий словарь shared_memory = {"counter": 0}.
    • Три потока (или asyncio задачи), каждый выполняет 1000 раз:
      • Читает counter из словаря.
      • Увеличивает на 1.
      • Записывает обратно.
    • После завершения всех потоков ожидаемое значение counter = 3000.
  4. Запустите симуляцию несколько раз – убедитесь, что результат почти всегда меньше 3000 (race condition).
  5. Зафиксируйте в логах среднее отклонение.

Ожидаемый результат этапа Рабочий скрипт, демонстрирующий race condition; понимание проблемы.

Этап 2: Реализация Redlock (1 час)

Действия

  1. Создайте модуль distributed_lock.py.
  2. Используйте библиотеку redlock-py (или реализуйте алгоритм вручную согласно спецификации):
    • Redlock требует список Redis-подключений по одному для каждого инстанса.
    • Для простоты используйте один Redis-инстанс (это компромисс – в реальном Redlock нужно >=3, но для учебной задачи допустимо).
    • Класс DistributedLock с методами:
      • acquire(resource_identifier, ttl=10000) – пытается захватить блокировку на ресурс (например, "memory:update") с TTL 10 секунд. Возвращает True/False.
      • release(lock) – освобождает блокировку, если она принадлежит нам.
    • Используйте uuid для уникального идентификатора блокировки (lock token).
  3. Реализуйте правильное время удержания: если операция обновления заняла больше TTL, блокировка истекает автоматически (следует обрабатывать и перезахватывать).
  4. Добавьте логирование каждого успешного/неуспешного захвата и освобождения.

Ожидаемый результат этапа Класс DistributedLock, готовый к использованию.

Этап 3: Интеграция с обновлением памяти (1 час)

Действия

  1. Создайте новый скрипт simulation_with_lock.py.
  2. В каждом воркере перед чтением/записью counter:
    • Вызвать lock.acquire("memory:update").
    • Если получен lock – выполнить операцию и сразу release.
    • Если не получен – повторить попытку через случайную задержку (backoff).
  3. Убедитесь, что воркеры используют единый экземпляр DistributedLock (или создают свой, но с одинаковой конфигурацией Redis).
  4. Параметры:
    • Воркеры запускаются параллельно через threading.Thread или concurrent.futures.ThreadPoolExecutor.
    • Каждый воркер делает 1000 операций.
  5. Запустите и проверьте, что итоговый counter == 3000 (или близко, если учесть отбрасывание операций из-за недоступности lock – нужно решить: либо ждать, либо отбрасывать; в учебной цели – ожидать).

Ожидаемый результат этапа Симуляция с блокировкой, которая даёт корректное значение счётчика (с незначительными потерями из-за ожидания блокировки, но без race).

Этап 4: Тестирование (45 минут)

Действия

  1. Напишите тесты в test_distributed_lock.py с использованием pytest:
    • test_basic_acquire_release: захват и освобождение блокировки (один клиент).
    • test_concurrent_contention: запуск 5 параллельных корутин/потоков, каждая пытается захватить блокировку и удержать её 0.1 сек. Проверьте, что нет одновременного удержания.
    • test_lock_expiry: подтвердите, что блокировка автоматически истекает после TTL; другой клиент может её захватить.
    • test_fencing_token (опционально): проверьте, что lock не может быть освобождён другим клиентом (безопасность).
  2. Запустите тесты: pytest -v test_distributed_lock.py.
  3. Исправьте все найденные ошибки.

Ожидаемый результат этапа Все тесты проходят; код покрыт на 80%+ (по зачётным сценариям).

Этап 5: Документация и анализ (30 минут)

Действия

  1. Напишите README.md, в котором опишите:
    • Архитектуру: как работает Redlock (кратко).
    • Инструкцию по запуску: docker run ..., pip install ..., запуск симуляции.
    • Результаты сравнения: без блокировки (counter <3000) и с блокировкой (counter ==3000).
    • Ограничения: при использовании одного Redis-инстанса нет полной отказоустойчивости Redlock.
  2. Добавьте docstring в класс DistributedLock и публичные методы.
  3. Создайте файл docker-compose.yml (опционально) для поднятия 3 Redis инстансов и запуска приложения.

Ожидаемый результат этапа Пакет с кодом, тестами и документацией.

5. Критерии приемки (Definition of Done)

  • Реализован класс DistributedLock, корректно реализующий протокол Redlock (захват, освобождение, timeout, расширение).
  • Написана симуляция обновления памяти тремя воркерами с использованием блокировки.
  • Итоговое значение общего счётчика в симуляции с блокировкой стабильно равно ожидаемому (3000) с учётом допустимого отклонения <0.1% из-за отбрасывания операций.
  • Все unit-тесты проходят (минимум 4 теста: захват/освобождение, конкурентность, истечение, безопасность).
  • Код содержит логирование ключевых событий (захват, освобождение, ошибки).
  • Прилагается README с описанием архитектуры, инструкцией по запуску и результатами сравнения.
  • Код соответствует PEP8 (проверено flake8 или black).

6. Ожидаемый результат

  • Основной артефакт: директория с проектом distributed-lock-memory/, содержащая:
    • distributed_lock.py – реализация распределённой блокировки.
    • simulation_no_lock.py – демонстрация race condition.
    • simulation_with_lock.py – демонстрация корректного обновления.
    • test_distributed_lock.py – тесты.
    • README.md – документация.
    • requirements.txt – список зависимостей.
    • (Опционально) docker-compose.yml для многократного Redis.
  • Критерий успеха: при одновременном запуске трёх воркеров на 1000 операций каждый итоговый счётчик в памяти равен 3000 (с точностью до 0.1% потерь из-за конкурентных отказов захвата).

7. Возможные сложности и их решение

СложностьРешение
Нестабильное поведение при TTL блокировки – операция обновления может длиться дольше TTL, и блокировка истекает, позволяя другому агенту войтиИспользовать lock extension (периодически продлевать блокировку, пока идёт операция). Реализовать watchdog в отдельном потоке.
Один Redis-инстанс не даёт полной отказоустойчивости Redlock (по статье Мартина Клеппмана)Для учебной задачи достаточно одного. В комментариях указать, что в production нужно ≥3 независимых Redis.
Гонка при освобождении блокировки – если клиент освобождает блокировку после того, как она уже была захвачена другимВсегда проверять, что блокировка всё ещё принадлежит нам (сравнение lock token).
Проблемы с синхронизацией времени – Redlock использует таймстемпы на клиентеУбедиться, что время на всех клиентах синхронизировано (например, через NTP). В локальной среде обычно не проблема.
Ошибки connection к Redis – клиент не может подключитьсяДобавить retry-логику с exponential backoff; ловить исключения redis.ConnectionError.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Подготовка окружения и baseline40 минут
Этап 2: Реализация Redlock1 час
Этап 3: Интеграция с обновлением памяти1 час
Этап 4: Тестирование45 минут
Этап 5: Документация и анализ30 минут
Итого (чистое время)~4 часа
Примечание для первого раза+1 час на изучение Redlock и решение неожиданных проблем

9. Связанные вопросы из базы знаний

ВопросТема
42«Что такое distributed lock и зачем он нужен?»
43«Алгоритм Redlock: плюсы и минусы»
44«Race condition в параллельных системах»
101«Использование Redis как брокера сообщений»
199(текущая задача)
233«Проблема split-brain в распределённых системах»
278«CAP-теорема и её влияние на блокировки»
301«Idempotent operations и fencing tokens»
402«Тайм-ауты и retry-логика в распределённых системах»
500«Docker Compose для инфраструктуры Redis»

10. Чек-лист самопроверки

  • Я понимаю, что такое distributed lock и почему он необходим для обновления общей памяти агентами.
  • Я написал baseline без блокировки и убедился, что race condition существует.
  • Я реализовал класс DistributedLock на основе Redlock (любая реализация: библиотечная или собственная).
  • Я интегрировал блокировку в симуляцию и добился корректного итогового счётчика.
  • Я написал тесты, которые проверяют корректность блокировки (конкурентность, истечение, безопасность) и они проходят.
  • Я задокументировал проект (README, docstring) и указал ограничения.
  • Я убедился, что код соответствует PEP8 и не содержит явных багов.