Кластеры с репликацией и аварийным переключением

Примечание редактора: эта статья была отредактирована, чтобы использовать более продуманную взаимосвязь между первичными и вторичными машинами, а не прежнюю терминологию, нечувствительную к культурным особенностям. В дальнейшем, в зависимости от терминологии, которую вы используете локально, вам может потребоваться обновить то, что мы называем здесь первичным / вторичным, в соответствии с вашими системами. Спасибо!

В распределенных системах достижение отказоустойчивости - один из ключевых критериев успеха.

Давайте посмотрим, как добиться отказоустойчивости и репликации в сети Redis с кластером Redis и контрольными устройствами.

В этом уроке я расскажу:

  • Введение в Redis
  • Создание приложения с весенней загрузкой с кешем Redis с помощью Docker
  • Различные способы построения отказоустойчивой сети Redis
  • Шардинг с помощью кластера Redis
  • Репликация с помощью кластера Redis
  • Первичная-вторичная сеть Redis с дозорным (без сегментирования)

Введение в Redis

Redis - это хранилище структур данных в памяти с открытым исходным кодом (под лицензией BSD), используемое в качестве базы данных, кеша и брокера сообщений.

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

Чтобы добиться выдающейся производительности, Redis работает в памяти.

- Введение в Redis

Обратите внимание на некоторые ключевые особенности Redis, прежде чем мы углубимся в код

  • Максимальный объем памяти. По умолчанию Redis не имеет ограничений по памяти в 64-битных системах и 3 ГБ в 32-битных системах. Большая память может содержать больше данных и увеличивать коэффициент совпадений, один из наиболее важных показателей, но при определенном ограничении памяти коэффициент совпадений будет на том же уровне.
  • Алгоритмы вытеснения. Когда размер кеша достигает предела памяти, старые данные удаляются, чтобы освободить место для новых. Redis предлагает Last Recently Used и Least Frequently Used алгоритмы выселения. Моментальные снимки RDB на определенный момент времени после определенного интервала времени или количества записей AOF создает журналы сохраняемости при каждой операции записи.
  • Долговечность. По разным причинам вам может потребоваться сохранить кэш. После запуска кеш изначально пуст, полезно будет заполнить его данными снэпшота на случай восстановления после простоя. Redis поддерживает разные способы достижения стойкости.

Разработка приложения Spring Boot + Redis + Docker

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



Давайте создадим наше приложение Redis!

Шаг 1. Создайте простое приложение со списком задач Spring Boot

Во-первых, давайте создадим простое приложение со списком TODO. Мы не будем создавать его для нескольких пользователей, просто чтобы было проще.

На данный момент мы просто храним один простой список TODO в нашем приложении в качестве кеша. Мы еще не используем базу данных.

Шаг 2. Загрузите и запустите образ Redis Docker

Загрузите официальный образ Redis из концентратора Docker:

docker pull redis

После этой команды новый образ должен присутствовать в вашем локальном репозитории (введите Docker images, чтобы проверить его).

Проект в GitHub настроен для использования как автономного, так и кластерного режима.

Во-первых, давайте использовать Redis в автономном режиме. Запускаем образ Redis, который мы вытащили из Dockerhub:

docker run --rm -p 4025:6379 -d --name redis-1 redis redis-server

Шаг 3. Интеграция Redis в приложение Spring Boot

Мы будем использовать кеш весенней загрузки, чтобы общаться с Redis. Spring поставляется с несколькими аннотациями, которые вы можете добавить, чтобы помочь ему работать с кешем Redis.

Добавьте @EnableCaching в конфигурацию вашего приложения, чтобы включить эти аннотации:

Теперь давайте создадим наше приложение Docker, чтобы приложение Spring Boot могло взаимодействовать с Redis. Dockerfile для проекта находится в репозитории github. Я сосредотачиваюсь на Redis, поэтому избегаю подробностей о Dockerfile для приложения весенней загрузки.

Если вы хотите узнать больше о Dockerfile, используемом для проекта, и понять, как избежать времени сборки Docker и использовать кеширование, прочтите эту статью:



DOCKER_BUILDKIT=1 docker build -t learnings/spring-boot-redis-cluster .

После того, как наш образ создан, следующим шагом будет его запуск:

docker run --rm -p 4024:4024 --name spring-boot-redis learnings/spring-boot-redis-cluster

Если вы запустите приложение и зайдете на страницу http://localhost:4024/app/, вы получите сообщение об ошибке .ConnectTimeoutException: connection timed out.

Это потому, что мы хотим, чтобы наши два образа Docker взаимодействовали друг с другом - по умолчанию они находятся в своей собственной сети и изолированы друг от друга.

На данный момент у нас есть два варианта:

  • Используйте сеть Docker

Or,

  • Создайте docker-compose, который строит сеть по умолчанию

Я создам сеть Docker. Для этой демонстрации я создаю сеть Docker, но дайте мне знать в комментариях, если вы хотите, чтобы файл docker-compose был добавлен в проект.

docker network create spring-redis-network

Теперь давайте подключим наши загрузочные образы Redis и Spring к этой сети:

docker network connect spring-redis-network redis-1

Теперь мы можем найти IP-адрес нашего экземпляра Redis и обновить его в application.yml.

Вы можете избежать этого поиска, если используете docker-compose , так как он может связывать службы без указания специфических IP-адресов:

docker inspect spring-redis-network

В моем случае IP-адрес был 172.18.0.2

"Name": "redis-1", 
"EndpointID": "88b100f3569bb4ed68ac8cbf84f4b5a20493e11c5e7336a052bbbd25bb5f4205", "MacAddress": "02:42:ac:12:00:02", 
"IPv4Address": "172.18.0.2/16", 
"IPv6Address": ""

Теперь мы можем обновить это в application.yml:

redis: 
   host: 172.18.0.2 
   port: 6379

Примечание: мы используем порт 6379, как и в сети Docker - открытый порт для хоста - 4025.

Теперь давайте создадим наш образ для приложения еще раз и подключимся к созданной нами сети:

DOCKER_BUILDKIT=1 docker build -t learnings/spring-boot-redis-cluster

Мы можем подключиться к сети, передав --net при запуске нашего образа Docker:

docker run --rm --net spring-redis-network -p 4024:4024 --name spring-boot-redis learnings/spring-boot-redis-cluster

Вы должны увидеть элементы списка дел на http://localhost:4024/app/

Мы можем убедиться, что кеш создан в нашем образе Docker Redis:

docker exec -it redis-1 redis-cli --scan

Шаг 4. Измените наше приложение, чтобы использовать аннотации Spring Cache

Есть пять основных аннотаций, которые вы обычно используете с Spring Cache:

  • @CachePut: используется для обновления кеша.
  • @Cacheable: чтобы вернуть кешированный ответ для метода
  • @CacheEvict: Чтобы удалить запись в кеше, которая больше не нужна - подумайте об удалении для объекта.
  • @Caching: Java не позволяет использовать один и тот же тип аннотации дважды в методе или классе. Итак, если вы хотите сказать @CacheEvict в двух разных кэшах в одном методе, @Cacheable можно использовать для агрегирования других аннотаций кеша.

Я добавил их в наш ToDoListController, и это выглядит так:

Давайте снова запустим наше приложение и проверим статистику Redis,

DOCKER_BUILDKIT=1 docker build -t learnings/spring-boot-redis-cluster . 
docker run --rm --net spring-redis-network -p 4024:4024 --name spring-boot-redis learnings/spring-boot-redis-cluster 
docker exec -it redis-1 redis-cli info stats

Если вы хотите проверить код до этого момента, взгляните на тег.

Шардинг с помощью кластеров Redis

Мы создали базовое приложение для весенней загрузки с кешем Redis.

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

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

HASH_SLOT = CRC16(key) mod 16384

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

Давайте остановим наш образ Redis и создадим кластер:

docker stop redis-1 
docker stop spring-boot-redis

Давайте раскрутим еще два узла Redis, чтобы построить кластер. Мы также передаем файл конфигурации Redis, расположенный в проекте / redis-conf.

Redis требует как минимум трех узлов для работы кластера.

В образах Redis по умолчанию отключена поддержка кластера Redis, поэтому нам нужно добавить файл конфигурации и передать его в образы Redis Docker. Я добавил это в корень проекта под /redis-conf.

Приступим к осколкам:

docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 4025:6379 -d --name redis-1 redis redis-server /redis_config/node1.conf 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 4026:6379 -d --name redis-2 redis redis-server /redis_config/node2.conf 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 4027:6379 -d --name redis-3 redis redis-server /redis_config/node3.conf

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

В следующем разделе мы рассмотрим аварийное переключение.

Выполните следующее, чтобы создать кластер:

docker exec -it redis-1 redis-cli --cluster create 172.18.0.2:6379 172.18.0.3:6379 172.18.0.4:6379

В выводе вы должны увидеть что-то вроде этого:

Мы можем проверить нашу сеть Docker, чтобы получить новый IP-адрес для узлов Redis.

docker inspect spring-redis-network

Наш application-cluster.yml выглядит так:

Давайте остановим наше приложение загрузки Docker Spring и перезапустим его с конфигурацией кластера:

docker stop spring-boot-redis 
DOCKER_BUILDKIT=1 docker build -t learnings/spring-boot-redis-cluster . 
docker run --rm --net spring-redis-network -e "SPRING_PROFILES_ACTIVE=cluster" -p 4024:4024 --name spring-boot-redis learnings/spring-boot-redis-cluster

Вы можете обновить приложение в своем браузере, чтобы убедиться, что оно работает.

Мы также можем проверить конфигурацию кластера на любом узле:

docker exec -it redis-2 redis-cli cluster nodes

Шардинг позволяет распределять наши данные по нескольким узлам для больших наборов данных и сокращает объем поиска за счет хеширования.

В нашем кластере отсутствуют две ключевые проверки безопасности распределенной системы: обработка отказа и репликация.

Репликация с помощью Redis Cluster

Кластер Redis позволяет нам выполнять отработку отказа и репликацию.

Мы собираемся настроить наши узлы в конфигурации «первичный-вторичный», где у нас есть один родительский и две реплики.

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

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

Нужны ли нам часовые?

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

При использовании кластера Redis вам не нужен Sentinel.

Redis Cluster выполняет автоматическое переключение при отказе, если возникает какая-либо проблема в любом основном экземпляре.

Во-первых, мы должны преобразовать наш кластер в первичный-вторичный. Давайте добавим еще три узла, которые будут работать как реплики наших трех основных узлов.

Сначала мы останавливаем наши узлы, а затем запускаем их снова:

docker stop redis-1 
docker stop redis-2 
docker stop redis-3 
# Start redis nodes 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 4025:6379 -d --name redis-1 redis redis-server /redis_config/node1.conf 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 4026:6379 -d --name redis-2 redis redis-server /redis_config/node2.conf 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 4027:6379 -d --name redis-3 redis redis-server /redis_config/node3.conf 
# Start replicas 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 5025:6379 -d --name redis-1-replica redis redis-server /redis_config/node1-replica.conf
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 5026:6379 -d --name redis-2-replica redis redis-server /redis_config/node2-replica.conf
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf:/redis_config -p 5027:6379 -d --name redis-3-replica redis redis-server /redis_config/node3-replica.conf

Давайте проверим нашу сеть Docker, поскольку нам нужны IP-адреса для создания кластера:

docker inspect spring-redis-network 
docker exec -it redis-1 redis-cli --cluster create 172.18.0.2:6379 172.18.0.3:6379 172.18.0.4:6379 172.18.0.6:6379 172.18.0.7:6379 172.18.0.8:6379 --cluster-replicas 1

Если вы видите следующую ошибку, остановите свои работающие узлы, так как у них есть данные в памяти, и они должны быть пустыми при создании кластера:

[ERR] Node 172.18.0.3:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

Если все пойдет хорошо, вы должны увидеть следующий результат:

Мы можем проверить наш кластер с помощью:

docker exec -it redis-2 redis-cli cluster nodes

Наш последний application-cluster.yml выглядит так:

Давайте снова запустим наше приложение и проверим статистику Redis:

DOCKER_BUILDKIT=1 docker build -t learnings/spring-boot-redis-cluster . 
docker run --rm --net spring-redis-network -p 4024:4024 --name spring-boot-redis learnings/spring-boot-redis-cluster 
docker exec -it redis-1 redis-cli info stats

Теперь мы должны протестировать наше аварийное переключение. Давайте остановим один из наших серверов с помощью docker stop redis-2.

Если вы запустите docker exec -it redis-1 redis-cli cluster nodes, вы увидите, что ранее использовавшийся вторичный ресурс теперь назначен первичным.

В случае родительского сбоя Redis автоматически повышает вторичную реплику до первичной.

Первичная-вторичная (реплика) сеть Redis с Sentinel без сегментирования

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

Если мы не используем кластеры Redis, нам нужны дозорные для отработки отказа.

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

Поскольку конфигурация дозорных сильно отличается от кластеров, я поместил эту конфигурацию в /redis-conf-sentinel

Давайте добавим наши сигнальные серверы, чтобы обеспечить автоматическое переключение при отказе,

Стражам нужно только посмотреть на первичные узлы, чтобы принять решение об отказе.

sentinel monitor redis-cluster 172.18.0.2 6379 2

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

sentinel down-after-milliseconds redis-cluster 5000

Мы можем добавить ниже, чтобы разрешить тайм-аут для завершения текущей репликационной записи перед запуском аварийного переключения:

sentinel failover-timeout redis-cluster 10000

Давайте остановим все наши контейнеры Docker:

docker stop $(docker ps -a -q)

docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf-sentinel:/redis_config -p 4025:6379 -d --name redis-1 redis redis-server /redis_config/node1.conf d
ocker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf-sentinel:/redis_config -p 5025:6379 -d --name redis-1-replica redis redis-server /redis_config/node1-replica-1.conf
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf-sentinel:/redis_config -p 5026:6379 -d --name redis-2-replica redis redis-server /redis_config/node1-replica-2.conf
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf-sentinel:/redis_config -p 6025:6379 -d --name sentinel-1 redis redis-server /redis_config/sentinel1.conf --sentinel 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf-sentinel:/redis_config -p 6026:6379 -d --name sentinel-2 redis redis-server /redis_config/sentinel2.conf --sentinel 
docker run --rm --net spring-redis-network -v /mnt/c/Development/github/spring-boot-redis-cluster/redis-conf-sentinel:/redis_config -p 6027:6379 -d --name sentinel-3 redis redis-server /redis_config/sentinel3.conf --sentinel 
docker logs sentinel-2

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

Добавим отдельный application-sentinel.yml и запустим приложение:

docker stop spring-boot-redis DOCKER_BUILDKIT=1 
docker build -t learnings/spring-boot-redis-cluster . 
docker run --rm --net spring-redis-network -e "SPRING_PROFILES_ACTIVE=sentinel" -p 4024:4024 --name spring-boot-redis learnings/spring-boot-redis-cluster

Мы можем остановить наш основной экземпляр, чтобы убедиться, что дозорные работают:

docker stop redis-1

Вы должны увидеть такие журналы в своем дозорном docker logs sentinel-1:

1:X 18 Jun 2020 21:25:06.046 # +sdown primary redis-cluster 172.18.0.2 6379 1:X 18 Jun 2020 21:25:07.679 # +new-epoch 1 1:X 18 Jun 2020 21:25:07.891 # +vote-for-leader bfebc5c7d07121c78633024dcbc89a14bf1e4563 1 1:X 18 Jun 2020 21:25:07.948 # +odown primary redis-cluster 172.18.0.2 6379 #quorum 3/2 1:X 18 Jun 2020 21:25:07.948 # Next failover delay: I will not start a failover before Thu Jun 18 21:25:28 2020 1:X 18 Jun 2020 21:25:08.580 # +config-update-from sentinel bfebc5c7d07121c78633024dcbc89a14bf1e4563 172.18.0.7 6379 @ redis-cluster 172.18.0.2 6379 1:X 18 Jun 2020 21:25:08.580 # +switch-primary redis-cluster 172.18.0.2 6379 172.18.0.4 6379 1:X 18 Jun 2020 21:25:08.581 * 

Вот и все! Мы реализовали приложение Redis с весенней загрузкой и узнали, как создавать различные сети Redis.

Если у вас есть какие-либо вопросы или отзывы, не стесняйтесь оставлять свои мысли в разделе комментариев.

По вопросам, связанным с кодом, вы можете создавать проблемы прямо в репозитории GitHub.