1 of 48

Раскапываем Redis

2024

2 of 48

О спикере

Сидоркин Алексей

Архитектор в ГК Иннотех

В разработке с 2015

Проекты

  • CRM банка Зенит
  • ERP компании «Моё Дело»
  • CRM Национальное Рейтинговое Агентство
  • НОТА ЮНИОН�(группа продуктов HR Tech)

2

3 of 48

План

- AOF vs RDB

- Что такое Redis

- PHP клиенты для Redis

- Redis Cluster и Redis Sentinel

- Структуры данных в Redis

- Redis Streams vs Kafka

3

4 of 48

Конфигураци Redis

Redis — это высокоскоростная NoSQL in memory база данных

  • Поизводительность:
    • GET/SET100 000 – 600 000 ops/sec (на обычном сервере).
    • LPUSH/LRANGE50 000 – 300 000 ops/sec.
    • Тяжёлые команды (KEYS) — могут упасть до 100–1000 ops/sec.

CPU (Главное узкое место!)

  • Redis использует один поток для обработки команд → упирается в тактовую частоту CPU.
  • Совет: Выбирайте инстансы с высоким IPC (инструкций за такт) и минимум 3.5+ GHz.

Пропускная способность сети

  • 1 Gbps120 000 ops/sec (для команд размером ~100 байт).
  • 10 Gbps позволяет достичь 1 000 000+ ops/sec.

4

5 of 48

Конфигураци Redis

5

6 of 48

Redis Cluster vs Sentinel

Как выбрать стратегию для отказоустойчивости и распределения нагрузки ?

6

7 of 48

Redis Cluster vs Sentinel

7

8 of 48

Redis Cluster vs Sentinel

// 1. Создаем объект Sentinel

$sentinel = new RedisSentinel('sentinel.example.com', 26379, 0.5,null, 100);

// 2. Получаем текущего мастера

$master = $sentinel->getMasterAddrByName('mymaster');

// 3. Подключаемся к мастеру

$redis = new Redis();

$redis->connect($master[0], $master[1]);

// 4. Работаем с Redis

$redis->set('presentation', 'Redis+Sentinel works!');

echo $redis->get('presentation');

8

9 of 48

Redis Cluster vs Sentinel

// 1. Создаем объект Sentinel

$sentinel = new RedisSentinel('sentinel.example.com', 26379, 0.5,null, 100);

// 2. Получаем текущего мастера

$master = $sentinel->getMasterAddrByName('mymaster');

// 3. Подключаемся к мастеру

$redis = new Redis();

$redis->connect($master[0], $master[1]);

// 4. Работаем с Redis

$redis->set('presentation', 'Redis+Sentinel works!');

echo $redis->get('presentation');

9

10 of 48

Redis Cluster vs Sentinel

// 1. Создаем объект Sentinel

$sentinel = new RedisSentinel('sentinel.example.com', 26379, 0.5,null, 100);

// 2. Получаем текущего мастера

$master = $sentinel->getMasterAddrByName('mymaster');

// 3. Подключаемся к мастеру

$redis = new Redis();

$redis->connect($master[0], $master[1]);

// 4. Работаем с Redis

$redis->set('presentation', 'Redis+Sentinel works!');

echo $redis->get('presentation');

10

11 of 48

Redis Cluster vs Sentinel

// 1. Создаем объект Sentinel

$sentinel = new RedisSentinel('sentinel.example.com', 26379, 0.5,null, 100);

// 2. Получаем текущего мастера

$master = $sentinel->getMasterAddrByName('mymaster');

// 3. Подключаемся к мастеру

$redis = new Redis();

$redis->connect($master[0], $master[1]);

// 4. Работаем с Redis

$redis->set('presentation', 'Redis+Sentinel works!');

echo $redis->get('presentation');

11

12 of 48

Redis Cluster vs Sentinel

Redis Sentinel — Мониторинг и автоматический failover

  • Роли:
    • Master: Принимает запись.
    • Replica: Чтение + резерв.
    • Sentinel: Мониторит мастер и реплики, запускает failover.
  • Как работает:
    • 3+ Sentinel-ноды для кворума.
    • При падении мастера выбирает новую реплику.
  • Плюсы:
    • Простота настройки.
    • Подходит для сценариев "один мастер + несколько реплик".
  • Минусы:
    • Нет шардинга (все данные на мастере).

12

13 of 48

Redis Cluster vs Sentinel

13

14 of 48

Redis Cluster vs Sentinel

Redis Cluster — Горизонтальное масштабирование

Содержание:

  • Как работает:
    • Данные делятся на 16384 слота (хеш-слоты).
    • Каждый мастер отвечает за диапазон слотов.
    • Минимум 3 мастер-ноды (+ реплики для каждой).
  • Плюсы:
    • Шардинг → нагрузка распределяется.
    • Автоматическая маршрутизация команд.
  • Минусы:
    • Сложнее настройка.
    • Не все команды работают в рамках одной ноды (например, MGET без hash tags).

14

15 of 48

Redis Cluster vs Sentinel

Транзакции в кластере (PHP пример)

Содержание:

$redis->multi()

->set("{order:100}:status", "paid")

->hset("{order:100}:items", "product1", 2)

->expire("{order:100}:lock", 3600)

->exec(); // Сработает, если все ключи в одном слоте

Ошибка:

$redis->multi()

->set("order:100", "data")

->set("user:50", "data")

->exec();

15

16 of 48

Redis Cluster vs Sentinel

Обработка ошибок шардирования��try {

$redis->mget(["key1", "key2"]); // Может вызвать MOVED ошибку

} catch (RedisException $e) {

if (strpos($e->getMessage(), 'MOVED') !== false) {

// разделяем запрос

$redis->get("key1");� $redis->get("key2");

}

}

16

17 of 48

Redis Cluster vs Sentinel

Топологии развертывания

17

18 of 48

Redis Cluster vs Sentinel

  1. Sentinel — простое решение для failover.
  2. Cluster — масштабируемость + отказоустойчивость.
  3. Выбор зависит от объема данных и нагрузки.

18

19 of 48

AOF vs RDB

Выбор стратегии сохранения данных в Redis

19

20 of 48

AOF vs RDB

RDB (Snapshotting) — Снимки данных

Содержание:

  • Как работает:
    • Периодические бинарные снимки всего dataset’а.
    • Блокирует главный поток при форке (2-10 мс на 1 ГБ RAM)
    • Настраивается через save {i sec} {n keys} (если n ключей изменился за i сек).
  • Плюсы:
    • Компактные файлы (хорошо для бэкапов).
    • Быстрое восстановление.
  • Минусы:
    • Риск потери данных между снэпшотами.
    • Блокировка потока при сохранении (SAVE).

20

21 of 48

AOF vs RDB

AOF (Append-Only File) — Лог команд

Содержание:

  • Как работает:
    • Логирует каждую команду (например, SET user:1 "Alice").
    • Настраивается через appendonly yes.
  • Плюсы:
    • Максимальная долговечность (можно восстановить до последней команды).
    • Гибкость (appendfsync everysec/always/no).
  • Минусы:
    • Большой размер файла.
    • Медленнее RDB при восстановлении.

21

22 of 48

AOF vs RDB

Гибридная стратегия: Совместное использование AOF и RDB в Redis

Гибридный формат (RDB-preamble)

  • Активируется через aof-use-rdb-preamble yes (Redis 4+)
  • При перезаписи AOF:
    1. Сначала сохраняет данные в RDB-формате
    2. Затем дописывает новые команды в AOF-стиле

[RDB-данные] + [AOF-команды после снэпшота]

Этапы загрузки AOF с RDB-preamble

  1. Быстрая загрузка RDB-части (до 10x быстрее чистого AOF)
  2. Применение оставшихся AOF-команд
  3. Валидация: Проверка контрольных сумм (checksum)

22

23 of 48

AOF vs RDB

Механизм

Основные риски

Методы минимизации

RDB

- Потеря данных между снэпшотами

- Блокировка при SAVE (не BGSAVE)

- Несовместимость формата между версиями

Увеличить частоту снэпшотов (save 60 1000)

Всегда использовать BGSAVE

Проверять версии при миграции

AOF

- Рост размера файла

- Высокая нагрузка на диск при appendfsync always

- Медленное восстановление

Регулярный AOF rewrite

Использовать everysec вместо always

SSD-диски

RDB-preamble

- Двойной расход CPU при перезаписи

- Временный рост использования памяти (~2x)

Мониторить auto-aof-rewrite-percentage

Выделять резерв памяти

Золотые правила:

  1. Для кэша → только RDB (save "" + appendonly no).
  2. Для транзакций → AOF (appendfsync everysec) + RDB (save 60 1).
  3. Для гибридного решения → Redis 4+ с aof-use-rdb-preamble yes.
  4. Всегда тестируйте восстановление из бэкапов!

Гибридный режим с RDB-preamble — оптимальный выбор для большинства продакшен-сценариев, сочетающий преимущества обоих подходов. Для специфичных случаев (например, pure-кэширование) допустимы чистые RDB или AOF.

23

24 of 48

PHP клиенты для Redis

Битва клиентов

24

25 of 48

PHP клиенты для Redis

Параметр

phpredis (C-расширение)

predis (Pure PHP)

amphp/redis (Async)

swoole/redis (Async, корутины)

Зависимости

Требует установки C-расширения

Чистый PHP (Composer)

Требует amphp/amp

Требует swoole (C-расширение)

25

26 of 48

PHP клиенты для Redis

Общая Производительность

Операция

phpredis (ms)

predis (ms)

amphp/redis (ms)

swoole/redis (ms)

SET

120

350

180

140

GET

110

340

170

130

INCR

115

360

190

135

LPUSH

125

370

200

145

LRANGE

130

380

210

150

Тестирование производительности

Для теста были выполнены 10 000 операций следующих типов:

26

27 of 48

PHP клиенты для Redis

Общая Работа с Sentinel и Cluster

Параметр

phpredis (C-расширение)

predis (Pure PHP)

amphp/redis (Async)

swoole/redis (Async, корутины)

Поддержка Sentinel

✅ Да (с PHP 8.2+)

✅ Да (гибкая конфигурация)

❌ Нет (только прямое подключение)

✅ Да (через конфигурацию)

Поддержка кластера

✅ Да RedisCluster

✅ Да (гибко)

❌ Нет

✅ Да (через Swoole\Redis\Cluster)

27

28 of 48

PHP клиенты для Redis

Общая Работа c Асинхронностью

Параметр

phpredis (C-расширение)

predis (Pure PHP)

amphp/redis (Async)

swoole/redis (Async, корутины)

Асинхронность

❌ Нет (синхронный)

❌ Нет (синхронный)

✅ Да (на основе Amp)

✅ Да (корутины Swoole)

28

29 of 48

PHP клиенты для Redis

Когда что выбирать?

Выбирайте phpredis если:

  • Нужна максимальная производительность (высоконагруженные синхронные приложения).

Выбирайте Predis если:

  • Нет прав на установку расширений
  • Нужна гибкость (кластеры, Sentinel, кастомные драйверы).

Выбирайте amphp/redis если:

  • Нужна асинхронность (HTTP/WebSocket-серверы на Amp).

Выбирайте swoole/redis если:

  • Не выбирайте deprecated с апреля 2024

29

30 of 48

Структуры данных в Redis

Я Расскажу тебе о своих структурах

30

31 of 48

Структуры данных в Redis

Strings (Строки)��Основное использование:

  • Кэширование HTML/JSON
  • Счетчики
  • Бинарные данные

$redis->set('user:100:profile', json_encode($userData));

$redis->incr('page:home:views'); // Инкремент счетчика

Особенности:

  • Макс. размер ключа или строки 512 МБ
  • Атомарные операции (INCR, APPEND)

31

32 of 48

Структуры данных в Redis

Hashes (Хеши)

Идеально для:

  • Хранения объектов
  • Частичного обновления полей

$redis->hMSet('product:123', [

'name' => 'Phone',

'price' => 599,

'stock' => 10

]);

$price = $redis->hGet('product:123', 'price');

Плюсы:

  • Экономия памяти vs JSON-строк (есть модуль Redis для работы с json)
  • Возможность работы с отдельными полями

32

33 of 48

Структуры данных в Redis

Lists (Списки)�

Сценарии:

  • Очереди задач
  • История действий
  • Лента событий

// Добавление в очередь

$redis->lPush('emails:queue', json_encode($emailTask));

// Обработка

while ($task = $redis->rPop('emails:queue')) {

process_email(json_decode($task, true));

}

Важно:

  • Макс. длина 2³²-1 элементов
  • Блокирующие операции (BLPOP)

33

34 of 48

Структуры данных в Redis

Sets (Множества)�

Применение:

  • Уникальные элементы
  • Тегирование
  • Связи между объектами

// Добавление тегов

$redis->sAdd('article:42:tags', 'php', 'redis', 'database');

// Поиск общих тегов

$commonTags = $redis->sInter('article:42:tags', 'article:56:tags');

Операции:

  • UNION, DIFF, INTER
  • SCAN для больших множеств

34

35 of 48

Структуры данных в Redis

Sorted Sets (Упорядоченные множества)�

Кейсы:

  • Рейтинги/топы
  • Таймлайны
  • Приоритетные очереди

Особенности:

  • Сортировка по score (double)
  • Диапазонные запросы (ZRANGEBYSCORE)

35

36 of 48

Структуры данных в Redis

HyperLogLog�

Для чего:

  • Уникальные подсчеты
  • Аналитика (уникальные посетители)

// Подсчет уникальных посещений

$redis->pfAdd('site:2023-10:visitors', ['user1', 'user2', 'user1']);

$uniqueVisitors = $redis->pfCount('site:2023-10:visitors'); // => 2

Плюсы:

  • Фиксированный размер 12 КБ
  • Погрешность ~0.81%

36

37 of 48

Структуры данных в Redis

Geospatial�

Для чего:

  • Поиск ближайших объектов
  • Геоаналитика
  • Отслеживание перемещений

/ Добавление точек

$redis->geoAdd('stores', 13.361389, 38.115556, 'store1');

// Поиск в радиусе

$nearby = $redis->geoRadius('stores', 13.361389, 38.115556, 10, 'km');

Особенности:

  • Использует алгоритм geohash – для эффективного индексирования и поиска.
  • Точность ограничена geohash – возможны небольшие погрешности при больших расстояниях.
  • Нет поддержки сложных геометрических фигур (только точки + радиус).

37

38 of 48

Структуры данных в Redis

Bitmaps�

Кейсы:

  • Посещаемость сайта�Фичи-флаги
  • Анализ аудитории

// Отметить посещение

$redis->setBit('user:logins:2023-10', 100, 1);

// Проверить активность

$wasActive = $redis->getBit('user:logins:2023-10', 100);

Особенности:

  • Экономия памяти – 1 бит на флаг (вместо 1 байта в строках).
  • Быстрые битовые операции (AND, OR, XOR, NOT).
  • Поддержка больших диапазонов (до 2³² бит ≈ 512 МБ на ключ).
  • Атомарные операции – безопасность в многопоточных сценариях.

38

39 of 48

Структуры данных в Redis

Redis Streams: Event-Driven �Аналогия: Kafka в Redis�Использование:

  • Очереди событий
  • Логирование

39

40 of 48

Структуры данных в Redis

Redis Streams: Event-Driven �Аналогия: Kafka в Redis�Использование:

  • Очереди событий
  • Логирование

Терминология:

  • Стрим — лог событий (например, user:actions)
  • Запись = ID + Пары ключ-значение
  • ID = <время>-<последовательность> (например, 1698765432000-0)
  • Группы потребителей — конкурирующие подписчики

40

41 of 48

Структуры данных в Redis

Redis Streams: Event-Driven �Аналогия: Kafka в Redis�Использование:

  • Очереди событий
  • Логирование

Терминология:

  • Стрим — лог событий (например, user:actions)
  • Запись = ID + Пары ключ-значение
  • ID = <время>-<последовательность> (например, 1698765432000-0)
  • Группы потребителей — конкурирующие подписчики

[ Стрим "orders" ]

├─ 1698765432000-0: { "event": "created", "user_id": 42 }

├─ 1698765432001-0: { "event": "paid", "user_id": 42 }

└─ 1698765432002-0: { "event": "shipped", "user_id": 42 }

41

42 of 48

Структуры данных в Redis

Redis Streams

42

43 of 48

Структуры данных в Redis

Базовые команды (PHP примеры)

$id = $redis->xAdd('user:events', '*', [

'action' => 'login',

'ip' => '192.168.1.1',

'user_id' => 100

]);

// $id = "1698765432000-0"

Публикация события:

Чтение с начала:

$events = $redis->xRead(['user:events' => '0'], 10);

// [

// "user:events" => [

// "1698765432000-0" => ["action" => "login", ...]

// ]

// ]

43

44 of 48

Структуры данных в Redis

Группы потребителей

Создание группы:

���Чтение группой:

������

Подтверждение� обработки:

44

45 of 48

Структуры данных в Redis

Характеристика

Redis Streams

Apache Kafka

Установка

Redis (≥5.0)

Отдельный кластер

Персистентность

Зависит от настроек AOF/RDB

Гарантирована

Производительность

~1M ops/sec на ноду

~100K-500K ops/sec

Макс. размер

Доступная память

Терабайты

Идеальный кейс

Реал-тайм события, небольшие задержки

Большие объемы данных

45

46 of 48

Структуры данных в Redis

Если вам нужно...

Выбирайте

Пример использования

Кеширование, счетчики, флаги

Strings

Кеш страниц, счетчик лайков

Очереди, стеки, ленты новостей

Lists

Фоновые задачи, история действий

Уникальные элементы, теги

Sets

Подписчики, черные списки

Рейтинги, сортировка

Sorted Sets

Топ игроков, таймлайн постов

Хранение объектов (JSON-подобные данные)

Hashes

Профили пользователей, настройки

Бинарные флаги, битовая аналитика

Bitmaps

Онлайн-статусы, аудит активности

Подсчет уникальных значений (приблизительный)

HyperLogLog

Уникальные посетители за день

Работа с геоданными

Geospatial

Поиск ближайших кафе, трекинг курьеров

Потоковая обработка событий

Streams

Логи действий, event-driven архитектура

46

47 of 48

Redis не Open Source

Критерий

Redis

Valkey

Происхождение

Разработан Salvatore Sanfilippo (2009), сейчас поддерживается Redis Ltd.

Форк Redis 7.2.4, разрабатывается сообществом (Linux Foundation)

Лицензия

С 2024: Redis Source Available License (RSAL) – ограничения на коммерческое использование

BSD 3-Clause – полностью открытая

Поддержка

Коммерческая (Redis Ltd.) + облачные решения (Redis Cloud)

Сообществом (альтернатива из-за лицензирования Redis)

Основная цель

Универсальное key-value хранилище + дополнительные модули (JSON, поиск и т. д.)

Сохранение open-source альтернативы с акцентом на совместимость

47

48 of 48

Дополнительная информация, �ссылки и код

Спасибо за внимание!

Вопросы?