Черги і блокування. Теорія і практика

Ми видихнули після HighLoad++ і продовжуємо публікації кращих доповідей минулих років. HighLoad++ вийшов прекрасним, кількість організаційних поліпшень стрибкоподібно переросло в нову якість продукту. Хабр, до речі, вів текстову трансляцію конференції (перший, другий дні).

Олександр Календарев

Олександр Календарев ( akalend
Доброго дня, шановні колеги! Моя доповідь буде про річ, без якої не обходиться жоден HighLoad-проект — про сервера черг, і якщо встигну, то розповім про блокування (примітка расшифровщика — встиг :).



Про що буде доповідь? Я розповім про те, де і як використовуються черги, навіщо це все потрібно, розповім трохи про протоколи.



Оскільки наша конференція називає HighLoad Junior, я хотів би піти від Junior-проекту. Є у нас типовий Junior-проект — це якась веб-сторінка, яка звертається до бази. Може бути, це електронний магазин чи ще щось там. І ось, до нас пішли-пішли користувачі, і на якомусь етапі ми отримали помилочку (може бути й інша помилка):



Ми полізли в Інтернет, стали досліджувати, як можна масштабуватися, вирішили дістати бэкендов.



Пішли користувачі, і користувачі, і у нас з'явилася ще одна помилочка:



Тоді ми полізли в логи, подивилися, як можна отмасштабировать SQL-сервер. Знайшли і зробили реплікацію.



Але тут у нас в MySQL полізли помилки:



Ну, помилки можуть бути і у більш простих конфігураціях, це я тут образно показав.

І в цей момент ми починаємо замислюватися про нашій архітектурі.



Ми нашу архітектуру розглядаємо «під мікроскопом» і виділяємо дві речі:



Перша — якісь критичні елементи нашої логіки, які потрібно зробити; і друга — якісь повільні і непотрібні речі, які можна відкласти на потім. І цю архітектуру ми намагаємося розділити:



Ми розділили її на дві частини.

Одну частину ми намагаємося покласти на один сервер, а іншу — на інший. Такий патерн я називаю «хитрий учень».



Сам колись цим «страждав»: говорив батькам, що я вже зробив уроки і втік гуляти, а на наступний день ці уроки читав перед уроками і розповідав вчителям за дві хвилини.

Є більш наукова назва цього патерну:



І в результаті ми приходимо до такої архітектурі, де є веб-сервер і бекенд-сервер:



Між собою їх треба якось пов'язати. Коли це два сервера, це робиться простіше. Коли їх кілька, це трохи складніше. І ми замислюємося, як їх пов'язати? І одне з рішень спілкування між цими серверами — це черга.



Що ж таке черга? Чергу — це список.



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

Переходимо до другої частини   де і як вона використовується? Коли я працював в такому проекті:



Цей аналог Яндекс.Маркету. У цьому проекті крутиться дуже багато різних сервісів. І ці сервіси як-то повинні були синхронізувати. Синхронізувалися вони через базу даних.



Як влаштована чергу на базі даних?



Є якийсь лічильник — в MySQL це автоинкремент, в Postgress це через сіккенс реалізується; є якісь дані.

Записуємо дані:



Читаємо дані:



Видаляємо з черги, але для повного щастя потрібні lock'в.



Добре це чи погано?

Це повільно. Мій даний патерн для цього не існує, але в деяких випадках чергу через базу даних багато роблять.



По-перше, через базу даних можна зберігати історію. Тоді додається полі deleted, воно може бути як прапором, так і timestamp писати. Мої колеги через чергу роблять спілкування, яке через партнерську мережу йде, вони банери записують — скільки було кліків, потім у них аналітика на Вертике знаходить кластери, які групи користувачів на які банери більше відгукуються.

Ми ж для цього використовуємо MongoDb.



В принципі, все те ж саме, тільки використовується якась колекція. Пишемо в цю колекцію, читаємо з цієї колекції. З видаленням — прочитали елемент, він автоматично пішов.



Це також повільно, але все-таки швидше, ніж DB. Для наших потреб, ми це в статистиці використовуємо, це нормально.

Далі, я працював в такому проекті, це соціальна іграшка була — «однорукий бандит»:



Всі знають, натискаєш кнопочку, крутяться у нас барабанчики, випадають збігу. Іграшка була реалізована на PHP, але мене попросили її отрефактурить, тому що наша база даних не справлялася.



Я для цього використовував Tarantool. Tarantool в двох словах — це key value сховище, воно зараз вже ближче до документно-орієнтованих баз даних. Був реалізований у мене на Tarantool оперативний кеш, це трохи допомогло. Вирішили все це організувати через чергу. Все прекрасно працювало, але одного разу у нас це все впало. Впав бекенд-сервер. І в Tarantool почали накопичуватися черги, накопичуватися дані, і пам'ять переповнилася, тому що це Only Memory сховище.



Пам'ять переповнилася, все впало, дані користувачів за півдня загубилися. Користувачі трошки незадоволені, десь чогось грали, програли, виграли. Хто програв, тому добре, хто виграв   того гірше. Висновок який? Потрібно робити моніторинг.

Що тут треба моніторити? Довжину черги. Якщо ми бачимо, що вона перевищує середню довжину раз в 5 або 10, 20 разів, то повинні слати SMS — у нас такий сервіс зроблений на Telegram'е. Telegram безкоштовний, SMS все-таки коштує грошей.

Що ще нам дає Tarantool? Tarantool — гарне рішення, там є шардінг з коробки, реплікація з коробки.



Ще в Tarantool є чудовий пакет з Queue.



Те, що я реалізовував — це було ще 4-5 років тому, тоді такого пакета ще не було. Зараз з'явився дуже хороший API у Tarantool, якщо хто користується Python, у них API, взагалі заточений під черги. Я сам на PHP з 2002-го року, 15 років вже як. Розробляв модуль під Tarantool на PHP, тому PHP мені трішки ближче.

Тут є дві операції: запис у чергу і читання з черги. Хочу звернути увагу на цю циферку (0,1 синім на слайді) — це у нас timeout. І, взагалі, при підході до написання бэкендовских скриптів, які розбирають чергу, є два підходи: синхронний і асинхронний.



Що таке синхронний підхід? Це коли ми читаємо з черги і, якщо є дані, то ми їх обробляємо, якщо даних немає, то ми встаємо в блокировочку і чекаємо, поки дані прийдуть. Дані прийшли, ми поїхали читати далі.



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



Які плюшки даємо нам пакет Queue? Там є черги з пріоритетами, такого я більше не зустрічав ніде серед інших серверів черг. Там ще можна задати життя елемента черги — іноді це дуже корисно. Підтвердження доставки — і це все є. Про синхронності та асинхронності я говорив.



Redis. Це у нас зоопарк, де є багато різних структур даних. Чергу реалізується на списках. Чим хороші списки? Вони хороші тим, що час доступу до першого елементу списку, або до останнього відбувається за постійний час. Як реалізується чергу? З початку списку пишемо, з кінця списку читаємо. Можна робити навпаки, це не принципово.



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



Все було прекрасно, але одного разу нам сказали зверху: «Давайте використовувати статистику, наші партнери хочуть знати, скільки у нас куплено юнітів, які найбільш купуються юніти, скільки у нас пішло і з якого рівня користувачів і т. п.». І запропонували нам не винаходити велосипед і використовувати зовнішні скрипти статистики. І все було чудово.



Тільки мій скрипт відпрацьовував 50 мс, а коли зверталися до зовнішнього скрипту, там була якась Америка, це 250 мс мінімум, а то і 2 з гаком секунди ping туди йшов. Відповідно, вся іграшка зависла.

Ми застосували таку схему:



І все у нас було добре, все працювало швидко. Але одного разу наш адмін пішов у відпустку. Адмін пішов у відпустку, все було добре першу тиждень, а через тиждень ми дізналися, що Redis тече. Redis тече, адміна немає, ми приходимо, дивимося з ранку на консоль, дивимося, скільки залишилося пам'яті, скільки до swap'а ще залишилося, зітхали: «Ох, як добре, що пронесло на сьогодні». У п'ятницю до нас прийшло багато користувачів, особливо після обіду, пам'яті не вистачило.



Висновки ті ж, що і з Tarantool. Інший проект, ті ж помилки. Пам'ять треба моніторити. Довжину черги треба моніторити. Про моніторинг багато всі говорять, повторюватися не буду.



У Redis також можна виконувати як блокуючі, так і неблокирующие операції читання; операція Count потрібна, як раз, саме для моніторингу.

Як-то ще я віддалено працював у проекті завантаження відео з популярних відеохостингів:



В чому проблема цього проекту? В тому, що якщо ми завантажуємо відео, то ми конвертуємо його, якщо відео трохи довше, веб-клієнт просто відвалюється. Застосували таку схему:



Все у нас добре. Закачали файл. Але черга працює у нас тільки в одну сторону, а ми повинні поінформувати наш веб-скрипт — файл-то вже закачаний.



Як це робиться? Це робиться двома способами.









Перший — ми через якийсь певний timeout перевіряємо статус. Що може бути статусом? Це keyvalue сховище — краще вже взяти той же Redis. Key може послужити якийсь MD5 хеш від а ДО нашого. І після того, як ми сконвертировали, ми keyvalue пишемо статус. Статус може бути: «виконано», «конвертується», «не знайдено» або ще чого-небудь. Через секунду, через якийсь timeut, скрипт запропонує статус, побачить, що виконано або не виконано, покаже всі клієнту. Все зрозуміло. Це перший спосіб, ми використовували пулинг.

Другий — веб-сокети.









Ми завантажуємо файл — це другий спосіб. Це підписки. Тут, як раз, використовували веб-сокети.

Як це робиться? Як тільки ми почали загрузочку, ми відразу підписуємося на канал в Redis. Якщо там можна було, припустимо, memcaсhed використовувати або ще що-небудь, якщо ми Redis не використовували, то тут до Redis прив'язане. Підписуємося на якийсь канал, ім'я каналу. Грубо кажучи, той же MD5 хеш від ДО а.

Як тільки ми завантажили файл, ми беремо і пушим в канал, що у нас статус «виконано» або статус «не знайдено». І відразу ж миттєво у нас Push віддає статус на веб-скрипт. Після цього завантажуємо файл, якщо він знайдений.



Не зовсім прямо, приблизно така схема.



Як це робиться? Є якийсь джерело даних — температура вулкана, кількість зірок, видимих в телескопи, спрямовані НАСА, кількість угод за конкретними акціями… Ми приймаємо ці дані, і наш скрипт бэкграундовский, який прийняв ці дані, пушит їх в якийсь канал. Наш веб-скрипт через веб-сокет, зазвичай використовуються ноди JS, підписується на певний канал, як тільки там дані виходять, він через веб-сокет ці дані передає на клієнтський скрипт, і вони там відображаються.



Є таке рішення   MamecachedQ. Це досить старе рішення, я б сказав, одне з перших. Воно було породжене використанням Mamecached і BerkeleyDb, це вбудовується, одне з ранніх, keyvalue сховище.

Чим пам'ятки це рішення? Тим, що використовується протокол Mamecached.



У чому великий мінус — ми тут ніяк не отмониторим довжину черги. Про що я говорив — моніторинг потрібен, а тут цього моніторингу немає.

Говорячи про чергах, не можна не сказати про Zerro MQ.



Zerro MQ — гарне і швидке рішення, але це не брокер черг, це треба розуміти. Це просто API, тобто ми з'єднуємо одну точку з іншою точкою. Або одну точку з безліччю точок. Але тут немає ніяких черг, якщо одна з точок пропаде, то якісь дані загубляться. Я, звичайно, можу на тому ж Zerro MQ написати того ж брокера та його реалізувати…



Apache Kafka. Якось я намагався це рішення використовувати. Це рішення з стека hadoop. Воно, в принципі, добре, високопродуктивне рішення, але воно потрібно там, де є великий потік даних і потрібно його обробити. А так, я б більш легкі рішення використовував.



Його потрібно ще дуже довго налаштовувати, синхронізувати через Zookepek і т. д.

Протоколи. Що таке протоколи?



Я вам показав купу всяких рішень. Спільнота IT подумав і сказав: «Чого ми всі винаходимо велосипеди, давайте ми все це справа застандартизируем». І придумало протоколи. Один з найбільш ранніх протоколів — це STOMP.



Його опис покриває все, що можна робити з чергами.

Другий протокол, MQTT — це Message Queue Telemetry Transport протокол.



Він бінарний, на відміну від STOMP, покриває, в принципі, всю ту ж функціональність, що і STOMP, але більш швидко за рахунок того, що бінарний.

Ось найбільш яскраві представники, брокера черг, які працюють з протоколами:



ActiveMQ всі три протоколу використовує, навіть чотири (є ще один). RabbitMQ використовує три протоколу; Qpid використовує Q і P.

Тепер коротко про AMQP — Advanced Message Queuing Protocol.



Якщо про нього довго розповідати, то можна години півтори, не менше, говорити про його особливості. Я коротко. Ми брокер уявімо як якийсь ідеальний поштовий сервіс. Exchange — це буде поштову скриньку відправника, куди приходить повідомлення.



Цей Exchange має тип, властивості.



Тут на PHP написано, як його оголошувати. До речі, цей драйвер теж я розробляв.



Раз є ящик відправника, у нас повинен бути скриньку одержувача. Ящик одержувача має таку особливість, що ми за одне звернення можемо взяти тільки один лист. Ящик одержувача також має ім'я, властивість. Приблизно так його треба оголошувати:



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



Цей маршрут визначається ключем маршрутизації.



Коли ми оголошуємо зв'язок, то ми обов'язково вказуємо ключ маршрутизації. Це один із шляхів оголошення зв'язку.

Є другий підхід. Ми можемо оголосити Exchange і з нього зробити Bind на чергу, тобто це навпаки — ми можемо з черги зробити зв'язок Exchange або з Exchange чергу, це без різниці.


Є у нас повідомлення. У повідомленні має бути обов'язково зазначений routingKey, тобто це той ключ, за яким маршрутом побіжить наш листоноша.



Наші листоноші можуть бути трьох типів:



  1. Перший тип — це сліпі листоноші. Вони не можуть прочитати ключ, вони біжать тільки по тому маршруту, який ми їм проклали. Ці листоноші біжать, і це
    найшвидші листоноші.
  2. Листоноші другого типу можуть трохи читати, вони звіряють буковки, тільки не знають, як чого… Звірили буковки, що збігається ключ нашого повідомлення та
    відповідної стежки, по якій бігти, і біжать по тій стежці. Тобто наш одержувач йде чисто по ключу збігу.

  3. третій вид листонош — це Topic. Ми можемо задати маску, маска така ж, як у файловій системі, і по цій масці почтальончики наші відносять листи.
Приблизно так виглядає, як надсилаються повідомлення:



Які у нас типові помилочки бувають?



Типові помилочки бувають в тому, що люди часто забувають визначати зв'язок. Зараз третій Rabbit — він більш-менш пристойний, у нього є веб-інтерфейс, можна через веб-інтерфейс все подивитися: що там оголошено, які черги, який у них тип, які там exchange, які у них типи.

Друга типова помилка. Коли ми оголошуємо чергу або exchange, вони у нас за замовчуванням autodelete — закінчилась сесія, чергу вбилася. Тому її потрібно кожен раз переобъявлять. В принципі, це небажано робити, а краще зробити постійну чергу і ще призначити durable. Durable — це такий ознака, що якщо у нас чергу durable, то після перезавантаження RabbitMQ у нас ця черга буде жити.



Що можна сказати про RabbitMQ? Він не дуже приємний в адмініструванні, зате його можна розширити, якщо ми знаємо Erlang. Він дуже вимогливий по пам'яті. RabbitMQ працює через эрланговское вбудовується рішення, але воно дуже багато пам'яті їсть. Є деякі плагіни, які працюють з іншими сховищами, але я, чесно кажучи, з ними не працював.



Ось в такому журналі «Системний адміністратор» я написав статтю «Кролик в пісочниці» — там, в принципі, те ж саме, що я вам тут розповів.



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

Блокировочки



Розповідав я про такий проект в самому початку. Якби я робив цей проект сьогодні, то використовував би микросервисы.



А микросервисы вимагають синхронізації як взаємодії. В якості синхронізації у нас використовується такий інструмент, як Apache Zookeeper.



В основу філософії Apache Zookeeper лежить znode. Znode за аналогією з елементом файлової системи має певний шлях. І є у нас операція створення ноди, створення дітей ноди, отримання дітей, отримання даних і запис чогось дані.

Znode бувають двох типів: прості та ефемерні.



Ефемерні — це такі znode, які, якщо у нас спеціальна сесія померла, то znode уничтожилась, autodelete.

Послідовності — це автоинкрементные znode, тобто це znode, які мають якесь ім'я і числовий префікс автоінкрементний.



На прикладі конфігурації на льоту розповім, як приблизно це все працює. Є у нас дві групи процесів — група процесів a і група b процесів. Процеси 1 конектяться до процесів 2 і як-то взаємодіють. Процеси 2, коли запускаються, пишуть свою конфігурацію в Zookeeper.



Кожен процес створює свою znode — перший процес, другий, третій.

І ось ми один з процесів зупинили або, наприклад, запустили. У мене тут на прикладі зупинки процесів показано:



У нас процес зупинено, з'єднання порвалося, znode у нас пішла, надсилається event, що ми слухали цю znode, що в ній одна znode пропала.









Надсилається event, ми перераховуємо конфігурацію. Все дуже гарно працює.



Приблизно так все це синхронізується. Є інші приклади, як з бэкапами там хтось синхронізував.



На цьому підсумковому слайді я хотів би продемонструвати всі можливості серверів черг. Де у нас знаки питань — це спірний момент, або просто не було даних. Наприклад, база даних у нас масштабується, правильно? Незрозуміло, але, в принципі, масштабується. Але можна масштабувати черги на них чи ні? В принципі, немає. Тому у мене тут питання. За ActiveMQ у мене просто немає даних. З Redis можу пояснити — ACL є, але він не зовсім правильний. Можна сказати, що його немає. Масштабується Redis? Через клієнт масштабується, таких якихось елементів, коробкових рішень, я не бачив.



Ось такі висновки:

  1. Треба кожен інструмент використовувати за призначенням. Я багато спілкувався з різними розробниками, RabbitMQ зараз використовує тільки неледачий, але в
    більшості випадків той же RabbitMQ можна замінити Redis'ом.

  2. Швидкість, помножена на надійність. Що я хотів цим сказати? Чим швидше працює інструмент, тим у неї менша надійність. Але з іншого боку,
    величина цієї константи може змінюватися — це моє особисте спостереження.

  3. Ну і про моніторинг тут багато говорили і до мене професіонали.
Контакти
» akalend
» akalend@mail.ru

Ця доповідь — розшифровка одного з кращих виступів на навчальній конференції розробників високонавантажених систем HighLoad++ Junior.

Також деякі з цих матеріалів використовуються нами в навчальному онлайн-курс по розробці високонавантажених систем HighLoad.Guide — це ланцюжок спеціально підібраних листів, статей, матеріалів, відео. Вже зараз у нашому підручнику понад 30 унікальних матеріалів. Підключайтеся!

Ну і головна новина — ми почали підготовку весняного фестивалю "Російські інтернет-технології", в який входить вісім конференцій, включаючи HighLoad++ Junior. Ми, звичайно, жадібні коммерсы, але зараз продаємо квитки за собівартістю — можна встигнути до підвищення цін :)

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.