Про переїзд з Redis на Redis-cluster

Про переїзд з Redis на Redis-cluster

Приходячи в продукт, який розвивається більше десяти років, зовсім не дивно зустріти в ньому застарілі технології. Але якщо через півроку ви повинні тримати навантаження в 10 разів вище, а ціна падінь збільшиться в сотні разів? В цьому випадку вам необхідний крутий Highload Engineer. Але через брак покоївки такого, вирішувати проблему довірили мені. У першій частині статті я розповім, як ми переїжджали з Redis на Redis-cluster, а в другій частині дам поради, як почати користуватися кластером і на що звернути увагу при експлуатації.

вибір технології

Чи так поганий окремий Redis (standalone redis) у конфігурації 1 майстер і N слейвів? Чому я називаю його застарілою технологією?

Ні, Redis не так поганий ... Однак, є деякі недоліки, які не можна ігнорувати.

  • По-перше, Redis не підтримує механізмів аварійного відновлення після падіння майстра. Для вирішення цієї проблеми ми використовували конфігурацію з автоматичним перекиданням VIP-ів на новий майстер, зміною ролі одного зі слейвів та перемиканням інших. Цей механізм працював, але назвати його надійним рішенням не можна було. По-перше, траплялися помилкові спрацьовування, а по-друге, він був одноразовим, і після спрацьовування були потрібні ручні дії зі взводу пружини.

  • По-друге, наявність лише одного майстра призводило до проблеми шардування. Доводилося створювати кілька незалежних кластерів «1 майстер і N слейвів», потім вручну розносити бази цими машинами і сподіватися, що завтра одна з баз не розпухне настільки, що її доведеться виносити на окремий інстанс.

Які є варіанти?

  • Найдорожче і найбагатше рішення – Redis-Enterprise. Це коробкове рішення з повною технічною підтримкою. Незважаючи на те, що воно виглядає ідеальним з технічного погляду, нам не підійшло з ідеологічних міркувань.
  • Redis-cluster. З коробки є підтримка аварійного перемикання майстра та шардування. Інтерфейс майже відрізняється від звичайної версії. Виглядає перспективно, про підводні камені поговоримо далі.
  • Tarantool, Memcache, Aerospike та інші. Всі ці інструменти роблять приблизно те саме. Але кожен має свої недоліки. Ми вирішили не класти всі яйця в один кошик. Memcache і Tarantool ми використовуємо для інших завдань, і, забігаючи наперед, скажу, що на нашій практиці проблем з ними було більше.

Специфіка використання

Погляньмо, які завдання ми історично вирішували Redis'ом і яку функціональність використовували:

  • Кеш перед запитами до віддалених сервісів на кшталт 2GIS Golang

    GET SET MGET MSET "SELECT DB"

  • Кеш перед MYSQL PHP

    GET SET MGET MSET SCAN "KEY BY PATTERN" "SELECT DB"

  • Основне сховище для сервісу роботи з сесіями та координатами водіїв Golang

    GET SET MGET MSET "SELECT DB" "ADD GEO KEY" "GET GEO KEY" SCAN

Як бачите, жодної вищої математики. У чому тоді складність? Давайте розберемо окремо за кожним методом.

метод
Опис
Особливості Redis-cluster
Рішення

ОТРИМАТИСЯ
Записати/прочитати ключ

MGET MSET
Записати/прочитати кілька ключів
Ключі лежатимуть на різних нодах. Готові бібліотеки вміють робити Multi-операції лише в рамках однієї ноди
Замінити MGET на pipeline з N GET операцій

SELECT DB
Вибрати базу, з якою працюватимемо
Не підтримує декілька баз даних
Складати все в одну основу. Додати до ключів префікси

SCAN
Пройти по всіх ключах у базі
Оскільки у нас одна база, проходити по всіх ключах у кластері надто затратно
Підтримувати інваріант всередині одного ключа та робити HSCAN за цим ключем. Або відмовитися зовсім

GEO
Операції роботи з геоключом
Геоключ не шардується

KEY BY PATTERN
Пошук ключа за патерном
Оскільки ми маємо одну базу, будемо шукати по всіх ключах у кластері. Занадто затратно
Відмовитись або підтримувати інваріант, як і у випадку зі SCAN-ом

Redis vs Redis-cluster

Що ми втрачаємо і що отримуємо під час переходу на кластер?

  • Недоліки: втрачаємо функціональність кількох баз.
    • Якщо ми хочемо зберігати в одному кластері логічно не пов'язані дані, доведеться робити милиці у вигляді префіксів.
    • Втрачаємо всі операції "по базі", такі як SCAN, DBSIZE, CLEAR DB і т.п.
    • Multi-операції стали значно складнішими у реалізації, тому що може вимагатися звернення до кількох нодів.
  • Переваги:
    • Відмовостійкість у вигляді аварійного перемикання майстра.
    • Шардування на боці Redis.
    • Перенесення даних між нодами атомарно та без простоїв.
    • Додавання та перерозподіл потужностей та навантажень без простоїв.

Я зробив би висновок, що якщо вам не потрібно забезпечувати високий рівень відмовостійкості, то переїзд на кластер того не вартий, тому що це може бути нетривіальним завданням. Але якщо спочатку вибирати між окремою версією та кластерною, то варто вибирати кластер, тому що він нічим не гірший і на додаток зніме з вас частину головного болю.

Підготовка до переїзду

Почнемо з вимог до переїзду:

  • Він має бути безшовним. Повна зупинка сервісу на 5 хвилин не влаштовує нас.
  • Він має бути максимально безпечним та поступовим. Хочеться мати якийсь контроль за ситуацією. Бухнути відразу все і молитись над кнопкою відкату ми не бажаємо.
  • Мінімальні втрати даних під час переїзду. Ми розуміємо, що переїхати атомарно буде дуже складно, тому допускаємо деяку розсинхронізацію між даними у звичайному та кластерному Redis.

Обслуговування кластера

Перед самим переїздом слід подумати про те, а чи можемо ми підтримувати кластер:

  • графіки. Ми використовуємо Prometheus та Grafana для графіків завантаження процесорів, зайнятої пам'яті, кількості клієнтів, кількості операцій GET, SET, AUTH тощо.
  • Експертиза. Уявіть собі, що завтра під вашою відповідальністю буде величезний кластер. Якщо він зламається, ніхто, крім вас, відремонтувати його не зможе. Якщо він почне гальмувати - всі побіжать до вас. Якщо потрібно додати ресурси або перерозподілити навантаження, знову до вас. Щоб не посидіти у 25, бажано передбачити ці випадки та перевірити заздалегідь, як технологія поведеться при тих чи інших діях. Поговоримо про це докладніше у розділі «Експертиза».
  • Моніторинги та оповіщення. Коли кластер ламається, про це хочеться дізнатися першим. Тут ми обмежилися оповіщенням у тому, що це ноди повертають однакову інформацію про стан кластера (так, буває й інакше). А решту проблем швидше помітити за сповіщеннями сервісів-клієнтів Redis.

Переїзд

Як переїжджатимемо:

  • Насамперед потрібно підготувати бібліотеку для роботи з кластером. Як основа для версії на Gо ми взяли go-redis і трохи змінили під себе. Реалізували Multi-методи через pipeline-и, а також трохи поправили правила повторення запитів. З версією для PHP виникло більше проблем, але зрештою ми зупинилися на php-redis. Нещодавно вони запровадили підтримку кластера, і на наш погляд вона має гарний вигляд.
  • Далі потрібно розгорнути сам кластер. Робиться це буквально дві команди на основі конфігураційного файлу. Докладніше налаштування обговоримо нижче.
  • Для поступового переїзду ми використовуємо dry-mode. Так як у нас є дві версії бібліотеки з однаковим інтерфейсом (одна для звичайної версії, інша для кластера), нічого не варто зробити обгортку, яка працюватиме з окремою версією і паралельно дублюватиме всі запити в кластер, порівнювати відповіді та писати розбіжності в логі ( у нашому випадку в NewRelic). Таким чином, навіть якщо при викочуванні кластерна версія зламається, наш production це не торкнеться.
  • Викочивши кластер у dry-режимі, ми можемо спокійно дивитися на графік розбіжностей відповідей. Якщо частка помилок повільно, але вірно рухається до деякої невеликої константи, то все добре. Чому розбіжності все одно є? Тому що запис в окремій версії відбувається дещо раніше, ніж у кластері, і за рахунок мікрологу дані можуть розходитися. Залишилося тільки подивитися на логи розбіжностей, і якщо всі вони можна пояснити неатомарністю запису, то можна йти далі.
  • Тепер можна переключити dry-mode у зворотний бік. Писатимемо і читатимемо з кластера, а дублюватимемо в окрему версію. Навіщо? Протягом наступного тижня хочеться спостерігати за роботою кластера. Якщо раптом з'ясується, що в піку навантаження є проблеми, або ми щось не врахували, у нас завжди є аварійний відкат на старий код та актуальні дані завдяки dry-mode.
  • Залишилося відключити dry-mode та демонтувати окрему версію.

Експертиза

Спочатку коротко про влаштування кластера.

Насамперед, Redis — key-value сховище. Як ключ використовуються довільні рядки. Як значення можуть використовуватися числа, рядки та цілі структури. Останніх безліч, але для розуміння загального пристрою нам це не важливо.
Наступний після ключів рівень абстракції – слоти (SLOTS). Кожен ключ належить одному з 16 слотів. Усередині кожного слота може бути скільки завгодно ключів. Таким чином, всі ключі розпадаються на 383 16 безлічі, що не перетинаються.
Про переїзд з Redis на Redis-cluster

Далі, у кластері має бути N майстер-нід. Кожну ноду можна представляти як окремий інстанс Redis, який знає все про інші ноди всередині кластера. Кожна майстер-нода містить кілька слотів. Кожен слот належить лише одній майстер-ноді. Усі слоти потрібно розподілити між нодами. Якщо якісь слоти не розподілені, то ключі, що зберігаються в них, будуть недоступні. Кожну майстер-ноду має сенс запускати на окремій логічній чи фізичній машині. Також варто пам'ятати, що кожна нода працює тільки на одному ядрі, і якщо ви хочете запустити на одній логічній машині кілька екземплярів Redis, то переконайтеся, що вони працюватимуть на різних ядрах (ми не пробували так робити, але теоретично все має працювати) . По суті, майстер-ноди забезпечують звичайне шардування, і більша кількість майстер-нів дозволяє масштабувати запити на запис та читання.

Після того, як всі ключі розподілені по слотам, а слоти розкидані по майстер-нодів, до кожної майстер-ноди можна додати довільну кількість слейв-нод. Усередині кожного такого зв'язування «майстер-слейв» працюватиме звичайна реплікація. Слейви потрібні для масштабування запитів на читання та аварійного перемикання у разі виходу з ладу майстра.
Про переїзд з Redis на Redis-cluster

Тепер поговоримо про операції, які краще вміти робити.

Звертатися до системи ми через Redis-CLI. Оскільки Redis не має єдиної точки входу, виконувати наступні операції можна на будь-якій ноді. У кожному пункті окремо звертаю увагу на можливість виконання операції під навантаженням.

  • Перше та найголовніше, що нам знадобиться: операція cluster nodes. Вона повертає стан кластера, показує перелік нод, їх ролі, розподіл слотів тощо. Додаткову інформацію можна отримати за допомогою cluster info та cluster slots.
  • Добре вміти додавати і видаляти ноди. Для цього є операції cluster meet та cluster forget. Зверніть увагу, що cluster forget необхідно застосувати до кожної ноди, як до майстрів, так і до реплік. А cluster meet достатньо викликати лише на одній ноді. Така відмінність може бентежити, тому краще дізнатися про нього до того, як запустили кластер в експлуатацію. Додавання ноди безпечно виконується в бою і ніяк не торкається роботи кластера (що логічно). Якщо ж ви збираєтеся видалити ноду з кластера, слід переконатися, що на ній не залишилося слотів (інакше ви ризикуєте втратити доступ до всіх ключів на цій ноді). Також не видаляйте майстер, який має слейви, інакше виконуватиметься непотрібне голосування за нового майстра. Якщо на нодах вже немає слотів, то це невелика проблема, але навіщо нам зайві вибори, якщо можна спершу видалити слейви.
  • Якщо потрібно насильно поміняти місцями майстер і слейв, підійде команда cluster failover. Викликаючи їх у бою, треба розуміти, що протягом виконання операції майстер буде недоступний. Зазвичай перемикання відбувається менш ніж за секунду, але не атомарно. Чи можете розраховувати, що частина запитів до майстра в цей час завершиться з помилкою.
  • Перед видаленням ноди з кластера не повинно залишатися слотів. Перерозподіляти їх краще за допомогою команди cluster reshard. Слоти будуть перенесені з одного майстра, на інший. Вся операція може займати кілька хвилин, це залежить від обсягу даних, що переносяться, проте процес переносу безпечний і на роботі кластера ніяк не позначається. Таким чином, всі дані можна перенести з однієї ноди на іншу прямо під навантаженням, і не турбуватися про їхню доступність. Однак є й тонкощі. По-перше, перенесення даних пов'язаний з певним навантаженням на ноду одержувача та відправника. Якщо нода одержувача вже сильно завантажена процесором, то не варто навантажувати її ще й прийомом нових даних. По-друге, як тільки на майстру-відправнику не залишиться жодного слота, всі його слейви відразу перейдуть до майстра, на який ці слоти були перенесені. І проблема в тому, що всі ці слейви разом захочуть синхронізувати дані. І вам пощастить, якщо це буде часткова, а не повна синхронізація. Враховуйте це і поєднуйте операції переносу слотів і відключення/перенесення слейвів. Або сподівайтеся, що у вас достатній запас міцності.
  • Що робити, якщо ви виявили, що кудись втратили слоти? Сподіваюся, ця проблема вас не торкнеться, але якщо є операція cluster fix. Вона сяк-так розкидає слоти по нодах у випадковому порядку. Рекомендую перевірити її роботу, попередньо видаливши з кластера ноду з розподіленими слотами. Оскільки дані у нерозподілених слотах і так недоступні, турбуватися про проблеми з доступністю цих слотів вже пізно. У свою чергу, на розподілені слоти операція не вплине.
  • Ще одна корисна операція – monitor. Вона дозволяє в реальному часі бачити весь перелік запитів, що йдуть на ноду. Більше того, по ній можна зробити grep та дізнатися, чи є потрібний трафік.

Також варто згадати про процедуру аварійного перемикання майстра. Якщо коротко, то вона є, і, як на мене, чудово працює. Однак не варто думати, що якщо висмикнути шнур із розетки на машині з майстер-нодою, Redis відразу перемкнеться і клієнти не помітять втрати. На моїй практиці перемикання відбувається кілька секунд. Протягом цього часу частина даних буде недоступною: виявляється недоступність майстра, ноди голосують за нового, слейви перемикаються, дані синхронізуються. Кращий спосіб переконатися самостійно в тому, що робоча схема, це провести локальні навчання. Підніміть кластер на своєму ноутбуці, дайте мінімальне навантаження, зімітуйте падіння (наприклад, заблокувавши порти), оцініть швидкість перемикання. На мою думку, тільки погравши таким чином день-два, можна бути впевненим у роботі технології. Ну, чи сподіватися, що софт, яким користується половина інтернету, напевно працює.

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

Найчастіше, конфігурація - перше, що потрібно для початку роботи з інструментом., А коли все запрацювало, конфіг і чіпати не хочеться. Потрібні певні зусилля, щоб змусити себе повернутися до налаштувань і ретельно їх прошерстити. На моїй пам'яті ми мали як мінімум два серйозні факапи через неуважність до конфігурації. Зверніть особливу увагу на такі пункти:

  • Тайм-аут 0
    Час, за який закриваються неактивні з'єднання (в секундах). 0 - не закриваються
    Не кожна бібліотека вміла коректно закривати з'єднання. Відключивши це налаштування, ми ризикуємо впертись у ліміт за кількістю клієнтів. З іншого боку, якщо така проблема є, то автоматичний розрив втрачених сполук замаскує її, і ми можемо не помітити. Крім того, не варто вмикати це налаштування при використанні persist-з'єднань.
  • Save xy & appendonly yes
    Збереження RDB-снепшота.
    Проблеми RDB/AOF ми детально обговоримо нижче.
  • stop-writes-on-bgsave-error no & slave-serve-stale-data yes
    Якщо увімкнено, то при поломці RDB-снепшота майстер перестане приймати запити на зміну. Якщо з'єднання з майстром втрачено, то слейв може відповідати на запити (yes). Або припинить відповідати (no)
    Нас не влаштовує ситуація, коли Redis перетворюється на гарбуз.
  • repl-ping-slave-period 5
    Через цей проміжок часу ми почнемо турбуватися про те, що майстер зламався і настав час провести процедуру failover'a.
    Прийде вручну знаходити баланс між хибними спрацьовуваннями та запуском failover'а. На практиці це 5 секунд.
  • repl-backlog-size 1024mb & epl-backlog-ttl 0
    Рівно стільки даних ми можемо зберігати в буфері для репліки, що відвалилася. Якщо буфер закінчиться, доведеться повністю синхронізуватися.
    Практика підказує, що краще поставити значення більше. Причин, через які репліка може почати відставати, достатньо. Якщо вона відстає, то, швидше за все, ваш майстер вже важко справляється, а повна синхронізація стане останньою краплею.
  • maxclients 10000
    Максимальна кількість одноразових клієнтів.
    На наш досвід, краще поставити значення більше. Redis чудово справляється з 10 тис. з'єднань. Тільки переконайтеся, що в системі достатньо сокетів.
  • maxmemory-policy volatile-ttl
    Правило, за яким видаляються ключі за умови досягнення ліміту доступної пам'яті.
    Тут важливо не саме правило, а розуміння, як це відбуватиметься. Redis можна похвалити за вміння штатно працювати під час досягнення ліміту пам'яті.

Проблеми RDB та AOF

Хоча сам Redis зберігає всю інформацію в оперативній пам'яті, є механізм збереження даних на диск. А точніше, три механізми:

  • RDB-snapshot – повний зліпок усіх даних. Встановлюється за допомогою конфігурації SAVE XY і читається як «Зберігати повний снепшот всіх даних кожні X секунд, якщо змінилося хоча б ключів Y».
  • Append-only file - список операцій у порядку їх виконання. Додає нові операції, що прийшли в файл кожні Х секунд або кожні Y операцій.
  • RDB and AOF – комбінація двох попередніх.

У всіх методів є свої переваги і недоліки, я не перераховуватиму їх усі, лише зверну увагу на не очевидні, на мій погляд, моменти.

По-перше, для збереження RDB-снепшота потрібно викликати FORK. Якщо даних багато, це може повісити весь Redis на період від кількох мілісекунд до секунди. Крім того, системі потрібно виділити пам'ять під такий снепшот, що призводить до необхідності тримати на логічній машині подвійний запас оперативної пам'яті: якщо для Redis виділено 8 Гб, то на віртуалці з ним має бути доступно 16.

По-друге, є проблеми із частковою синхронізацією. У режимі AOF при перепідключенні слейва замість часткової синхронізації може виконуватися повна. Чому так відбувається, я не зміг зрозуміти. Але пам'ятати це варто.

Ці два пункти вже змушують задуматися про те, чи нам потрібні ці дані на диску, якщо і так все дублюється слейвами. Втратити дані можна лише при виході з ладу всіх слейвів, а це проблема рівня пожежі в ДЦ. Як компроміс можна запропонувати зберігати дані тільки на слейвах, але в цьому випадку потрібно переконатися, що ці слейви ніколи не стануть майстром при аварійному відновленні (для цього є налаштування пріоритету слейвів в їх конфізі). Для себе ми в кожному конкретному випадку думаємо над тим, чи потрібно зберігати дані на диск і найчастіше відповідаємо «ні».

Висновок

Наприкінці сподіватимусь, що зміг дати загальне уявлення про роботу redis-cluster-а тим, хто зовсім про нього не чув, а також звернув увагу на якісь неочевидні моменти для тих, хто вже давно ним користується.
Дякуємо за приділений час і, як завжди, коментарі на тему вітаються.

Джерело: habr.com

Додати коментар або відгук