Хранение чего-либо в сеансе ASP.NET вызывает задержки 500 мс
Эта проблема:
Когда вы используете сеанс на сайте ASP.NET, это приводит к значительным задержкам (кратным 500 мс) при загрузке нескольких запросов почти одновременно.
Конкретнее, моя проблема
Наш сайт использует сессию исключительно для SessionId. Мы используем этот ключ для поиска таблицы БД, в которой есть информация о пользователе и т. Д. Это казалось хорошим дизайном, поскольку в нем хранились минимальные данные сеанса. К сожалению, SessionId изменится, если вы ничего не сохраняете в сессии, поэтому мы храним Session["KeepId"] = 1;
, Этого достаточно, чтобы убедить SessionId не меняться.
На кажущейся не связанной ноте, сайт предоставляет настроенные изображения продукта через действие контроллера. Это действие генерирует изображение, если необходимо, затем перенаправляет на кэшированное изображение. Это означает, что одна веб-страница может содержать изображения или другие ресурсы, отправляя десятки запросов через конвейер ASP.NET.
По какой-либо причине, когда вы (а) отправляете несколько запросов одновременно и (б) имеете сессию, вовлеченную каким-либо образом, вы в конечном итоге получите случайные задержки в 500 мс примерно в 1/2 раза. В некоторых случаях эти задержки будут 1000 мс или более, всегда увеличиваясь с интервалами ~500 мс. Больше запросов означает больше времени ожидания. Наша страница с десятками изображений может ждать более 10 секунд для некоторых изображений.
Как воспроизвести проблему:
- Создайте пустое веб-приложение ASP.NET MVC
Создайте пустое действие контроллера:
public class HomeController : Controller { public ActionResult Test() { return new EmptyResult(); } }
Создайте страницу test.html с несколькими тегами img, которые выполняют это действие:
<img src="Home/Test?1" /> <img src="Home/Test?2" /> <img src="Home/Test?3" /> <img src="Home/Test?4" />
Запустите страницу и посмотрите быстрое время загрузки в firebug:
Выполните одно из следующих действий (оба имеют одинаковый результат):
Добавьте пустой обработчик Session_Start в ваш Global.asax.cs
public void Session_Start() { }
Положить что-то в сессию
public class HomeController : Controller { public ActionResult Test() { HttpContext.Current.Session["Test"] = 1; return new EmptyResult(); } }
Запустите страницу еще раз и обратите внимание на случайные / случайные задержки в ответах.
Что я знаю до сих пор
- Это происходит с MVC 2 & 3 и WebForms
- Это происходит с любым браузером (мы пробовали FF, Chrome, IE, Safari)
- Это происходит независимо от того, подключен ли отладчик. Кроме того, ничто не должно быть фактически сохранено в сеансе (см. Пример Session_Start выше).
- Это происходит с WebDev.exe, IIS Express и IIS
Мой вопрос
Как я могу использовать сессию, но избежать этих задержек? Кто-нибудь знает об исправлении?
Если нет исправления, я думаю, что нам придется обойти сессию и использовать куки напрямую ( сессия использует куки).
6 ответов
Как упоминал Гатс, проблема в том, что ASP.NET блокирует сеанс, поэтому каждый запрос на один и тот же сеанс должен выполняться последовательно.
Раздражает то, что если бы я выполнил все 5 запросов последовательно (из примера), это заняло бы ~40 мс. С блокировкой ASP.NET это более 1000 мс. Похоже, ASP.NET говорит: "Если сеанс используется, то поспите 500 мс и повторите попытку".
Если вместо InProc вы используете StateServer или SqlServer, это не поможет - ASP.NET по-прежнему блокирует сеанс.
Есть несколько разных исправлений. Мы закончили тем, что использовали первый.
Используйте куки вместо
Файлы cookie отправляются в заголовке каждого запроса, поэтому вы должны держать его в поле зрения и избегать конфиденциальной информации. Тем не менее, сессия использует куки по умолчанию, чтобы запомнить кто есть кто, сохраняя строку ASPNET_SessionId. Все, что мне было нужно, - это идентификатор, поэтому нет смысла терпеть блокировку сеансов ASP.NET, когда это просто оболочка вокруг идентификатора в cookie.
Таким образом, мы полностью избегаем сеанса и вместо этого храним файл cookie в файле cookie. Файлы cookie не блокируются, поэтому задержки устранены.
Использовать атрибуты MVC
Вы можете использовать сессию, но избегайте блокировок определенных запросов, делая сессию только для чтения.
Для приложений MVC 3 используйте этот атрибут на контроллере, чтобы сделать сеанс доступным только для чтения (он не работает для определенного действия):
[SessionState(SessionStateBehavior.ReadOnly)]
Отключить сеанс для некоторых маршрутов
Вы также можете отключить сеанс через маршрутизацию MVC, но это немного сложно.
Отключить сеанс на определенной странице
Для веб-форм вы можете отключить сеанс для определенных страниц ASPX.
Я взглянул на структуру ASP.NET. Класс, в котором управляется состояние сеанса, определяется здесь: http://referencesource.microsoft.com/
Вот как это работает (упрощенный код):
LOCKED_ITEM_POLLING_INTERVAL = 500ms;
LOCKED_ITEM_POLLING_DELTA = 250ms;
bool GetSessionStateItem() {
item = sessionStore.GetItem(out locked);
if (item == null && locked) {
PollLockedSession();
return false;
}
return true;
}
void PollLockedSession() {
if (timer == null) {
timer = CreateTimer(PollLockedSessionCallback, LOCKED_ITEM_POLLING_INTERVAL);
}
}
void PollLockedSessionCallback() {
if (DateTime.UtcNow - lastPollCompleted >= LOCKED_ITEM_POLLING_DELTA) {
isCompleted = GetSessionStateItem();
lastPollCompleted = DateTime.UtcNow;
if(isCompleted) {
ResetPollTimer();
}
}
}
Сводка: если элемент сеанса не может быть получен, поскольку он заблокирован другим потоком, будет создан таймер. Он будет регулярно объединять сессии, чтобы попытаться получить элемент снова (каждые 500 мс по умолчанию). Как только элемент был успешно извлечен, таймер очищается. Кроме того, существует проверка, чтобы убедиться, что между вызовами GetSessionStateItem() имеется заданная задержка (LOCKED_ITEM_POLLING_DELTA
= 250 мс по умолчанию).
Можно изменить значение по умолчанию LOCKED_ITEM_POLLING_INTERVAL
путем создания следующего ключа в реестре (это повлияет на все сайты, работающие на компьютере):
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET
SessionStateLockedItemPollInterval (this is a DWORD)
Другой способ (который является взломом) - изменить значение с помощью отражения:
Type type = typeof(SessionStateModule);
FieldInfo fieldInfo = type.GetField("LOCKED_ITEM_POLLING_INTERVAL",
BindingFlags.NonPublic | BindingFlags.Static);
fieldInfo.SetValue(null, 100); //100ms
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: точные последствия снижения этого значения неизвестны. Это может увеличить конфликты потоков на сервере, создать потенциальные взаимоблокировки и т. Д. Лучшее решение - избегать использования состояния сеанса или украшать ваш контроллер с помощью SessionStateBehavior.ReadOnly
атрибут, как предложили другие пользователи.
Есть несколько способов ускорить сеанс в ASP.NET. Прежде всего несколько ключевых моментов:
Сеанс является поточно-ориентированным, что означает, что он не очень хорошо работает с параллельными клиентскими запросами, которым необходим доступ к нему Если вам нужны асинхронные процессы для доступа к данным типа сеанса, вы можете использовать какую-либо другую форму временного хранилища, например, кеш или вашу базу данных (для асинхронного отслеживания прогресса это необходимо). В противном случае информация о сеансе пользователя будет заблокирована в контексте запроса, чтобы избежать проблем с его изменением. Следовательно, почему запрос 1 в порядке, тогда следующие запросы имеют задержку. PS это абсолютно необходимо, чтобы гарантировать целостность данных в сеансе... как заказы пользователей в электронной коммерции.
Сеанс сериализуется и выходит на сервер. Файл cookie используется для поддержки сеанса, хотя это другой тип файлов cookie, поэтому я не ожидаю большой разницы в производительности.
Поэтому мой любимый способ ускорить сеансовые запросы - всегда использовать сервер состояний. Это устраняет проблемы, возникающие в процессе, а также означает, что при тестировании вы можете перестроить свой проект без необходимости повторного входа на свой сайт. Это также делает возможным проведение сеансов в простых сценариях балансировки нагрузки. http://msdn.microsoft.com/en-us/library/ms972429.aspx
PS Я должен добавить, что технически состояние службы вне процесса должно быть медленнее. По моему опыту, это быстрее и проще в разработке, но я думаю, это зависит от того, как он используется.
Я просто хотел добавить немного деталей к отличному ответу @bendytree. При использовании WebForms вы не можете полностью отключить только состояние сеанса
<%@ Page EnableSessionState="False" %>
но также установите его только для чтения:
<%@ Page EnableSessionState="ReadOnly" %>
Это решило описанную проблему в моем случае. Смотрите документацию
просто интересно, может ли кто-нибудь ответить на мою проблему, которая кажется связанной с вышеизложенным.
Мы используем Redis в качестве поставщика состояния сеанса и наблюдаем точно такое же поведение:
Обратите внимание, что мы уже сократили LOCKED_ITEM_POLLING_INTERVAL до 100 мс, а также украсили контроллер поиска атрибутом [SessionState(SessionStateBehavior.ReadOnly)]. Тем не менее, проблема сохраняется.
Есть идеи, что еще мы могли бы попробовать? Оранжевые линии — это команды REDIS, первая — GET, а затем все последующие EVAL.
Since problem is related to parallel calls. First check if that can be reduced specially if its hitting some API's ( calling some stored procs usually ). Try to combine the results into single DB call, optimized with a single return result. So that single call from the web page can avoid causing session locking issues.
Another option to be tried via these settings
If you are using the updated Microsoft.Web.RedisSessionStateProvider(starting from 3.0.2) you can add this to your web.config to allow concurrent sessions.
<appSettings>
<add key="aspnet:AllowConcurrentRequestsPerSession" value="true"/>
</appSettings>
или Убедитесь, что используется более новый модуль состояния сеанса:
<system.webServer>
<modules>
<!-- remove the existing Session state module -->
<remove name="Session" />
<add name="Session" preCondition="integratedMode" type="Microsoft.AspNet.SessionState.SessionStateModuleAsync, Microsoft.AspNet.SessionState.SessionStateModule, Version=1.1.0.0, Culture=neutral" />
</modules>
</system.webServer>
И, наконец, код от Microsoft для реализации пользовательского модуля состояния сеанса, который хранит информацию о сеансе в памяти с использованием хэш-таблицы.