- PVSM.RU - https://www.pvsm.ru -

Архитектура Stack Overflow

image

Чтобы понять, как все это работает, давайте начнем с показателей Stack Overflow. Итак, ниже приводится статистика за 12 ноября 2013 [1] и 9 февраля 2016 года:

статистика

  • 209,420,973 (+61,336,090) HTTP-запросов к нашему балансировщику нагрузки;
  • 66,294,789 (+30,199,477) страниц было загружено;
  • 1,240,266,346,053 (+406,273,363,426) битов (1.24 TБ) отосланного HTTP-трафика;
  • 569,449,470,023 (+282,874,825,991) битов (569 ГБ) всего получено;
  • 3,084,303,599,266 (+1,958,311,041,954) битов (3.08 ТБ) всего отослано;
  • 504,816,843 (+170,244,740) SQL-запросов (только из HTTP-запросов);
  • 5,831,683,114 (+5,418,818,063) обращений к Redis;
  • 17,158,874 (not tracked in 2013) поисков в Elastic;
  • 3,661,134 (+57,716) запросов Tag Engine;
  • 607,073,066 (+48,848,481) мс (168 часов) выполнения SQL-запросов;
  • 10,396,073 (-88,950,843) мс (2.8 часов) затрачено на обращение к Redis;
  • 147,018,571 (+14,634,512) мс (40.8 часов) затрачено на запросы к Tag Engine;
  • 1,609,944,301 (-1,118,232,744) мс (447 часов) затрачено на обработку в ASP.Net;
  • 22.71 (-5.29) мс в среднем (19.12 мс в ASP.Net) на формирование каждой из 49,180,275 запрошенных страниц;
  • 11.80 (-53.2) мс в среднем (8.81 мс в ASP.Net) на формирование каждой из 6,370,076 домашних страниц.

Вы можете спросить, почему существенно сократилась продолжительность обработки в ASP.Net по сравнению с 2013 годом (когда было 757 часов) несмотря на прибавление 61 миллиона запросов в день. Это произошло как и из-за модернизации оборудования в начале 2015 года, так и из-за некоторого изменения параметров в самих приложениях. Пожалуйста, не забывайте, что производительность – это наша отличительная особенность. Если Вы хотите, чтобы я более подробно рассказал о характеристиках оборудования – без проблем. В следующем посте будут подробные спецификации железа всех серверов, которые обеспечивают работу сайта.

Итак, что изменилось за прошедшие 2 года? Кроме замены некоторых серверов и сетевого оборудования, не очень многое. Вот укрупненный список хардварной части, которая обеспечивает работу ресурса (выделены различия по сравнению с 2013 годом):

  • 4 Microsoft SQL Servers (новое железо для 2-х из них);
  • 11 Web-серверов IIS (новое оборудование);
  • 2 сервера Redis [2] (новое оборудование);
  • 3 сервера Tag Engine (новое оборудование для 2-х из 3-х);
  • 3 сервера Elasticsearch [3] (те же, старые);
  • 4 балансировщика нагрузки HAProxy [4] (добавлено 2 для поддержки CloudFlare);
  • 2 брандмауэра Fortinet 800C [5] (вместо Cisco 5525-X ASAs);
  • 2 маршрутизатора Cisco ASR-1001 [6] (вместо маршрутизаторов Cisco 3945);
  • 2 маршрутизатора Cisco ASR-1001-x [7] (новые!).

Что нам необходимо, чтобы запустить Stack Overflow? Этот процесс не сильно изменился с 2013 года, но из-за оптимизации и нового железа, нам необходим только один web-сервер. Мы этого не хотели, но несколько раз успешно проверили. Вношу ясность: я заявляю, что это работает. Я не утверждаю, что это (запуск SO на единственном web-сервере) — хорошая затея, хотя каждый раз выглядит весьма забавно.

Теперь, когда у нас есть некоторые числа для понятия масштаба, давайте рассмотрим, как мы это делаем. Так как немногие системы работают в полной изоляции (и наша не исключение), часто конкретные архитектурные решения почти не имеют смысла без общей картины того, как эти части взаимодействуют между собой. Цель нашей статьи – охватить эту общую картину. Во многочисленных последующих постах будут подробно рассмотрены отдельные области. Это логистический обзор только основных особенностей нашего «hardware», а уже потом, следующих постах, они будут рассмотрены более детально.

Для того, чтобы Вы поняли, как сегодня выглядит наше оборудование, привожу несколько своих фото рэковой стойки А (в сравнении с ее «сестрой» стойкой B), сделанных во время нашего переоборудования в феврале 2015 года:

image

И, как не парадоксально, с той недели у меня в альбоме есть еще 255 фотографий (в сумме 256, и да — это не случайное число). Теперь, давайте рассмотрим оборудование. Вот логическая схема взаимодействия главных систем:

image

Основные правила

Вот некоторые всеобще применяемые правила, поэтому я не буду повторять их для каждой системы:

  • Все резервировано.
  • Все сервера и сетевые устройства связаны между собой, по крайней мере, 2 x 10 ГБит/с каналами.
  • Все сервера имеют 2 входа питания и 2 подвода питания от 2-х ИБП, подключенных к двум генераторам и двум сетевым линиям.
  • Все сервера между рэками A и B имеют резервированного партнера (redundant partner).
  • Все сервера и сервисы дважды резервированы через другой дата-центр (Колорадо), хотя здесь я буду больше говорить о Нью-Йорке.
  • Все резервировано.

В сети Интернет

Сначала Вы должны найти нас – это DNS. Процесс нахождения нас должен быть быстрым, поэтому мы поручаем это CloudFlare [8] (в настоящее время), так как их серверы DNS ближе почти всех остальных DNS мира. Мы обновляем наши записи DNS через API, а они делают «хостинг» DNS. Но поскольку мы «тормозы» с глубоко укоренившимися проблемами доверия (к другим), мы также все еще имеем наши собственные DNS-сервера. Если произойдет апокалипсис (вероятно, вызванный GPL, Punyon [9] или кэшированием), а люди все еще будут хотеть программировать, чтобы не думать о нем, мы переключимся на них.

После того, как Вы найдете наше «секретное убежище», пойдет HTTP-трафик через одного из наших четырех Интернет провайдеров (Level 3, Zayo, Cogent, и Lightower в Нью-Йорке), и через один из наших четырех локальных маршрутизаторов. Для достижения максимальной эффективности, мы вместе с нашими провайдерами используем BGP (довольно стандартный) для управления трафиком и обеспечения нескольких путей его передачи. Маршрутизаторы ASR-1001 и ASR-1001-X объединены в 2 пары, каждая из которых обслуживает 2 провайдера в режиме активный/активный. Таким образом, мы обеспечиваем резервирование. Хотя они подключены все к той же физической сети 10 Гбит/с, внешний трафик проходит по отдельным изолированным внешним VLAN, которые также подключены к балансировщикам нагрузки. После прохождения через маршрутизаторы, трафик направляется к балансировщикам нагрузки.

Я думаю, что самое время упомянуть, что между нашими двумя дата-центрами мы используем линию MPLS [10] на 10 Гбит/с, но это напрямую не связано с обслуживанием сайта. Она служит для дублирования данных и их быстрого восстановления в случаях, когда нам нужна пакетная передача. «Но Ник, это не резервирование!» Да, технически Вы правы (абсолютно правы): это – единственное «пятно» на нашей репутации. Но постойте! Через наших провайдеров мы имеем еще две более отказоустойчивые линии OSPF (по стоимости MPLS — № 1, а это № 2 и 3). Каждое из упомянутых устройств быстрее подключается к соответствующему устройству в Колорадо, и при отказе они распределяют между собой сбалансированный трафик. Мы смогли заставить оба устройства соединяться с обоими устройствами 4-мя способами, но все они и так одинаково хороши.

Идем дальше.

Балансировщики нагрузки (HAProxy [11])

Балансировщики нагрузки работают на HAProxy 1.5.15 под CentOS 7 [12], предпочтительной у нас разновидности Linux. HAProxy также ограничивает и трафик TLS (SSL). Для поддержки HTTP/2 [13] мы скоро начнем внимательно изучать HAProxy 1.7.

В отличие от всех других серверов с двойным сетевым подключением по LACP 10 Гбит/с, каждый балансировщик нагрузки имеет по 2 пары каналов 10 Гбит/с: одну для внешней сети и одну для DMZ. Для более эффективного управляемого согласования SSL эти «коробки» имеют память 64 ГБ или больше. Когда мы можем кэшировать в памяти больше сессий TLS для повторного использования, тратится меньше времени на образование нового соединения с тем же самым клиентом. Это означает, что мы можем возобновлять сессии и быстрее, и с меньшими затратами. Учитывая, что RAM в переводе на доллары довольно дешевая, это – легкий выбор.

Сами балансировщики нагрузки – довольно простые устройства. Мы создаем иллюзию, что разные сайты «сидят» на различных IP (в основном по вопросам сертификации и управления DNS), и маршрутизируем на различные выходные буфера основываясь, главным образом, на заголовках хоста. Единственными «знаменитыми» вещами, которые мы делаем, является ограничение скорости и некоторые захваты заголовков (отсылаемых с нашего уровня веб-узлов) в сообщение системного журнала HAProxy [14]. Поэтому мы можем делать запись метрик производительности для каждого запроса. Мы также расскажем об этом позднее.

Уровень веб-узлов (IIS 8.5, ASP.Net MVC 5.2.3 и .Net 4.6.1)

Балансировщики нагрузки регулируют трафик 9-ти серверов, которые мы называем «Primary» (01-09), и 2-х web-серверов «Dev/meta» (10-11, среда нашей площадки). Серверы Primary управляют такими вещами, как Stack Overflow, Careers и всеми сайтами Stack Exchange, кроме сайтов meta.stackoverflow.com и meta.stackexchange.com, которые размещены на 2-х последних серверах. Основное приложение Q&A само по себе многопользовательское. Это означает, что одно приложение обслуживает запросы для всех Q&A сайтов. Скажем по-другому – мы можем управлять всей сетью Q&A одним пулом приложений на одном сервере. Другие приложения, такие как Careers, API v2, Mobile API и т.д. размещены отдельно. Вот так выглядят основной и dev-уровни в IIS:

Архитектура Stack Overflow - 4

Вот так выглядит распределение Stack Overflow по уровню веб-узлов, напоминая Opserver [15] (наша внутренняя dashboard):

image

… а вот так выглядят web-серверы с точки зрения нагрузки:

image

В следующих постах я коснусь того, почему мы так «сверхоснащены», но самыми важными моментами являются: кольцевое построение (rolling builds), операционный резерв и избыточность.

Сервисное звено (IIS, ASP.Net MVC 5.2.3, .Net 4.6.1 и HTTP.SYS)

В основе этих web-серверов лежит очень похожее «сервисное звено». Оно также работает на IIS 8.5 под Windows 2012R2 и отвечает за внутренние службы, поддерживая вычислительный уровень веб-узлов и другие внутренние системы. Здесь два крупных «игрока»: «Stack Server», который управляет Tag Engine и основан на http.sys (не под IIS), и Providence API (работает на IIS). Забавный факт: я должен установить подобие на каждом из этих 2 процессов, прописываясь на разные сокеты, потому что Stack Server просто «пробивает» кэши L2 и L3, когда с интервалом в 2 минуты приходят обновленные опросные списки.

Эти сервисные «коробки» выполняют ответственную работу по поднятию Tag Engine и внутренних API, где нам нужна избыточность, но не 9-ти кратная. Например, загрузка из базы данных (в настоящее время 2-х) всех постов и их тэгов, которые изменяются каждую n-ную минуту, не очень «дешевая». Мы не хотим загружать все это 9 раз на уровень веб-узлов; достаточно 3 раза и это обеспечивает нам достаточную «безопасность». Кроме того, на уровне оборудования мы конфигурируем эти «коробки» по-разному, чтобы они были более оптимизированы под различные характеристики вычислительной нагрузки Tag Engine и гибкого индексирования (которое также здесь работает). Сам по себе «Tag Engine» является относительно сложной темой и ему будет посвящен отдельный пост. Основной момент: когда Вы заходите на /questions/tagged/java, вы нагружаете Tag Engine, чтобы определить соответствующие запросы. Он делает все наше сопоставление тэгов вне процесса поиска, поэтому новая навигация и все прочее используют эти сервисы для обработки данных.

Кэш и Pub/Sub (Redis [2])

Здесь мы используем Redis для нескольких вещей и они вряд ли будут сильно изменяться. Несмотря на выполнение примерно 160 миллиардов операций в месяц, в каждый момент загрузка центрального процессора менее 2%. Обычно намного ниже:

image

С Redis мы имеем систему кэшей L1/L2. «L1» – это кэш HTTP на web-серверах или каком-либо работающем приложении. «L2» снижает скорость Redis и делает оценки. Наши оценки сохраняются в формате Protobuf через protobuf-dot-net от Марка Грэвелла (Marc Gravell). Для клиента мы используем StackExchange.Redis [16] – собственно разработанный и открытый источник. Когда один web-сервер получает «промах кэша» (cache miss) L1, либо L2, он берет оценку из источника (запрос к базе данных, вызов API и т.д.) и помещает результат и в местный кэш, и в Redis. Следующий сервер, нуждающийся в оценке, может получить «промах кэша» L1, но найдет оценку в L2/Redis, экономя на запросе к базе данных или вызове API.

Также у нас работает много Q&A сайтов, поэтому у каждого сайта есть свое собственное кэширование L1/L2: по ключевому префиксу в L1 и по ID базы данных в L2/Redis. Более подробно этот вопрос мы рассмотрим в следующих постах.

Вместе с 2-мя основными серверами Redis (мастер/ведомый), которые управляют всеми запросами к сайтам, у нас также есть система машинного обучения, работающая на 2-х более специализированных серверах (из-за памяти). Она используется для отображения рекомендованных запросов на домашней странице, улучшения выдачи и т.д. Эта платформа, называемая Providence, у нас обслуживается Кевином Монтроузом (Kevin Montrose).

Основные серверы Redis имеют по 256 ГБ RAM (используется около 90 ГБ), а в серверах Providence установлено по 384 ГБ RAM (используется около 125 ГБ).

Redis используется не только для работы с кэшем – он также имеет алгоритм «publish & subscriber» (публикация и подписка), работающий так, что один сервер может разослать сообщение, а все другие «подписчики» получат его – включая нижерасположенных клиентов на ведомых серверах Redis. Мы используем этот алгоритм для очистки кэша L1 на других серверах, когда один web-сервер делает удаление для сохранения согласованности. Но есть и другое важное применение: websockets.

Websocket’ы (NetGain [17])

Мы используем websocket’ы для отправки пользователям в реальном времени обновлений, таких как уведомления, подсчет голосов, новое навигационное исчисление, новые ответы и комментарии и т.п.

Сами сокет-серверы используют необработанные сокеты, выполняемые на уровне веб-узлов. Это — очень маленькое приложение на вершине нашей открытой библиотеки: StackExchange.NetGain [17]. Во время пиковой нагрузки у нас одновременно открыто около 500,000 параллельных каналов websocket. Это — множество браузеров. Забавный факт: некоторые из этих страниц были открыты более 18 месяцев назад. Мы не знаем почему. Кто-то должен проверить, живы ли еще эти разработчики.

Вот так изменялось на этой неделе число одновременно открытых websocket:

image

Почему websocket? При нашем масштабе они намного более эффективны, чем опрос. Просто таким способом мы можем передать пользователю больше данных при меньшем количестве используемых ресурсов, к тому же более быстро. Но у них тоже есть проблемы, хотя временный порт и полный перебор всех дескрипторов файлов на балансировщике нагрузки – несерьезные проблемы. Мы расскажем о них позже.

Поиск (Elasticsearch [18])

Спойлер: это не то, от чего можно прийти в восторг.

Уровень веб-узлов выполняет солидную поисковую работу по сравнению с Elasticsearch 1.4, который использует очень тонкий высокоэффективный клиент StackExchange.Elastic. В отличие от большинства других средств, мы не планируем выкладывать его в открытый доступ просто потому, что он отражает только очень небольшое подмножество API, которые мы используем. Я убежден, что его выпуск «в свет» принесет больше вреда, чем пользы из-за путаницы среди разработчиков. Мы используем Elastic для поиска, вычисления связанных запросов и советов, как формировать вопрос.

Каждый кластер Elastic (по одному такому есть в каждом дата-центре) имеет 3 узла, а каждый сайт имеет свой собственный индекс. Careers имеет несколько дополнительных индексов. Это делает нашу систему немного нестандартной: наши 3 серверных кластера немного больше «накачаны» всеми этими хранилищами SSD, памятью в 192 ГБ и двойной сетью по 10 Гбит/с на каждый канал.

Те же самые прикладные домены (да, мы здесь тронуты на .Net Core …) в Stack Server, на котором установлен Tag Engine, также непрерывно индексируют элементы в Elasticsearch. Здесь мы применяем некоторые уловки, такие как ROWVERSION в SQL Server [19] (источник данных), сравниваемый с документом «последней позиции» в Elastic. Так как он ведет себя как последовательность, мы можем просто использовать и индексировать любые элементы, которые изменились с момента последнего прохода.

Главной причиной того, что мы остановились на Elasticsearch вместо чего-то подобного полнотекстовому поиску SQL, является масштабируемость и лучшее распределение денег. SQL CPU достаточно дорогие, Elastic же дешевый и сегодня имеет намного лучшие характеристики. Почему не Solr [20]? Мы хотим искать по всей сети (сразу много индексов), но это не поддерживалось Solr во время принятия решения. Причиной того, что мы еще не перешли на 2.x, является солидная доля «types» [21], из-за которых нам надо будет все переиндексировать для обновления. У меня просто нет достаточно времени, чтобы когда-нибудь составить план и сделать необходимые для перехода изменения.

Базы данных (SQL-сервер)

Мы используем SQL Server как наш единственный источник достоверной информации. Все данные Elastic и Redis получают от SQL-сервера. У нас работают 2 кластера SQL-серверов под AlwaysOn Availability Groups [22]. У каждого из этих кластеров есть один мастер (выполняющий почти всю нагрузку) и одна реплика в Нью-Йорке. Кроме этого, еще есть одна реплика в Колорадо (наш дата-центр с динамическим копированием). Все реплики асинхронные.

Первый кластер – набор серверов Dell R720xd, у каждого 384 ГБ RAM, 4 TB PCIe SSD и 2 x 12 ядер. На нем установлены Stack Overflow, Sites (имеет дурную славу, я объясню позже), PRIZM и базы данных Mobile.

Второй кластер – это набор серверов Dell R730xd, у каждого 768 ГБ RAM, 6 TB PCIe SSD и 2 x 8 ядер. На этом кластере стоит все остальное. Список «всего остального» включает Careers, Open ID, Chat, наш Exception log и некоторые Q&A сайты (например, Super User, Server Fault и т.д.).

Использование CPU на уровне баз данных – это то, чего мы предпочитаем иметь в минимальном количестве, но, фактически, в данный момент оно немного повысилось из-за некоторых проблем с кэшем, которые мы сейчас решаем. Сейчас NY-SQL02 и 04 назначены мастерами, 01 и 03 – репликами, которые мы сегодня перезагрузили после обновления SSD. Вот как выглядят прошедшие 24 часа:

image

Мы используем SQL довольно просто. Ведь просто – это быстро. Хотя некоторые запросы могут быть безумными, наше взаимодействие с SQL само по себе – просто классика. У нас есть одна «древняя» Linq2Sql [23], но все новые разработки используют Dapper [24] – наш выложенный в открытый доступ Micro-ORM, использующий POCOs [25]. Позвольте сказать по-другому: Stack Overflow имеет только 1 хранимую в базе данных процедуру, и я намереваюсь перевести тот последний кусок в код.

Библиотеки

Хорошо, позвольте нам переключиться на что-то, что может непосредственно помочь Вам. Некоторые из этих вещей я упомянул выше. Здесь я предоставлю список большинства открытых .Net-библиотек, которые мы поддерживаем для использования во всем мире. Мы выкладываем их в открытый доступ, поскольку они не имеют никакой ключевой ценности для бизнеса, но могут помочь миру разработчиков. Я надеюсь, что Вы их сочтете полезными:

  • Dapper [24] (.Net Core) — высокопроизводительный Micro-ORM для ADO.Net;
  • StackExchange.Redis [16] – высокопроизводительный клиент Redis;
  • MiniProfiler [26] – малообъемный профайлер, который мы используем на каждой странице (поддерживает только Ruby, Go и Node);
  • Exceptional [27] – регистратор ошибок для SQL, JSON, MySQL, и т.п;
  • Jil [28] — высокопроизводительный (де) сериализатор JSON ;
  • Sigil [29] – помощник генерации .Net CIL (когда C# не достаточно быстрый);
  • NetGain [17] — высокопроизводительный сервер websocket ;
  • Opserver [30] — контрольная приборная панель, опрашивающая напрямую большинство систем и также получающая данные от Orion, Bosun или WMI;
  • Bosun [31] – система мониторинга серверных СУБД, написанная на Go.

Далее последует подробный список имеющегося железа, на котором работает наш софт. Затем мы пойдем дальше. Оставайтесь с нами.

Автор: ua-hosting.company

Источник [32]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/setevy-e-tehnologii/113881

Ссылки в тексте:

[1] за 12 ноября 2013: http://nickcraver.com/blog/2013/11/22/what-it-takes-to-run-stack-overflow/

[2] Redis: http://redis.io/

[3] Elasticsearch: http://www.elasticsearch.org/

[4] HAProxy: http://haproxy.1wt.eu/

[5] Fortinet 800C: http://www.fortinet.com/products/fortigate/enterprise-firewalls.html

[6] ASR-1001: http://www.cisco.com/c/en/us/products/routers/asr-1001-router/index.html

[7] ASR-1001-x: http://www.cisco.com/c/en/us/products/routers/asr-1001-x-router/index.html

[8] CloudFlare: https://www.cloudflare.com/

[9] Punyon: https://twitter.com/JasonPunyon

[10] MPLS: https://en.wikipedia.org/wiki/Multiprotocol_Label_Switching

[11] HAProxy: http://www.haproxy.org/

[12] CentOS 7: https://www.centos.org/

[13] HTTP/2: https://en.wikipedia.org/wiki/HTTP/2

[14] системного журнала HAProxy: https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#3.1-log

[15] Opserver: https://github.com/Opserver/Opserver

[16] StackExchange.Redis: https://github.com/StackExchange/StackExchange.Redis

[17] NetGain: https://github.com/StackExchange/NetGain

[18] Elasticsearch: https://www.elastic.co/products/elasticsearch

[19] ROWVERSION в SQL Server: https://msdn.microsoft.com/en-us/library/ms182776.aspx

[20] Solr: http://lucene.apache.org/solr/

[21] солидная доля «types»: https://github.com/elastic/elasticsearch/issues/8870

[22] AlwaysOn Availability Groups: https://msdn.microsoft.com/en-us/library/hh510230.aspx

[23] Linq2Sql: https://msdn.microsoft.com/en-us/library/bb425822.aspx

[24] Dapper: https://github.com/StackExchange/dapper-dot-net

[25] POCOs: https://en.wikipedia.org/wiki/Plain_Old_CLR_Object

[26] MiniProfiler: http://miniprofiler.com/

[27] Exceptional: https://github.com/NickCraver/StackExchange.Exceptional

[28] Jil: https://github.com/kevin-montrose/Jil

[29] Sigil: https://github.com/kevin-montrose/sigil

[30] Opserver: https://github.com/opserver/Opserver/tree/overhaul

[31] Bosun: http://bosun.org/

[32] Источник: https://habrahabr.ru/post/278391/