Post main image

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

  • Масштабирование - абстрактное определение, увеличение числа функциональных блоков, выполняющих схожую функцию.
  • Кластеризация - способ масштабирования за счет распределения нагрузки по многим серверам.
  • Репликация - создание полной копии базы данных, с синхронизацией.
    • Master - главный сервер, на который осуществляется запись.
    • Slave - копия, в которую синхронизируются данные.
  • Multi-master репликация - репликация, при которой существует несколько master серверов, синхронизирующихся друг с другом.
  • Шардинг, сегментация, или партицирование - логическое разбиение данных так, чтобы их можно было разнести, например разбиение по хеш-функции.
    • Вертикальный шардинг - разбиение по различным таблицам в рамках одного сервера.
    • Горизонтальный шардинг - разбиение по различным таблицам различных серверов.

Введение в Redis Cluster

Основные цели

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

  • Если некоторые узлы не работают, кластер перераспределяет с них нагрузку на другие узлы.
  • Если не работают ключевые узлы, то весь кластер заканчивает работу.

Основные порты

Кластер используется два TCP-соединения.

  • Клиентский порт - как правило 6379. Как видно из названия, используется клиентами.
  • Порт кластерной шины - клиентский порт + 10000, например 16379. Этот порт используется для работы кластера: обнаружение ошибок, обновление конфигурации, авторизации и прочего.

Сегментация

Кластер не использует консистентное хеширование, вместо этого используется так называемые хэш-слоты (анг. hash-slot). Весь кластер имеет 16384 слотов, для вычисления хэш-слота для ключа используется формула crc16(key) % 16384. Каждый узел Redis отвечает за конкретное подмножество хэш-слотов, например:

  • Узел A седержит хеш-слоты от 0 до 5500.
  • Узел B седержит хеш-слоты от 5501 до 11000.
  • Узел C седержит хеш-слоты от 11001 до 16383.

Это позволяет легко добавлять и удалять узлы кластера. Если мы добавляем узел D, то можно просто переместить некоторые хеш-лоты других узлов. Если же удаляем A, то нам необходимо постепенно переместить все хэш-слоты узла А на другие, а когда хэш-слотов не останется, то просто удалить узел из кластера. Всё это выполняется постепенно командами, нам не нужно приостанавливать выполнение операций, не нужно пересчитывать процентное соотношение хеш-слотов к количеству узлов, никаких прочих остановок системы.

Возникает вопрос - Redis имеет сложные структуры данных с составными ключами, ведь хэш-функция посчитает для них разные значения? Имеется и ответ:

  • Составные ключи одной команды, одной транзакции или одного скрипта Lua, гарантировано попадут в один хэш-слот.
  • Пользователь может принудительно сделать составные ключи частью одного хэш слота с помощью концепта хэш-тегов.

Если в кратце, то хэш-теги говорят Redis что именно хешировать, цель хэширования задается в фигурных скобках. Так, хэши этих ключей будут равны - foo.{key}.bar и baz.{key}.biz.

Репликация

Каждый хэш-слот имеет от 1 (master) до N (N-1 slave) реплик. Таким образом, если выйдет из строя некоторый слот, то кластер назначит его slave master-ом.

Гарантия целостности

Может возникнуть ситуация:

  • Клиент пишет в узел B.
  • B отвечает ОК.
  • B пытается записать в slave B1, B2, B3.

Как можно заметить, B не ждет репликации данных, а сразу отвечает клиенту ОК. При этом, если запись может не попасть в B1, а возможность стать мастером у B1 остается. Разумеется, в этом случае B1 будет иметь не все данные. Redis Cluster пользоволяет использовать команду WAIT <numbers of replicas> <timeout>.

Также может возникнуть ситуация, когда A, C, A1, B1, C1 перестают видеть B, а клиент всё еще продолжает записывать туда данные. Если этот сбой будет очень короткий, то ничего страшного не случится, но если B не будет доступен продолжительное время, то мастером станет B1. Это время устанавливается в параметрах и называется таймаутом узла (анг. node timeout). В частности, это время также дается для записи клиенту, если клиент не укладывается в данное время, то узел больше не принимает записи. Также, если мастер не будет видеть это время другие узлы, то он самостоятельно перестанет работать.

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

Конфигурация Redis Cluster задается в файле redis.conf.

  • cluster-enabled <yes/no> - флаг включение Redis Cluster.
  • cluster-config-file - файл, в который будет писаться системная информация: другие узлы, их состояние, глобальные переменные и прочее.
  • cluster-node-timeout - максимальное время, которое master-узел может быть недоступен, по истечению этого времени он помечается как нерабочий и slaves начинают его аварийное переключение.
  • cluster-slave-validity-factor - это число умноженное на таймаут узла - максимальное время, через которое slave начнет аварийное переключение.
  • cluster-migration-barrier - минимальное количество slaves master-а.
  • cluster-require-full-coverage <yes/no> - параметр, который говорит, нужно ли продолжать работоспособность всего кластера, если хотя бы часть хэш-слотов не покрыта.

Создание и использование Redis Cluster

Настройка

Чтобы создать кластер, во-первых необходимо запустить несколько узлов в режиме cluster mode. Минимальная конфигурация для этого:

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

Redis Cluster требует как минимум три узла, поэтому чтобы создать тестовый кластер, например, с тремя master и тремя slave узлами следует выполнить действия:

  • Создаем директории
    mkdir cluster-test
    cd cluster-test
    mkdir 7000 7001 7002 7003 7004 7005
    
  • В каждой директории создаем файл redis.conf, заменяем значение port на число из имени директории.
  • Запускаем узлы, например redis-server 7002/redis.conf. Мы увидим что-то типа 34821:M 01 Apr 11:39:06.018 * No cluster configuration found, I'm 5d68f16ba5a6125f1e55017c4966b78d56817e0d. В данном случае 5d68f16ba5a6125f1e55017c4966b78d56817e0d - это уникальный идентификатор Node ID, он никогда не меняется, может поменяться IP-адрес, порт, но не он.
  • Устанавливаем утилиту redis-trib, с помощью которой можно развернуть кластер.
    git clone https://github.com/antirez/redis
    gem install redis
    
  • Запускаем ./redis/src/redis-trib.rb create --replicas 1 127.0.0.1:7000 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, где опция --replicas говорит, сколько slave будет у каждого мастера. Когда команда попросит создать конфигурацию, то соглашаемся. По окончанию увидим вывод [OK] All 16384 slots covered..

В директории исходного кода по пути ./utils/ можно найти больше утилит.

Некоторые полезные команды

redis-cli -p 7000 cluster nodes - посмотреть список узлов. redis-cli -p 7002 debug segfault - вызвать segfault на одном из узлов. redis-cli -p 7003 cluster failover - переключает slave на мастер. redis-cli -p 7000 cluster forget b47bebb03d1342d22818623ad109b084b599e308 - удалить неиспользуемый узел. ./redis-trib del-node 127.0.0.1:7000 <node-id> - удалить узел. ./redis-trib.rb add-node 127.0.0.1:7002 127.0.0.1:7004 - добавить узел 127.0.0.1:7002, если использовать опцию --slave, то добавиться как slave, --master-id ID как slave конкретного мастера.

Решардинг

Под решардингом понимается перемещение хэш-слотов между узлами - вызываем ./redis-trib.rb reshard 127.0.0.1:7000 и следуем инструкциям:

 > ./redis-trib.rb reshard 127.0.0.1:7000
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 95a95d655730734f6bae77633dfba5642f14b7e3 127.0.0.1:7000
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
M: aa9dd8e76746863e232701c3342c67ef407d1956 127.0.0.1:7001
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
M: b47bebb03d1342d22818623ad109b084b599e308 127.0.0.1:7002
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
S: f639ce60360e0dad1b941c9efa5b317c57ed45f3 127.0.0.1:7005
   slots: (0 slots) slave
   replicates 95a95d655730734f6bae77633dfba5642f14b7e3
S: 7a44229d6806e9f0e4545d9419f2067f5a47f4cf 127.0.0.1:7003
   slots: (0 slots) slave
   replicates aa9dd8e76746863e232701c3342c67ef407d1956
S: 14ef302c003f0e688dd20cd78677e644d3f8090b 127.0.0.1:7004
   slots: (0 slots) slave
   replicates b47bebb03d1342d22818623ad109b084b599e308
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 50
What is the receiving node ID? b47bebb03d1342d22818623ad109b084b599e308
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1:all

Ready to move 50 slots.
  Source nodes:
    M: 95a95d655730734f6bae77633dfba5642f14b7e3 127.0.0.1:7000
   slots:0-5460 (5461 slots) master
   1 additional replica(s)
    M: aa9dd8e76746863e232701c3342c67ef407d1956 127.0.0.1:7001
   slots:5461-10922 (5462 slots) master
   1 additional replica(s)
  Destination node:
    M: b47bebb03d1342d22818623ad109b084b599e308 127.0.0.1:7002
   slots:10923-16383 (5461 slots) master
   1 additional replica(s)
  Resharding plan:
    Moving slot 5461 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5462 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5463 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5464 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5465 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5466 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5467 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5468 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5469 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5470 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5471 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5472 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5473 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5474 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5475 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5476 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5477 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5478 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5479 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5480 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5481 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5482 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5483 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5484 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5485 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 5486 from aa9dd8e76746863e232701c3342c67ef407d1956
    Moving slot 0 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 1 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 2 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 3 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 4 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 5 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 6 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 7 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 8 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 9 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 10 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 11 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 12 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 13 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 14 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 15 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 16 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 17 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 18 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 19 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 20 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 21 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 22 from 95a95d655730734f6bae77633dfba5642f14b7e3
    Moving slot 23 from 95a95d655730734f6bae77633dfba5642f14b7e3
Do you want to proceed with the proposed reshard plan (yes/no)? yes
Moving slot 5461 from 127.0.0.1:7001 to 127.0.0.1:7002:
39288:M 01 Apr 12:13:42.048 # New configEpoch set to 7
39288:M 01 Apr 12:13:42.048 # configEpoch updated after importing slot 5461
Moving slot 5462 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5463 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5464 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5465 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5466 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5467 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5468 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5469 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5470 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5471 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5472 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5473 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5474 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5475 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5476 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5477 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5478 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5479 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5480 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5481 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5482 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5483 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5484 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5485 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 5486 from 127.0.0.1:7001 to 127.0.0.1:7002:
Moving slot 0 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 1 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 2 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 3 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 4 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 5 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 6 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 7 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 8 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 9 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 10 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 11 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 12 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 13 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 14 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 15 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 16 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 17 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 18 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 19 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 20 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 21 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 22 from 127.0.0.1:7000 to 127.0.0.1:7002:
Moving slot 23 from 127.0.0.1:7000 to 127.0.0.1:7002:

Коротко о репликации

При создании кластера мы указываем количество master-узлов, а также мультипликатор количества slave для каждого мастера. При этом мы можем вручную добавить slave - CLUSTER REPLICATE <master-node-id>. Это может быть полезно, рассмотрим пример. Есть три master и три slave. В 4:00 падает один из slave, а в 4:30 падает его master, таким образом мы теряем хэш-слоты и кластер становится неработоспособен. Теперь представим туже схему, но один из master имеет два slave, тогда в 4:00 один из slave отличного от данного master, и весь кластер начинает миграцию «лишнего» slave в slave другого master, в 4:30 падает master, его slave начинает аварийное переключение, кластер продолжает работоспособность. Параметр, который отвечает за минимальное количество slave называется cluster-migration-barrier.

Redis Cluster спецификация

Основные свойства и дизайн кластера

Основные цели Redis Cluster в порядке их значимости в дизайне:

  • Высокая производительности и масштабируемость до 1000 узлов.
  • Безопасная запись.
  • Доступность, о которой говорилось ранее.

Redis Cluster не поддерживает базы и команду SELECT.

Redis Cluster Bus

Узлы кластера общаются между собой с помощью TCP по бинарному протоколу, который называется шиной кластера. По протоколу отправляются информация об узлах, сообщения очередей, пинги, а также пользовательские аварийные переключения. Так как Redis Cluster не может быть использован как прокси, то при редиректе на другие узлы в ответе будут сообщения типа MOVED, -ASK.

(error) MOVED 15891 127.0.0.1:7004