Введение

Представьте себе типичную утреннюю рутину: миллионы пользователей открывают приложения, отправляют сообщения и ждут, что их запросы обрабатываются мгновенно. Все это требует стабильных, надежных систем обмена сообщениями. Когда речь идет о масштабируемых приложениях с высокой нагрузкой, разработчики часто обращаются к паттернам Pub/Sub (публикации и подписки). Одной из популярных технологий для этого является Redis — система in-memory, которая может стать ядром таких систем. Но как сделать эту систему высокодоступной (HA), чтобы она оставалась надежной даже при сбоях узлов?

Сегодня мы рассмотрим, как создать высокодоступный Redis Pub/Sub кластер, который выдерживает нагрузки и гарантирует, что сообщения не теряются даже при отказе одного из серверов.

Что такое Pub/Sub и почему это важно?

Pub/Sub (Publisher-Subscriber) — это архитектурный паттерн, который упрощает взаимодействие между компонентами сложных распределённых систем, разделяя отправителей (издателей) и получателей (подписчиков) сообщений. В отличие от прямого обмена данными между компонентами, модель Pub/Sub создает слой абстракции, где один компонент публикует сообщение, а другие компоненты подписываются на его получение. При этом издатели и подписчики могут существовать независимо друг от друга, не зная о точном местоположении и количестве участников.

Как работает Pub/Sub?

В системе Pub/Sub издатель отправляет сообщения в канал (или тему), а подписчики, которые подписаны на этот канал, получают опубликованные сообщения. Канал выступает посредником между издателем и подписчиками. Это позволяет компонентам приложения обмениваться информацией асинхронно, не блокируя выполнение своих операций.

Когда издатель отправляет сообщение в канал, он не заботится о том, кто и когда это сообщение получит. Подписчики, в свою очередь, получают только те сообщения, которые опубликованы в каналы, на которые они подписаны. Важной особенностью Pub/Sub является то, что подписчики могут не знать, сколько издателей существует, а издатели могут не знать, сколько подписчиков получают их сообщения.

Пример использования Pub/Sub в реальных приложениях

Рассмотрим сценарий в системе доставки еды, где Pub/Sub может быть использован для отправки уведомлений о новых заказах. В такой системе процесс может выглядеть следующим образом:

  1. Клиент делает заказ через приложение. Как только заказ оформлен, система публикует сообщение в канал orders, указывая детали заказа (что заказано, когда и куда доставить).

  2. Кухня подписана на канал orders, чтобы оперативно получать новые заказы. Как только сообщение поступает в канал, его сразу обрабатывает сотрудник кухни или автоматизированная система, которая готовит блюдо.

  3. Система доставки может быть подписана на другой канал deliveries, где публикуются сообщения, когда заказ готов и нужно организовать его доставку. Каждый подписчик (например, водитель или доставка дронов) получает информацию и действует в зависимости от своей зоны ответственности.

Этот подход делает взаимодействие между компонентами гибким, масштабируемым и асинхронным. Компоненты могут работать параллельно, не блокируя друг друга. Это особенно важно в системах, где требуется высокая производительность и способность обрабатывать большое количество запросов в реальном времени.

Почему Pub/Sub важен в масштабируемых системах?

Асинхронная коммуникация — одно из ключевых преимуществ Pub/Sub. В отличие от традиционных методов, где один компонент напрямую отправляет данные другому (что может вызвать задержки и блокировки), в модели Pub/Sub взаимодействие происходит независимо от времени обработки. Это позволяет системе лучше масштабироваться, так как компоненты не блокируют друг друга, и взаимодействие между ними становится более гибким. Например, в системе оповещений, если один из подписчиков недоступен, остальные продолжают получать сообщения, что позволяет избежать полной остановки системы.

Модель Pub/Sub также идеальна для сценариев, где отправителю сообщений не нужно знать о наличии или количестве получателей. Например, в системе оповещений или событийного мониторинга, где одно событие может быть интересно нескольким независимым подписчикам.

Ограничения и необходимость высокой доступности

Хотя Pub/Sub обеспечивает гибкость и масштабируемость, у него есть критические ограничения, если рассматривать его на основе одного сервера Redis. По умолчанию, Redis Pub/Sub не сохраняет сообщения. Это означает, что если подписчик в момент публикации сообщения не подключён, он пропустит это сообщение. Это может стать проблемой в критически важных системах, где сообщения должны гарантированно доставляться каждому подписчику.

Но более серьёзное ограничение связано с тем, что один сервер Redis не обеспечивает высокой доступности. Если сервер Redis выйдет из строя, все данные, которые передаются через Pub/Sub, будут потеряны, а приложение перестанет функционировать. Например, если сервер Redis, на котором опубликованы заказы на кухню, перестанет работать, вся информация о заказах будет потеряна, что может привести к нарушению бизнес-процессов.

Решение: использование Redis в высокодоступной конфигурации (HA)

Чтобы обеспечить надёжную работу системы Pub/Sub в критически важных приложениях, необходимо настроить Redis в режиме высокой доступности (HA). В высокодоступной архитектуре сообщения и данные должны быть реплицированы на несколько узлов, чтобы в случае сбоя одного из узлов система могла продолжить работу без потерь.

Для достижения высокой доступности используется кластеризация Redis (Redis Cluster) или Redis Sentinel:

  1. Redis Cluster — это распределённая система с шардированием данных, где каждый мастер-узел хранит определённую часть данных и имеет одну или несколько реплик. В случае сбоя мастер-узла одна из его реплик становится новым мастером. Это помогает обеспечить отказоустойчивость, так как данные распределяются между несколькими узлами.

  2. Redis Sentinel — это система управления фейловером, которая следит за состоянием узлов Redis и автоматически переключает роли между мастером и репликами в случае сбоя. Sentinel также уведомляет подключённые клиенты о том, что произошло переключение на новый мастер.

Использование Redis в режиме высокой доступности гарантирует, что даже если один сервер выйдет из строя, сообщения и данные, передаваемые через Pub/Sub, будут обработаны другими узлами, что важно для непрерывной работы систем.

Почему Redis?

Redis как Pub/Sub-брокер привлекателен своей легкостью в настройке и высокой производительностью. Он поддерживает практически моментальную отправку и доставку сообщений. Кроме того, его механизм репликации позволяет настроить высокодоступный кластер с минимальными накладными расходами.

Однако есть нюанс: стандартный Pub/Sub в Redis не сохраняет сообщения, если подписчик не подключен. Это может быть проблемой в высоконагруженных системах, где сообщения критически важны. Поэтому нам необходимо продумать стратегию для обработки отказов.

Создание HA кластера Redis Sentinel

Для обеспечения высокой доступности (HA) Redis, можно использовать механизм Redis Sentinel. Sentinel — это система управления фейловером и мониторинга, которая автоматически переключает роли между мастер-узлом и репликами, если мастер выходит из строя. Эта технология идеально подходит для обеспечения отказоустойчивости в системах, где важна непрерывная работа, и где требуется минимальное время простоя при сбоях.

Redis Sentinel обеспечивает высокую доступность путем управления ролью мастера и реплик, но не распределяет данные между узлами. Все ключи и данные хранятся на мастере и реплицируются на реплики. Поэтому для продюсеров и консьюмеров важно всегда подключаться именно к мастеру, так как только он может принимать записи и обрабатывать запросы на публикацию в каналах Pub/Sub.

image
image

Продюсеры и консьюмеры в Sentinel

  • Продюсеры подключаются к мастер-узлу, так как только мастер может принимать новые записи и публиковать сообщения в каналы Pub/Sub.

  • Консьюмеры также должны подключаться к мастер-узлу, поскольку только мастер транслирует сообщения по каналам Pub/Sub.

В случае, если мастер выходит из строя, Redis Sentinel выполняет фейловер и переводит одну из реплик в роль нового мастера. Продюсеры и консьюмеры должны переподключиться к новому мастеру для продолжения работы. Клиенты Redis можно настроить так, чтобы они автоматически находили новый мастер через Sentinel.

Как работает Redis Sentinel

Redis Sentinel отслеживает состояние всех узлов Redis (мастера и реплик), регулярно отправляя запросы PING. Если мастер-узел перестает отвечать на запросы в течение определенного времени, Sentinel помечает его как недоступный и инициирует процесс фейловера. В рамках фейловера одна из реплик становится новым мастером, и клиенты перенаправляются на него. Это позволяет системе продолжать работу без существенных сбоев.

  1. Мониторинг: Sentinel непрерывно отслеживает состояние мастера и реплик, проверяя их доступность через механизм PING-PONG.

  2. Обнаружение сбоя: если Sentinel не получает ответ от мастера в течение заданного времени (down-after-milliseconds), он отмечает его как "недоступный".

  3. Выбор нового мастера: Sentinel запускает процесс выборов среди реплик, чтобы определить, какая реплика станет новым мастером. Выбор происходит на основе ряда критериев, таких как задержка репликации и доступность.

  4. Обновление конфигурации: новый мастер становится главным узлом, и Sentinel обновляет клиентов Redis, чтобы они подключались к новому мастеру.

  5. Продюсеры и консьюмеры: все клиенты, которые были подключены к старому мастеру, должны переподключиться к новому мастеру, чтобы продолжить публикацию и получение сообщений.

Таким образом, продюсеры и консьюмеры работают исключительно с мастером, и при фейловере им нужно переключиться на новый мастер.

Ограничения использования Redis Sentinel

Несмотря на отказоустойчивость, которую обеспечивает Redis Sentinel, есть несколько важных ограничений:

  • Отсутствие шардирования: Sentinel работает с мастер-реплика топологиями, но не поддерживает шардирование данных. Это означает, что каждый мастер управляет всей базой данных, что может стать узким местом для производительности при высоких нагрузках.

  • Отказ мастера и потеря сообщений: если основной узел (мастер) выходит из строя, Redis Sentinel переключает роль мастера на одну из реплик. Однако все сообщения, отправленные через Pub/Sub до фейловера, будут потеряны, поскольку Pub/Sub не сохраняет сообщения на диске или в памяти для повторной доставки.

  • Прерывание подписок: когда происходит фейловер, клиенты Redis должны переподключиться к новому мастеру. Это может привести к тому, что подписки на Pub/Sub-каналы будут разорваны, и подписчики могут пропустить сообщения, отправленные в промежутке между разрывом соединения и повторным подключением.

  • Сложность управления крупными кластерами: Redis Sentinel подходит для управления небольшими кластерами. Для крупных систем, требующих горизонтального масштабирования и шардирования, лучше использовать Redis Cluster.

Шаги настройки Redis Sentinel

Для настройки отказоустойчивого кластера Redis с Sentinel, следует:

  1. Настроить несколько инстансов Redis: минимальная конфигурация включает один мастер и как минимум две реплики для обеспечения надёжности.

  2. Установить и настроить Redis Sentinel: каждый Sentinel будет мониторить состояние всех узлов Redis и автоматически переключать роли между ними при сбоях.

  3. Настроить клиентов для работы с Sentinel: клиенты Redis должны быть настроены так, чтобы автоматически находить новый мастер после фейловера.

Пример конфигурации Redis Master и Replica

Конфигурация Redis Master (redis-master.conf):

port 6379
bind 0.0.0.0
protected-mode yes
dir /data
dbfilename dump.rdb
appendonly yes # Включает персистентность

Конфигурация Redis Replica (redis-replica.conf):

port 6379
bind 0.0.0.0
protected-mode yes
dir /data
dbfilename dump.rdb
appendonly yes
replicaof redis-master 6379  # Указываем мастера для репликации

Пример конфигурации Redis Sentinel (sentinel.conf):

port 26379
bind 0.0.0.0
dir /tmp
protected-mode no

# Указываем, что Sentinel будет следить за мастером с именем "mymaster" на порту 6379
sentinel monitor mymaster redis-master 6379 2  # 2 - это количество Sentinel'ов, которые должны согласиться, что мастер недоступен

# Время, через которое Sentinel будет считать мастер недоступным (в миллисекундах)
sentinel down-after-milliseconds mymaster 5000

# Время для фейловера (в миллисекундах)
sentinel failover-timeout mymaster 10000

# Как часто Sentinel будет проверять состояние реплик
sentinel parallel-syncs mymaster 1

Пример Docker Compose файла для Redis Sentinel

Теперь давайте создадим docker-compose.yml, который позволяет запустить высокодоступную конфигурацию Redis с одним мастером, двумя репликами и тремя инстансами Sentinel.

docker-compose.yml:

version: '3.8'

services:
  redis-master:
    image: redis:7.0-alpine
    container_name: redis-master
    hostname: redis-master
    ports:
      - "6379:6379"
    volumes:
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

  redis-replica1:
    image: redis:7.0-alpine
    container_name: redis-replica1
    hostname: redis-replica1
    depends_on:
      - redis-master
    volumes:
      - ./redis-replica.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    environment:
      - REDIS_REPLICATION_MODE=replica
    networks:
      - redisnet

  redis-replica2:
    image: redis:7.0-alpine
    container_name: redis-replica2
    hostname: redis-replica2
    depends_on:
      - redis-master
    volumes:
      - ./redis-replica.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    environment:
      - REDIS_REPLICATION_MODE=replica
    networks:
      - redisnet

  redis-sentinel1:
    image: redis:7.0-alpine
    container_name: redis-sentinel1
    hostname: redis-sentinel1
    depends_on:
      - redis-master
    ports:
      - "26379:26379"
    volumes:
      - ./sentinel.conf:/usr/local/etc/redis/sentinel.conf
    command: ["redis-server", "/usr/local/etc/redis/sentinel.conf", "--sentinel"]

  redis-sentinel2:
    image: redis:7.0-alpine
    container_name: redis-sentinel2
    hostname: redis-sentinel2
    depends_on:
      - redis-master
    ports:
      - "26380:26379"
    volumes:
      - ./sentinel.conf:/usr/local/etc/redis/sentinel.conf
    command: ["redis-server", "/usr/local/etc/redis/sentinel.conf", "--sentinel"]

  redis-sentinel3:
    image: redis:7.0-alpine
    container_name: redis-sentinel3
    hostname: redis-sentinel3
    depends_on:
      - redis-master
    ports:
      - "26381:26379"
    volumes:
      - ./sentinel.conf:/usr/local/etc/redis/sentinel.conf
    command: ["redis-server", "/usr/local/etc/redis/sentinel.conf", "--sentinel"]

networks:
  redisnet:
    driver: bridge

Что тут вообще происходит?

  • redis-master-1, redis-master-2, и redis-master-3: эти три сервиса создают мастер-узлы Redis, которые будут выполнять шардирование данных.

  • redis-replica-1, redis-replica-2, и redis-replica-3: эти узлы являются репликами для каждого мастера, которые будут использоваться для фейловера в случае отказа мастера.

  • Каждый узел имеет уникальный порт, начиная с 6379 до 6384. Это позволяет избежать конфликтов и развернуть несколько инстансов Redis на одной машине.

Запуск и работа Redis Sentinel

Чтобы запустить кластер Redis с Sentinel:

  • Убедитесь, что у вас есть конфигурационные файлы для мастера, реплик и Sentinel.

  • Запустите все сервисы с помощью docker-compose:

docker-compose up -d
  • Теперь у вас работает высокодоступный кластер Redis с одним мастер-узлом и двумя репликами, которые находятся под управлением трёх инстансов Sentinel.

Проверка статуса кластера

Для проверки статуса кластера Redis с Sentinel используйте следующую команду, подключаясь к одному из Sentinel:

docker exec -it redis-sentinel1 redis-cli -p 26379 sentinel masters

Эта команда покажет состояние мастера и его реплик, а также информацию о том, какой из узлов активен в данный момент.

Пример реализации Pub/Sub в Redis Sentinel

Ниже приведены примеры издателя (Publisher) и подписчика (Subscriber) с использованием Pub/Sub в кластере Redis, управляемом Sentinel.

Пример издателя (Publisher):

import redis

def publish_message():
    redis_client = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)
    channel = 'orders'

    for i in range(5):
        message = f'Order {i + 1}'
        redis_client.publish(channel, message)
        print(f"Published: {message}")

if __name__ == "__main__":
    publish_message()

Что тут происходит?

  • Издатель подключается к мастеру Redis и публикует сообщения в канал orders.

  • Клиент работает с Sentinel, который отслеживает фейловер и автоматически направляет его на новый мастер, если текущий мастер выйдет из строя.

Пример подписчика (Subscriber):

import redis

def subscribe_channel():
    redis_client = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)
    pubsub = redis_client.pubsub()
    pubsub.subscribe('orders')

    print("Subscribed to 'orders' channel.")

    while True:
        message = pubsub.get_message()
        if message and not message['type'] == 'subscribe':
            print(f"Received: {message['data']}")
        time.sleep(0.1)

if __name__ == "__main__":
    subscribe_channel()

Что тут происходит?

  • Подписчик подключается к Redis и подписывается на канал orders.

  • Как только сообщение публикуется в этот канал, подписчик его получает.

Конфигурация HA-кластера Redis Cluster

Redis Cluster отличается от Redis Sentinel тем, что он поддерживает горизонтальное масштабирование и шардирование данных. Redis Cluster разделяет данные между несколькими мастер-узлами (шардами), каждый из которых имеет одну или несколько реплик для обеспечения отказоустойчивости.

image
image

Шаги настройки Redis Cluster

  1. Создать несколько Redis-узлов: нам нужны как минимум три мастер-узла и три реплики, чтобы обеспечить высокую доступность и отказоустойчивость.

  2. Настроить кластеризацию: Redis Cluster использует шардирование данных с распределением по слотам, и все узлы должны быть настроены для работы в режиме кластера.

  3. Связать мастер-узлы с репликами: реплики будут назначены для каждого мастер-узла для обеспечения фейловера в случае отказа.

Пример конфигурации для Redis Cluster

Конфигурация Redis Cluster (redis.conf):

port 6379
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
appendfilename "appendonly.aof"
dir "/data"
bind 0.0.0.0
protected-mode no

Эта конфигурация включает:

  • cluster-enabled yes — включает режим кластера.

  • cluster-config-file nodes.conf — файл конфигурации кластера, который будет автоматически создаваться Redis при запуске.

  • appendonly yes — включает персистентность с использованием AOF (Append-Only File) для сохранности данных.

Docker Compose файл для запуска HA кластера Redis Cluster

Теперь создадим docker-compose.yml, который позволяет запустить Redis Cluster с тремя мастер-узлами и тремя репликами.

docker-compose.yml:

version: '3.8'

services:
  redis-master-1:
    image: redis:7.0-alpine
    container_name: redis-master-1
    hostname: redis-master-1
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
    networks:
      - redisnet
    ports:
      - "6379"
    expose:
      - "6379"

  redis-master-2:
    image: redis:7.0-alpine
    container_name: redis-master-2
    hostname: redis-master-2
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
    networks:
      - redisnet
    ports:
      - "6380"
    expose:
      - "6380"

  redis-master-3:
    image: redis:7.0-alpine
    container_name: redis-master-3
    hostname: redis-master-3
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
    networks:
      - redisnet
    ports:
      - "6381"
    expose:
      - "6381"

  redis-replica-1:
    image: redis:7.0-alpine
    container_name: redis-replica-1
    hostname: redis-replica-1
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
    networks:
      - redisnet
    ports:
      - "6382"
    expose:
      - "6382"

  redis-replica-2:
    image: redis:7.0-alpine
    container_name: redis-replica-2
    hostname: redis-replica-2
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
    networks:
      - redisnet
    ports:
      - "6383"
    expose:
      - "6383"

  redis-replica-3:
    image: redis:7.0-alpine
    container_name: redis-replica-3
    hostname: redis-replica-3
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis-master.conf:/usr/local/etc/redis/redis.conf
    networks:
      - redisnet
    ports:
      - "6384"
    expose:
      - "6384"

networks:
  redisnet:
    driver: bridge

Пояснение Docker Compose файла:

  • redis-master-1, redis-master-2, и redis-master-3: эти три сервиса создают мастер-узлы Redis, которые будут выполнять шардирование данных.

  • redis-replica-1, redis-replica-2, и redis-replica-3: эти узлы являются репликами для каждого мастера, которые будут использоваться для фейловера в случае отказа мастера.

  • Каждый узел имеет уникальный порт, начиная с 6379 до 6384. Это позволяет избежать конфликтов и развернуть несколько инстансов Redis на одной машине.

Запуск кластера Redis Cluster

После запуска всех контейнеров с помощью команды

docker-compose up -d

Создайте кластер с помощью команды redis-cli. Для этого подключитесь к одному из мастер-узлов и выполните команду

docker exec -it redis-master-1 redis-cli --cluster create \
    redis-master-1:6379 redis-master-2:6380 redis-master-3:6381 \
    redis-replica-1:6382 redis-replica-2:6383 redis-replica-3:6384 \
    --cluster-replicas 1

Эта команда создаст кластер с тремя мастер-узлами и тремя репликами, где каждая реплика будет назначена одному из мастеров.

Что тут происходит?

  • --cluster create: Команда для создания нового кластера Redis.

  • --cluster-replicas 1: Опция указывает, что каждому мастер-узлу должна быть назначена одна реплика для обеспечения отказоустойчивости.

Проверка кластера

После создания кластера вы можете проверить его состояние с помощью команды:

docker exec -it redis-master-1 redis-cli --cluster check redis-master-1:6379

Эта команда проверит состояние кластера и покажет информацию о слотах и назначенных узлах.

Как Redis Cluster распределяет ключи?

Redis Cluster использует 16,384 слота шардирования для распределения данных между узлами. Когда вы создаете кластер, Redis автоматически распределяет эти слоты между мастер-узлами, и каждый мастер отвечает за определенные слоты. Это распределение происходит при создании кластера с помощью команды redis-cli --cluster create.

Процесс распределения ключей по узлам

  1. Ключ хешируется с помощью алгоритма CRC16.

  2. Результат хеширования используется для назначения ключа одному из 16,384 слотов.

  3. Слоты равномерно распределяются между мастер-узлами при создании кластера, и каждый мастер-узел отвечает за свою группу слотов.

  4. Когда кластер запущен, Redis Cluster автоматически перенаправляет операции с ключами на мастер-узел, который отвечает за соответствующий слот.

Само распределение ключей не указывается явно в конфигурационных файлах, таких как redis.conf, потому что Redis Cluster автоматически управляет этим процессом. Однако, команда создания кластера, которую мы выполняем после запуска контейнеров, распределяет слоты между мастер-узлами:

redis-cli --cluster create \
    redis-master-1:6379 redis-master-2:6380 redis-master-3:6381 \
    redis-replica-1:6382 redis-replica-2:6383 redis-replica-3:6384 \
    --cluster-replicas 1

В этой команде:

  • Узлы redis-master-1, redis-master-2, и redis-master-3 становятся мастер-узлами кластера.

  • Redis автоматически распределяет 16,384 слота между этими мастер-узлами.

  • Реплики redis-replica-1, redis-replica-2, и redis-replica-3 назначаются соответствующим мастерам для обеспечения отказоустойчивости.

Если у нас три мастер-узла, Redis может распределить слоты примерно следующим образом:

  • redis-master-1 отвечает за слоты 0–5461.

  • redis-master-2 отвечает за слоты 5462–10922.

  • redis-master-3 отвечает за слоты 10923–16383.

При добавлении нового ключа Redis Cluster автоматически определяет, какой узел будет обрабатывать этот ключ, в зависимости от того, в какой слот он попадает.

После создания кластера вы можете проверить распределение слотов с помощью команды:

redis-cli --cluster check redis-master-1:6379

Эта команда покажет, какой мастер-узел отвечает за какой диапазон слотов, а также статус кластера.

Как Redis Cluster управляет подключениями клиентов?

В Redis Cluster консьюмеры (как и продюсеры) не подключаются напрямую к конкретным мастер-узлам для обработки определённых ключей. Вместо этого они могут подключаться к любому узлу в кластере, а Redis Cluster сам перенаправляет запросы на нужный мастер-узел, который отвечает за данные, связанные с запрашиваемыми ключами. Давайте разберем, как это работает на практике.

Redis Cluster использует механизм, который делает взаимодействие с распределенными узлами прозрачным для клиентов. Вот что происходит под капотом:

  1. Подключение к любому узлу: консьюмер (или продюсер) может подключаться к любому узлу в кластере, даже если этот узел не является мастером для интересующих ключей. Это избавляет от необходимости знать, какой узел отвечает за конкретные данные.

  2. Перенаправление (Redis Cluster Redirect): если узел, к которому подключился клиент, не обслуживает запрашиваемый ключ (то есть этот ключ принадлежит другому слоту, который управляется другим мастер-узлом), Redis автоматически отправляет клиенту ответ с кодом MOVED. Этот ответ указывает клиенту, к какому узлу нужно обратиться для выполнения операции с этим ключом.

  3. Клиентская библиотека: большинство клиентских библиотек Redis, поддерживающих режим кластера (например, redis-py, Jedis для Java или ioredis для Node.js), автоматически обрабатывают перенаправления. Когда библиотека получает команду MOVED, она перенаправляет запрос на нужный узел кластера. Это позволяет клиенту работать с Redis Cluster так, как будто это единый сервер, хотя под капотом происходит перенаправление на нужные узлы.

Как это работает на практике

Допустим, у нас есть Redis Cluster с тремя мастер-узлами, которые распределяют между собой 16,384 слота шардирования. Консьюмеру не нужно заранее знать, какой узел отвечает за конкретный слот, чтобы получить данные с ключом.

Процесс работы выглядит примерно так:

  1. Консьюмер подключается к любому узлу в кластере, например, к redis-master-1.

  2. Запрос на данные: консьюмер отправляет запрос на получение данных по определённому ключу, например, GET mykey.

  3. Определение слота: Redis вычисляет слот для ключа mykey с помощью хеш-функции CRC16. Допустим, ключ попадает в слот, который обслуживается redis-master-2.

  4. Ответ с кодом MOVED: если консьюмер подключён не к тому узлу (например, к redis-master-1), узел возвращает сообщение MOVED с указанием узла redis-master-2, который обслуживает нужный слот.

  5. Автоматическое перенаправление: клиентская библиотека получает ответ MOVED и перенаправляет запрос на узел redis-master-2, где и происходит обработка запроса.

  6. Получение данных: консьюмер получает данные с узла, который отвечает за ключ.

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

Оптимизация запросов и фейловер в Redis Cluster

Повторные запросы и оптимизация

  1. Кэширование слотов: чтобы избежать постоянных перенаправлений, клиентские библиотеки Redis часто кэшируют информацию о слотах. Это означает, что после первого перенаправления библиотека будет знать, какой узел отвечает за конкретный слот, и в следующий раз отправит запрос напрямую к нужному узлу без необходимости перенаправления.

  2. Фейловер и репликация: если мастер-узел выходит из строя, Redis Cluster автоматически поднимает одну из его реплик в роли нового мастера. Клиентские библиотеки Redis Cluster обновляют свою информацию о слотах после фейловера и перенаправляют запросы на новый мастер.

Фейловер и перенаправление запросов

При фейловере в Redis Cluster (когда мастер-узел выходит из строя), Redis назначает одну из реплик в качестве нового мастера. Клиентские библиотеки Redis автоматически обновляют свою информацию о слотах после фейловера.

  1. Фейловер: когда мастер выходит из строя, Redis Cluster автоматически поднимает реплику на место мастера.

  2. Перенаправление: клиентские библиотеки получат сообщение MOVED, если попытались отправить запрос на старый мастер, и перенаправят запрос на новый мастер.

  3. Обновление кэша: после получения MOVED клиентская библиотека обновит свой кэш слотов, чтобы отправлять запросы на новый мастер.

Пример Pub/Sub для Redis Cluster

Для реализации продюсера и консьюмера в Pub/Sub с использованием Redis Cluster, который поддерживает работу с перенаправлением запросов между узлами кластера, можно использовать библиотеку redis-py (Python) или аналогичную библиотеку для другого языка, поддерживающую кластерные режимы Redis. Мы это сделаем на Python с использованием redis-py-cluster.

Требования:

  1. Redis Cluster уже запущен и работает с тремя мастер-узлами.

  2. Мы будем использовать библиотеку redis-py-cluster, которая поддерживает автоматическое перенаправление запросов между узлами.

Установка зависимостей

Убедитесь, что у вас установлена библиотека redis-py-cluster. Если нет, установите ее с помощью pip:

pip install redis-py-cluster

Реализация продюсера

Продюсер будет публиковать сообщения в определённый канал Pub/Sub, а Redis Cluster сам позаботится о том, какой мастер-узел будет обрабатывать этот запрос.

from redis.cluster import RedisCluster

def publish_messages():
    # Подключаемся к одному из узлов кластера
    startup_nodes = [{"host": "127.0.0.1", "port": "6379"}]  # один из мастер-узлов кластера
    redis_cluster = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)

    channel_name = 'my_channel'

    for i in range(1, 6):
        message = f"Message {i}"
        # Публикуем сообщение в канал Pub/Sub
        redis_cluster.publish(channel_name, message)
        print(f"Published: {message}")

if __name__ == "__main__":
    publish_messages()

Что тут происходит?

  • RedisCluster: мы создаем клиент Redis, который подключается к одному из узлов кластера.

  • publish: используем метод publish для отправки сообщения в канал Pub/Sub. Redis Cluster сам позаботится о том, чтобы перенаправить запрос на нужный узел, если это необходимо.

  • Сообщения публикуются в канал my_channel, и Redis Cluster автоматически обработает публикацию.

Реализация консьюмера

Консьюмер будет подписываться на канал Pub/Sub и получать сообщения, даже если данные распределены по разным узлам.

from redis.cluster import RedisCluster

def consume_messages():
    # Подключаемся к одному из узлов кластера
    startup_nodes = [{"host": "127.0.0.1", "port": "6379"}]  # один из мастер-узлов кластера
    redis_cluster = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)

    channel_name = 'my_channel'

    # Подписываемся на канал Pub/Sub
    pubsub = redis_cluster.pubsub()
    pubsub.subscribe(channel_name)

    print(f"Subscribed to {channel_name}. Waiting for messages...")

    # Получаем сообщения
    while True:
        message = pubsub.get_message()
        if message and not message['type'] == 'subscribe':
            print(f"Received: {message['data']}")
        # Маленькая задержка для снижения нагрузки на процессор
        time.sleep(0.1)

if __name__ == "__main__":
    consume_messages()

Объяснение:

  • pubsub(): Мы используем метод pubsub() для подписки на канал Pub/Sub.

  • subscribe(): Подписываемся на канал my_channel. Redis Cluster автоматически перенаправляет запросы на узел, который обрабатывает этот канал.

  • get_message(): Этот метод проверяет наличие новых сообщений в канале. Как только сообщение поступает, мы его обрабатываем.

  • Консьюмер также подключается к любому узлу кластера и получает сообщения через механизм Pub/Sub.

Как это работает?

  1. Подключение: продюсер и консьюмер могут подключаться к любому узлу кластера.

  2. Хеширование ключа канала: Redis Cluster автоматически рассчитывает слот, соответствующий каналу Pub/Sub (my_channel), с использованием хеш-функции CRC16.

  3. Перенаправление: если узел, к которому подключился продюсер или консьюмер, не обслуживает слот, связанный с каналом, Redis Cluster отправляет клиенту команду MOVED, перенаправляя его к правильному узлу.

  4. Обработка: когда сообщение публикуется или получено, Redis Cluster заботится о том, чтобы данные обрабатывались правильным узлом, независимо от того, где изначально был сделан запрос.

Заключение

Внедрение высокодоступного кластера Redis для Pub/Sub — это ключевой шаг в создании отказоустойчивой и масштабируемой системы обмена сообщениями. Использование Redis Sentinel для управления фейловерами или Redis Cluster для автоматического шардирования данных и обработки ключей позволяет обеспечить бесперебойную работу приложений, даже при сбое отдельных узлов.

Ключевой момент заключается в том, что правильно настроенная архитектура Redis может справляться с высокими нагрузками, обеспечивать гибкость и сохранять производительность даже в условиях отказа узлов. Sentinel управляет репликацией и фейловерами, гарантируя, что продюсеры и консьюмеры смогут продолжать свою работу, автоматически переподключаясь к новому мастеру. Redis Cluster добавляет горизонтальное масштабирование, позволяя распределять ключи и данные по множеству узлов, а клиентские библиотеки автоматически перенаправляют запросы на нужные узлы.

Для сценариев, требующих высокой производительности и надежности, таких как системы заказов или уведомлений в реальном времени, Redis Pub/Sub с высокой доступностью становится незаменимым инструментом. При этом необходимо учитывать такие ограничения, как потеря сообщений при фейловере и необходимость в дополнительных механизмах хранения сообщений для обеспечения гарантированной доставки.

Рассмотренные в статье примеры конфигураций и кода показывают, как можно построить такую систему на практике, используя Docker для развертывания Redis кластера и Python для работы с Pub/Sub. Это позволяет разработчикам быстро начать работу с Redis и настроить высокодоступную систему обмена сообщениями.

Дополнительные ресурсы

Книги

  1. "Redis Essentials", авторы Maxwell Dayvson Da Silva и Hugo Lopes Tavares (2015)

    Эта книга охватывает ключевые темы Redis, включая его настройку и конфигурацию для обеспечения высокой доступности с помощью Redis Sentinel и кластеризации. Это отличный ресурс для понимания, как Redis используется в реальных продуктивных средах.

  2. "Redis in Action", автор Josiah L. Carlson (2013)

    Данная книга является подробным исследованием Redis, охватывающим различные сценарии использования и передовые функции, такие как Pub/Sub, репликация и персистентность. В книге также рассматриваются практические примеры настройки высокой доступности.

  3. "Mastering Redis", автор Jeremy Nelson (2016)

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

  4. "Designing Data-Intensive Applications", автор Martin Kleppmann (2017)

    Хотя эта книга не является специфичной для Redis, она изучает распределённые системы, репликацию и консистентность — важные концепции для настройки высокой доступности с использованием инструментов, таких как Redis.

Статьи и ресурсы

  1. "Redis Cluster Tutorial" от Redis.io

    Официальное руководство, которое объясняет, как настроить Redis Cluster, а также детально рассматривает механизм распределения слотов и управление фейловером. Это обязательное руководство для работы с высокой доступностью Redis.

    Redis Cluster Tutorial

  2. "Redis High Availability Guide: Setting Up Redis with Sentinel", Hevo Data

    Статья предоставляет подробное руководство по настройке Redis Sentinel для обеспечения высокой доступности, включая фейловер и конфигурации мастер-реплик.

    Hevo Data

  3. "Caching at Scale with Redis", автор Lee Atchison

    Эта электронная книга фокусируется на том, как эффективно масштабировать Redis и оптимизировать его производительность, обсуждая стратегии кэширования в высоконагруженных средах.

    Caching at Scale with Redis

  4. "Best Practices for Redis High Availability", Redis Labs

    В этом документе описываются лучшие практики для конфигурирования Redis с использованием Sentinel и кластеризации для обеспечения высокой доступности. Также представлены рекомендации по репликации и управлению фейловером.

    Redis Labs Documentation

Комментарии (8)


  1. maslyaev
    21.10.2024 23:29

    Как в pub/sub реализуется гарантия доставки сообщения?


    1. vQFd4 Автор
      21.10.2024 23:29

      Если коротко отвечать, то никак, я об этом упомянул в статье в разделе ограничений. Но в целом в зависимости от вариантов использования Pub/Sub
      1/ это может в принципе не требоваться из-за особенностей реализуемого бизнес-процесса
      2/ можно воспользоваться List + AOF
      3/ можно попробовать реализовать OutBox/inBox + подтверждение доставки на стороне приложения


  1. BloodyR00t
    21.10.2024 23:29

    Не понятно, это высокопроизводительное решение по сравнению с чем. Не могли бы вы предоставить конкретные результаты тестов производительности для описанных конфигураций Redis (Sentinel, Cluster, Pub/Sub)? Было бы полезно увидеть метрики, такие как время отклика и пропускная способность. А также сравнение с другими решениями, которые в этих же тестах были бы менее производительны.


    1. vQFd4 Автор
      21.10.2024 23:29

      На самом деле вопросы производительности я не обсуждаю, тут все про HA. Производительность если и упоминаю, то косвенно исключительно из общих соображений, например в конфигурации Sentinel мастер управляет всеми каналами, что сказывается на производительности, и в этом случает Redis Cluster предпочтительнее.

      А так да, постараюсь сделать load test в равнении с rabbit/kafka


  1. alexanderfedyukov
    21.10.2024 23:29

    Redis все-таки скорее про распределенный кэш. Для публикаций и подписок больше подходят брокеры сообщений (Kafka\Artemis\Rabit\etc). Тем более, что для pub/sub Redis дает только at most once семантику (слабейшую), т.е. не может гарантировать примерно ничего и нужно быть готовым как к дублям так и потерям сообщений.


    1. vQFd4 Автор
      21.10.2024 23:29

      В целом да, но иногда эти гарантии и не требуются из-за природы бизнес-процесса. Например, rabbit тоже не все так просто, особенно, если вы решит сделать HA кластер. Даже с quorum queues он вполне успешно может терять сообщения


  1. rSedoy
    21.10.2024 23:29

    Стоило упомянуть альтернативы внутри того же Redis: простейшее - собрать подобное на lists и более навороченный - Redis Streams


    1. vQFd4 Автор
      21.10.2024 23:29

      ну как бы ни list, ни Redis Streams не являются альтернативами для Redis Sentinel или Redis Cluster. List или, например, ZSet, или Redis Streams решают проблему персистентности сообщений и в каком-то виде обеспечивают гарантии доставки. Их использование совместно с Redis Sentinel или Redis Cluster это тоже отдельная история, которую по-хорошему лучше отдельно описать, там есть свои нюансы касающиеся ожиданий гарантий доставки