Щоб було легше зрозуміти, про що ця стаття, почнемо зі зміни середньодобової статистики Stack Overflow. Наступні дані наведені зі статистики станом на 12 листопада 2013 року:
- Балансувач навантаження приймав 148 084 833 HTTP-запити
- З них 36 095 312 були завантаженими сторінками
- Для передачі використовується 833 992 982 627 байт (776 ГБ) HTTP-трафіку
- Загалом було отримано 286 574 644 032 байти (267 ГБ) даних
- Загалом було надіслано 1 125 992 557 312 байт (1 048 ГБ) даних
- 334 572 103 SQL-запити (включно лише з HTTP-запитами)
- 412 865 051 запитів на Redis
- 3 603 418 запитів на Tag Engine
- Це зайняло 558 224 585 мс (155 годин) на SQL-запити
- Запити Redis займали 99 346 916 мс (27 годин)
- Витратив 132 384 059 мс (36 годин) на запит на тег-двигун
- Обробка процесу зайняла 2 728 177 045 мс (757 годин) ASP.Net процесу
Наступні дані показують зміни в статистиці станом на 9 лютого 2016 року, тож ви можете порівняти:
- HTTP-запити, отримані балансувальником навантаження: 209 420 973 (+61 336 090)
- 66 294 789 (+30 199 477) з яких завантажується сторінка
- HTTP-дані надіслані: 1 240 266 346 053 (+406 273 363 426) байтів (1,24 ТБ)
- Загальна кількість отриманих даних: 569 449 470 023 (+282 874 825 991) байтів (569 ГБ)
- Загальна кількість надісланих даних: 3 084 303 599 266 (+1 958 311 041 954) байтів (3,08 ТБ)
- SQL-запити (лише з HTTP-запитів): 504,816,843 (+170,244,740)
- Відвідування кешу Redis: 5,831,683,114 (+5,418,818,063)
- Еластичні пошуки: 17 158 874 (не відстежені у 2013 році)
- Запити на двигуни тегів: 3,661,134 (+57,716)
- Сукупний час, витрачений на виконання SQL-запитів: 607 073 066 (+48 848 481) мс (168 годин)
- Час влучання кешу Redis: 10 396 073 (-88 950 843) мс (2,8 години)
- Час, витрачений на запити двигуна тегів: 147 018 571 (+14 634 512) мс (40,8 годин)
- Час, витрачений на ASP.Net обробку: 1 609 944 301 (-1 118 232 744) мс (447 годин)
- 22,71 (-5,29) МС: 49 180 275 сторінок випусків — середній час рендерингу (з яких 19,12 мс витрачається на ASP.Net)
- 11,80 (-53,2) мс 6 370 076 середній час рендерингу перших сторінок (з яких 8,81 мс витрачається за ASP.Net)
Можливо, ви дивуєтеся, чому ASP.Net обробляє на 61 мільйон запитів більше на день, але скорочує час обробки на 757 годин (порівняно з 2013 роком). Це здебільшого завдяки оновленню наших серверів на початку 2015 року, а також завдяки значній оптимізації продуктивності в додатку. Не забувайте: продуктивність — це все ще перевага. Якщо вас цікавлять конкретні деталі обладнання, не хвилюйтеся, я розповім деталі обладнання серверів, з яких працюють ці сайти, у вигляді додатку в наступній статті (оновлю це посилання, коли настане час).
То що ж змінилося за останні два роки? Нічого особливого, просто заміню деякі сервери та мережеве обладнання. Ось огляд серверів, на яких сьогодні працював ваш сайт (зверніть увагу, як вони змінилися з 2013 року)
- 4 сервери Microsoft SQL Server (2 з яких використовують нове обладнання)
- 11 веб-серверів IIS (нове обладнання)
- 2 сервери Redis (нове обладнання)
- 3 сервери Tag Engine (2 з яких використовують нове обладнання)
- 3 сервери Elasticsearch (ті ж, що й вище)
- 4 сервери балансування навантаження HAProxy (2 додано для підтримки CloudFlare)
- 2 мережеві пристрої (ядро Nexus 5596 + 2232TM Fabric Extender, усі пристрої оновлені до пропускної здатності 10 Гбіт/с)
- 2 x міжмережеві екрани Fortinet 800C (замінюють Cisco 5525-X ASA)
- 2 маршрутизатори Cisco ASR-1001 (замінюють маршрутизатори Cisco 3945)
- 2 роутери Cisco ASR-1001-x (НОВІ!) )
Що нам потрібно, щоб Stack Overflow працював? З 2013 року мало що змінилося, але завдяки оптимізаціям і новому апаратному забезпеченню, згаданому вище, нам тепер потрібен лише один веб-сервер. Ми вже ненавмисно перевіряли цю ситуацію, з успіхом кілька разів. Зверніть увагу: я просто сказав, що це працює, я не казав, що це хороша ідея. Але кожного разу, коли це трапляється, це досить цікаво.
Тепер, коли ми маємо базові дані щодо ідей масштабування серверів, давайте подивимось, як ми створили ці класні веб-сторінки. Мало систем існують повністю незалежно (і наша не є винятком), і без цілісного підходу, який інтегрує ці частини, значення архітектурного планування значно зменшується. Наша мета — зрозуміти загальну ситуацію. У майбутньому буде багато статей, які глибоко розглянуть кожну конкретну галузь. Ця стаття є лише коротким викладом логічної структури ключового обладнання, а наступна стаття міститиме конкретні деталі про це обладнання.
Якщо хочете побачити, як виглядає це обладнання сьогодні, ось кілька фотографій, які я зробив Кабінет А (Шафа Б точно така сама), коли оновлював сервер у лютому 2015 року:
Тепер давайте заглибимося в архітектурне планування. Нижче наведено короткий виклад логічної архітектури основних існуючих систем:
Основні принципи
Ось кілька поширених принципів, які не потрібно вводити по черзі:
- Все має резервні копії.
- Усі сервери та мережеві пристрої мають щонайменше два з'єднання з пропускною здатністю 10Gbps.
- Усі сервери мають два джерела живлення, які забезпечують живлення через два блоки UPS, два генератори позаду них і два джерела напруги в мережі.
- Усі сервери мають резервну копію, розташовану в стійках A та Rack B.
- Усі сервери та сервіси мають подвійні резервні копії в окремому дата-центрі (у Колорадо), хоча я в основному охоплюю Нью-Йорк.
- Все має резервні копії.
Інтернет
Спочатку вам потрібно знайти наш вебсайт, який є DNS-функцією. Пошук сайтів дуже швидкий, тому тепер ми передаємо це CloudFlare, бо у них є DNS-сервери в кожному куточку світу. Ми оновлюємо DNS-записи через API, і вони відповідають за «управління» DNS. Однак у наших лиходіївських умах у нас все ще є власні DNS-сервери через глибоко вкорінені проблеми з довірою. Коли апокаліпсис стає апокаліптичним — можливо, через проблеми з GPL, Punyon або кешуванням — і люди все ще хочуть програмувати, щоб відволікти їхню увагу, ми переходимо на власні DNS-сервери.
Як тільки ваш браузер знаходить наше сховище, HTTP-трафік від чотирьох наших провайдерів (Level 3, Zayo, Cogent і Lightower у Нью-Йорку) потрапляє в один із чотирьох наших просунутих маршрутизаторів. Ми використовуємо протокол Border Gateway Protocol (BGP, дуже стандартний протокол) для однорангового трафіку від мережевих провайдерів, щоб контролювати його та забезпечувати найефективніший спосіб доступу до наших сервісів. Маршрутизатори ASR-1001 та ASR-1001-X поділяються на дві групи, кожна з яких має використовувати активний/активний режим для обробки трафіку від обох мережевих провайдерів — тут є резервні копії. Хоча всі вони мають однакову фізичну пропускну здатність 10 Гбіт/с, зовнішній трафік все одно не залежить від трафіку з зовнішнього VLAN і окремо підключений до балансування навантаження. Після того, як трафік проходить через роутер, ви переходите до балансування навантаження.
Думаю, настав час згадати, що між двома дата-центрами є MPLS з пропускною здатністю 10 Гбіт/с, хоча це не зовсім безпосередньо пов'язано з веб-сервісами. Ми використовуємо цю технологію для виконання віддаленої реплікації та швидкого відновлення даних з метою реагування на певні надзвичайні ситуації. "Але, Нік, тут немає зайвого!" З технічної точки зору ви праві (у позитивному сенсі), це насправді єдина точка відмови на цьому рівні. Але зачекайте! Через мережевого провайдера ми також маємо два додаткові резервні маршрути OSPF (MPLS — перший вибір, а це другий і третій варіанти з економічних причин). Кожен із згаданих раніше комплектів пристроїв буде підключений до дата-центру Колорадо відповідно для балансування навантаження мережевого трафіку у разі аварійного перемикання. Звісно, ми могли б з'єднати ці два набори пристроїв між собою, щоб було чотири набори шляхів, але забудьте, давайте рухатися далі.
Балансування навантаження (HAProxy)
Балансування навантаження реалізовано на HAProxy 1.5.15, що працює на CentOS 7 (нашій улюбленій версії Linux). І додати TLS (SSL) безпечний протокол передачі на HAProxy. Ми також стежимо за HAProxy 1.7, який негайно забезпечить підтримку протоколу HTTP/2.
На відміну від інших серверів із двома мережевими з'єднаннями LACP зі швидкістю 10 Гбіт/с, кожен балансувальник навантаження має два з'єднання по 10 Гбіт/с: одне для зовнішньої мережі, інше для DMZ. Ці сервери мають 64 ГБ або більше пам'яті для ефективнішої роботи з SSL-протоколом. Коли ми можемо кешувати та повторно використовувати більше TLS-сесій у пам'яті, ми споживаємо менше обчислювальних ресурсів при підключенні до одного й того ж клієнта. Це означає, що ми можемо відновлювати сесії швидше та дешевше. Пам'ять настільки дешева, що це легкий вибір.
Сам баланс навантаження легко налаштувати. Ми слухаємо різні вебсайти на різних IP-адресах (переважно для керування сертифікатами та DNS), а потім направляємо трафік на різні бекенди (переважно на основі заголовків хостів). Єдине, що ми робимо тут — це обмежуємо швидкість і збираємо деяку інформацію з заголовка (з веб-шару), щоб увійти в системні журнали HAProxy, таким чином ми можемо записувати метрики продуктивності для кожного запиту. Ми детально розповімо про це пізніше.
Веб-шар (IIS 8.5, ASP.Net MVC 5.2.3 та .Net 4.6.1)
Балансування навантаження розподіляє трафік між 9 тим, що ми називаємо основним веб-сервером (01-09) та 2 веб-серверами розробки (10-11, наше тестове середовище). Головний сервер виконує Stack Overflow, Careers та всі сайти Stack Exchange, тоді як meta.stackoverflow.com і meta.stackexchange.com працюють на двох інших серверах. Основний додаток для запитань і відповідей є мультиорендним, тобто один додаток обробляє всі запити з сайту запитань і відповідей. Іншими словами, ми можемо запускати весь додаток Q&A на одному пулі додатків на одному сервері. Інші додатки, такі як Careers, API v2, Mobile API тощо, є незалежними. Ось що ви бачите в IIS для master та dev серверів:
Ось розподіл веб-рівня Stack Overflow, як це видно в Opserver (наша внутрішня панель моніторингу):
А ось споживання ресурсів цих веб-серверів:
Я детальніше розповім у наступній статті про те, чому ми надаємо так багато ресурсів, зосереджуючись на постійній побудові, поблажливості та дублюваннях.
Сервісний рівень (IIS, ASP.Net MVC 5.2.3, . NET 4.6.1 та HTTP. SYS)
Поруч із веб-шаром знаходиться сервісний рівень. Вони також працюють поверх IIS 2012 у Windows 8.5R2. Цей рівень виконує деякі внутрішні сервіси, які підтримують веб-шар та інші внутрішні системи виробничого середовища. Два основні сервіси: «Stack Server», який запускає тег-рушій і базується на http.sys (не IIS); API Providence (на основі IIS). Цікавий факт: мені довелося співвідносити два процеси для підключення до різних сокетів, оскільки Stack Server часто отримував доступ до кешів L2 і L3 при оновленні списку проблем кожні дві хвилини.
Машини, що виконують ці сервіси, критично важливі для движка тегів і бекенд-API, тому вони мають бути надлишковими, але не надлишковими у 9 разів. Наприклад, ми завантажуємо всі статті та їхні теги з бази даних кожні n хвилин (наразі 2 хвилини), що немало. Ми не хочемо повторювати цю операцію завантаження 9 разів на веб-рівні, 3 рази для нас достатньо безпечно. Ми також використовуємо різні апаратні конфігурації для цих серверів, щоб краще оптимізувати обчислювальні та завантажені характеристики тег-двигуна та еластичних індексних завдань (які також працюють у цьому шарі). Сам «движок тегів» — це досить складна тема, яку буде розглянуто у спеціальній статті. Основний принцип полягає в тому, що коли ви отримуєте адресу /questions/tagged/java, ви заходите в движок тегування, щоб отримати відповідні питання. Рушій обробляє все зіставлення тегів, крім /search, тому всюди, включаючи нову навігацію, отримує дані через цей сервіс.
Кешування та публікація/підписка (Redis)
Ми використовували Redis у деяких місцях, і він має стабільну стабільність. Хоча на місяць відбувається до 160 мільярдів операцій, потужність процесора на один екземпляр не перевищує 2%, що зазвичай менше:
Ми використовуємо Redis для систем кешування рівня L1/L2. Рівень «L1» — це HTTP-кеш, який працює на веб-сервері або в будь-якому подібному застосунку. Рівень «L2» призначений для отримання даних через Redis після того, як кеш попереднього рівня вийшов з ладу. Наші дані зберігаються у форматі Protobuf, реалізованому через protobuf-dot-net, написаний Марком Гравелом. Для клієнта Redis ми використали бібліотеку StackExchange.Redis — відкриту бібліотеку, розроблену власними силами. Якщо веб-сервер не влучає в кеші L1 і L2, він отримує дані зі своїх джерел даних (запити до бази, виклики API тощо) і зберігає результати у локальний кеш і Redis. Наступний сервер може бути відсутній у кеші L1 при отриманні тих самих даних, але він отримає дані у L2/Redis, усуваючи потребу в запитах до бази даних або викликах API.
Ми також запускаємо багато сайтів запитань і відповідей, кожен із власним кешом L1/L2: key як префіксом у кеші L1 та ID бази даних у кеші L2/Redis. Ми розглянемо цю тему у майбутніх статтях.
Окрім двох основних серверів Redis (один майстер і один підлеглий), які запускають усі екземпляри сайтів, ми також налаштували екземпляр машинного навчання (переважно через обмеження пам'яті) за допомогою двох інших виділених підлеглих серверів. Ця група серверів використовується для надання послуг, таких як рекомендація запитань на головній сторінці та покращення підбору робіт. Ця платформа називається Providence, і Кевін Монтроуз писав про неї.
Основний сервер Redis має 256 ГБ оперативної пам'яті (приблизно 90 ГБ використано), а сервер Providence — 384 ГБ оперативної пам'яті (приблизно 125 ГБ).
Redis призначений не лише для кешування, він також має механізм публікації та підписки, де один сервер може публікувати повідомлення, а інші абоненти отримують його (включно з Redis від клієнтів на сервері). Ми використовуємо цей механізм для очищення кешу L1 на інших сервісах, щоб підтримувати узгодженість кешу на веб-сервері. Але у нього є ще одне важливе застосування: вебсокети.
Websockets (NetGain)
Ми використовуємо websockets для надсилання оновлень користувачів у реальному часі, таких як сповіщення у верхній панелі, голосування, нова навігація, нові відповіді, коментарі та інше.
Сам сокетний сервер працює на веб-рівні, використовуючи нативні сокети. Це дуже невелика програма, заснована на нашій відкритій бібліотечній реалізації: StackExchange.NetGain. У пікові періоди у нас було близько 500 000 одночасних websocket-з'єднань, що є дуже багато браузерів. Цікавий факт: деякі з цих браузерів відкриті вже понад 18 місяців, і вам доведеться знайти когось, щоб перевірити, чи ці розробники ще живі. Наступний графік показує закономірність конкурентності веб-сокетів цього тижня:
Навіщо використовувати websockets? У нашому масштабі це набагато ефективніше, ніж опитування. Таким чином, ми можемо просто переносити більше даних з меншими ресурсами і бути більш актуальними для користувачів. Однак цей підхід не позбавлений недоліків: тимчасові порти, вичерпані файлові обробки на балансувальниках навантаження — це дуже цікаві проблеми, про які ми поговоримо пізніше.
Пошук (Elasticsearch)
Спойлер: тут нема чим захоплюватися. Веб-шар використовує Elasticsearch 1.4 і реалізує надлегкий, високопродуктивний клієнт StackExchange.Elastic. На відміну від більшості речей, ми не плануємо відкривати вихідний код цієї частини, просто тому, що вона відкриває дуже невелику частину API, які нам потрібно використовувати. Я впевнений, що публічність переважить втрати і лише заплутає розробників. Ми використовуємо elastic:/search у цих місцях для розрахунку пов'язаних питань і даємо поради при їх ставленні.
Кожен Elastic кластер (по одному на кожен дата-центр) містить 3 вузли, кожен зі своїм індексом. На сайті кар'єри також є додаткові індекси. Трохи менш стандартна частина нашої конфігурації в еластичних колах полягає в тому, що наш кластер із трьох серверів трохи потужніший за звичайну конфігурацію: кожен сервер використовує SSD-сховище, 192 ГБ пам'яті, подвійне мережеве підключення з пропускною здатністю 10 Гбіт/с.
У тому ж домені Stack Server (так, нас тут розкинув .Net Core) також є рушій тегів, який також використовує Elasticsearch для безперервної індексації. Тут ми використовуємо невеликий трюк, наприклад, ROWVERSION у SQL Server (джерело даних) для порівняння з документом «останнього місця» в Elastic. Оскільки він, очевидно, є послідовним, нам легко сканувати та індексувати контент, якщо його змінили після останнього візиту.
Головна причина, чому ми використовуємо Elasticsearch замість таких технологій, як SQL повнотекстовий пошук, — це його масштабованість і економічна ефективність. SQL відносно дорогий на процесорах, тоді як Elastic значно дешевший і останнім часом має багато нових функцій. Чому б не скористатися Solr? Нам потрібно шукати по всій мережі (з кількома індексами одночасно), і Solr не підтримує цей сценарій на момент прийняття рішень. Причина, чому ми ще не використовували 2.x, полягає в тому, що типи у 2.x сильно змінилися, а це означає, що нам доводиться переіндексувати все, якщо ми хочемо оновлюватися. У мене просто немає часу планувати зміни вимог і міграції.
База даних (SQL Server)
Ми використовуємо SQL Server як єдине джерело істини. Усі дані в Elastic і Redis надходять із SQL Server. У нас є два кластери SQL Server, і ми налаштовані з групами доступності AlwaysOn. Кожен кластер має основний сервер у Нью-Йорку (який бере на себе майже все навантаження) і сервер-репліку, а також сервер реплік у Колорадо (наш центр відновлення після аварій). Усі операції копіювання асинхронні.
Перший кластер — це набір серверів Dell R720xd, кожен з 384 ГБ пам'яті, PCIe SSD з 4 ТБ простору та два 12-ядерні процесори. Вона включає Stack Overflow, Sites (це погана назва, поясню це пізніше), PRIZM і базу даних Mobile.
Другий кластер — це набір серверів Dell R730xd, кожен з 768 ГБ пам'яті, PCIe SSD з 6 ТБ простору та два 8-ядерні процесори. Цей кластер містить усі інші бази даних, включно з Careers, Open ID, Chat, журналами винятків та іншими сайтами запитань і відповідей (наприклад, Super User, Server Fault тощо).
На рівні бази даних ми хочемо тримати завантаження процесора на дуже низькому рівні, хоча на практиці завантаження процесора буде трохи вищим, коли виникають заплановані проблеми кешування (які ми вирішуємо). Наразі NY-SQL02 і 04 є основними серверами, а 01 і 03 — репліками, і ми щойно перезавантажили їх сьогодні через оновлення SSD. Ось як вони виступили за останні 24 години:
Наше використання SQL дуже просте. Просто означає швидко. Хоча деякі запитні оператори можуть бути спотворені, наша взаємодія з SQL відбувається досить нативно. У нас є деякий спадковий Linq2Sql, але всі наші нові розробки використовують Dapper — наш відкритий micro-ORM фреймворк, який використовує POCO. Дозвольте пояснити інакше: у Stack Overflow у базі даних зберігається лише одна процедура, і я збираюся закрити цю останню збережену процедуру та замінити її кодом.
Бібліотека
Давайте змінимо думку, ось що може допомогти вам більш безпосередньо. Я вже згадував деякі з них раніше, але дам вам список багатьох відкритих .Net-бібліотек, які ми підтримуємо і якими користуються всі. Ми відкриваємо їхній код, бо вони не мають основної бізнес-цінності, але можуть допомогти розробникам по всьому світу. Сподіваюся, ви зможете ними скористатися зараз:
- Dapper (.Net Core) – високопродуктивний мікро-ORM-фреймворк для ADO.Net
- StackExchange.Redis – високопродуктивний клієнт Redis
- MiniProfiler – легкий профайлер, який ми використовуємо на кожній сторінці (також підтримує Ruby, Go та Node)
- Винятково – для логування помилок у SQL, JSON, MySQL тощо
- Jil – високопродуктивна JSON-серіалізація та десеріалізація
- Sigil – .Net Generation Helper (використовується, коли C# недостатньо швидкий)
- NetGain – високопродуктивний веб-сокетний сервер
- Opserver – моніторингова панель моніторингу, яка безпосередньо опитує більшість систем і може отримувати інформацію з Orion, Bosun або WMI
- Боцман – система моніторингу у фоновому режимі, написана на Go
|