Redis Cluster и высокая доступность: настройка отказоустойчивого кэша для микросервисов
Введение — зачем нужен Redis Cluster в микросервисной архитектуре
Микросервисная архитектура предполагает десятки, а иногда и сотни независимых сервисов, каждый из которых может обращаться к общему кэшу. Единственный инстанс Redis в такой среде становится узким местом: он не масштабируется горизонтально, не обеспечивает отказоустойчивость и ограничен объёмом памяти одного сервера.
Redis Cluster решает эти проблемы сразу: данные автоматически распределяются по нескольким узлам (шардирование), каждый мастер-узел имеет реплики для аварийного переключения, а клиенты умеют перенаправлять запросы при изменении топологии. Для высоконагруженных микросервисов, где требуется отказоустойчивый Redis с латентностью в миллисекундах, это стандартное production-решение.
Отличия Redis Sentinel от Redis Cluster: когда что использовать
Два основных механизма обеспечения высокой доступности Redis — это Redis Sentinel и Redis Cluster. Они решают разные задачи.
- Redis Sentinel — система мониторинга и автоматического failover для единственного мастера. Sentinel следит за мастером, при его падении выбирает нового лидера из реплик и уведомляет клиентов. Данные не шардируются, весь датасет хранится в одном месте. Подходит для умеренных объёмов данных и простых сценариев.
- Redis Cluster — горизонтально масштабируемый режим с автоматическим шардированием данных по нескольким мастерам. Каждый мастер имеет собственные реплики. Cluster поддерживает до 1000 узлов и обеспечивает линейный рост производительности по записи и чтению. Подходит для больших объёмов данных, высоких RPS и требований к горизонтальному масштабированию.
Когда выбирать Sentinel: объём данных помещается в одну машину, нужен простой failover, нет необходимости масштабировать запись.
Когда выбирать Cluster: данные не помещаются на один сервер, нужно горизонтальное масштабирование, высокие RPS на запись, микросервисная архитектура с несколькими независимыми доменами данных.
Архитектура Redis Cluster: шардирование, слоты, репликация
Hash Slots
Redis Cluster делит пространство ключей на 16384 слота (hash slots). Каждый ключ при помощи CRC16 хэша маппируется в один из слотов. Слоты равномерно распределяются между мастер-узлами. Например, в кластере из трёх мастеров:
- Мастер 1: слоты 0–5460
- Мастер 2: слоты 5461–10922
- Мастер 3: слоты 10923–16383
При добавлении или удалении узлов слоты мигрируют без остановки кластера — это называется resharding.
Репликация
Каждый мастер-узел может иметь одну и более реплик. Репликация асинхронная. При падении мастера кластер автоматически проводит failover: реплика становится новым мастером. Для этого большинство мастер-узлов должны согласиться (кворум). Рекомендуемый минимум — 3 мастера + 3 реплики (6 узлов).
Команды внутри кластера
Если ключ находится на другом узле, Redis возвращает ответ MOVED или ASK, и клиент перенаправляет запрос. Умные клиенты кэшируют топологию кластера и сразу обращаются к нужному узлу.
Пошаговая настройка Redis Cluster в Docker и Kubernetes
Настройка в Docker
Создадим конфигурационный файл для каждого узла. Пример redis-7001.conf:
port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes
bind 0.0.0.0
protected-mode noАналогичные файлы создаём для портов 7002–7006. Запускаем контейнеры:
for port in 7001 7002 7003 7004 7005 7006; do
docker run -d --name redis-$port \
--net host \
-v $(pwd)/redis-$port.conf:/usr/local/etc/redis/redis.conf \
redis:7.2 redis-server /usr/local/etc/redis/redis.conf
doneИнициализируем кластер:
redis-cli --cluster create \
127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1Флаг --cluster-replicas 1 означает одну реплику на каждый мастер. Redis CLI автоматически распределит узлы.
Настройка Redis Cluster в Kubernetes
Для Kubernetes используем StatefulSet и Headless Service, чтобы каждый Pod имел стабильное DNS-имя.
apiVersion: v1
kind: Service
metadata:
name: redis-cluster
spec:
clusterIP: None
selector:
app: redis-cluster
ports:
- port: 6379
targetPort: 6379
- port: 16379
targetPort: 16379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
spec:
serviceName: redis-cluster
replicas: 6
selector:
matchLabels:
app: redis-cluster
template:
metadata:
labels:
app: redis-cluster
spec:
containers:
- name: redis
image: redis:7.2
ports:
- containerPort: 6379
- containerPort: 16379
command: ["redis-server"]
args:
- --cluster-enabled
- "yes"
- --cluster-config-file
- /data/nodes.conf
- --cluster-node-timeout
- "5000"
- --appendonly
- "yes"
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1GiПосле деплоя инициализируем кластер, подключившись к одному из Podов:
kubectl exec -it redis-cluster-0 -- redis-cli --cluster create \
$(kubectl get pods -l app=redis-cluster -o jsonpath='{range.items[*]}{.status.podIP}:6379 {end}') \
--cluster-replicas 1Паттерны работы с Redis Cluster из Go и PHP клиентов
Go: библиотека go-redis
Библиотека go-redis поддерживает Redis Cluster из коробки через redis.NewClusterClient.
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"redis-cluster-0.redis-cluster:6379",
"redis-cluster-1.redis-cluster:6379",
"redis-cluster-2.redis-cluster:6379",
},
// Автоматически обновлять слоты при изменении топологии
RouteByLatency: true,
ReadOnly: true, // читать с реплик
})
ctx := context.Background()
err := rdb.Set(ctx, "user:1001", "Alice", 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "user:1001").Result()
if err != nil {
panic(err)
}
fmt.Println("user:1001 =>", val)
}Опция ReadOnly: true позволяет направлять запросы на чтение к репликам, снижая нагрузку на мастеры. RouteByLatency выбирает ближайший узел по задержке.
PHP: библиотека Predis и phpredis
В PHP наиболее популярны два клиента: Predis и расширение phpredis.
Predis:
<?php
require 'vendor/autoload.php';
$client = new Predis\Client(
[
['host' => 'redis-cluster-0.redis-cluster', 'port' => 6379],
['host' => 'redis-cluster-1.redis-cluster', 'port' => 6379],
['host' => 'redis-cluster-2.redis-cluster', 'port' => 6379],
],
[
'cluster' => 'redis',
'parameters' => [
'password' => null,
],
]
);
$client->set('order:555', json_encode(['status' => 'pending']));
$value = $client->get('order:555');
echo $value;phpredis (расширение):
<?php
$redis = new RedisCluster(null, [
'redis-cluster-0.redis-cluster:6379',
'redis-cluster-1.redis-cluster:6379',
'redis-cluster-2.redis-cluster:6379',
]);
$redis->set('session:abc123', 'user_data');
echo $redis->get('session:abc123');Важный нюанс в PHP: при использовании multi-key операций (MGET, MSET) все ключи должны хэшироваться в один слот. Для этого используют hash tags — часть ключа в фигурных скобках: {user:1001}:profile, {user:1001}:settings. Кластер хэширует только часть в скобках, гарантируя попадание в один слот.
Обработка failover и переподключения на стороне клиента
Failover в Redis Cluster происходит за 5–15 секунд (зависит от cluster-node-timeout). В этот период часть запросов будет завершаться ошибками. Клиент должен корректно обрабатывать такие ситуации.
Стратегии обработки ошибок
- Retry с экспоненциальной задержкой: при получении ошибки
CLUSTERDOWNилиLOADING— повторить запрос через нарастающие интервалы (100ms, 200ms, 400ms...). - Circuit Breaker: если процент ошибок превышает порог, временно прекратить запросы к Redis и использовать fallback (например, пустой ответ или обращение к БД).
- Обновление топологии: при получении
MOVEDклиент должен немедленно запросить актуальную топологию черезCLUSTER SLOTSилиCLUSTER SHARDS.
Пример обработки в Go:
func getWithRetry(ctx context.Context, rdb *redis.ClusterClient, key string) (string, error) {
var val string
var err error
for i := 0; i < 3; i++ {
val, err = rdb.Get(ctx, key).Result()
if err == nil {
return val, nil
}
if errors.Is(err, redis.Nil) {
return "", nil // ключ не найден — это не ошибка
}
time.Sleep(time.Duration(100*(i+1)) * time.Millisecond)
}
return "", fmt.Errorf("redis unavailable after retries: %w", err)
}Мониторинг Redis Cluster: метрики, алерты, инструменты
Ключевые метрики
cluster_state— должен бытьok. Любое другое значение требует немедленного алерта.cluster_slots_fail— количество слотов в состоянии FAIL. Должно быть 0.connected_slaves— количество подключённых реплик для каждого мастера.used_memoryvsmaxmemory— заполненность памяти. При превышении 80% — алерт.instantaneous_ops_per_sec— текущий RPS.rejected_connections,evicted_keys— признаки перегрузки.keyspace_hits/keyspace_misses— hit rate кэша. Должен быть выше 90%.
Инструменты мониторинга
- Redis Exporter + Prometheus + Grafana — стандартный стек. Деплоим
oliver006/redis_exporterкак sidecar или отдельный Deployment, настраиваем scrape в Prometheus и используем готовые Grafana dashboards (например, ID 763). - redis-cli cluster info — быстрая ручная диагностика:
redis-cli -c -h redis-cluster-0.redis-cluster cluster info. - redis-cli --cluster check — проверка целостности кластера:
redis-cli --cluster check redis-cluster-0.redis-cluster:6379. - RedisInsight — официальный GUI-инструмент от Redis Ltd. с визуализацией топологии, медленных запросов и памяти.
Пример Prometheus-алерта
groups:
- name: redis_cluster
rules:
- alert: RedisClusterDown
expr: redis_cluster_state != 1
for: 1m
labels:
severity: critical
annotations:
summary: "Redis Cluster не в состоянии OK"
- alert: RedisMemoryHigh
expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "Использование памяти Redis превышает 85%"Антипаттерны и частые ошибки при работе с Redis Cluster
- Использование команд, не поддерживаемых в Cluster. Команды, работающие с несколькими ключами из разных слотов (MGET, SUNION, EVAL с несколькими ключами), выбросят ошибку. Решение: использовать hash tags для группировки ключей в один слот, или переработать логику.
- Хранение «горячих» ключей на одном слоте. Если один ключ получает 90% нагрузки, его мастер станет узким местом. Решение: шардировать горячие ключи вручную (
user:counter:1,user:counter:2...). - Игнорирование ответов MOVED/ASK. Примитивные клиенты без поддержки Cluster будут получать ошибки при обращении к слотам, лежащим на другом узле. Всегда используйте клиентские библиотеки с нативной поддержкой Redis Cluster.
- Отсутствие реплик. Запуск кластера без реплик (
--cluster-replicas 0) означает, что потеря любого мастера приводит к потере данных и деградации кластера. В production — всегда минимум одна реплика. - Слишком маленький cluster-node-timeout. Слишком агрессивный таймаут (менее 2000ms) вызывает ложные failover из-за кратковременных сетевых задержек. Рекомендуемое значение — 5000–15000ms.
- Lua-скрипты с ключами из разных слотов. EVAL в Redis Cluster требует, чтобы все ключи находились в одном слоте. Нарушение этого правила вызовет ошибку
CROSSSLOT. - Отсутствие мониторинга cluster_state. Кластер может перейти в состояние FAIL незаметно, если не настроены алерты. Мониторинг — обязателен в production.
Заключение
Redis Cluster — зрелое и надёжное решение для организации отказоустойчивого кэша в микросервисной архитектуре. Автоматическое шардирование по 16384 слотам, встроенный failover и горизонтальное масштабирование делают его выбором для highload-систем, где единственный инстанс Redis уже не справляется.
При правильной настройке — с корректным количеством реплик, грамотными клиентами на Go или PHP, полноценным мониторингом через Prometheus и Grafana, а также осознанным применением hash tags — Redis Cluster обеспечивает надёжную основу для кэширования в Kubernetes-окружениях и распределённых системах.
Ключевые рекомендации для production: минимум 3 мастера + 3 реплики, cluster-node-timeout не менее 5000ms, мониторинг cluster_state и used_memory, retry-логика на стороне клиента, hash tags для multi-key операций. Следуя этим принципам, вы получите стабильный, масштабируемый и отказоустойчивый Redis для ваших микросервисов.
Технологии
Теги
Руслан Исмаилов
Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →