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

233

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

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

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

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

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

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

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

  • 7
    Хорошо, так что я был немного шутлив со своим названием. Тем не менее, ИМХО, потрясение производительности, которое навязывает стандартная реализация сеанса, поражает. Кроме того, могу поспорить, что ребятам из Stack Overflow пришлось сделать немало нестандартных разработок, чтобы добиться производительности и масштабируемости, которых они достигли, и похвалы им. Наконец, Stack Overflow - это приложение MVC, а не WebForms, что, я уверен, помогает (хотя по-прежнему используется та же инфраструктура сеансов).
  • 21
    -1: начинается с ложной предпосылки
Показать ещё 5 комментариев
Теги:
architecture
performance
session
iis

9 ответов

67
Лучший ответ

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

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

Это было:

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

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

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

  • 18
    Эта ссылка является интересной находкой, но я должен предупредить вас о нескольких вещах - у примера кода есть некоторые проблемы: во-первых, ReaderWriterLock устарел в пользу ReaderWriterLockSlim - вы должны использовать его вместо этого. Во-вторых, lock (typeof(...)) также устарела - вместо этого следует блокировать частный экземпляр статического объекта. В-третьих, фраза «Это приложение не запрещает одновременным веб-запросам использовать один и тот же идентификатор сеанса» является предупреждением, а не функцией.
  • 1
    Представьте, что PageA и PageB работают одновременно в одном сеансе пользователя. Обе страницы изменяют переменные сеанса. PageA заканчивается немного раньше, чем PageB. Используя этот пользовательский модуль состояния сеанса, что происходит, когда обе страницы изменяют одну и ту же переменную сеанса одновременно? Этот модуль передает одну и ту же SessionStateItemCollection на обе страницы. Этот класс задокументирован как не поддерживающий потоки. Плохие вещи возникают под нагрузкой.
Показать ещё 12 комментариев
176

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

<% @Page EnableSessionState="ReadOnly" %>

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

<% @Page EnableSessionState="False" %>

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

<sessionState mode="Off" />

Мне любопытно, как вы думаете, что "коллекция ThreadSafe" сделает, чтобы стать потокобезопасной, если она не использует блокировки?

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

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

  • 2
    Привет, Джоэл. Спасибо, что уделили время на этот ответ. Это хорошие предложения и пища для размышлений. Я не понимаю ваших аргументов в пользу того, что все значения для сеанса должны быть заблокированы исключительно по всему запросу. Значения ASP.Net Cache могут быть изменены в любое время любым потоком. Почему это должно быть иначе для сессии? Кроме того, у меня есть одна проблема с опцией readonly: если разработчик добавляет значение в сеанс, когда он находится в режиме только для чтения, он молча завершается неудачей (без исключения). Фактически он сохраняет значение для остальной части запроса - но не за его пределами.
  • 5
    @James - Я просто догадываюсь о мотивации дизайнеров, но я думаю, что более распространено, когда несколько значений зависят друг от друга в сеансе одного пользователя, а не в кеше, который можно очистить из-за отсутствия использования или низкого причины памяти в любое время. Если одна страница устанавливает 4 связанных переменных сеанса, а другая читает их после того, как были изменены только две, это может легко привести к ошибкам, которые трудно диагностировать. Я полагаю, что дизайнеры решили рассматривать «текущее состояние сеанса пользователя» как единое целое для целей блокировки по этой причине.
Показать ещё 5 комментариев
29

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

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

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

17

Я подготовил библиотеку на основе ссылок, размещенных в этом потоке. Он использует примеры из 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:

  • 0
    Требуются библиотеки из решения ScaleOut, что не бесплатно?
  • 1
    Да, я создал реализацию только для SOSS. Вы можете использовать упомянутых провайдеров сеансов Redis, это бесплатно.
Показать ещё 5 комментариев
11

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

  • Не используйте сеанс вообще
  • Использовать сеанс как есть и выполнять точную настройку, как упомянуто joel.

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

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

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

2

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

  • По умолчанию установите SessionStateBehavior.ReadOnly на все действия контроллера, переопределив DefaultControllerFactory
  • В отношении действий контроллера, которые требуют записи в состояние сеанса, отметьте атрибутом, чтобы установить его на 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
{ }

Подключите созданный контроллер factory в 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 в действиях контроллера, которые требуют записи состояния сеанса.

1

Отмечает состояние сеанса контроллера как readonly или disabled, решает проблему.

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

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

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

  • По умолчанию
  • Отключено
  • ReadOnly
  • Обязательно
0

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

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

Это работает во всех проектах, которые я тестировал.

-4

Измените режим отладки на off в вашем файле web.config.

Разработчики обычно используют его при обработке и создании вашего приложения для ошибок.

Но нам это не нужно при хостинге.

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню