Redis часто объясняют фразой «быстрая key-value база в памяти». Формально это верно, но практически почти бесполезно. Из такого определения непонятно, почему там есть списки, множества, очереди, блокировки, consumer groups, репликация и кластер.
Нормальная модель такая:
Redis является отдельным сервером структур данных. Клиент посылает ему атомарные команды, а Redis хранит типизированные значения преимущественно в оперативной памяти.
По состоянию на 11 июля 2026 года актуальная ветка Redis Open Source имеет версию 8.8. В Redis 8 встроены JSON, Search, Time Series, вероятностные структуры и векторный поиск, которые раньше ассоциировались с отдельным Redis Stack. Однако для понимания Redis сначала нужно освоить классическое ядро: ключи, TTL, строки, hashes, lists, sets, sorted sets, streams, память, persistence, replication и cluster. ([hub.docker.com][1])
1. Что Redis представляет собой физически
У тебя работает процесс:
redis-server
Приложение подключается к нему по TCP, обычно на порт 6379, и посылает команды:
SET user:42:name Igor
GET user:42:name
INCR article:15:views
HSET user:42 name Igor age 38
Внутри Redis существует пространство ключей:
key -> value
Но value не обязательно является строкой.
user:42:name -> String
user:42 -> Hash
online-users -> Set
leaderboard -> Sorted Set
payments -> Stream
notifications -> List
Ключ всегда является строковым или бинарным идентификатором. Значение имеет конкретный Redis-тип. Команда должна соответствовать типу значения.
Например:
SET user:42 Igor
HSET user:42 name Igor
Вторая команда завершится ошибкой WRONGTYPE, потому что user:42 уже содержит String, а не Hash.
Redis не является объектной базой, где внутри одного ключа произвольно лежит что угодно. У каждого ключа есть один определенный тип.
2. Почему Redis быстрый
Главные причины:
- Рабочий набор находится в оперативной памяти.
- Redis выполняет операции непосредственно над своими структурами данных.
- Большинство основных команд имеют сложность
O(1)илиO(log N). - Команды в основном выполняются последовательно одним главным потоком, поэтому внутри обычной команды не нужны тяжелые блокировки между потоками.
- Сервер использует неблокирующую обработку сетевых соединений.
- Несколько команд можно отправить pipeline-пакетом, сократив количество сетевых круговых поездок.
Современный Redis использует дополнительные потоки для фоновых операций и части ввода-вывода, однако с точки зрения исполнения большинства команд он остается преимущественно однопоточным. Поэтому две команды не изменяют один ключ одновременно: сервер последовательно исполняет сначала одну, затем другую. Но из этого же следует обратная сторона: одна тяжелая команда способна задержать всех остальных клиентов. ([Redis][2])
Схема:
Client A: GET user:1
Client B: INCR counter
Client C: HSET user:2 name Alex
┌─────────────────┐
│ Redis main loop │
└────────┬────────┘
│
GET user:1
│
INCR counter
│
HSET user:2 name Alex
Поэтому фраза «Redis быстрый» не означает, что любая команда над любым объемом данных безопасна.
GET small-key
обычно дешевая операция.
А вот:
KEYS *
SMEMBERS enormous-set
HGETALL enormous-hash
LRANGE enormous-list 0 -1
SUNION huge-set-1 huge-set-2
могут надолго занять главный поток.
Redis представляет собой скоростную кассу с одним очень быстрым кассиром. Если один посетитель привезет к кассе товарный поезд, вся очередь встанет.
3. Что Redis умеет решать
Redis применяют не для одной задачи, а для нескольких классов задач:
| Задача | Что используется |
|---|---|
| Кэширование | String, Hash, TTL, eviction |
| Сессии | String или Hash с TTL |
| Счетчики | INCR, INCRBY |
| Rate limiting | счетчики, Sorted Set, Lua, INCREX в Redis 8.8 |
| Очереди | List или Stream |
| Event log | Stream |
| Pub/Sub-уведомления | Pub/Sub |
| Таблица лидеров | Sorted Set |
| Уникальные элементы | Set |
| Онлайн-пользователи | Set или Bitmap |
| Распределенная блокировка | SET NX PX и безопасное освобождение |
| Геопоиск | Geospatial commands |
| Приблизительный подсчет уникальных значений | HyperLogLog |
| JSON-документы | Redis JSON |
| Временные ряды | Redis Time Series |
| Полнотекстовый и векторный поиск | Redis Search, Vector Sets |
Главная ошибка состоит в том, чтобы увидеть в Redis просто «очень быстрый HashMap». На самом деле сила Redis находится в серверных атомарных операциях над структурами данных.
Например, можно забрать значение, изменить его в Java и записать обратно:
int value = Integer.parseInt(redis.get("counter"));
redis.set("counter", Integer.toString(value + 1));
Но между GET и SET другой процесс тоже может прочитать старое значение. Обновления затрут друг друга.
Правильно:
INCR counter
Redis атомарно увеличит счетчик без клиентской гонки.
4. Запускаем Redis в Docker
Для локального изучения:
services:
redis:
image: redis:8.8
container_name: redis
restart: unless-stopped
ports:
- "127.0.0.1:6379:6379"
volumes:
- redis-data:/data
command:
- redis-server
- --appendonly
- "yes"
- --appendfsync
- everysec
- --save
- "60"
- "1000"
volumes:
redis-data:
Запуск:
docker compose up -d
Подключение:
docker exec -it redis redis-cli
Проверка:
PING
Ответ:
PONG
Порт опубликован только на 127.0.0.1, поэтому Redis доступен с этой машины, но не выставлен наружу. Это существенно: официальный Docker-образ предупреждает, что при публикации Redis-порта без пароля сервер может оказаться открыт для внешнего подключения. ([Redis][3])
5. Ключи
5.1. Именование
Redis не задает схему ключей. Ее создает приложение.
Обычно используют формат:
<domain>:<entity>:<id>:<attribute>
Примеры:
user:42
user:42:session
article:15:views
payment:781
payment:781:lock
cache:product:558
rate-limit:user:42:login
Двоеточие не обладает специальной семантикой. Это соглашение для человека, клиента и инструментов наблюдения.
Хороший ключ:
order:781:status
Плохие варианты:
status
object
data
x
Ключ должен сообщать:
- Какому домену он принадлежит.
- Какую сущность представляет.
- Какой идентификатор используется.
- Какова цель хранения.
5.2. Основные команды ключей
EXISTS user:42
TYPE user:42
DEL user:42
UNLINK user:42
RENAME old-key new-key
EXPIRE user:42 60
TTL user:42
PERSIST user:42
DEL удаляет ключ и освобождает его память синхронно. Для очень большого объекта это может задержать главный поток.
UNLINK немедленно отсоединяет ключ от keyspace, а фактическое освобождение памяти выполняет в другом потоке. Для больших ключей UNLINK безопаснее с точки зрения задержек. ([Redis][4])
6. TTL: время жизни ключа
TTL является одной из центральных возможностей Redis.
SET session:abc user-42 EX 3600
Ключ автоматически исчезнет приблизительно через час.
То же самое отдельной командой:
SET session:abc user-42
EXPIRE session:abc 3600
Проверка:
TTL session:abc
Возможные результаты:
3572 ключ существует и истечет через 3572 секунды
-1 ключ существует, но TTL не установлен
-2 ключ не существует
Миллисекунды:
PEXPIRE session:abc 5000
PTTL session:abc
Абсолютное время:
EXPIREAT key 1780000000
PEXPIREAT key 1780000000000
Удаление TTL:
PERSIST session:abc
Атомарная запись с TTL предпочтительнее последовательности SET плюс EXPIRE:
SET verification:code:42 918224 EX 300 NX
Здесь одновременно происходят:
- Запись значения.
- Установка срока жизни.
- Проверка, что ключ еще не существует.
SET поддерживает EX, PX, NX, XX, GET, KEEPTTL и другие условия. Обычная перезапись ключа может убрать старый TTL, а KEEPTTL позволяет его сохранить. ([Redis][5])
7. Основные структуры данных
7.1. String
String хранит последовательность байтов. Это может быть текст, число, JSON, бинарный объект, изображение или сериализованная структура.
SET user:42:name Igor
GET user:42:name
Установка только при отсутствии:
SET user:42:name Igor NX
Установка только при наличии:
SET user:42:name Igor XX
С TTL:
SET cache:article:15 "{...json...}" EX 300
Счетчики:
SET article:15:views 0
INCR article:15:views
INCRBY article:15:views 10
DECR article:15:views
INCR атомарен.
String подходит для:
- кэшированного ответа;
- токена;
- сессии;
- счетчика;
- feature flag;
- idempotency key;
- сериализованного объекта.
Redis String является бинарно-безопасным значением и поддерживает не только GET/SET, но и счетчики, диапазоны байтов, битовые операции и условную запись. ([Redis][6])
7.2. Hash
Hash представляет объект как набор полей:
user:42
name -> Igor
age -> 38
city -> Moscow
Команды:
HSET user:42 name Igor age 38 city Moscow
HGET user:42 name
HMGET user:42 name city
HGETALL user:42
HINCRBY user:42 loginCount 1
HDEL user:42 city
HEXISTS user:42 name
HLEN user:42
Hash удобен, когда поля объекта нужно читать и обновлять отдельно.
HSET payment:781 status CREATED amount 5000 currency RUB
HSET payment:781 status COMPLETED
В String с JSON пришлось бы:
- Прочитать весь JSON.
- Десериализовать.
- Изменить поле.
- Сериализовать.
- Записать весь объект.
Но у Hash есть важное ограничение: обычный TTL привязан ко всему ключу user:42, а не независимо к каждому полю.
Hash не заменяет реляционную таблицу. У Redis нет внешних ключей, joins и привычных SQL-ограничений.
7.3. List
List является упорядоченной последовательностью строк.
LPUSH notifications message-1
LPUSH notifications message-2
RPUSH notifications message-3
LRANGE notifications 0 -1
Получение с удалением:
LPOP notifications
RPOP notifications
Блокирующее ожидание:
BLPOP notifications 30
Клиент будет ждать до 30 секунд, пока элемент не появится.
Применения:
- стек;
- очередь;
- deque;
- ограниченная лента последних событий.
Например, хранение последних ста действий пользователя:
LPUSH user:42:activity event-json
LTRIM user:42:activity 0 99
Но List не является полноценным надежным брокером сообщений. После LPOP элемент исчезает. Если worker забрал сообщение и умер до обработки, сообщение потеряно, если ты отдельно не построил механизм подтверждений и восстановления.
Для надежной очереди обычно лучше Redis Streams.
7.4. Set
Set является неупорядоченным множеством уникальных строк.
SADD online-users user-1 user-2 user-3
SADD online-users user-2
Повторный user-2 не создаст дубликат.
SISMEMBER online-users user-2
SMEMBERS online-users
SCARD online-users
SREM online-users user-2
Операции над множествами:
SINTER group:a group:b
SUNION group:a group:b
SDIFF group:a group:b
Применения:
- уникальные теги;
- роли пользователя;
- участники группы;
- онлайн-пользователи;
- уже обработанные идентификаторы;
- пересечение интересов.
SADD user:42:roles admin editor
SISMEMBER user:42:roles admin
Проверка принадлежности, добавление и удаление элементов выполняются эффективно, а Redis также поддерживает пересечение, объединение и разность множеств. ([Redis][7])
7.5. Sorted Set
Sorted Set содержит уникальные элементы, каждому из которых соответствует числовой score.
Igor -> 1500
Alex -> 1200
Maria -> 1800
Команды:
ZADD leaderboard 1500 Igor
ZADD leaderboard 1200 Alex
ZADD leaderboard 1800 Maria
По убыванию:
ZREVRANGE leaderboard 0 -1 WITHSCORES
Позиция:
ZREVRANK leaderboard Igor
Изменение счета:
ZINCRBY leaderboard 100 Igor
Диапазон по score:
ZRANGE leaderboard 1000 1600 BYSCORE WITHSCORES
Применения:
- leaderboard;
- очередь приоритетов;
- расписание задач;
- индексация по времени;
- sliding-window rate limiter;
- ранжирование;
- поиск ближайших событий.
Для запланированных заданий score может быть Unix timestamp:
ZADD scheduled-jobs 1783785600 job-781
Worker получает все наступившие задания:
ZRANGE scheduled-jobs -inf 1783785600 BYSCORE
Sorted Set сохраняет уникальность элементов и одновременно упорядочивает их по score, поэтому хорошо подходит для рейтингов, приоритетных очередей и диапазонных запросов. ([Redis][8])
7.6. Stream
Stream является журналом событий, похожим на append-only log.
Добавление:
XADD payments * paymentId 781 status CREATED amount 5000
Redis создаст идентификатор:
1783785600123-0
Чтение:
XRANGE payments - +
Ожидание новых сообщений:
XREAD BLOCK 10000 STREAMS payments $
Stream хранит сообщения, поэтому потребитель может читать историю повторно.
Consumer Group
Создание группы:
XGROUP CREATE payments payment-workers 0 MKSTREAM
Чтение из группы:
XREADGROUP GROUP payment-workers worker-1 \
COUNT 10 BLOCK 5000 \
STREAMS payments >
После обработки:
XACK payments payment-workers 1783785600123-0
Сообщение, полученное группой, но еще не подтвержденное, находится в Pending Entries List.
Проверка:
XPENDING payments payment-workers
Это принципиально отличается от Pub/Sub:
Pub/Sub:
сообщение пришло прямо сейчас
подписчик отключен -> сообщение потеряно
Stream:
сообщение записано в журнал
consumer может прочитать его позже
группа отслеживает pending и acknowledgements
Redis Streams поддерживает журнал, consumer groups, повторное чтение и подтверждение обработки. Pub/Sub использует семантику at-most-once: если подписчик не получил сообщение из-за ошибки или отключения, Redis его не восстановит. ([Redis][9])
Stream против Kafka
Stream и Kafka похожи формой, но не тождественны.
Redis Stream:
- проще поднять;
- удобно использовать рядом с кэшем и transient state;
- хорошо подходит для умеренных потоков и локальной событийной обработки;
- сообщения хранятся внутри Redis memory model;
- retention нужно контролировать.
Kafka:
- изначально построена как распределенный долговременный commit log;
- рассчитана на большие объемы, партиции и длительное хранение;
- имеет другую модель масштабирования и дискового хранения.
Stream не нужно автоматически называть «маленькой Kafka». Это отдельная структура с частично пересекающимися сценариями.
7.7. Bitmap
Bitmap фактически хранится внутри String, но Redis позволяет работать с отдельными битами.
SETBIT active:2026-07-11 42 1
GETBIT active:2026-07-11 42
BITCOUNT active:2026-07-11
Если каждому пользователю соответствует числовой ID, один бит обозначает активность пользователя.
Применения:
- дневная активность;
- feature flags;
- посещаемость;
- булевы признаки для миллионов объектов.
Bitmap особенно эффективен, когда множество идентификаторов плотное и каждому объекту соответствует один бит. ([Redis][10])
7.8. HyperLogLog
HyperLogLog приблизительно подсчитывает количество уникальных значений, используя мало памяти.
PFADD visitors user-1 user-2 user-3
PFADD visitors user-2
PFCOUNT visitors
Результат будет оценкой количества уникальных пользователей.
Это нужно, когда точное множество хранить слишком дорого, а небольшая статистическая погрешность допустима:
- уникальные посетители;
- уникальные IP;
- количество различных поисковых запросов;
- cardinality огромного потока.
7.9. Geospatial
GEOADD cities 37.6173 55.7558 Moscow
GEOADD cities 30.3351 59.9343 Saint-Petersburg
Поиск объектов в радиусе:
GEOSEARCH cities \
FROMLONLAT 37.6173 55.7558 \
BYRADIUS 700 km \
WITHDIST
Применения:
- ближайшие магазины;
- курьеры;
- точки выдачи;
- географические объекты.
7.10. Redis 8: JSON, Search, Time Series, Vector Sets
В Redis 8 единая поставка включает дополнительные структуры:
- JSON;
- полнотекстовый и структурированный поиск;
- Time Series;
- Bloom Filter;
- Cuckoo Filter;
- Count-Min Sketch;
- Top-K;
- t-digest;
- Vector Sets;
- с Redis 8.8 также Array.
Это уже следующий слой. Он не отменяет классические структуры, а добавляет специализированные модели. ([Redis][7])
8. Атомарность
Одна Redis-команда выполняется как единая операция относительно других команд.
Например:
INCR counter
атомарен.
А последовательность:
GET counter
SET counter new-value
не атомарна как единое целое.
Между командами может вмешаться другой клиент:
Client A: GET counter -> 10
Client B: GET counter -> 10
Client A: SET counter 11
Client B: SET counter 11
Ожидали: 12
Получили: 11
9. Transactions: MULTI, EXEC, WATCH
9.1. MULTI/EXEC
MULTI
INCR account:1:operations
DECR account:1:balance
EXEC
После MULTI команды не исполняются сразу, а помещаются в очередь. EXEC запускает их последовательно без вклинивания команд другого клиента. ([Redis][11])
Но Redis-транзакция не является полной аналогией SQL-транзакции.
Главные свойства:
- команды внутри
EXECисполняются последовательно; - другой клиент не вклинится между ними;
- Redis не предоставляет привычный автоматический rollback результатов уже исполненных команд;
- результат каждой команды возвращается отдельно;
- проверка и вычисление на клиенте требуют
WATCHили server-side script/function.
9.2. Optimistic locking через WATCH
WATCH account:1:balance
GET account:1:balance
Приложение вычисляет новое значение.
MULTI
SET account:1:balance 900
EXEC
Если после WATCH другой клиент изменил наблюдаемый ключ, EXEC не выполнит транзакцию. Клиент должен повторить операцию.
Это compare-and-set модель.
10. Lua scripts и server-side functions
Когда нужно атомарно:
- Прочитать несколько ключей.
- Проверить условие.
- Изменить данные.
- Вернуть результат.
Можно выполнить Lua-скрипт:
EVAL "
local current = redis.call('GET', KEYS[1])
if not current then
return 0
end
if tonumber(current) <= 0 then
return -1
end
redis.call('DECR', KEYS[1])
return 1
" 1 inventory:product:42
Скрипт исполняется на стороне Redis и не допускает вклинивания другой команды в середину логики.
Но длинный Lua-скрипт блокирует обработку других команд. Скрипт должен быть маленьким, ограниченным и вычислительно предсказуемым. Lua не является способом перенести внутрь Redis половину бизнес-сервиса.
11. Pipeline
Без pipeline:
client -> SET a 1 -> server
client <- OK <- server
client -> SET b 2 -> server
client <- OK <- server
client -> SET c 3 -> server
client <- OK <- server
Три сетевых round trip.
С pipeline:
client -> SET a 1
SET b 2
SET c 3 -> server
client <- OK
OK
OK <- server
Pipeline сокращает сетевые задержки, отправляя несколько команд без ожидания ответа после каждой. ([Redis][12])
Важно:
Pipeline не является транзакцией.
Он оптимизирует транспорт, но сам по себе не гарантирует, что между командами не будут исполнены команды других клиентов.
Pipeline = меньше сетевых поездок
MULTI/EXEC = изолированная последовательность команд
Lua = атомарная серверная логика
12. Кэширование
Самый распространенный паттерн называется cache-aside.
1. Приложение запрашивает cache:product:42.
2. Redis возвращает значение -> отдаем клиенту.
3. Redis не нашел ключ -> читаем PostgreSQL.
4. Записываем результат в Redis с TTL.
5. Отдаем клиенту.
Псевдокод:
Product product = redis.get("cache:product:" + id);
if (product != null) {
return product;
}
product = database.findProduct(id);
redis.set(
"cache:product:" + id,
serialize(product),
Duration.ofMinutes(10)
);
return product;
12.1. Инвалидация
После изменения продукта:
UPDATE product in PostgreSQL
DELETE cache:product:42
Следующее чтение восстановит кэш.
Можно обновлять кэш сразу, но удаление часто проще: база остается источником истины, а кэш реконструируется.
12.2. Cache stampede
Ключ истек. Одновременно пришло 1000 запросов.
1000 запросов -> Redis miss
1000 запросов -> PostgreSQL
Redis перестал защищать базу именно в момент нагрузки.
Защита:
- локальный single-flight;
- короткая блокировка на перестроение;
- soft TTL;
- background refresh;
- случайный разброс TTL;
- stale-while-revalidate.
12.3. Cache penetration
Клиенты постоянно спрашивают несуществующие объекты:
product:999999999
Каждый раз Redis miss, затем обращение к базе.
Решение: кэшировать факт отсутствия на небольшой срок.
SET cache:product:999999999 "__NOT_FOUND__" EX 30
12.4. Cache avalanche
Огромное количество ключей имеет одинаковый TTL и истекает одновременно.
Плохо:
TTL каждого ключа = ровно 3600 секунд
Лучше:
TTL = 3600 + random(0..600)
13. Ограничение памяти и eviction
Redis хранит данные в RAM, поэтому необходимо отвечать на вопрос:
Что произойдет, когда память закончится?
Конфигурация:
maxmemory 2gb
maxmemory-policy allkeys-lfu
Основные политики:
| Политика | Поведение |
|---|---|
noeviction |
не удаляет ключи, новые записи получают ошибку |
allkeys-lru |
вытесняет давно не использованные ключи |
allkeys-lfu |
вытесняет редко используемые ключи |
allkeys-random |
удаляет случайные ключи |
volatile-lru |
LRU только среди ключей с TTL |
volatile-lfu |
LFU только среди ключей с TTL |
volatile-random |
случайный ключ среди ключей с TTL |
volatile-ttl |
ключ с наименьшим оставшимся TTL |
В Redis 8.8 также присутствуют allkeys-lrm и volatile-lrm, ориентированные на время последней модификации. Если ни у одного ключа нет TTL, политики volatile-* фактически ведут себя как noeviction. ([Redis][13])
Практическая логика:
Redis только кэш:
allkeys-lfu или allkeys-lru
Redis содержит важное состояние:
noeviction
Redis смешивает постоянные данные и кэш:
архитектурно опасно
Лучше не смешивать в одном инстансе:
cache keys, которые можно удалить
и:
critical state, которое удалять нельзя
Иначе одна eviction policy должна одновременно обслуживать два несовместимых смысла.
14. Big keys
Big key означает не только огромную String.
Это может быть:
String размером 200 MB
Hash с миллионом полей
Set с миллионом участников
List с миллионом элементов
Stream без retention
Проблемы big keys:
- тяжелое чтение;
- тяжелое удаление;
- задержка репликации;
- сетевые всплески;
- дорогая миграция между cluster nodes;
- fork и copy-on-write pressure;
- одна команда может блокировать остальных;
- неравномерное распределение памяти по shards.
Поиск:
redis-cli --bigkeys
Информация:
MEMORY USAGE some-key
OBJECT ENCODING some-key
redis-cli --bigkeys постепенно сканирует keyspace и показывает крупнейшие ключи по типам. ([Redis][14])
Не следует использовать в production:
KEYS *
Для обхода keyspace:
SCAN 0 MATCH user:* COUNT 100
Продолжать нужно с курсором, который вернул Redis, пока курсор снова не станет 0.
SCAN работает инкрементально и не требует одним вызовом обходить всю базу, но во время изменяющегося keyspace может вернуть один элемент несколько раз. Клиент обязан допускать дубликаты. ([Redis][15])
15. Persistence: что переживет перезапуск
Redis работает в памяти, но может сохранять состояние на диск.
Есть четыре режима:
- Без persistence.
- RDB.
- AOF.
- RDB и AOF одновременно. ([Redis][16])
15.1. Без persistence
Redis restart -> данные исчезли
Это допустимо для полностью реконструируемого кэша.
15.2. RDB
Redis периодически создает snapshot всего dataset.
save 60 1000
Означает: создать snapshot, если за 60 секунд произошло как минимум 1000 изменений.
Плюсы:
- компактный файл;
- удобно делать резервные копии;
- быстрое восстановление;
- сравнительно низкая постоянная нагрузка на диск.
Минусы:
- между snapshots можно потерять последние изменения;
- snapshot использует
fork; - большой dataset создает copy-on-write нагрузку;
- возможны latency spikes.
15.3. AOF
Redis записывает изменяющие команды в append-only log:
SET user:1 Igor
INCR counter
HSET payment:781 status COMPLETED
При запуске Redis воспроизводит журнал.
appendonly yes
appendfsync everysec
Режимы:
| Настройка | Смысл |
|---|---|
appendfsync always |
fsync после записей, безопаснее, но дорого |
appendfsync everysec |
fsync примерно раз в секунду |
appendfsync no |
момент фактической записи решает ОС |
При everysec в аварии можно потерять примерно одну секунду последних записей. AOF со временем переписывается, чтобы журнал не рос бесконечно. ([Redis][16])
15.4. Persistence не является backup
Если приложение выполнило:
FLUSHALL
Redis честно запишет эту команду в AOF.
Если данные испорчены логически, репликация также распространит повреждение.
Поэтому нужны:
- отдельные snapshots;
- копии вне Redis-машины;
- проверка восстановления;
- retention;
- наблюдение за состоянием AOF/RDB.
Реплика не заменяет backup. AOF не заменяет backup. RAID не заменяет backup. Три разных механизма защищают от разных разрушений.
16. Replication
Топология:
writes
Client ─────────────► Primary
│
├────────► Replica 1
└────────► Replica 2
Primary принимает записи и передает поток изменений replicas.
Репликация по умолчанию асинхронная:
Client -> Primary: SET x 1
Primary -> Client: OK
Primary -> Replica: SET x 1
OK может быть возвращен до того, как replica гарантированно применила изменение.
Следствие:
При аварийном failover возможно потерять уже подтвержденную запись.
Команда WAIT позволяет дождаться подтверждения от заданного количества replicas:
SET payment:781 COMPLETED
WAIT 1 1000
Но даже WAIT не превращает Redis в строго согласованную CP-систему. Он сокращает окно риска, но не создает транзакционный консенсус. ([Redis][17])
Реплики можно использовать для чтения, но появляется eventual consistency:
записал в primary
сразу прочитал из replica
увидел старое значение
Нужно заранее определить:
- какие чтения допускают lag;
- какие должны идти в primary;
- что происходит после failover;
- допустима ли потеря подтвержденной записи.
17. Sentinel
Sentinel решает high availability для схемы primary плюс replicas.
┌────────────┐
│ Sentinel 1 │
├────────────┤
│ Sentinel 2 │
├────────────┤
│ Sentinel 3 │
└──────┬─────┘
│ наблюдают
▼
Primary
/ \
Replica Replica
Sentinel:
- Наблюдает за primary и replicas.
- Определяет недоступность primary.
- Договаривается с другими Sentinel.
- Выбирает replica.
- Повышает ее до primary.
- Сообщает клиентам новый адрес primary.
Один Sentinel бессмыслен как механизм отказоустойчивости, потому что сам становится single point of failure. Sentinel задуман как распределенная система из нескольких процессов, принимающих решение совместно. ([Redis][18])
Sentinel не шардирует данные.
Sentinel = high availability без sharding
18. Redis Cluster
Redis Cluster решает две задачи:
- Sharding.
- High availability.
Keyspace делится на 16 384 hash slots.
CRC16(key) mod 16384 -> slot
Пример:
Node A -> slots 0..5500
Node B -> slots 5501..11000
Node C -> slots 11001..16383
Клиент вычисляет slot ключа и обращается к нужной node. Cluster-aware client умеет обрабатывать перенаправления MOVED и ASK.
Redis Cluster автоматически распределяет keyspace между узлами и использует replicas для failover, но при крупных сбоях, например потере большинства primary nodes, кластер может стать недоступен. Redis Cluster также не гарантирует строгую согласованность и при некоторых отказах способен потерять подтвержденные записи. ([Redis][19])
18.1. Multi-key operations
Команда:
MGET user:1 user:2
в Cluster допустима только тогда, когда ключи находятся в одном slot.
Для этого используются hash tags:
user:{42}:profile
user:{42}:settings
user:{42}:sessions
При вычислении slot Redis использует содержимое {42}, поэтому все три ключа попадут в один slot.
MGET user:{42}:profile user:{42}:settings
будет возможен.
Но hash tags способны создать hot shard, если слишком много данных случайно привязать к одному значению.
19. Sentinel против Cluster
| Свойство | Один Redis | Sentinel | Cluster |
|---|---|---|---|
| Простота | высокая | средняя | низкая |
| Репликация | вручную | да | да |
| Автоматический failover | нет | да | да |
| Sharding | нет | нет | да |
| Несколько primary | нет | нет | да |
| Cluster-aware client | не нужен | Sentinel-aware | нужен |
| Multi-key ограничения | нет | нет | есть |
Выбор:
Небольшой кэш:
один Redis
Нужна HA, dataset помещается на одну машину:
primary + replicas + Sentinel
Нужно распределять dataset или write load:
Redis Cluster
Не следует поднимать Cluster только потому, что слово звучит солиднее. У Cluster появляется целый лес: slots, redirections, resharding, replica placement, failover, split brain conditions, cross-slot restrictions и cluster-aware clients.
20. Pub/Sub
Publisher:
PUBLISH notifications "hello"
Subscriber:
SUBSCRIBE notifications
Все активные подписчики получат сообщение.
Но Redis не сохраняет сообщение для отключенных subscribers. Нет ack, replay и pending list.
Применения:
- live notifications;
- cache invalidation;
- обновление UI;
- сигнал «что-то изменилось»;
- необязательные transient events.
Не подходит для:
- платежей;
- задач, которые обязательно должны быть выполнены;
- событий, требующих replay;
- обработки с подтверждением.
Для этого нужен Stream или отдельный брокер.
21. Распределенная блокировка
Базовое получение lock:
SET lock:payment:781 random-owner-token NX PX 10000
Здесь:
NXозначает создать только при отсутствии;PX 10000задает TTL 10 секунд;random-owner-tokenидентифицирует владельца.
Нельзя освобождать блокировку простым:
DEL lock:payment:781
Сценарий:
Worker A получил lock на 10 секунд.
Worker A завис.
TTL истек.
Worker B получил тот же lock.
Worker A ожил и выполнил DEL.
Worker A удалил lock Worker B.
Освобождение должно атомарно проверить owner token:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
Redis документирует как single-instance lock, так и Redlock для нескольких независимых узлов. Однако блокировка с TTL не превращает бизнес-операцию в математически невозможную для повторного исполнения. Процесс может зависнуть дольше TTL, сеть может задержать ответы, а операция во внешней системе может продолжиться после утраты lock. ([Redis][20])
Для критической целостности нужны дополнительные механизмы:
- idempotency key;
- unique constraint;
- version column;
- fencing token;
- транзакция основной базы;
- проверка состояния самой предметной сущности.
Redis-lock координирует попытки. Он не заменяет инвариант.
22. Rate limiting
Fixed window
INCR rate:user:42:2026-07-11T17:30
EXPIRE rate:user:42:2026-07-11T17:30 60
Разрешаем, пока значение не превысило лимит.
Проблема: INCR и EXPIRE должны выполняться атомарно. Иначе процесс может упасть между командами, оставив вечный счетчик.
Решения:
- Lua;
- transaction;
- специализированная команда;
- в Redis 8.8 команда
INCREX.
Sliding window через Sorted Set
Score равен timestamp:
ZADD rate:user:42 1783785600123 request-id
ZREMRANGEBYSCORE rate:user:42 -inf 1783785540123
ZCARD rate:user:42
EXPIRE rate:user:42 60
Вся последовательность должна быть Lua-скриптом или server-side function, иначе несколько клиентов смогут нарушить границу.
Redis 8.8 добавил INCREX, объединяющий увеличение счетчика, ограничения и expiration в одной атомарной операции. ([Redis][21])
23. Производительность клиента
Очень часто bottleneck находится не внутри Redis, а между приложением и Redis.
23.1. Не создавать соединение на каждый запрос
Плохо:
HTTP request
connect Redis
authenticate
execute GET
disconnect
Нужно:
- долгоживущие соединения;
- connection pooling там, где он действительно нужен;
- отдельные соединения для blocking commands;
- отдельное соединение для Pub/Sub;
- cluster-aware client для Redis Cluster.
23.2. Настроить timeouts
Нужны разные ограничения:
- connect timeout;
- command timeout;
- socket timeout;
- pool acquisition timeout;
- retry policy.
Бесконечный timeout превращает Redis failure в зависание всего HTTP thread pool.
23.3. Retry не всегда безопасен
Сценарий:
Client -> INCR counter
Redis исполнил INCR
Ответ потерялся
Client повторил INCR
Итог: увеличение произошло дважды.
Повтор чтения обычно безопасен. Повтор изменения зависит от идемпотентности.
23.4. Pipelining
Для сотен независимых команд:
1000 x GET по одному -> 1000 round trips
pipeline 1000 GET -> значительно меньше round trips
Но нельзя создавать безграничные pipelines. Redis и клиент должны удерживать команды и ответы в памяти. Обычно используются ограниченные batches.
24. Наблюдение
Минимальный набор:
INFO
INFO memory
INFO stats
INFO clients
INFO replication
INFO persistence
INFO commandstats
Что смотреть:
used_memory
used_memory_rss
mem_fragmentation_ratio
connected_clients
blocked_clients
evicted_keys
expired_keys
keyspace_hits
keyspace_misses
instantaneous_ops_per_sec
rejected_connections
master_repl_offset
replica lag
aof_last_write_status
rdb_last_bgsave_status
Slowlog
SLOWLOG GET 20
SLOWLOG LEN
Slowlog измеряет время исполнения команды внутри Redis и не включает сетевую передачу ответа клиенту. Поэтому высокая application latency при пустом slowlog может означать сетевую проблему, очередь соединений, паузы клиента или перегрузку инфраструктуры. ([Redis][22])
Latency monitor
CONFIG SET latency-monitor-threshold 100
LATENCY LATEST
LATENCY DOCTOR
Redis имеет встроенный latency monitor, который регистрирует различные классы задержек, включая медленные команды, fork и фоновые операции. ([Redis][23])
Проверка с CLI
redis-cli --latency
redis-cli --bigkeys
redis-cli --stat
25. Безопасность
Redis не следует публиковать прямо в интернет.
Минимум:
- Привязка к внутреннему интерфейсу.
- Firewall.
- ACL users.
- Пароли или сертификаты.
- TLS для недоверенной сети.
- Запрет опасных administrative commands обычным приложениям.
- Отдельные пользователи для разных сервисов.
- Не хранить secrets в открытом виде без осознанной модели угроз.
ACL позволяет ограничивать:
- доступные команды;
- доступные шаблоны ключей;
- Pub/Sub channels;
- пользователей и пароли. ([Redis][24])
Пример концептуально:
user payment-service:
разрешено GET, SET, HGET, HSET
разрешены ключи payment:*
запрещены FLUSHALL, CONFIG, SHUTDOWN
TLS поддерживается Redis Open Source, но конкретный binary должен быть собран или поставлен с TLS support. ([Redis][25])
26. Команды, которые требуют особой осторожности
KEYS *
FLUSHALL
FLUSHDB
CONFIG SET
DEBUG
SHUTDOWN
SAVE
MONITOR
SMEMBERS huge-set
HGETALL huge-hash
LRANGE huge-list 0 -1
SORT huge-list
SUNION huge-set-a huge-set-b
EVAL long-running-script
MONITOR удобен для отладки, но под нагрузкой создает серьезный overhead и может раскрывать аргументы команд.
SAVE выполняет snapshot синхронно. Для production обычно используют BGSAVE.
FLUSHALL является настоящим огнеметом. Он делает ровно то, что написано на корпусе.
27. Типовые архитектурные ошибки
Ошибка 1. Redis без TTL как бесконечный склад
Каждый запрос создает новый ключ.
Ни один ключ не истекает.
Память медленно заполняется.
Через месяц Redis падает или начинает eviction.
Для каждого класса ключей должно быть известно:
Кто создает?
Кто удаляет?
Есть ли TTL?
Можно ли вытеснить?
Как восстановить?
Ошибка 2. Смешивание кэша и критических данных
cache:product:42
payment:idempotency:781
session:abc
business-counter
Все лежит в одном Redis с allkeys-lru.
При нехватке памяти Redis может удалить ключ, который приложение считало важным.
Ошибка 3. Использование Redis как базы «потому что быстро»
Сначала нужно определить:
- нужны ли joins;
- нужны ли constraints;
- нужна ли строгая durability;
- нужна ли история изменений;
- нужна ли транзакция с другими сущностями;
- допустима ли потеря последних записей;
- что произойдет при failover;
- как делается backup и restore.
Redis может быть primary database для подходящей модели, но это должно быть сознательным решением, а не результатом опьянения скоростью.
Ошибка 4. Неограниченный Stream
XADD events * ...
бесконечно.
Нужно retention:
XADD events MAXLEN ~ 100000 * field value
Иначе журнал растет вместе с памятью.
Ошибка 5. Один гигантский Hash
all-users -> Hash с десятками миллионов fields
Получается один hot key, который невозможно нормально шардировать по пользователям.
Лучше:
user:1
user:2
user:3
если модель операций не требует общей структуры.
Ошибка 6. Ожидание exactly-once
Redis Stream, lock или retry сами по себе не создают exactly-once business effect.
Сообщение может быть:
- обработано, но не подтверждено;
- доставлено повторно;
- подтверждено после частичного выполнения;
- повторно выдано после смерти consumer.
Consumer должен быть идемпотентным.
28. Что нужно знать на собеседовании
Нужно уметь без воды объяснить:
- Почему Redis быстрый.
- Что означает преимущественно однопоточное исполнение.
- Чем Redis отличается от Memcached.
- Какие структуры данных существуют.
- String против Hash.
- List против Stream.
- Pub/Sub против Stream.
- Что такое TTL.
- Что такое eviction.
- LRU против LFU.
- RDB против AOF.
- Почему replication не гарантирует отсутствие потери записей.
- Sentinel против Cluster.
- Что такое hash slots и hash tags.
- Почему
KEYS *опасен. - Что такое big key и hot key.
- Pipeline против transaction.
MULTI/EXECпротив Lua.- Как устроен cache-aside.
- Что такое cache stampede.
- Как строится distributed lock.
- Почему простой
DEL lockнекорректен. - Почему retries могут продублировать запись.
- Как контролировать память.
- Какие метрики смотреть.
29. Практическая последовательность изучения
Этап 1. Базовые ключи
SET name Igor
GET name
TYPE name
EXISTS name
DEL name
Этап 2. TTL
SET session:1 user-42 EX 10
TTL session:1
GET session:1
Подождать 10 секунд:
GET session:1
Этап 3. Структуры
HSET user:42 name Igor age 38
HGETALL user:42
SADD user:42:roles admin editor
SMEMBERS user:42:roles
ZADD leaderboard 100 Igor 120 Alex
ZREVRANGE leaderboard 0 -1 WITHSCORES
LPUSH jobs job-1 job-2
RPOP jobs
Этап 4. Атомарность
Открыть два терминала и одновременно выполнять:
INCR counter
Затем сравнить с клиентской схемой GET плюс SET.
Этап 5. Transaction
MULTI
INCR a
INCR b
EXEC
Затем изучить WATCH.
Этап 6. Stream
XGROUP CREATE payments workers 0 MKSTREAM
XADD payments * paymentId 1 status CREATED
XREADGROUP GROUP workers worker-1 STREAMS payments >
XPENDING payments workers
XACK payments workers <message-id>
Этап 7. Persistence
SET persistent:test hello
Перезапустить:
docker compose restart redis
Проверить:
GET persistent:test
Этап 8. Memory и eviction
В отдельном учебном контейнере:
CONFIG SET maxmemory 10mb
CONFIG SET maxmemory-policy allkeys-lru
Заполнять Redis и наблюдать:
INFO memory
INFO stats
Этап 9. Диагностика
INFO
SLOWLOG GET 10
MEMORY USAGE some-key
redis-cli --bigkeys
redis-cli --latency
30. Итоговая модель Redis
Redis нужно держать в голове сразу в пяти плоскостях:
1. Data structures
String, Hash, List, Set, Sorted Set, Stream
2. Time
TTL, expiration, scheduled values, retention
3. Atomicity
atomic commands, MULTI/EXEC, WATCH, Lua
4. Memory
maxmemory, eviction, big keys, hot keys
5. Reliability
RDB, AOF, replication, Sentinel, Cluster
Главная формула:
Redis силен не потому, что просто хранит данные в RAM. Он силен потому, что выполняет атомарные серверные операции над специализированными структурами данных.
И одновременно его главная опасность:
Поскольку команды проходят через общий контур исполнения, неправильно выбранная структура, большой ключ или тяжелая операция способны превратить сверхбыстрый сервер в однополосный тоннель, поперек которого кто-то припарковал грузовик.