Мировые информационные ресурсы

Способы увеличения производительности

Показывать лекцию целиком

Общие вопросы производительности веб-приложений

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

При работе веб-приложения существенное влияние на производительность оказывают две составляющие:

  • программный код самого веб-сервера;
  • программный код прикладного приложения.
  • Приложения ASP.NET работают в рамках веб-сервера Internet Information Services, который поставляется в составе семейства операционных систем Windows Server (IIS). Актуальная версия этого веб-сервера – IIS7 – содержит ряд механизмов по увеличению производительности приложений.

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

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

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

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

    Наиболее частые проблемы веб-приложений, которые снижают производительность приложения:

  • частые и сложные обращения к базе данных в процессе обработки запроса;
  • выполнение сложного программного кода (например, вычислительных операций), который существенно загружает центральный процессор.
  • Сложность этих проблем заключается в том, что иногда логика приложения просто не может не обращаться к базе данных и не исполнять сложный программные код, при этом исключить эти этапы нельзя. Запросы к базе данных можно оптимизировать только отчасти, при этом они все равно могут сильно снижать производительность приложения.

    Для решения этой задачи можно использовать кэширование данных на стороне сервера. Идея этого подхода заключается в том, что при первом обращении к странице сложный программный код обрабатывается и результат сохраняется в специальном участке памяти – кэше. При обработке последующих запросов, сервер не выполняет сложных и длительных операций, а изымает данные из кэша и передает их клиенту. Зачастую данные изменяются не настолько быстро, поэтому для пользователя этот процесс остается прозрачным, а производительность приложения при этом существенно возрастает. Схематически этот процесс представляется следующим образом.

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

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

    Краткие итоги

    Производительность веб-приложений зависит от реализации веб-сервера и программного кода приложения. Веб-сервер IIS7 содержит ряд механизмов для увеличения производительности приложений. Например, вместо создания потока на каждый HTTP-запрос используется пул готовых потоков. Для увеличения производительности приложения также может использоваться кэширование данных на стороне сервера. Платформа ASP.NET содержит мощный механизм кэширования, который позволяет реализовать этот сценарий.

    Кэширование выходного потока

    Кэширование – это сохранение результата во временном кэше с целью использования этого результата впоследствии при обработке последующих HTTP-запросов. Одной из разновидностей кэширования в ASP.NET является кэширование выходного потока, т.е. тех данных, которые передаются клиенту после обработки запроса. Таким образом, кэширование выходного потока позволяет задать режим, при котором вся информация, которая отдается клиенту (т.е. HTML) будет сохраняться в кэше. При последующих обращениях к этой же странице, HTML-код этой страницы будет изъят из кэша, при этом код страницы запущен не будет. Такой способ определения кэширования наиболее простой с точки зрения реализации. Для задания режима кэширования выходного потока нужно воспользоваться директивой страницы OutputCache, указав ее в разметке страницы. После этого, будет включен режим кэширования выходного потока.

    Определение директивы OutputCache в странице выглядит следующим образом.

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

    Код страницы выглядит при этом следующим образом.

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

    У директивы OutputCache обычно задаются два параметра – время жизни данных в кэше (Duration) и условие нахождения данных в кэше (одно из набора – VaryByParam, VaryByHeader, VaryByControl или VaryByCustom).

    Параметр Duration определяет время жизни данных в кэше в секундах. Этот параметр связан с устареванием данных в кэше. Страница будет находиться в кэше ровно столько времени, сколько указано в этом параметре. Это означает, что при указании значения 60, кэш будет очищен (и страница обновлена) только по истечении 60 секунд после первого обращения.

    При задании параметра VaryByParam указывается зависимость нахождения страницы в кэша от параметров, которые задаются в адресной строке (в случае использования HTTP-метода GET). Если значение параметра VaryByParam равно "none", то при любых значениях параметров в кэше в один момент времени содержится только одна копия страницы. При указании в этом параметре имени какого-либо параметра запроса, для каждого различного значения этого параметра создается отдельная копия страницы в кэше; также возможно указать несколько параметров. При указании данному параметру значения "*" учитываются все параметры, которые присутствуют в GET-запросе.

    Например, если значение параметра VaryByParam равно "none", то при обращении к странице с любыми параметрами "Default.aspx?param=1", "Default.aspx?param=2", "Default.aspx?param=5" и т.д. в кэше будет создана единственная копия страницы, которая будет возвращаться каждый раз, пока не истечет время жизни, указанное в параметре Duration. Если в параметре VaryByParam указать "param" или "*", то при каждом различном значении параметра будет создана отдельная копия страницы в кэше.

    Параметры VaryByHeader, VaryByControl и VaryByCustom работают аналогично, за тем исключением, что в них используются другие данные:

  • при задании VaryByHeader учитывается значение HTTP-заголовоков;
  • при задании VaryByControl учитывается состояние элемента управления;
  • при задании VaryByCustom используется собственный метод для определения кэширования, который должен возвращать строку. Другими словами, используя параметр VaryByCustom можно определить собственный алгоритм задания условия, в каких случаях создавать новую копию страницы в кэше. Например, в этом методе можно определить, что условием будет текущий web-браузер.
  • Не сложно догадаться, что при кэшировании выходного потока кэшируется вся страница целиком. Этот факт может стать проблемой, если на странице кроме кэшируемых данных содержатся данные, которые должны обновляться чаще, чем все остальное содержимое. В этом случае проблему можно решить двумя способами – использованием фрагментарного кэширования или послекэшевых подстановок.

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

    Механизм послекэшевых подстановок подразумевает обратный фрагментарному кэшированию подход. В этом случае кэшируется вся страница, и лишь небольшая ее часть обновляется всегда. Для этих целей используется элемент управления Substitution, который имеет параметр MethodName. В этом параметре указывается имя статического метода, который исполняется всегда, даже когда страница находится в кэше. Результат выполнения данного метода помещается в ту часть страницы, где находится элемент управления Substitution. Метод объявляется статическим, т.к. если страница находится в кэше, то объект класса Page не создается, и обработка страницы не запускается. Этот метод имеет следующий вид.

    Кэширование выходного потока в приложениях ASP.NET MVC Framework реализуются аналогичным образом. Однако, вместо использования директивы страницы, используется фильтр ASP.NET MVC Framework – OutputCache.

    Параметры фильтра OutputCache аналогичны параметрам директивы страницы OutputCache.

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

    Краткие итоги

    Кэширование выходного потока – это одна из разновидностей кэширования, которая доступна в рамках ASP.NET. В случае кэширования выходного потока в кэше сохраняется результат генерации всей страницы. Для определения кэширования выходного потока используется директива страницы OutputCache, в которой задаются параметры кэширования. Если требуется кэшировать только часть страницы, то можно использовать фрагментарное кэширование (выделить часть страницы в элемент управления на основе UserControl и определить параметры кэширования для него) и послекэшевые подстановки. Для приложений на базе ASP.NET MVC Framework для определения кэширования используется фильтр OutputCache, который работает аналогичным образом.

    Кэширование данных

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

    Для кэширования данных используется глобальный объект Cache. Этот объект представляет собой ассоциативную коллекцию "ключ – значение". Это означает, что данные, которые содержатся в кэше всегда ассоциированы с некоторым ключом, который выбирается разработчиком приложения. Этот ключ также необходим при попытке получения данных из кэша. Следующий фрагмент кода демонстрирует простой пример работы с объектом Cache.

    В случае если необходимые данные отсутствуют в кэше, то обращение к объекту Cache с заданным ключом вернет значение "null". Поэтому, стандартная логика работы с кэшем сводится к следующему алгоритму: в момент загрузки страницы проверяется содержимое кэша по нужному ключу, если нужные данные содержаться в кэше, то они отображаются пользователю; если данных в кэше нет, то производится выполнение операции по получении данных и сохранение их в кэш. Этот сценарий может быть реализован следующим образом.

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

    Как видно, первые два параметра (key и value) по аналогии с приведенными ранее примерами задают ключ и кэшируемый объект.

    Следующим параметром (CacheDependency) задаются зависимости кэша. Зависимости предназначены для борьбы с устареванием данных в кэше. Зависимости – это специальные объекты .NET Framework, которые отслеживают внешние изменения данных и, если это произошло, удаляют текущий ключ вместе с данными из кэша. В стандартной поставке ASP.NET можно задать зависимости кэша на таблицу в базе данных, файл, папку или другой ключ кэша. При этом если объект (например, файл), на который указывает зависимость изменится или удалится, то данный ключ также немедленно удалится из кэша. Кроме того, используя класс AgregateCacheDependency можно устанавливать агрегатные зависимости, т.е. зависимости на несколько источников.

    Следующие два параметра (absTime и slidingTime) задают время жизни данных в кэше. Первый параметр предназначен для задания абсолютного времени, когда данный ключ должен быть удален из кэша. Примером такого значения может быть "10 января 2009 года, 11 часов 13 минут 00 секунд". Второй параметр задает "скользящее" время жизни ключа в кэше. Например, это значением может равняться 30 секундам; при этом если в течении указанного времени происходит обращение к этому ключу, то время жизни ключа в кэше снова устанавливается равным 30 секундам и т.д. Таким образом, данный ключ будет удален из кэша, если в течение всего этого времени не было ни одного обращения к данному ключу в кэше. В конкретном случае должен использоваться только один из предложенных параметров определения времени жизни данных в кэше. При этом другой параметр должен быть равен либо значению Cache.NoAbsoluteExpiration, либо Cache.NoSlidingExpiration соответственно (в зависимости от того, какой параметр используется).

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

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

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

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

    Таким образом, объект Cache позволяет реализовать сложные сценарии кэширования данных на стороне сервера, без необходимости кэшировать выходной поток (генерируемый код HTML).

    Краткие итоги

    Кэширование данных – альтернативный подход к кэшированию, реализованный в рамках ASP.NET. Для кэширования данных используется глобальный объект Cache. Этот объект представляет собой коллекцию "ключ-значение". Каждый кэшируемый объект ассоциируется с уникальным ключом. Для сохранения данных в кэше используется метод Insert, который позволяет определить зависимости кэша, время жизни, приоритет и метод обратного вызова.

    Контрольные вопросы

  • С какими проблемами производительности сталкиваются разработчики веб-приложений?
  • Каким образом проблемы производительности решаются на уровне веб-сервера IIS7?
  • Что такое кэширование?
  • Как кэширование позволяет увеличить производительность приложения?
  • Как решается проблема устаревания данных в кэше?
  • Что такое кэширование выходного потока?
  • Какие параметры имеет директива OutputCache и для чего они предназначены?
  • Что такое фрагментарное кэширование?
  • Что такое послекэшевая подстановка?
  • Чем кэширование данных отличается от кэширования выходного потока?
  • С помощью какого глобального объекта осуществляется кэширование данных?
  • Какие параметры доступны в рамках метода Insert объекта Cache и что они означают?
  • Чем абсолютное время жизни данных в кэше отличается от скользящего?
  • Что такое зависимости кэша?
  • Для чего необходим приоритет при добавлении данных в кэш?
  • Для чего необходим метод обратного вызова при добавлении данных в кэш? Приведите примеры использования.
  • Вернуться к учебному плану