- сервери додатків
- балансування навантаження
- ресурсомісткі обчислення
- сесії
- статичний контент
- кешування
- Бази даних
- вместо Висновки
Уже чимало слів було сказано на цю тему як в моєму блозі, так і за його межами. Мені здається настав підходящий момент для того, щоб перейти від часткового до загального і спробувати поглянути на дану тему окремо від будь-якої успішної її реалізації.
Приступимо?
Для початку має сенс визначитися з тим, про що ми взагалі будемо говорити. В даному контексті перед веб-додатком ставляться три основні мети:
- масштабованість - здатність своєчасно реагувати на безперервне зростання навантаження і непередбачені напливи користувачів;
- доступність - надання доступу до додатка навіть в разі надзвичайних обставин;
- продуктивність - навіть найменша затримка в завантаженні сторінки може залишити негативне враження у користувача.
Основною темою розмови буде, як не важко здогадатися, масштабованість, а й інші цілі не думаю, що залишаться в стороні. Відразу хочеться сказати пару слів про доступність, щоб не повертатися до цього пізніше, маючи на увазі як "само собою зрозуміло": будь-який сайт так чи інакше прагне до того, щоб функціонувати максимально стабільно, тобто бути доступним абсолютно всім своїм потенційним відвідувачам в абсолютно кожен момент часу, але часом трапляються всякі непередбачені ситуації, які можуть стати причиною тимчасової недоступності. Для мінімізації потенційних збитків доступність додатку необхідно уникати наявності компонентів в системі, потенційний збій в яких привів би до недоступності будь-якої функціональності або даних (або хоча б сайту в цілому). Таким чином кожен сервер або будь-який інший компонент системи повинен мати хоча б одного дублера (не важливо в якому режимі вони працюватимуть: паралельно або один "підстраховує" інший, перебуваючи при цьому в пасивному режимі), а дані повинні бути реплікуються як мінімум в двох примірниках (причому бажано не на рівні RAID, а на різних фізичних машинах). Зберігання декількох резервних копій даних десь окремо від основної системи (наприклад на спеціальних сервісах або на окремому кластері) також допоможе уникнути багатьох проблем, якщо щось піде не так. Не варто забувати і про фінансову сторону питання: підстраховка на випадок збоїв вимагає додаткових істотних вкладень в устаткування, які має сенс намагатися мінімізувати.
Масштабованість прийнято розділяти на два напрямки:
Вертикальна масштабованістьЗбільшення продуктивності кожного компонента системи c метою підвищення загальної продуктивності. Горизонтальна масштабованість Розбиття системи на більш дрібні структурні компоненти та рознесення їх по окремим фізичним машинам (або їх групам) і / або збільшення кількості серверів паралельно виконують одну і ту ж функцію.
Так чи інакше, при розробці стратегії зростання системи доводиться шукати компроміс між ціною, часом розробки, підсумкової продуктивність, стабільністю і ще масою інших критеріїв. З фінансової точки зору вертикальна масштабованість є далеко не найпривабливішим рішенням, адже ціни на сервера з великою кількістю процесорів завжди ростуть практично експоненціально щодо кількості процесорів. Саме по-цьому найбільш цікавий горизонтальний підхід, так як саме він використовується в більшості випадків. Але і вертикальна масштабованість часом має право на існування, особливо в ситуаціях, коли основну роль відіграє час і швидкість виконання завдання, а не фінансове питання: адже купити ВЕЛИКИЙ сервер істотно швидше, ніж практично заново розробляти програми, адаптуючи його до роботи на великій кількості паралельно працюючих серверів.
Закінчивши із загальними словами давайте перейдемо до огляду потенційних проблем і варіантів їх рішень при горизонтальному масштабуванні. Прохання особливо не критикувати - на абсолютну правильність і достовірність не претендую, просто "думки вголос", та й навіть згадати всі моменти даної теми у мене точно не вийде.
сервери додатків
В процесі масштабування самих додатків рідко виникають проблеми, якщо при розробці завжди мати на увазі, що кожен екземпляр додатка повинен бути безпосередньо ніяк не пов'язаний зі своїми "колегами" і повинен мати можливість обробити абсолютно будь-який запит користувача незалежно від того де оброблялися попередні запити даного користувача і що конкретно він хоче від програми в цілому в поточний момен.
Далі, забезпечивши незалежність кожного окремого запущеного додатку, можна обробляти всі більша і більша кількість запитів в одиницю часу просто збільшуючи кількість паралельно функціонуючих серверів додатків, що беруть участь в системі. Все досить просто (відносно).
балансування навантаження
Наступна задача - рівномірно розподілити запити між доступними серверами додатків. Існує маса підходів до вирішення цього завдання і ще більше продуктів, що пропонують їх конкретну реалізацію.
Устаткування
Мережеве обладнання, що дозволяє розподіляти навантаження між декількома серверами, зазвичай коштує досить значні суми, але серед інших варіантів зазвичай саме цей підхід пропонує найвищу продуктивність і стабільність (в основному завдяки якості, плюс таке обладнання інколи постачається парами, які працюють за принципом HeartBeat ). У цій індустрії досить багато серйозних брендів, що пропонують свої рішення - є з чого вибрати: Cisco, Foundry, NetScalar і багато інших. Програмне забезпечення У цій області ще більшу різноманітність можливих варіантів. Отримати програмно продуктивність яку можна порівняти з апаратними рішеннями не так-то просто, та й HeartBeat доведеться забезпечувати програмно, але зате обладнання для функціонування такого рішення є звичайним сервер (можливо не один). Таких програмних продуктів досить багато, зазвичай вони представляють собою просто HTTP-сервери, що перенаправляють запити своїм колегам на інших серверах замість відправки безпосередньо на обробку інтерпретатору мови програмування. Для прикладу можна згадати, скажімо, nginx з mod_proxy. Крім цього мають місце більш екзотичні варіанти, засновані на DNS, тобто в процесі визначення клієнтом IP-адреси сервера з необхідним йому інтернет-ресурсів адреса видається з урахуванням навантаження на доступні сервера, а також деяких географічних міркувань.
Кожен варіант має свої асортименти позитивних і негативних сторін, саме по-цьому однозначного вирішення цього завдання не існує - кожен варіант гарний у своїй конкретній ситуації. Не варто забувати, що ніхто не обмежує Вас у використанні лише одного з них, при необхідності може запросто бути реалізована і практично довільна комбінація з них.
ресурсомісткі обчислення
У багатьох додатках використовуються будь-які складні механізми, це може бути конвертування відео, зображень, звуку, або просто виконання будь-яких ресурсномістких обчислень. Такі завдання вимагає окремої уваги якщо ми говоримо про Мережу, так як користувач інтернет-ресурсу врядли буде щасливий спостерігати за завантажується декілька хвилин сторінкою в очікуванні лише для того, щоб побачити повідомлення на кшталт: "Операція завершена успішно!".
Для уникнення подібних ситуацій варто постаратися мінімізувати виконання ресурсномістких операцій синхронно з генерацією інтернет сторінок. Якщо якась конкретна операція не впливає на нову сторінку, що відправляється користувачеві, то можна просто організувати чергу завдань, які необхідно виконати. В такому випадку в момент коли користувач здійснив всі дії, необхідні для початку операції, сервер додатків просто додає нове завдання в чергу і відразу починає генерувати наступну сторінку, не чекаючи результатів. Якщо завдання насправді дуже трудомістка, то така черга і обробники завдань можуть розташовуватися на окремому сервері або кластері.
Якщо результат виконання операції задіяний в наступній сторінці, яка відправляється користувачу, то при асинхронному її виконанні доведеться кілька схитрувати і будь-яким чином відвернути користувача на час її виконання. Наприклад, якщо мова йде про конвертування відео в flv, то наприклад можна швидко згенерувати скріншот з першим кадром в процесі складання сторінки і підставити його на місце відео, а можливість перегляду динамічно додати на сторінку вже після, коли конвертування буде завершено.
Ще один непоганий метод обробки таких ситуацій полягає просто в тому, щоб попросити користувача "зайти пізніше". Наприклад, якщо сервіс генерує скріншоти веб-сайтів з різних браузерів з метою продемонструвати правильність їх відображення власникам або просто цікавиться, то генерація сторінки з ними може займати навіть не секунди, а хвилини. Найбільш зручним для користувача в такій ситуації буде пропозиція відвідати сторінку за вказаною адресою через стільки-то хвилин, а не чекати біля моря погоди невизначений термін.
сесії
Практично всі веб-додатки якимось чином взаємодіють зі своїми відвідувачами і в переважній більшості випадків у них є присутнім необхідність відстежувати переміщення користувачів по сторінках сайту. Для вирішення цього завдання зазвичай використовується механізм сесій, який полягає в привласненні кожному відвідувачу унікального ідентифікаційного номера, який йому передається для зберігання в cookies або, в разі їх відсутності, для постійного "тягання" за собою через GET. Отримавши від користувача деякий ID разом з черговим HTTP-запитом сервер може подивитися в список вже виданих номерів і однозначно визначити хто його відправив. З кожним ID може асоціюватися якийсь набір даних, який веб-додаток може використовувати на свій розсуд, ці дані зазвичай за замовчуванням зберігаються в файлі в тимчасовій директорії на сервері.
Здавалося б все просто, але ... але запити відвідувачів одного і того ж сайту можуть обробляти відразу кілька серверів, як же тоді визначити чи не був виданий отриманий ID на іншому сервері і де взагалі зберігаються його дані?
Найбільш поширеними рішеннями є централізація чи децентралізація сесійних даних. Кілька абсурдна фраза, але, сподіваюся, пара прикладів зможе прояснити ситуацію:
Централізоване зберігання сесій
Ідея проста: створити для всіх серверів загальну "скарбничку", куди вони зможуть складати видані ними сесії і дізнаватися про сесії відвідувачів інших серверів. У ролі такої "скарбнички" теоретично може виступати і просто примонтировать по мережі файлова система, але з деяких причин більш перспективним виглядає використання будь-якої СУБД, так як це позбавляє від маси проблем, пов'язаних зі зберіганням сесійних даних в файлах. Але в варіанті із загальною базою даних не варто забувати, що навантаження на нього буде неухильно зростати зі зростанням кількості відвідувачів, а також варто заздалегідь передбачити варіанти виходу з проблематичних ситуацій, пов'язаних з потенційними збоями в роботі сервера з цієї СУБД. Децентралізоване зберігання сесій Наочний приклад - зберігання сесій в memcached , Спочатку розрахована на розподілене зберігання даних в оперативній пам'яті система дозволить отримувати всім серверам швидкий доступ до будь-яких сесійним даними, але при цьому (на відміну від попереднього способу) будь-якої єдиний центр їх зберігання буде відсутній. Це дозволить уникнути вузьких місць з точок зору продуктивності і стабільності в періоди підвищених навантажень.
В якості альтернативи сесіям іноді використовують схожі за призначенням механізми, побудовані на cookies, тобто всі необхідні додатком дані про користувача зберігаються на клієнтській стороні (ймовірно в зашифрованому вигляді) і запитуються в міру необхідності. Але крім очевидних переваг, пов'язаних з відсутністю необхідності зберігати зайві дані на сервері, виникає ряд проблем з безпекою. Дані, що зберігаються на стороні клієнта навіть в зашифрованому вигляді, являють собою потенційну загрозу для функціонування багатьох додатків, так як будь-який бажаючий може спробувати модифікувати їх в своїх інтересах або з метою нашкодити додатку. Такий підхід хороший тільки якщо є впевненість, що абсолютно будь-які маніпуляції з збережені у користувачів даними безпечні. Але чи можна бути впевненими на 100%?
статичний контент
Поки що обсяги статичних даних невеликі - ніхто не заважає зберігати їх в локальній файловій системі і надавати доступ до них просто через окремий легкий веб-сервер на кшталт lighttpd (Я маю на увазі в основному різні форми медіа-даних), але рано чи пізно ліміт сервера по дисковому простору або файлової системи за кількістю файлів в одній директорії буде досягнутий, і доведеться думати про перерозподіл контенту. Тимчасовим рішенням може стати розподіл даних по їх типу на різні сервера, або, можливо, використання ієрархічної структури каталогів.
Якщо статичний контент відіграє одну з основних ролей в роботі додатка, то варто задуматися про застосування розподіленої файлової системи для його зберігання. Це, мабуть, один з небагатьох способів горизонтально масштабувати обсяг дискового простору шляхом додавання додаткових серверів без будь-яких кардинальних змін в роботі самого додатка. На який саме кластерної файлової системи зупинити свій вибір нічого зараз радити не хочу, я вже опублікував далеко не один огляд конкретних реалізацій - спробуйте прочитати їх всі і порівняти, якщо цього мало - вся інша Мережа у Вашому розпорядженні.
Можливо такий варіант з яких-небудь причин буде не реалізуємо, тоді доведеться "винаходити велосипед" для реалізації на рівні додатку принципів схожих з сегментированием даних щодо СУБД, про які я ще згадаю далі. Цей варіант також цілком ефективний, але вимагає модифікації логіки додатка, а значить і виконання додаткової роботи розробниками.
Альтернативою цим підходам виступає використання так званих Content Delievery Network - зовнішніх сервісів, що забезпечують доступність Вашого контенту користувачам за певну матеріальну винагороду сервісу. Перевага очевидно - немає необхідності організовувати власну інфраструктуру для вирішення цього завдання, але зате з'являється інша додаткова стаття витрат. Список таких сервісів наводити не буду, якщо кому-небудь знадобиться - знайти буде неважко.
кешування
Кешування має сенс проводити на всіх етапах обробки даних, але в різних типах додатків найбільш ефективними є лише деякі методи кешування.
СУБД
Практично всі сучасні СУБД забезпечують вбудовані механізми для кешування результатів певних запитів. Цей метод досить ефективний, якщо Ваша система регулярно робить одні й ті ж вибірки даних, але також має ряд недоліків, основними з яких є інвалідація кеша всієї таблиці при найменшому її зміні, а також локальне розташування кешу, що неефективно при наявності декількох серверів в системі зберігання даних. Додаток На рівні додатків зазвичай проводиться кешування об'єктів будь-якої мови програмування. Цей метод дозволяє зовсім уникнути значної частини запитів до СУБД, сильно знижуючи навантаження на неї. Як і самі додатки такий кеш повинен бути незалежний від конкретного запиту і сервера, на якому він виконується, тобто бути доступним всім серверам додатків одночасно, а ще краще - бути розподіленим по декільком машинам для більш ефективної утилізації оперативної пам'яті. Лідером в цьому аспекті кешування по праву можна назвати memcached , Про який я свого часу вже встиг детально розповісти . HTTP-сервер Багато веб-сервери мають модулі для кешування як статичного контенту, так і результатів роботи скриптів. Якщо сторінка рідко оновлюється, то використання цього методу дозволяє без будь-яких видимих для користувача змін уникати генерації сторінки у відповідь на досить велику частину запитів. Reverse proxy Поставивши між користувачем і веб-сервером прозорий проксі-сервер, можна видавати користувачу дані з кешу проксі (який може бути як в оперативній пам'яті, так і дисковим), не доводячи запити навіть до HTTP-серверів. У більшості випадків цей підхід актуальний тільки для статичного контенту, в основному різних форм медіа-даних: зображень, відео тощо. Це дозволяє веб-серверам зосередитися тільки на роботі з самими сторінками.
Кешування за своєю суттю практично не вимагає додаткових витрат на обладнання, особливо якщо уважно спостерігати за використанням оперативної пам'яті іншими компонентами серверами і утилізувати всі доступні "надлишки" під найбільш підходящі певної програми форми кеша.
Інвалідація кеша в деяких випадках може стати нетривіальним завданням, але так чи інакше універсального рішення всіх можливих проблем з нею пов'язаних написати неможливо (принаймні особисто мені), так що залишимо це питання до кращих часів. У загальному випадку рішення цього завдання лягає на саме веб-додаток, який зазвичай реалізує якийсь механізм інвалідаціі засобами видалення об'єкта кешу через певний період часу з моменту створення або останнього використання, або "вручну" при виникненні певних подій з боку користувача або інших компонентів системи.
Бази даних
На закуску я залишив найцікавіше, адже цей невід'ємний компонент будь-якого веб-додатки викликає більше проблем при зростанні навантажень, ніж всі інші разом узяті. Часом навіть може здатися, що варто взагалі відмовитися від горизонтального масштабування системи зберігання даних на користь вертикального - просто купити той самий ВЕЛИКИЙ сервер за шести- або семизначну суму не-рублів і не забивати собі голову зайвими проблемами.
Але для багатьох проектів таке кардинальне рішення (і то, за великим рахунком, тимчасове) не підходить, а значить перед ними залишилася лише одна дорога - горизонтальне масштабування. Про неї і поговоримо.
Шлях практично будь-якого веб проекту з точки зору баз даних починався з одного простого сервера, на якому працював весь проект цілком. Потім в один прекрасний момент настає необхідність винести СУБД на окремий сервер, але і він з часом починає не справлятися з навантаженням. Докладно зупинятися на цих двох етапах сенсу особливого немає - все відносно тривіально.
Наступним кроком зазвичай буває master-slave з асинхронної репликацией даних, як працює ця схема вже неодноразово згадувалося в блозі, але, мабуть, повторюся: при такому підході всі операції запису виконуються лише на одному сервері (master), а інші сервера (slave) отримують дані безпосередньо від "майстра", обробляючи при цьому лише запити на читання даних. Як відомо, операції читання і запису будь-якого веб-проекту завжди ростуть пропорційно зростанню навантаження, при цьому зберігається майже фіксованим співвідношення між обома типами запитів: на кожен запит на оновлення даних зазвичай доводиться в середньому близько десятка запитів на читання. Згодом навантаження зростає, а значить зростає і кількість операцій запису в одиницю часу, а сервер-то обробляє їх всього один, а потім він же ще й забезпечує створення деякої кількості копій на інших серверах. Рано чи пізно витрати операцій реплікації даних стануть бути настільки високі, що цей процес стане займати дуже велику частину процесорного часу кожного сервера, а кожен slave зможе обробляти лише порівняно невелика кількість операцій читання, і, як наслідок, кожен додатковий slave-сервер почне збільшувати сумарну продуктивність лише незначно, теж займаючись здебільшого лише підтриманням своїх даних відповідно до "майстром".
Тимчасовим вирішенням цієї проблеми, можливо, може стати заміна master-сервера на більш продуктивний, але так чи інакше не вийде нескінченно відкладати перехід на наступний "рівень" розвитку системи зберігання даних: "sharding", якому я зовсім недавно присвятив окремий пост "Сегментування баз даних" . Так що дозволю собі зупинитися на ньому лише коротко: ідея полягає в тому, щоб розділити всі дані на частини по будь-якою ознакою і зберігати кожну частину на окремому сервері або кластері, таку частину даних в сукупності з системою зберігання даних, в якій вона знаходиться , і називають сегментом або shard 'ом. Такий підхід дозволяє уникнути витрат, пов'язаних з реплицирования даних (або скоротити їх у багато разів), а значить і істотно збільшити загальну продуктивність системи зберігання даних. Але, на жаль, перехід до цієї схеми організації даних вимагає масу витрат іншого роду. Так як готового рішення для її реалізації не існує, доводиться модифікувати логіку додатку або додавати додаткову "прошарок" між додатком і СУБД, причому все це найчастіше реалізується силами розробників проекту. Готові продукти здатні лише полегшити їх роботу, надавши якийсь каркас для побудови основної архітектури системи зберігання даних і її взаємодії з іншими компонентами програми.
На цьому етапі ланцюжок зазвичай закінчується, так як сегментовані бази даних можуть горизонтально масштабироваться для того, щоб повною мірою задовольнити потреби навіть самих високонавантажених інтернет-ресурсів. До місця було б сказати пару слів і про власне самій структурі даних в рамках баз даних і організації доступу до них, але будь-які рішення сильно залежать від конкретного додатка і реалізації, так що дозволю собі лише дати пару загальних рекомендацій:
ДенормализацияЗапити, що комбінують дані з декількох таблиць, зазвичай при інших рівних вимагають більшого процесорного часу для виконання, ніж запит, який стосується лише одну таблицю. А продуктивність, як уже згадувалося на початку розповіді, надзвичайно важлива на просторах Мережі. Логічне розбиття даних Якщо якась частина даних завжди використовується окремо від основної маси, то іноді має сенс виділити її в окрему незалежну систему зберігання даних. Низькорівнева оптимізація запитів Ведучи і аналізуючи логи запитів, можна визначити найбільш повільні з них. Заміна знайдених запитів на більш ефективні з тією ж функціональністю може допомогти більш раціонально використовувати обчислювальні потужності.
У цьому розділі варто згадати ще один, більш специфічний, тип інтернет-проектів. Такі проекти оперують даними, що не мають чітко формалізовану структуру, в таких ситуаціях використання реляційних СУБД в якості сховища даних, м'яко кажучи, недоцільно. У цих випадках зазвичай використовують менш суворі бази даних, з більш примітивною функціональністю в плані обробки даних, але зате вони здатні обробляти величезні обсяги інформації не чіпляючись до його якості та відповідності формату. В якості основи для такого сховища даних може служити кластерна файлова система, а для аналізу же даних в такому випадку використовується механізм під назвою MapReduce , Принцип його роботи я розповім лише коротко, так як в повному своєму масштабі він дещо виходить за рамки цієї розповіді.
Отже, ми маємо на вході якісь довільні дані в не факт що правильно дотриманням форматі. В результаті потрібно отримати якесь підсумкове значення або інформацію. Відповідно до даного механізму практично будь-який аналіз даних можна провести в наступні два етапи:
Map
Основною метою даного етапу є уявлення довільних вхідних даних у вигляді проміжних пар ключ-значення, що мають певний сенс і формально оформлених. Результати піддаються сортуванню і гуртування по ключу, а після чого передаються на наступний етап. Reduce Отримані після map значення використовуються для фінального обчислення необхідних підсумкових даних.
Кожен етап кожного конкретного обчислення реалізується у вигляді незалежного міні-додатки. Такий підхід дозволяє практично необмежено распараллеливать обчислення на величезній кількості машин, що дозволяє в миті обробляти обсяги практично довільних даних. Для цього достатньо лише запустити ці програми на кожному доступному сервері одночасно, а потім зібрати воєдино всі результати.
Прикладом готового каркаса для реалізації роботи з даними за таким принципом служить opensource проект Apache Foundation під назвою Hadoop , Про який я вже неодноразово розповідав раніше, та й статейку в Вікіпедію написав свого часу.
вместо Висновки
Якщо чесно, мені важко повірити, що я зміг написати настільки всеосяжний пост і сил на підведення підсумків вже практично не залишилося. Хочеться лише сказати, що в розробці великих проектів важлива кожна деталь, а неврахована дрібниця може стати причиною провалу. Саме по-цьому в цій справі вчитися коштує не на своїх помилках, а на чужих.
Хоч може бути цей текст і виглядає як якесь узагальнення всіх постів із серії "Архітектури високонавантажених систем" , Але навряд чи він стане фінальною крапкою, сподіваюся мені знайдеться що сказати по цій темі і в майбутньому, може бути один раз це буде засновано і на особистому досвіді, а не просто буде результатом переробки маси отриманої мною інформації. Хто знає?...
Приступимо?Але запити відвідувачів одного і того ж сайту можуть обробляти відразу кілька серверів, як же тоді визначити чи не був виданий отриманий ID на іншому сервері і де взагалі зберігаються його дані?
Але чи можна бути впевненими на 100%?
Хто знає?