redis sentinel что это
Русские Блоги
Redis Sentinel и Redis Cluster
Введение
Сегодня, с быстрым развитием Интернета, требования к стойкости к давлению прикладных систем становятся все выше и выше. Традиционный прикладной уровень + база данных больше не может соответствовать текущим потребностям. Поэтому появилось большое количество баз данных в памяти и баз данных Nosql, среди которых redis, memcache, mongodb, hbase и т. Д., Которые широко используются для повышения пропускной способности системы, поэтому правильное использование кэша является основным навыком для разработки. Эта статья в основном знакомит с различием и использованием Redis Sentinel и Redis Cluster.Основные операции Redis можно сослаться на его официальный документ. FЕсли вы заинтересованы в других типах кеша, вы можете найти материалы для самостоятельного изучения.
2. Введение в Redis Sentinel и Redis Cluster
Режим Redis Master-Slave выглядит следующим образом:
Как видно из рисунка выше, главный узел может быть подключен к нескольким ведомым устройствам. Структура узлов Redis, управляемых Redis Sentinel, следующая:
Из рисунка выше видно, что Sentinel фактически является мостом между Клиентом и Redis. Все клиенты получают услугу Redis Master через программу Sentinel. Прежде всего, Sentinel развертывается в кластере, и полученные Клиентом результаты могут быть связаны с любой службой Sentinel. Во-вторых, все службы Sentinel будут контролировать службу Redis Master-Slave.Если служба мониторинга не отвечает, Sentinel проводит внутренний арбитраж и выбирает slave из всех slave-устройств. И расценивайте других рабов как рабов нового Мастера. Наконец, сообщите всем клиентам о новом главном сервисном адресе. Если старый адрес главной службы будет перезапущен, на этот раз он будет установлен в качестве ведомой службы.
Sentinel может управлять узлами master-slave, и кажется, что стабильность Redis лучше гарантирована. Но если Sentinel является одним узлом, если Sentinel не работает, то модель «ведущий-ведомый» не может играть свою роль. К счастью, Sentinel также поддерживает кластерный режим.Кластерный режим Sentinel в основном имеет следующие преимущества:
Режим кластера Redis Sentinel может повысить стабильность и надежность всего кластера Redis, но когда главный узел узла зависает для повторного выбора нового главного узла, сложность выбора режима кластера Redis Sentinel, очевидно, выше, чем в одной точке. Режим Redis Sentinel требует более надежного алгоритма выбора. Давайте представим «арбитражное совещание» режима кластера Redis Sentinel (несколько Redis Sentinel обсуждают вместе, кто является главным узлом Redis)
1.1, «Арбитражное совещание» режима кластера Redis Sentinel
Когда ведущий контролируется сторожевым кластером, вам необходимо указать для него параметр. Этот параметр указывает количество сторожей, которое требуется, когда мастер должен быть признан недоступным и выполняется аварийное переключение. В этой статье мы временно называем этот параметр количеством голосов, но Когда переключение между основным и подчиненным компонентами переключения при сбое фактически инициируется, восстановление после сбоя не будет происходить немедленно, и большая часть сторожевого устройства в сторожевом устройстве будет авторизована перед выполнением переключения при сбое. Когда ODOWN, происходит переключение при сбое. После запуска аварийного переключения при попытке выполнить аварийное переключение дозорный получит разрешение «большинства» дозорного (если больше голосов, чем у большинства, попросите большее количество дозорного). Эта разница выглядит неуловимой, но легко Понять и использовать. Например, в кластере 5 часовых, а количество голосов установлено равным 2. Когда два стража считают, что мастер недоступен, сработает аварийное переключение, однако часовой, который выполняет аварийное переключение, должен сначала получить авторизацию как минимум от трех часовых. Отказоустойчивость может быть реализована. Если для количества голосов установлено значение 5, для достижения состояния ODOWN все 5 часовых должны быть субъективно признаны главными как недоступные, а для восстановления после сбоя все 5 часовых должны быть авторизованы.
Предыдущие отношения между узлами Redis Cluster показаны ниже:
3. Практика Redis Sentinel и Redis Cluster
Для использования Redis Sentinel и Redis Cluster необходимо представить следующие пакеты jar
1. Использование Redis Sentinel
2. Использование Redis Cluster
Выше описан процесс инициализации и простое использование Redis Sentinel и Redis Cluster. Для других более сложных приложений, пожалуйста, обратитесь к официальному API Redis
В-четвертых, стратегия истечения Redis
1. Регулярно удаляйте
2. Ленивое удаление
3. Регулярно удаляйте
Memcached использует только отложенное удаление, а redis использует как отложенное, так и периодическое удаление, что также является разницей между ними (это можно рассматривать как точку, в которой redis лучше, чем memcached);
Пять, ямы, на которые наступили во время использования Redis
1. В производственной среде необходимо настроить maxIdle, maxTotal, minIdle в GenericObjectPoolConfig. Поскольку значение по умолчанию слишком мало, если трафик в производственной среде относительно велик, будет возникать ситуация, ожидающая подключения с повторным подключением.
2. При использовании Redis Sentinel необходимо в конце выполнить метод jedis.close, чтобы освободить ресурсы.Этот метод close означает, что нормальное соединение возвращается в пул соединений и закрывается ненормальное соединение. В предыдущих версиях jedis для освобождения ресурсов вызывался метод returnResource. Если соединение не нормальное, оно будет использовано повторно. В это время произойдет очень странное исключение. Поэтому рекомендуется использовать более высокую версию джедаев
3. Чтобы лучше использовать пул соединений Redis, рекомендуется использовать JedisPoolConfig вместо GenericObjectPoolConfig. В JedisPoolConfig есть несколько параметров по умолчанию
Отказоустойчивая работа с Redis
Эта статья — переработанная версия доклада Отказоустойчивая работа с Redis с прошедшего 17 октября 2020 митапа PHP-разработчиков Йошкар-Олы.
Мы поговорим о подводных камнях использования Redis в системе, где важна отказоустойчивость — на примере хранения сессий в условном веб-сервисе, написанном на PHP, но многие замечания будут справедливы и для других платформ — например для микросервисов на Go. Статья будет полезна, если вы проектируете микросервисы или монолитные приложения с достаточно большой нагрузкой и интенсивно работаете с Redis либо столкнулись с потребностью в перепроектировании механизмов аутентификации и сессий.
Немного о Redis
Redis — это NoSQL СУБД с открытым исходным кодом, работающая с данными вида «ключ — значение». Если вы захотите узнать о преимуществах Redis, вы наверняка найдёте примерно такой список:
Redis хранит все данные в оперативной памяти, что повышает производительность
В Redis нет SQL и схемы хранилища, что опять же повышает производительность (нет интерпретатора SQL) и даёт гибкость
Нет никаких ACID транзакций, изменения просто сохраняются на диск в фоне, что тоже повышает производительность
Иначе говоря, Redis может быть очень быстрым.
Но давайте взглянем на это с точки зрения надёжности и отказоустойчивости:
Redis хранит все данные в оперативной памяти, и при аварийном завершении процесса или выключении машины данные будут потеряны
Redis не ориентирован на ACID транзакции, и на практике значит:
даже при включённом сохранении на диск данные могут быть потеряны частично либо полностью
в данных может быть нарушена целостность с точки зрения приложения
фоновое сохранение данных на диск может нанести ущерб производительности и отказоустойчивости
Значит ли это, что Redis плох? Вовсе нет. Наша статья о том, что при внедрении Redis в проект нужно рассматривать разные варианты и принимать взвешенные решения, а не верить слепо, что всё уже предусмотрено умными дядьками и никаких проблем на production не возникнет.
Теперь поговорим про это подробнее.
Сценарии отказа Redis
Допустим, мы используем классический метод развёртывания веб-приложений, и у нас есть две машины — на одной развёрнуто PHP приложение, на другой — Redis.
Ожидание в очереди
Из этих шагов только последний работает с той фантастической скоростью, которую обещают разработчики Redis. Все остальные могут вызывать проблемы.
Во-первых, Redis может упасть:
В этом случае приложение попытается установить соединение, но не сможет (сразу либо после произвольного таймаута, установленного для библиотеки Predis). Будет создано исключение, к которому код может быть не готов.
Во-вторых, может нарушиться сетевая связность:
В этом случае бесполезное ожидание и отказ по таймауту неизбежны, что может привести к веерным отказам: сначала бесполезные таймауты под нагрузкой приведут к повышению числа активных процессов php-fpm и числа соединений с СУБД, а потом закончится либо одно, либо другое, клиенты начнут получать 500-е и 504-е коды ошибок — сервис перестанет их обслуживать.
В-третьих, под высокой нагрузкой Redis может не успеть обработать полученную команду:
С точки зрения приложения произойдёт отказ по таймауту, с рисками веерного отказа. Что ещё хуже, веерные отказы могут произойти и без явных отказов Redis: приложение просто будет ждать дольше обычного и исчерпаются соединения с СУБД либо процессы php-fpm.
Наконец, у Redis может кончиться память, и тогда всё зависит от настроек maxmemory и maxmemory-policy. По умолчанию maxmemory-policy имеет значение noeviction, и это означает, что при нехватке памяти Redis перейдёт в режим readonly:
Даже если поменять maxmemory-policy, вы можете получить ситуацию, когда при нехватке памяти Redis выкидывает ключи, значительно сокращая время жизни сессий, токенов и других полезных данных. Будет ли ваше приложение работать нормально, если контракт на время жизни данных не выполняется?
Сценарии отказа Redis в Kubernetes
Давайте сгустим краски ещё больше: добавим Kubernetes и засунем в него Redis.
Kubernetes — это система управления кластером. Он запускает N приложений в контейнерах на меньшем числе машин, распределяя нагрузку такими сложными путями, что для сопровождения проекта в Kubernetes вам потребуется отдельная команда (если у вас всё кросс-функциональное, тогда её размажет ровным слоем по остальным командам).
Допустим, у нас есть кластер из 4 worker’ов, управляемых Kubernetes. Допустим, Kubernetes раскидал экземпляры PHP-приложения и Redis так, как показано на картинке:
После этого worker #3 вывели на обслуживание, в результате мы теряем единственный экземпляр Redis и 2 из 4 экземпляров приложения и получаем кратковременный отказ. Если Redis прислал свои данные не на сетевой диск, то получаем ещё и потерю всех его данных. Если же он писал на сетевой диск, то делал это медленно, поскрипывая винчестерами на весь дата-центр и мешая дежурным спать.
Как бы развернуть Redis, чтобы потеря одной worker-машины не создавала проблем?
У Redis предусмотрено два способа организации отказоустойчивости: Redis Sentinel и Redis Cluster. Прочитав документацию и собрав факты, вы увидите следующее:
Обе реализации являются скорее кирпичиками для построения отказоустойчивости с автоматическим failover
Redis Cluster имеет некоторую автоматизацию, но эта разница нивелируется в Kubernetes за счёт Redis Operator (например, spotahome/redis-operator), который основан на Redis Sentinel
Redis Cluster даёт шардирование, Redis Sentinel его не даст
Если вы хотите производительность выше той космической, что можно выжать из одного инстанса, подумайте о Redis Cluster вне Kubernetes. Если вы хотите засунуть Redis в Kuberntes, используйте Redis + Sentinel + Redis Operator.
Отказоустойчивость Sentinel и Cluster имеет свою цену:
Оба варианта меняют протокол взаимодействия с Redis, и клиент должен поддерживать новый вариант
Как минимум 3 экземпляра Redis Sentinel нужно, чтобы достигнуть кворума при выборке master
Сам Redis Sentinel нового мастера не назначит, его надо попросить, что и делает Redis Operator
Взаимодействие PHP-приложения с Redis Sentinel показано на схеме:
Сначала приложение обращается к Redis Sentinel и узнаёт, какой из экземпляров Redis является мастером. Затем приложение обращается к нужному экземпляру Redis.
Допустим, в нашем кластере распределено 3 redis и 3 redis-sentinel, причём redis master и один из sentinel оказались на одной машине #3:
После этого worker #3 вывели на обслуживание, в результате мы теряем redis master и один из redis-sentinel. Пока redis-sentinel не перезапустится на другой машине, а redis operator не попросит всех троих выбрать нового master, redis останется недоступен (по крайней мере на запись).
Чтобы PHP-приложение (или, например, NGINX/Lua) не обращалось к redis-sentinel на каждый запрос, можно ввести ещё одного игрока: redis-proxy (например, ifwe/twemproxy). Этот proxy является stateless, он может быть запущен внутри контейнера PHP-приложения либо как sidecar-контейнер, чтобы минимизировать задержки сети.
Взаимодействие PHP-приложения с Redis Proxy показано на схеме:
Worker, на котором находится redis master, выводится на обслуживание
Запускается новый redis
Посовещавшись, тройка redis sentinel выбирает новый redis мастером
Новый и абсолютно пустой redis в роли мастера просит реплики удалить все данные
Баг, приводящий к такому сценарию, исправили в spotahome/redis-operator, но кто даст гарантию, что это был последний баг?
Избежать проблемы можно путём добавления сетевого диска — например, NFS. Тогда NFS станет ещё одной точкой отказа.
Так что же делать?
Если вы на этапе проектирования, подумайте:
Так ли вам нужен Redis в этом классе/модуле/сервисе? избегайте хранения в Redis важных данные или данных, требующих контроля целостности
Так ли вам нужен Redis в Kubernetes, или подойдёт Redis на отдельной машине?
В целом стоит насторожиться, когда кто-то предлагает засунуть хранилища данных в Kubernetes. Если это предлагает коллега, расскажите ему анекдот про мужика, сено и скафандр.
Вы можете столкнутся с несколькими доводами в пользу применения Redis:
В проекте уже используют Redis
возражение: важно понимать, для чего именно использован Redis и на какие компромиссы при этом согласились
Данные имеют time to life
возражение: это не мешает хранить их в SQL СУБД
Redis быстрее SQL СУБД
возражение: перегруженный redis работает медленее, кроме того, взаимодействие с двумя хранилищами вместо одного добавляет сетевые задержки и точки отказа
Достойная причина использовать Redis — высокие нагрузки и данные, которые не так страшно потерять.
Как улучшить отказоустойчивость
Сложная система порождает новые сценарии катастроф. Что ещё хуже, вы не можете заранее предсказать все риски. Но можно практиковать избыточность, нагрузочное тестирование и тестирование тех сценариев отказа, которые вы можете предсказать.
Избыточность может проявляться по-разному:
В дополнительных сущностях: redis-sentinel, redis-operator, redis-proxy
В распределении redis по разным машинам или даже разным дата-центрам
В дополнительной отказоустойчивости со стороны приложения, а не инфраструктуры
Последний пункт почти не упоминается в статьях об отказоустойчивом Redis, а мы поговорим об этом подробнее.
Делаем кэширование необязательным
Допустим, наше приложение на PHP использует Redis как кэш. В таком случае мы должны:
При записи сохранять данные и в Redis, и в основную БД
При чтении читать сначала из Redis, а при ошибке — читать из БД
Сохранить небольшой timeout для клиента Redis — например, 100 или 300 миллисекунд
Рассмотрим схему классов для хранения данных сессии:
Интерфейс KeyValueCacheInterface объявляет обобщённый API для работы с кэшем, за которым скрыт Redis
Интерфейс SessionStorageInterface объявляет хранилище данных сессии
реализация DatabaseSessionStorage хранит сессии в таблице в БД
реализация KeyValueCacheSessionStorage хранит сессии в кэше
SessionService использует оба варианта хранилища, но работает с ними по-разному
При чтении данных в SessionService сначала пытаемся читать из Redis, в случае ошибки — читаем из БД:
Для обновления данных записываем и Redis, и в БД:
Такое решение имеет свои ограничения:
Возрастёт нагрузка на БД — в сравнении с хранением данных только в Redis
После отказа и восстановления кэша в нём появляются неактуальные данные, записанные в БД в период отказа
Усовершенствование в хранении сессий
На примере всё тех же сессий посмотрим, как можно усовершенствовать механизм отказоустойчивости.
Допустим, у нас есть сессии, которые хранятся в трёх хранилищах: session id хранится в Cookie, а данные сессии хранятся одновременно и в Redis, и в MySQL:
Сессию надо периодически обновлять, чтобы при активном использовании TTL (time to life) не истёк и сессия не закончилась. Чтобы уменьшить частоту обновления сессий, мы можем добавить в данные сессии дату последнего обновления, и обновлять сессию в обработчике запроса только в том случае, если с последнего обновления прошло больше определённого числа секунд (например, 60 секунд).
Кстати, именно так ведут себя современные PHP фреймворки — например, Symfony.
Обновлённое расположение данных будет выглядеть так:
Чтобы решить проблему инвалидации кэша при его недоступности, мы можем добавить в Cookie ещё одно значение: целочисленный generation number данных сессии, который увеличивается на единицу при каждом изменении данных сессии, и предпочитать чтение данных из БД в случае, если generation number в Redis не совпадает с тем, что пришёл из Cookie:
Сессии в Signed Cookies
Идею хранения данных в Cookie можно развить, но для этого надо сделать Cookie надёжным хранилищем, в котором нельзя подменить данные. Этого можно достигнуть с помощью Signed Cookies — например, можно в качестве значения хранить JWT (JSON Web Token). В этом случае:
Секретный ключ для подписи JWT хранится на сервере, и пользователь не может подменить содержимое Cookie, не нарушив целостность подписи JWT.
Секретные данные в Signed Cookie хранить по-прежнему нельзя, т.к. полезная нагрузка в JWT хранится в открытом виде
С таким подходом основные данные сессии можно переместить в Cookie небольшого размера (не более 1-2 КБ), и до истечения JWT вообще не обращаться к Redis и MySQL. Короткое время жизни такой Cookie обеспечит баланс между временем инвалидации данных и снижением нагрузки на сервера.
Состояние сессии, влияющее только на просмотр, можно хранить в открытом виде в Cookie, а можно точно так же поместить в JWT.
После этих изменений схема расположения данных выглядит, как показано ниже. Можно убрать из схемы Redis, поскольку Signed Cookie снизит число обращений к данным сессии на сервере.
Подытожим
Отказоустойчивости в работе с Redis или с любым другим вспомогательным хранилищем можно достигнуть не только за счёт схемы развёртывания и инфраструктуры, но и за счёт пересмотра архитектуры или доработок приложения, обеспечивающих устойчивость к отказам.
Чтобы это сработало, надо:
Учесть разные сценарии отказа Redis, в том числе связанные с превышением таймаутов, превышением лимита памяти или сетевых соединений
Пересмотреть архитектуру и реализацию системы с точки зрения взаимодействия с Redis
Решить, куда вносить доработки: в схему развёртывания, в реализацию взаимодействия с Redis или даже в архитектуру хранения данных
Для большей устойчивости к отказам и нагрузкам потребуется применить все три метода
Кроме того, стоит решить, будет ли использование Redis, memcached или иного быстрого хранилища уместным:
Возможно, требования по нагрузке и отказоустойчивости позволяют не усложнять и хранить всё, включая сессии, в основной БД
Возможно, идея использовать Redis пришла из-за того, что данные временные; в этом случае данные можно так же хранить в БД, не забывая:
Добавить в таблицу и во все связанные SQL-запросы колонку expiration date
Реализовать механизм очистки старых данных фоновыми задачами, запускаемыми через cron или CronJob (в Kubernetes), например, каждую ночь
В микросервисной архитектуре можно выделить работу с сессиями, cookie и токенами в отдельный сервис (который также может взять на себя задачи аутентификации). В монолите можно скрыть работу с Redis за абстракцией, а в реализации этой абстракции Redis рассматривать как вспомогательный кэш, недоступность которого не приводит к отказу.
Как мы Redis Cluster готовили
В мире опен сорс есть огромное количество технологий, подходов, паттернов, тулзов и аппов, которые юзает очень много компаний. Как превратить используемое ПО или технологию в конкурентное преимущество? Предлагаю рассмотреть на примере Redis Cluster — как мы прокладывали наш путь.
Начало
Стоит начать с того, что Redis — это, по сути, очень удобная штука. В двух словах, Redis — это персистентное key-value хранилище в памяти, со своим блэкджеком и куртизантками. Чаще всего его сравнивают с устаревшим Memcached, который не умеет делать почти ничего из того, что умеет делать Redis.
Например, проблемы, которые очень очень мешают жить:
Redis Cluster
Там же есть Redis Cluster! Скажете Вы, но я бы попросил не спешить. На самом деле, у Redis есть 2 типа кластеризации:
Redis Cluster — это штука повеселее, тут уже есть шардинг, репликация, отказоустойчивость, мастера-слейвы, разные там штуки прикольные и все такое. Это уже однозначно похоже на нормальный кластер, но все равно юзать это таким, как оно есть — не получится.
Почему это не работает
Для того, чтобы понять почему это не работает, нужно понять, как это работает внутри.
Во первых, сам процесс создания Redis Cluster из стенделон нод — это дикость и унижение, в том виде, в котором это есть сейчас. В нашем 2017 году все привыкли к дискаверингу, провижинингу и репорту типа «я все сделал, тут уже кластер, все ок!» — но реалии таковы, что в сорсах Redis есть скрипт, написанный на рубях, который занимается тем, что принимает как аргументы инстансы редиски, и потом соединяет их в кластер. Доверяете ли вы подобным штукам? Думаю да, доверяли, лет 100500 назад.
Окей, у нас есть кластер. Теперь немного теории: внутри кластера есть такие штуки, как hash slots. По сути, слот — это число, которое подразумевает набор данных, за которые ответственна конкретная нода кластера. Всего существует 16384 слота, которые равномерно делятся между всеми мастерами.
Кстати, о мастерах. По умолчанию Redis Cluster может состоять не менее чем из трех нод, и все это будут мастера. Соответсвенно, они поделят слоты между собой.
Второй нюанс использования кластера — это невероятная хрупкость. Например, у нас из кластера с 3 нодами отвалилась одна нода. Логичным решением было бы продолжить работать — у нас же есть 66,6% данных, но все совсем не радужно. В дефолтной конфигурации будет ответ ‘CLUSTER IS DOWN’ по запросу любого, даже живого ключа.
Если рассматривать кластер побольше, например из 6 нод (3 мастера и 3 слейва) — ситуация повторяется. Пока происходит автоматический промоутинг слейва в мастера после падения, ответ аналогичен — ‘CLUSTER IS DOWN’. А это — секунды, хотя эта задержка зависит от количества данных в кластере.
Третья проблема — это клиенты. Точнее, коннекторы в апликейшнах. Если взять предыдущий кейс, когда у нас идет промоутинг кластера — все клиенты отвалятся с socket error, или connection timeout, или с чем-то похожим, потому что держат коннект ко всем мастерам в кластере. Это тоже нужно доделывать.
Четвертый, и один из самых неприятных, нюанс — это изменение набора команд. Стандартные команды, которые работают по вайлдкардам — не работают, и это не удивительно. Это нужно переделывать по всему проекту, учить апликейшн работать как со стенделоном так и с кластером. По факту — это самая длинная и затратная часть имплементации Redis Cluster.
Как заставить это работать
Первый нюанс с провижинингом мы исправить не в состоянии, разве что сделать LWRP для Chef, и провижинить это как-то более нормально. По сути, примерно так мы и сделали.
А вот второй и третий — это уже наша компетенция!
Исправить ‘CLUSTER IS DOWN’ при отсутствии части слотов очень легко и просто — достаточно добавить параметр конфигурации:
Проблему с клиентами, которые отваливаются можно решить, потратив немного времени. В нашем проекте используется 2 языка — PHP и Java, поэтому нам нужно было делать два раза одну и ту же работу. Общий алгоритм сводится к таким степам:
Ни для кого не будет секретом, если я скажу, что в кластере с 6+ нод, смысла флашить данные на диск нет никакого. Соответственно, если отключить персистентность — все будет работать очень и очень быстро.
Результат
Что же получилось в результате?
А как Вы используете общедоступные инструменты?