company_banner

Как IIS поддерживает нашу BI-аналитику, и в чем особенности настройки под Highload


    В аналитической части бэкенда Яндекс.Денег активно используется Microsoft IIS, и уже накопился некоторый багаж знаний о его применении в высоконагруженной среде, которым хочется поделиться.


    Наша аналитика работает на стеке Microsoft (SQL Server и продукты SSIS, SSAS, SSRS) – одном из лучших на рынке BI-решений. Раз в основе нашего BI лежат сервисы одного вендора, то логично и для размещения веб-приложений использовать решение Microsoft – IIS.


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


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


    #1 Минимум операций на один запрос, а все тяжелое – на запуск сервиса


    Из обработки пользовательского запроса важно исключать логирование, обращения к другим сервисам и все прочее, что не требуется для ответа. Второстепенные действия можно выполнять в параллельных процессах вне основного потока. Особенно критично соблюдение этого правила, когда у сервиса есть некий SLA ответа.


    Все, что инициализируется «какое-то время», не должно делать этого при поступлении первого запроса от пользователя. Один из наших важнейших сервисов защиты от мошенничества – антифрод – когда-то требовал подгружать весь набор правил при старте каждого нового обработчика запросов.


    Представьте обычный сетевой брандмауэр, который при поступлении нового пакета пропускает его содержимое через длинный набор правил. Только в случае с антифродом количество правил и условий в них достигли таких объемов, что подгружать все это каждый раз при старте очередного потока обработки невозможно без значительного снижения отзывчивости сервиса. В пиковых нагрузках антифрода загрузка процессоров даже доходила до 100% на протяжении нескольких минут.

    Кроме того, долгая инициализация чревата каскадной деградацией производительности. Когда приходит первый запрос, антифрод создает под него отдельный обработчик и загружает туда весь список правил безопасности, а пользователь любуется предложением немного подождать.



    Прежний процесс ответа на запросы пользователей при нехватке ресурсов готовых обработчиков.


    Теперь представьте, что, пока загрузка правил для предыдущего обработчика не завершилась, прилетает запрос на перевод от других пользователей. Аналогичная операция запускается для каждого из них, и таких дублирующихся инициализаций может быть довольно много. Все это приводило к периодическим задержкам интерфейса в 30 секунд и более.


    Поэтому объект с правилами сделали единственным и повесили на запуск вместе с основным сервисом. Теперь обработчики запускаются за пару миллисекунд и не влияют на общую производительность сервера (всплески в пределах 10%). Изначально были мысли сделать их запуск заблаговременным, но с учетом быстрого старта каждого обработчика это не оказало бы заметного влияния на производительность.


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


    #2 Не забудьте про связанные запросы и разные узлы


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


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


    1. Использовать общую для всех узлов БД, в которой хранятся необходимые результаты всех запросов. Важный момент в том, что в такой базе хранятся не все запросы, а только те, которые могут пригодиться на других узлах – об этом тоже не стоит забывать при разработке приложения. Архитектурно такая БД легко может стать узким местом, поэтому нужно тщательно следить за ее производительностью. Например, вовремя переходить с обычных на SSD-диски.
    2. Передать эту задачу клиенту. То есть построить его таким образом, чтобы он включал результат предыдущего связанного запроса в последующий.
    3. Наконец, можно построить архитектуру приложения так, чтобы не было двух запросов на один процесс. Нет связанных запросов – нет проблем.

    К примеру, мы хотим узнать при проведении платежа, когда был открыт счет-источник. Для этого потребуется или найти саму операцию открытия счета, или сохранить некоторые ее данные отдельно для последующего использования. В нашем случае используется как раз вариант 1.


    #3 Прогревать или не прогревать


    Приложение должно быть всегда запущено, а сам запуск должен производиться заранее, до появления реальных запросов. Кроме того, сервис уже должен быть «прогрет» тестовыми запросами, чтобы сразу отрабатывать входящие с минимальными задержками.


    Этому в некоторой степени препятствуют настройки IIS по умолчанию, благодаря которым незагруженные процессы периодически останавливаются. Даже если их автоматически запускать при первом же релевантном запросе извне, приложению потребуется время на первичную инициализацию, обработку связанных запросов и прочее.


    Раньше перезапуски веб-приложения происходили раз в 30 минут на одном из узлов. Поэтому мы написали Powershell-скрипт, который снимал нагрузку с сервиса на балансировщике, перезагружал приложение и запускал несколько десятков запросов. После этого скрипт ожидал инициализации и, когда приложение начинало отвечать достаточно быстро, возвращал серверу «боевой» трафик. Однако алгоритм актуален до сих пор, несмотря на сокращение плановых перезапусков.

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


    #4 Правильный выбор популяции потоков


    У IIS есть ограниченный набор служебных потоков для работы с дисками, сетью и пр. Эти потоки используются для обработки большинства пользовательских запросов, и в какой-то момент ресурсов служебных потоков может не хватить.


    В IIS есть две ключевые настройки потоков, которые могут ограничивать производительность:


    • Queue Length – регулирует длину очереди входящих запросов.


    • Worker process – количество независимых копий приложения. Универсального совета по значению нет, поэтому нужно экспериментально подобрать подходящий именно в вашей среде.

    Кроме того, есть параметр Пул подключений к БД, который настраивается в клиенте СУБД на стороне веб-приложения. Здесь нужно очень аккуратно выбирать значения, иначе будет множество незадействованных соединений с базой, на каждое из которых расходуется память.


    Помимо служебных, есть рабочие потоки (WorkerThreads) – как раз те, которые использует серверное приложение для старта новых обработчиков того же антифрода. Этих потоков желательно выдавать ровно столько, сколько требуется для работы. Параметры можно грубо посчитать по следующему алгоритму:


    1. Возьмите из характеристик конкретной системы число запросов в единицу времени. Например, в нашем случае это значение линейно зависит от количества платежей всей платежной системы.
    2. Количество единиц времени на каждый запрос лучше выяснить экспериментально – например, включив протоколирование в приложении. Могут пригодиться и логи IIS – там есть как сами запросы, так и временны́е метки.
    3. Нижняя граница числа потоков получается перемножением п.1 и п.2.

    Если рассчитать не получается, лучше провести эксперимент в тестовой среде, потому что просто выставить максимальные значения – не лучшая идея. Напрасно потратите ресурсы сервера и увеличите время инициализации служб IIS.


    Разумеется, этот алгоритм можно рассматривать только как точку старта для оценки именно вашего приложения. Например, не учтены возможные пики нагрузки в вашей среде. Для полноценной оценки потребуется рассмотреть и множество других параметров, которые явно выходят за рамки этой статьи.


    К слову о ресурсах, стабильной работе 64-битных приложений могут помешать вспомогательные библиотеки, собранные под 32-бита, – вплоть до падения основного приложения. При отладке 64-битных приложений не стоит забывать и о том, что эмулятор IIS из состава Microsoft VisualStudio 2012 (и более ранних) не подойдет, так как он 32-битный и для отладки в 64-битном режиме нужно использовать экземпляр IIS, что чревато проблемами.


    #5 Выкладка приложения на весь кластер


    С точки зрения эксплуатации, для антифрода Яндекс.Денег нужен кластер 1+N, в котором можно вносить изменения только один раз вне зависимости от числа узлов. Поэтому эксперименты в выборе подходящей логики «выкатки» привели к следующему алгоритму:


    1. С одного из узлов снимается нагрузка, после чего устанавливается обновление.
    2. Выполняется проверка работоспособности приложения (результаты системы мониторинга, просмотр логов и т.п.).
    3. Скрипт выполняет удаленный бэкап приложения с соседнего рабочего сервера и распространяет релиз на остальные машины. При этом отключения нагрузки уже не происходит.

    К слову о мониторинге. Для каждого приложения подход, конечно, отличается, но в качестве одного из инструментов у нас используется парсинг логов IIS. Раз в сутки запускается скрипт, который просматривает логи с помощью Microsoft LogParser и формирует отчет со средним временем, количеством и соотношением статусов ответа (500, 200 и т.д.). Сам код не выкладываю, так как слишком многое там добавлено исключительно под реалии нашей системы. Но в качестве основы использовалась идея из статьи LogParser, PowerShell and Quick and dirty parsing of IIS files.

    Важно заранее унифицировать структуру размещения приложений на дисках. То есть располагать сайт в одинаковых папках на каждом сервере – это упростит дальнейшую автоматизацию. Для работы фермы IIS потребуется Shared Config для хранения единой конфигурации IIS для всех узлов, который лучше размещать на локальных дисках каждого сервера с синхронизацией скриптами.


    При размещении Shared Config на сетевой папке, возможны «спецэффекты» – например, IIS не сможет прочесть конфигурацию и погасит все приложения.

    Также может потребоваться хранилище общих данных (в нашем случае – правил антифрода). Для этого удобно использовать синхронизацию DFS: приложение работает с локальной папкой на узле, а данные этой папки синхронизируются по остальным машинам средствами DFS, который еще и конфликты сам обрабатывает. Для большей отказоустойчивости и удобства обновления можно использовать схему синхронизации Many-to-Many.


    Еще пара слов о балансировке


    Ключевой элемент любой распределенной и высоконагруженной системы – балансировщик входящих запросов. Например, запросов от пользователей к фронтенд-системам. Такой балансировщик может быть двух видов:


    1. Без «обратной связи» – внутренний алгоритм балансировщика просто распределяет входящий поток запросов по узлам кластера. Яркий пример – встроенная в Windows Server служба NLB. Она только проверяет, живы ли соседние узлы, и ничего не знает о состоянии приложений. Служба довольно гибка в распределении нагрузки и может управляться из командной строки, но работает только в одной локальной сети.
    2. Более интеллектуальный балансировщик, который оценивает состояние узлов и перераспределяет запросы при каких-то отклонениях. Например, для исходящих запросов от веб-серверов можно использовать Haproxy, который периодически опрашивает все узлы кластера и на основе этих данных распределяет запросы.

    Использование веб-сервера от Microsoft удобно для тех, кто использует стек приложений и разработки этого же производителя. Если вашему приложению не нужны большие структуры данных, то расходы памяти на хостах будут минимальными. Кроме того, бóльшая часть нужных опций и возможностей предоставляются из коробки, что явно проще сборки аналогов на Opensource.


    Если приложение написано на том же .Net – зачем с боем запускать его на «чужой» платформе?

    Яндекс.Деньги 105,17
    Об электронных платежах и устройстве Я.Денег
    Поделиться публикацией
    Комментарии 11
    • +1
      Ад. Ожидал увидеть киллерфичи ииса, а тут плановые перезагрузки
      • 0
        Кажется, заголовок был неоднозначным… Спасибо, подправили.
        • +1
          Так а в чём особенности «особенности настройки под Highload»? Вы даже логи парсите один раз в день судя по статье, если что-то пошло не так на продакшене вы это узнаете на следующий день?
          • 0
            Телеметрию мы смотрим как и все – в Графане. Об этом не стали упоминать, так как статья не про мониторинг.
            Суточный отчет удобен для понимания админами и разработчиками общего состояния и производительности приложений, и анализа их поведения.
            • 0
              Мониторинг это крайне важна часть высоконагруженных систем. Тот же файловый лог легко съест всё место и ещё дисковую подсистему нагнёт. А зачем вам повторно считывать логи если их уже прочитали и положили туда куда умеет ходить графана? Оттуда нельзя выгрузить статистику? И да, про что статья непонятно, никаких настроек iis под высокую нагрузку в статье нет.
      • 0
        А как же .NET Core? Вроде неплохо себя ведёт на Linux.
        • 0
          Его можно тоже в iis хостить. В этом случае iis будет по большей части как прокси выступать.
        • 0
          Для начала синхронизируем наши с вами представления о «хайлоаде». В качестве иллюстрации высоконагруженной системы в статье используется главным образом наша система защиты от мошенничества (антифрод). Она обрабатывает все входящие операции Яндекс.Денег – а это не одна сотня операций в секунду.


          Если можно, не могли бы рассказать о среднем tps на 1 MSSQL сервере? А то у меня в голове как то не укладывается MSSQL и highload :) У нас получалось не более пары сотен к сожалению :(
          • 0
            Если речь именно о tps, то на SQL Server он варьируется от 1000 до 10000 и более. Этому способствует оптимизация БД и запросов к ней, а также не самое слабое железо. Для увеличения производительности в OLTP потребуется высокая частота CPU, как можно больше памяти, в идеале, база должна умещаться в неё, ну и, конечно же, сила SSD.
          • 0
            Не сможет он прочитать и погасит всех когда общая папка была не доступна. Просто нужно иметь кластер на стороне хранилища и не будет этих проблем. Странно иметь шаред конфигурацию которая на каждом сервере своя:)
            • +1
              Локальная конфигурация – принятый у нас подход. В том числе и для 20 экземпляров одного java-сервиса. Конфигурация хранится и изменяется централизованно, и автоматически доставляется в локальные копии при деплое приложения. Это более гибкая и надёжная схема, которая избавляет от необходимости развертывания и поддержки кластера на стороне хранилища, да ещё не в одном датацентре.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое