За да е по-лесно да разберете за какво става дума тази статия, нека започнем с промяната в средната дневна статистика на Stack Overflow. Следните данни са от статистиката към 12 ноември 2013 г.:
- Балансьорът на натоварването приемаше 148 084 833 HTTP заявки
- От тях 36 095 312 бяха зареждания на страници
- За изпращане се използва 833 992 982 627 байта (776 GB) HTTP трафик
- Общо бяха получени 286 574 644 032 байта (267 GB) данни
- Общо бяха изпратени 1 125 992 557 312 байта (1 048 GB) данни
- 334 572 103 SQL заявки (включително само от HTTP заявки)
- 412 865 051 заявки в Redis
- 3 603 418 заявки за Tag Engine
- Отне 558 224 585 ms (155 часа) за SQL заявки
- Отне 99 346 916 ms (27 часа) за Redis заявки
- Прекарвах 132 384 059 ms (36 часа) за заявка за tag engine
- Обработката на процеса ASP.Net отне 2 728 177 045 ms (757 часа)
Следващите данни показват промените в статистиката към 9 февруари 2016 г., така че можете да сравните:
- HTTP заявки, получени от load balancer: 209,420,973 (+61,336,090)
- 66 294 789 (+30 199 477) от които се зареждат страници
- Изпратени HTTP данни: 1,240,266,346,053 (+406,273,363,426) байта (1.24 TB)
- Общо количество получени данни: 569 449 470 023 (+282 874 825 991) байта (569 GB)
- Общо количество изпратени данни: 3 084 303 599 266 (+1 958 311 041 954) байта (3.08 TB)
- SQL заявки (само от HTTP заявки): 504,816,843 (+170,244,740)
- Посещения в кеша на Redis: 5,831,683,114 (+5,418,818,063)
- Еластични търсения: 17 158 874 (не са проследени през 2013 г.)
- Заявки за tag engine: 3,661,134 (+57,716)
- Кумулативно време, използвано за изпълнение на SQL заявки: 607,073,066 (+48,848,481) ms (168 часа)
- Време за почитане на кеша в Redis: 10,396,073 (-88,950,843) ms (2.8 часа)
- Време, използвано от заявки за tag engine: 147,018,571 (+14,634,512) ms (40.8 часа)
- Време, използвано при обработка ASP.Net: 1,609,944,301 (-1,118,232,744) ms (447 часа)
- 22.71 (-5.29) ms 49 180 275 страници на броя средно време за рендиране (от които 19.12 ms се изразходват за ASP.Net)
- 11.80 (-53.2) ms 6 370 076 средно време за рендиране на първите страници (от които 8.81 ms се консумира за 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, всички устройства са ъпгрейднати до 10Gbps пропускателна способност)
- 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 устройства, два генератора зад тях и два източника на напрежение в мрежата.
- Всички сървъри имат резервно копие, разположено в Rack A и Rack B.
- Всички сървъри и услуги имат двойно резервни копия в отделен център за данни (в Колорадо), въпреки че основно покривам Ню Йорк.
- Всичко има излишни резервни копия.
Интернет
Първо трябва да намерите нашия уебсайт, който е свързан с DNS. Намирането на уебсайтове е бързо, затова сега го даваме на CloudFlere, защото те имат DNS сървъри във всеки ъгъл на света. Актуализираме DNS записите чрез API-та и те са отговорни за "управлението" на DNS. Въпреки това, в нашите злодейски умове все още имаме собствени DNS сървъри заради дълбоко вкоренените проблеми с доверието. Когато апокалипсисът е апокалиптичен – може би заради проблеми с GPL, Punyon или кеширането – и хората все още искат да програмират да отклоняват вниманието си, преминаваме към собствените си DNS сървъри.
След като браузърът ви намери нашето скривалище, HTTP трафикът от четирите ни интернет доставчици (Level 3, Zayo, Cogent и Lightower в Ню Йорк) влиза в един от четирите ни напреднали рутера. Използваме Border Gateway Protocol (BGP, много стандартен протокол) за peer-to-peer трафик от мрежови доставчици, за да го контролираме и да предоставим най-ефективния начин за достъп до нашите услуги. Рутерите ASR-1001 и ASR-1001-X са разделени на две групи, всяка от които трябва да използва активен/активен режим за обработка на трафика от двата мрежови доставчика – тук има резервни копия. Въпреки че всички имат една и съща физическа честотна лента от 10Gbps, трафикът отвън все още е независим от трафика от външния VLAN и е свързан отделно към баланса на натоварването. След като трафикът премине през рутера, ще дойдете до балансьора на натоварването.
Мисля, че е време да спомена, че имаме MPLS с 10Gbps пропускателна способност между двата центъра за данни, макар че това не е пряко свързано с уеб услугите. Използваме тази технология за извършване на репликация извън обекта и бързо възстановяване на данни при определени спешни ситуации. "Но, Ник, няма излишък в това!" Е, от техническа гледна точка си прав (в положителен смисъл), това всъщност е една единствена точка на провал на това ниво. Но чакай! Чрез мрежовия доставчик имаме и два допълнителни OSPF маршрута за превключване (MPLS е първият избор, а това са вторият и третият избор поради разходи). Всеки от споменатите по-рано комплекти устройства ще бъде свързан към центъра за данни в Колорадо съответно, за да балансира мрежовия трафик при превключване на натоварването в случай на превключване. Разбира се, можехме да свържем тези два комплекта устройства помежду си, така че да има четири набора пътеки, но нека го забравим, нека продължим.
Балансиране на натоварването (HAProxy)
Балансирането на натоварването е реализирано с HAProxy 1.5.15, работещ на CentOS 7 (нашата любима версия на Linux). И добавете TLS (SSL) защитен протокол за предаване на HAProxy. Следим и HAProxy 1.7, който ще осигури поддръжка за протокола HTTP/2 веднага.
За разлика от други сървъри с двойни 10Gbps LACP мрежови връзки, всеки балансьор на натоварването има две 10Gbps връзки: една за външната мрежа и друга за DMZ. Тези сървъри имат 64GB или повече памет, за да се справят по-ефективно с 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); Providence API (базиран на IIS). Интересен факт: трябваше да свържа двата процеса, за да се свържат с различни сокети, защото Stack Server често достъпваше L2 и L3 кешовете при обновяване на списъка с проблеми на интервали от две минути.
Машините, които изпълняват тези услуги, са критични за tag engine и бекенд API-тата, затова трябва да са излишни, но не 9x излишни. Например, зареждаме всички статии и техните тагове от базата данни на всеки n минути (в момента 2 минути), което не е малко. Не искаме да повтаряме тази операция за зареждане 9 пъти на уеб слоя, 3 пъти е достатъчно безопасно за нас. Също така използваме различни хардуерни конфигурации за тези сървъри, за да оптимизираме по-добре изчислителните и параметрите за зареждане на данни на tag engine и elastic index задачи (които също работят в този слой). Самият "таг енджин" е сравнително сложна тема, която ще бъде разгледана в специална статия. Основният принцип е, че когато достъпите адреса /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 извиквания.
Също така управляваме много Q&A сайтове, всеки със собствен L1/L2 cache: key като префикс в L1 кеша и ID на базата данни в L2/Redis кеша. Ще разгледаме тази тема в бъдещи статии.
Освен двата основни Redis сървъра (един главен и един подчинен), които управляват всички инстанции на сайт, ние също така настроихме инстанция за машинно обучение (главно поради проблеми с паметта), използвайки още два специализирани сървъра. Тази група сървъри се използва за предоставяне на услуги като препоръчване на въпроси на началната страница и по-добро съвпадение на работни места. Тази платформа се нарича Providence, а Кевин Монтроуз е писал за нея.
Основният Redis сървър има 256GB RAM (около 90GB използвани), а сървърът на Providence има 384GB памет (около 125GB използвани).
Redis не е само за кеширане, той има и механизъм за публикуване и абонамент, при който един сървър може да публикува съобщение, а други абонати могат да го получат (включително Redis от по-долните клиенти на сървъра). Използваме този механизъм, за да изчистим L1 кеша на други услуги, за да поддържаме консистентност на кеша на уеб сървъра. Но има и друга важна функция: websockets.
Websockets(NetGain)
Използваме websockets, за да изпращаме актуализации в реално време на потребителите, като известия в горната лента, гласове, нова навигация, нови отговори, коментари и други.
Самият сокет сървър работи на уеб слоя, използвайки нативни сокети. Това е много малко приложение, базирано на нашата отворена библиотечна имплементация: StackExchange.NetGain. В пиковите периоди имахме около 500 000 едновременни websocket връзки, което е много браузъри. Забавен факт: някои от тези браузъри са отворени повече от 18 месеца и ще трябва да намерите някой, който да провери дали тези разработчици все още са живи. Следната графика показва модела на паралелност на websocket тази седмица:
Защо да използвате websockets? В нашия мащаб това е много по-ефективно от анкетирането. По този начин можем просто да изпращаме повече данни с по-малко ресурси и да бъдем по-реално време за потребителите. Този подход обаче не е без проблеми: временни портове, изчерпани файлови дескриптори на балансьорите на натоварването са много интересни проблеми и за тях ще говорим по-късно.
Търсене (Elasticsearch)
Спойлер: Тук няма много за какво да се вълнуваш. Уеб слоят използва Elasticsearch 1.4 и реализира ултра-лек, високопроизводителен клиент StackExchange.Elastic. За разлика от повечето неща, не планираме да отваряме тази част, просто защото тя разкрива много малка част от API-тата, които трябва да използваме. Сигурен съм, че публикуването ѝ надвишава загубата и само ще обърка разработчиците. Използваме elastic:/search на тези места, за да изчисляваме свързани въпроси и да даваме предложения при задаване на въпроси.
Всеки Elastic клъстер (по един за всеки център за данни) съдържа 3 възела, всеки със собствен индекс. Сайтът за кариери има и някои допълнителни индекси. Малко по-малко стандартна част от нашата конфигурация в еластични кръгове е, че нашият клъстер от 3 сървъра е малко по-мощен от обичайната конфигурация: всеки сървър използва SSD памет, 192GB памет, двойна мрежа с честотна лента 10Gbps.
Същият приложен домейн на Stack Server (да, бяхме разхвърляни от .Net Core тук) също хоства tag engine, който използва 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 сървъри, всеки с 384GB памет, PCIe SSD с 4TB пространство и два 12-ядрени процесора. Включва Stack Overflow, Sites (лошо име, ще обясня по-късно), PRIZM и базата данни на Mobile.
Вторият клъстер е набор от Dell R730xd сървъри, всеки с 768GB памет, PCIe SSD с 6TB пространство и два 8-ядрени процесора. Този клъстер съдържа всички други бази данни, включително Careers, Open ID, Chat, логове с изключения и други Q&A сайтове (например Super User, Server Fault и др.).
На ниво база данни искаме да запазим използването на процесора на много ниско ниво, въпреки че на практика използването на процесора ще бъде малко по-високо, когато възникнат планирани проблеми с кеширането (които ние отстраняваме). В момента NY-SQL02 и 04 са основните сървъри, а 01 и 03 са реплики, и днес ги рестартирахме заради ъпгрейда на SSD-то. Ето как се представиха през последните 24 часа:
Използването на SQL е много просто. Простото означава бързо. Въпреки че някои заявки могат да бъдат изкривени, нашето взаимодействие със SQL се извършва по доста естествен начин. Имаме някои наследени Linq2Sql, но всички наши нови разработки използват Dapper – нашата отворена micro-ORM рамка, която използва POCO. Нека го обясня по друг начин: Stack Overflow има само една съхранена процедура в базата си данни и аз ще премахна тази последна останала запазена процедура и ще я заменя с код.
Библиотека
Е, нека променим мнението си, ето неща, които могат да ти помогнат по-пряко. Споменавал съм някои от тях преди, но ще ви дам списък с многото отворени .Net библиотеки, които поддържаме и които всички използват. Отваряме ги с отворен код, защото нямат основна бизнес стойност, но могат да помогнат на разработчици по целия свят. Надявам се сега да можеш да ги използваш:
- Dapper (.Net Core) – Високопроизводителна micro-ORM рамка за ADO.Net
- StackExchange.Redis – Високопроизводителен Redis клиент
- MiniProfiler – лек профайлър, който използваме на всяка страница (също поддържа Ruby, Go и Node)
- Изключителни – За логване на грешки в SQL, JSON, MySQL и др.
- Jil – Високопроизводителна JSON сериализация и десериализация
- Sigil – .Net помощник за генериране на CIL (използван когато C# не е достатъчно бърз)
- NetGain – Високопроизводителен websocket сървър
- Opserver – Мониторингово табло, което анкетира повечето системи директно и може да извлича информация от Orion, Bosun или WMI
- Боцман – Система за мониторинг на заден план, написана на Go
|