Я только что обнаружил, почему все сайты ASP.Net работают медленно, и я пытаюсь понять, что с этим делать. - PullRequest
269 голосов
/ 02 сентября 2010

Я только что обнаружил, что каждый запрос в веб-приложении ASP.Net получает блокировку сеанса в начале запроса, а затем освобождает ее в конце запроса!

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

  • Каждый раз, когда загрузка веб-страницы ASP.Net занимает много времени (возможно, из-за медленнойвызов базы данных или что-то еще), и пользователь решает, что он хочет перейти на другую страницу, потому что он устал ждать, ОНИ НЕ МОГУТ!Блокировка сеанса ASP.Net заставляет запрос новой страницы ждать, пока исходный запрос завершит свою мучительно медленную загрузку.Arrrgh.

  • Каждый раз, когда UpdatePanel загружается медленно, и пользователь решает перейти на другую страницу, прежде чем UpdatePanel закончит обновление ... ОНИ НЕ МОГУТ!Блокировка сеанса ASP.net заставляет запрос новой страницы ждать, пока исходный запрос завершит свою мучительно медленную загрузку.Double Arrrgh!

Так какие варианты?До сих пор я придумал:

  • Реализация пользовательского хранилища SessionStateDataStore, которое поддерживает ASP.Net.Я не нашел слишком много там, чтобы скопировать, и это кажется довольно рискованным и легко испортить.
  • Отслеживать все незавершенные запросы, и если запрос поступает от того же пользователя,отменить первоначальный запрос.Кажется, что это нечто экстремальное, но это сработает (я думаю).
  • Не используйте сессию!Когда мне нужно какое-то состояние для пользователя, я мог бы вместо этого просто использовать Cache и ключевые элементы для аутентифицированного имени пользователя, или что-то подобное.Опять же, кажется, что это нечто экстремальное.

Я действительно не могу поверить, что команда ASP.Net Microsoft оставила бы такое огромное узкое место в производительности в версии 4.0!Я что-то упускаю из виду?Насколько сложно было бы использовать коллекцию ThreadSafe для сессии?

Ответы [ 9 ]

199 голосов
/ 02 сентября 2010

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

<% @Page EnableSessionState="ReadOnly" %>

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

<% @Page EnableSessionState="False" %>

Если ни одна из ваших страниц не использует переменные сеанса, просто отключите состояние сеанса в файле web.config.

<sessionState mode="Off" />

Мне интересно, что делатьВы думаете, что «коллекция ThreadSafe» могла бы стать поточно-ориентированной, если бы она не использовала блокировки?

Редактировать: Я, вероятно, должен объяснить, что я подразумеваю как «отказаться от большей части этой блокировки».Любое количество страниц только для чтения или без сеанса может быть обработано для данного сеанса одновременно, не блокируя друг друга.Однако страница чтения-записи-сеанса не может начать обработку до тех пор, пока не будут выполнены все запросы только для чтения, и во время ее работы она должна иметь монопольный доступ к сеансу этого пользователя, чтобы поддерживать согласованность.Блокировка отдельных значений не будет работать, потому что, если одна страница изменит набор связанных значений в группе?Как бы вы обеспечили, чтобы другие страницы, работающие одновременно, получали согласованное представление переменных сеанса пользователя?

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

82 голосов
/ 07 сентября 2010

Ладно, такой большой реквизит Джоэлю Мюллеру за весь его вклад.Моим конечным решением было использование Custom SessionStateModule, подробно описанного в конце этой статьи MSDN:

http://msdn.microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx

Это было:

  • Очень быстрореализовать (на самом деле казалось проще, чем идти по пути провайдера)
  • Использовал много стандартной обработки сеанса ASP.Net "из коробки" (через класс SessionStateUtility)

Это имеетсделал огромную разницу в ощущении "привязанности" к нашему приложению.Я все еще не могу поверить, что пользовательская реализация ASP.Net Session блокирует сеанс для всего запроса.Это добавляет огромное количество медлительности на веб-сайты.Судя по количеству онлайн-исследований, которые мне пришлось провести (и разговорам с несколькими действительно опытными разработчиками ASP.Net), многие люди сталкивались с этой проблемой, но очень мало людей когда-либо понимали причину проблемы.Может быть, я напишу письмо Скотту Гу ...

Надеюсь, это поможет нескольким людям!

31 голосов
/ 03 марта 2012

Я начал использовать AngiesList.Redis.RedisSessionStateModule , который помимо использования (очень быстрого) сервера Redis для хранения (я использую порт Windows * - хотя есть также порт MSOpenTech ), он не блокирует сеанс.

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

MS решает, что каждый сеанс ASP.NET должен быть заблокирован по умолчанию только для обработки плохих данных.дизайн приложения - плохое решение, на мой взгляд.Тем более что кажется, что большинство разработчиков не понимают / даже не осознают, что сеансы были заблокированы, не говоря уже о том, что приложения, очевидно, должны быть структурированы, чтобы вы могли выполнять состояние сеанса только для чтения в максимально возможной степени (отказаться, где это возможно),

20 голосов
/ 10 августа 2014

Я подготовил библиотеку на основе ссылок, размещенных в этой теме.Он использует примеры из MSDN и CodeProject.Благодаря Джеймсу.

Я также внес изменения, рекомендованные Джоэлем Мюллером.

Код здесь:

https://github.com/dermeister0/LockFreeSessionState

Модуль HashTable:

Install-Package Heavysoft.LockFreeSessionState.HashTable

Модуль ScaleOut StateServer:

Install-Package Heavysoft.LockFreeSessionState.Soss

Пользовательский модуль:

Install-Package Heavysoft.LockFreeSessionState.Common

ЕслиВы хотите реализовать поддержку Memcached или Redis, установите этот пакет.Затем наследуйте класс LockFreeSessionStateModule и реализуйте абстрактные методы.

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

Некоторые поставщики сеансов без блокировки, использующие Redis:

11 голосов
/ 02 мая 2013

Если ваше приложение не имеет особых потребностей, я думаю, что у вас есть 2 подхода:

  1. Не использовать сессию вообще
  2. Используйте сессию как есть и выполняйте точную настройку, как упомянуто Джоэл.

Сеанс не только поточно-ориентированный, но и сохраняющий состояние, так что вы знаете, что до тех пор, пока текущий запрос не будет завершен, каждая переменная сеанса не изменится от другого активного запроса. Чтобы это произошло, вы должны убедиться, , что сеанс БУДЕТ БЛОКИРОВАН * до тех пор, пока текущий запрос не будет завершен.

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

Для конкретных проблем, которые вы упомянули, я думаю, вы должны проверить HttpContext.Current.Response.IsClientConnected . Это может быть полезно для предотвращения ненужных выполнений и ожидания на клиенте, хотя это не может полностью решить эту проблему, поскольку это может использоваться только путем объединения, а не асинхронно.

6 голосов
/ 20 февраля 2019

Если вы используете обновленный Microsoft.Web.RedisSessionStateProvider (начиная с 3.0.2), вы можете добавить его в web.config, чтобы разрешить одновременные сеансы.

<appSettings>
    <add key="aspnet:AllowConcurrentRequestsPerSession" value="true"/>
</appSettings>

Источник

3 голосов
/ 06 июля 2017

Пометка состояния сеанса контроллера как только для чтения или отключено решит проблему.

Вы можете украсить контроллер следующим атрибутом, чтобы отметить его только для чтения:

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]

перечисление System.Web.SessionState.SessionStateBehavior имеет следующие значения:

  • По умолчанию
  • Disabled
  • ReadOnly
  • Обязательно
3 голосов
/ 09 февраля 2017

Для ASPNET MVC мы сделали следующее:

  1. По умолчанию установите SessionStateBehavior.ReadOnly на все действия контроллера, переопределив DefaultControllerFactory
  2. На действиях контроллера, для которых требуется запись в состояние сеанса, отметьте атрибутом, чтобы установить его на SessionStateBehaviour.Required

Создание собственной ControllerFactory и переопределение GetControllerSessionBehaviour.

    protected override SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, Type controllerType)
    {
        var DefaultSessionStateBehaviour = SessionStateBehaviour.ReadOnly;

        if (controllerType == null)
            return DefaultSessionStateBehaviour;

        var isRequireSessionWrite =
            controllerType.GetCustomAttributes<AcquireSessionLock>(inherit: true).FirstOrDefault() != null;

        if (isRequireSessionWrite)
            return SessionStateBehavior.Required;

        var actionName = requestContext.RouteData.Values["action"].ToString();
        MethodInfo actionMethodInfo;

        try
        {
            actionMethodInfo = controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        }
        catch (AmbiguousMatchException)
        {
            var httpRequestTypeAttr = GetHttpRequestTypeAttr(requestContext.HttpContext.Request.HttpMethod);

            actionMethodInfo =
                controllerType.GetMethods().FirstOrDefault(
                    mi => mi.Name.Equals(actionName, StringComparison.CurrentCultureIgnoreCase) && mi.GetCustomAttributes(httpRequestTypeAttr, false).Length > 0);
        }

        if (actionMethodInfo == null)
            return DefaultSessionStateBehaviour;

        isRequireSessionWrite = actionMethodInfo.GetCustomAttributes<AcquireSessionLock>(inherit: false).FirstOrDefault() != null;

         return isRequireSessionWrite ? SessionStateBehavior.Required : DefaultSessionStateBehaviour;
    }

    private static Type GetHttpRequestTypeAttr(string httpMethod) 
    {
        switch (httpMethod)
        {
            case "GET":
                return typeof(HttpGetAttribute);
            case "POST":
                return typeof(HttpPostAttribute);
            case "PUT":
                return typeof(HttpPutAttribute);
            case "DELETE":
                return typeof(HttpDeleteAttribute);
            case "HEAD":
                return typeof(HttpHeadAttribute);
            case "PATCH":
                return typeof(HttpPatchAttribute);
            case "OPTIONS":
                return typeof(HttpOptionsAttribute);
        }

        throw new NotSupportedException("unable to determine http method");
    }

AcquireSessionLockAttribute

[AttributeUsage(AttributeTargets.Method)]
public sealed class AcquireSessionLock : Attribute
{ }

Подключить созданную фабрику контроллеров в global.asax.cs

ControllerBuilder.Current.SetControllerFactory(typeof(DefaultReadOnlySessionStateControllerFactory));

Теперь мы можем иметь как read-only, так и read-write состояние сеанса в одном Controller.

public class TestController : Controller 
{
    [AcquireSessionLock]
    public ActionResult WriteSession()
    {
        var timeNow = DateTimeOffset.UtcNow.ToString();
        Session["key"] = timeNow;
        return Json(timeNow, JsonRequestBehavior.AllowGet);
    }

    public ActionResult ReadSession()
    {
        var timeNow = Session["key"];
        return Json(timeNow ?? "empty", JsonRequestBehavior.AllowGet);
    }
}

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

0 голосов
/ 30 сентября 2016

Просто чтобы помочь кому-нибудь с этой проблемой (блокировка запросов при выполнении другого из того же сеанса) ...

Сегодня я начал решать эту проблему и, после нескольких часов исследований, решил ее с помощьюудаление метода Session_Start (даже если он пуст) из файла Global.asax .

Это работает во всех протестированных мной проектах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...