Redis: быстрое хранилище в памяти
Redis: быстрое хранилище в памяти
Redis - in-memory key-value хранилище, написанное на C. Применяется для кеширования, сессий, rate limiting, очередей, leaderboard, real-time счётчиков. Типичная latency на одной операции - 0.1ms, throughput - 100K+ ops/sec на ноутбуке. В отличие от PostgreSQL, нет JOIN и сложных запросов - только простые операции по ключу.
Архитектура: single-threaded event loop
Главный command processing thread у Redis - единственный. Это упрощает модель: атомарность операций «из коробки», нет race conditions внутри. Параллелизм достигается через event loop (epoll/kqueue) и фоновые I/O треды для дисковых операций (с Redis 6+).
Следствие: одна медленная команда (KEYS * на миллионы ключей, LRANGE 0 -1 на больших списках) блокирует весь сервер. Используйте SCAN вместо KEYS, LRANGE 0 100 с пагинацией.
Запуск через Docker
docker run -d --name redis -p 6379:6379 redis:7-alpine
docker exec -it redis redis-cli
Типы данных
Strings - ключ/значение
SET user:1:name "Alice"
GET user:1:name
INCR counter # атомарный инкремент
SET session:abc "data" EX 3600
TTL session:abc # секунды до expire
Counter via INCR - атомарный. Не нужна блокировка, не нужен MUTEX. Используется для просмотров страниц, лайков, ID-генерации.
Hashes - поля внутри ключа
HSET user:1 name "Alice" email "alice@mail.com" age 30
HGETALL user:1
HINCRBY user:1 age 1
Дешевле хранить много полей в одном hash, чем создавать N отдельных ключей. Redis оптимизирует мелкие hash-ы: ziplist-кодирование экономит память.
Lists - двусторонняя очередь
LPUSH queue:tasks "task1" "task2"
RPOP queue:tasks # достать с конца
BRPOP queue:tasks 5 # blocking - ждать до 5 секунд
LRANGE queue:tasks 0 -1
BLPOP/BRPOP блокирующие - основа простых message queue. Для надёжной доставки используйте Redis Streams.
Sets - уникальные элементы
SADD tags:post:1 "go" "backend" "tutorial"
SMEMBERS tags:post:1
SINTER tags:post:1 tags:post:2 # пересечение
SCARD tags:post:1 # размер
Полезно для уникальных счётчиков (UV), пересечений (общие интересы), членства (есть ли user в группе).
Sorted Sets - упорядоченные по score
ZADD leaderboard 100 "alice" 85 "bob" 95 "carol"
ZRANGE leaderboard 0 -1 WITHSCORES
ZRANK leaderboard "alice" # место в рейтинге
ZRANGEBYSCORE leaderboard 90 100
Идеально для leaderboard, time-based индексов, sliding window rate limiter (в уроке 4).
TTL и expiry
SET key value EX 60 # 60 секунд
SET key value PX 5000 # 5000 миллисекунд
EXPIRE key 30 # установить TTL после SET
PERSIST key # убрать TTL
TTL key # -1 = без TTL, -2 = ключ не существует
Redis удаляет ключи двумя способами: lazy (при попытке доступа) и active (фоновая задача каждые 100ms сканирует случайные 20 ключей). Поэтому ключ с истёкшим TTL может оставаться в памяти короткое время, не появляясь в GET.
Memory management
redis-cli CONFIG SET maxmemory 256mb
redis-cli CONFIG SET maxmemory-policy allkeys-lru
Политики при достижении лимита:
noeviction- новые SET возвращают ошибку (по умолчанию)allkeys-lru- выкидывать наименее недавно использованный ключ (типично для кеша)allkeys-lfu- наименее часто используемый (Redis 4+)volatile-lru- только из ключей с TTLallkeys-random- случайный
Для кеша обычно allkeys-lru или allkeys-lfu. Для очередей и сессий - noeviction, иначе можете потерять важные данные.
Pub/Sub
-- Подписчик
SUBSCRIBE notifications
-- Отправитель
PUBLISH notifications "new order #123"
Persistence: RDB vs AOF
- RDB - снимки всей памяти на диск через интервалы. Быстрый рестарт, но при крахе теряется до N минут данных.
- AOF (Append Only File) - лог каждой write-команды. С
appendfsync everysecтеряется не больше 1 секунды. Файл больше RDB. - RDB + AOF - рекомендуется для продакшена: AOF для durability, RDB для быстрого рестарта.
# redis.conf
save 900 1 # RDB: каждые 15 мин если ≥1 изменение
save 300 10
appendonly yes # включить AOF
appendfsync everysec # компромисс безопасность/скорость
Replication и Sentinel
Master-slave репликация: read-нагрузку можно распределить на реплики. Sentinel - кластер из 3+ узлов мониторинга, автоматический failover при падении master. Redis Cluster - шардирование на 16384 hash-слота, рост по горизонтали.
Команды диагностики
INFO memory # used_memory, fragmentation, evicted_keys
INFO clients # connected_clients, blocked_clients
INFO stats # total_commands_processed, instantaneous_ops_per_sec
SLOWLOG GET 10 # последние 10 медленных команд
CLIENT LIST # активные коннекты
DBSIZE # кол-во ключей в текущей БД
MEMORY USAGE key # размер значения в байтах
SLOWLOG особенно полезен: в проде он быстро покажет, какие команды блокируют event loop. Порог настраивается через slowlog-log-slower-than (по умолчанию 10000 микросекунд = 10ms).
Типичные ошибки
KEYS *в проде - линейный обход всей БД, блокирует single-threaded event loop на сотни мс. На большой базе кладёт сервис целиком. Для итерации - толькоSCANс курсором.FLUSHALL/FLUSHDBпод рукой у приложения - одной командой стираем все данные. В проде запретить черезrename-command FLUSHALL ""вredis.conf. То же дляDEBUG,CONFIG,SHUTDOWN.- Хранение без TTL - Redis превращается в «вечный кеш», память переполняется. У каждого ключа с кеш-семантикой должен быть
EX/PEXилиEXPIREсразу послеSET. Запомнить ставитьEXв момент записи проще, чем расчищать потом. maxmemory-policy noevictionбез алёрта на OOM - при заполнении памяти всеSET/HSETначнут возвращать ошибку. Если кеш - выбирайallkeys-lru/allkeys-lfu; если важные данные -volatile-lru+ мониторингused_memory.- AOF без
appendfsync everysec-appendfsync alwaysрежет throughput на порядок (fsync на каждый write), аnoтеряет до 30 секунд при крахе. Стандарт прода -everysec. - Большие значения (>100KB) - гигантский HASH или JSON-блоб в одном ключе блокирует event loop при чтении/записи. Дробить на части или хранить как Stream/List с пагинацией.
- Pub/Sub как очередь сообщений - у Pub/Sub нет персистентности и at-least-once. Подписчик был офлайн → сообщения потеряны. Для очередей - Streams или внешний брокер (RabbitMQ/Kafka).
Мини-практика
Запусти Redis в Docker. Создай хеш пользователя через HSET, добавь его в sorted set leaderboard со score, установи TTL для сессии через SET key val EX 1800. Проверь через TTL и EXISTS. Поэкспериментируй с CONFIG SET maxmemory-policy allkeys-lru и пронаблюдай за вытеснением при заполнении памяти большим количеством случайных ключей.