Збої у роботі чудово вчать вас, як не допустити збоїв у роботі. Я спричинив чимало таких випадків і сподіваюся, що, поділившись ними публічно, це допоможе деяким людям оминути першу частину навчальної програми про технічні збої.
Для протоколу, я дуже люблю Redis. Якщо ви використовуєте його правильно, він працює блискуче. Наведені нижче помилки були випадками, коли я використовував його неправильно.
1. Запуск одного екземпляра
Redis виконує команди в одному потоці, а це означає, що паралелізм у вашому програмному рівні створює конфлікт, оскільки команди стоять у черзі на сервері. За звичайних умов це, ймовірно, не викличе проблем, оскільки команди Redis зазвичай виконуються дуже швидко. Але в моменти дуже високого навантаження або якщо команди виконуються повільно, ви побачите таймаути або стрибки затримок, в залежності від того, як налаштовані ваші пули з'єднань.
Якщо ви занадто наївні, як я одного разу, ви погіршите ситуацію через погано реалізовану логіку програми. Я написав базовий сеансовий кеш за допомогою GET, який повторно виконував запит до бази даних і SET для заповнення кешу в разі невдачі. Важливо, що він утримував з'єднання з Redis протягом усього часу цієї операції та не дозволяв помилкам в SET звалити всю операцію. Збільшення трафіку в поєднанні з повільним виконанням запитів у Postgres призвело до того, що ця схема ефективно виводила з ладу наш пул з'єднань Redis на кілька хвилин. Протягом цих періодів з'єднання постійно обривалися, а користувачі замість робочого застосунку дивилися на сторінку з помилкою.
Найпростіший спосіб впоратися з паралелізмом в Redis - це розділити дані на декілька екземплярів. Існує кілька способів зробити це.
Якщо ваш застосунок містить кілька функціонально відокремлених абстракцій Redis, ви можете вручну шардити дані з кожної з цих функціональних областей у власний екземпляр. Цей підхід дозволяє варіювати параметри конфігурації, наприклад, політику витіснення, для кожної функціональної області. Недоліком є те, що якщо якась одна область стає занадто важкою, ви повертаєтесь до того, з чого почали, і вам доведеться знову створювати шард.
Крім того, щоб розділити дані на декілька екземплярів, ви можете скористатися кластером Redis Cluster. Здебільшого це дозволяє забути про те, як реалізується шардинг, якщо тільки ви не використовуєте багатоключові команди, транзакції або lua-скрипти. Якщо ви використовуєте щось із цього, ви повинні переконатися, що всі ключі в команді/транзакції/скрипті розпізнаються як один шард за допомогою хеш-тегів. Хеш-тег - це просто підряддок ключа, виділений відкриваючими та закриваючими фігурними дужками.
Redis Cluster може бути недоступний у вашому робочому середовищі, наприклад, якщо ви використовуєте GCP Memorystore. У такому випадку, звичайно, ви можете розбити простір ключів вручну. Але також є кілька автоматизованих варіантів. Twemproxy і Codis - це сторонні проксі-сервери з відкритим вихідним кодом, які ви можете встановити перед своїми екземплярами Redis, щоб вони виконували шардинг за вас.
2. Розміщуйте довготривалі операції всередині скриптів/функцій
Redis підтримує скрипти Lua (до версії 7) та функції (починаючи з версії 7) для логіки, яка має виконуватися атомарно.
Вони особливо корисні, коли вам потрібно об'єднати команди умовно або в циклі.Але через однопотокову природу Redis слід звертати увагу на час виконання цих скриптів. Зокрема, цикли можуть вийти з-під контролю, якщо ви не будете достатньо уважними.
Я припустився цієї помилки, коли реалізовував кеш для графа дозволів. У нашій моделі дозволи каскадом спускаються вниз по графу, тому я створив вторинне сховище для кожного вузла у вигляді відсортованої множини, заповненої ідентифікаторами його предків. Це дозволило нам видаляти цілі підграфи за одну операцію, оскільки зміна дозволів на будь-якому вузлі означала зміну дозволів для всіх його предків. Це працювало добре протягом тривалого часу, але з поступовим додаванням нових функцій до продукту розмір підграфів збільшувався. І кожне таке збільшення мало складний ефект, оскільки збільшувало кількість подій, що роблять кеш недійсним. Врешті-решт ми дійшли до того, що окремі цикли в нашому Lua-скрипті виконували тисячі ітерацій, і ми почали помічати сплески затримок при моніторингу. У періоди особливо інтенсивного трафіку це призводило до таймаутів у нашому пулі з'єднань Redis, оскільки команди застрягали в очікуванні запланованого часу.
Тому робіть свої скрипти та функції простими, а якщо вони не можуть бути простими, подумайте, чи є Redis правильним інструментом для того, що ви намагаєтесь зробити. У моєму випадку це виявилося не так.
3. Не встановлення сповіщення на використання пам'яті
Параметр maxmemory-policy
визначає, як Redis поводитиметься, коли доступна пам'ять буде вичерпана.
Загалом, він може або відмовляти у записі, або витісняти інші дані, щоб дозволити виконати успішний запис. Якщо ви реалізуєте кеш або будь-яке інше ефемерне сховище, де втрата даних є нормальним явищем, ви можете вибрати один з варіантів allkeys-*
і не турбуватися про використання пам'яті.В іншому випадку вам доведеться вибирати між noeviction
і volatile-*
, і спроектувати свою програму так, щоб вона могла коректно обробляти невдалий запис.
Звісно, вам би не хотілося, щоб коли такі невдалі записи траплялися, це було для вас несподіванкою. Налаштуйте моніторинг, щоб сповіщати про використання пам'яті на рівні 80%, 90% і 99%. Мені подобається мати кілька рівнів сповіщень, тому що іноді всі працюють над створенням нових функцій, і ранні сповіщення можуть втратити пріоритет або забутися. Це не означає, що їх можна ігнорувати, але я визнаю реальність роботи в стартапі. Сподіваємось, що ви ніколи не побачите сповіщення про 99%, тому що у вас був шанс або збільшити пам'ять, або зменшити її використання. Але приємно знати, що воно є, про всяк випадок.
Якось я написав абстракцію debounce для системи, яка генерувала багато подій оновлення, щоб зменшити активність повторного індексування в Elasticsearch. Щоб заощадити запити до бази даних при обробці відхилених подій, я сховав агреговані дані про події в Redis разом з міткою часу відхилення. Все було добре, поки ми не додали нову функцію в програму - вікі-сторінки. Сторінки могли містити дані зображень, закодовані в base64, тому ці події виявилися набагато більшими, ніж ті, які ми відправляли раніше. Окрім того, вони стали частішими, оскільки користувачі почали вносити багато дрібних змін до своїх сторінок. Це був екземпляр Redis без оповіщення, і, що дуже прикро, я не налаштував оповіщення про використання пам'яті. Я зрозумів, що щось не так, лише коли побачив різке збільшення кількості помилок.
4. Використання неправильної абстракції
Redis API набагато багатший, ніж просто GET
, SET
і DEL
.
Тут занадто багато усього, аби описати детально, але переконайтеся, що ви розумієте відмінності між хешами, списками, множинами та відсортованими множинами. Ознайомтеся з бітовими картами та бітовими полями.
У документації добре описана продуктивність великих операцій для кожної з абстракцій. Якщо ви заздалегідь розумієте свої дані та можливі компроміси, це може заощадити багато часу та болю від неправильного використання.
Однією з поширених помилок є серіалізація об'єктів у формат JSON перед тим, як зберігати їх у Redis. Це працює як для читання, так і для запису об'єктів як атомарних одиниць, але неефективно для читання або оновлення окремих властивостей в об'єкті, тому що ви платите за розбір або серіалізацію всього об'єкта при кожній команді. Замість цього, декомпозиція ваших об'єктів на хеші дозволяє отримати доступ до окремих властивостей безпосередньо. Для великих об'єктів це може бути значним приростом у швидкості.
Іншою помилкою може бути використання списків для великих колекцій. Якщо ви використовуєте LINDEX
, LINSERT
або LSET
у великому списку, будьте обережні. Ці команди виконують O(n), і, можливо, вам краще скористатися відсортованою множиною.
Ще немає коментарів