방금 모든 ASP.Net 웹 사이트가 느린 이유를 발견했으며 그에 대한 조치를 취하려고합니다.


275

방금 ASP.Net 웹 응용 프로그램의 모든 요청이 요청 시작시 세션 잠금을 얻은 다음 요청 끝에서 해제한다는 것을 알았습니다.

처음에 나에게 있었던 것처럼 이것에 대한 의미가 당신에게 상실되는 경우, 이것은 기본적으로 다음을 의미합니다.

  • ASP.Net 웹 페이지를로드하는 데 시간이 오래 걸리는 경우 (데이터베이스 호출이 느리거나 기타로 인해) 사용자는 대기에 지쳐서 다른 페이지로 이동하기로 결정합니다. ASP.Net 세션 잠금은 새 요청이 원래 요청이 느리게로드를 완료 할 때까지 대기하도록합니다. 아아

  • UpdatePanel이 느리게로드 될 때마다 사용자는 UpdatePanel이 업데이트를 마치기 전에 다른 페이지로 이동하기로 결정합니다. ASP.net 세션 잠금은 새 요청이 원래 요청이 느리게로드를 완료 할 때까지 대기하도록합니다. 더블 아아!

옵션은 무엇입니까? 지금까지 나는 생각해 냈습니다.

  • ASP.Net이 지원하는 사용자 정의 SessionStateDataStore를 구현하십시오. 복사 할 곳이 너무 많지 않아 위험이 높고 엉망이되기 쉽습니다.
  • 진행중인 모든 요청을 추적하고 요청이 동일한 사용자로부터 온 경우 원래 요청을 취소하십시오. 극단적 인 것 같지만 작동합니다 (제 생각에).
  • 세션을 사용하지 마십시오! 사용자에게 일종의 상태가 필요할 때 대신 캐시를 사용하고 인증 된 사용자 이름의 키 항목 또는 그와 같은 것을 사용할 수 있습니다. 다시 한 번 극단적 인 것 같습니다.

ASP.Net Microsoft 팀이 버전 4.0의 프레임 워크에서 엄청난 성능 병목 현상을 겪었을 것입니다. 나는 분명한 것을 놓치고 있습니까? 세션에 ThreadSafe 컬렉션을 사용하는 것이 얼마나 어려울까요?


40
이 사이트는 .NET 위에 구축되어 있습니다. 즉, 나는 그것이 아주 잘 확장한다고 생각합니다.
wheaties

7
좋아, 그래서 나는 내 타이틀에 약간의 애국심을 가지고 있었다. 그럼에도 불구하고 IMHO는 즉시 사용 가능한 세션 구현이 놀라운 성능을 발휘한다는 점에서 놀랍습니다. 또한 Stack Overflow 직원은 달성 한 성능과 확장 성을 얻기 위해 고도로 사용자 정의 개발을 수행해야한다고 생각합니다. 마지막으로, 스택 오버플로는 WebForms가 아닌 MVC APP이며 도움이됩니다 (그러나 여전히 동일한 세션 인프라를 사용했지만).
제임스


4
Joel Mueller가 문제를 해결하기위한 정보를 제공 한 경우 그의 답변을 정답으로 표시하지 않은 이유는 무엇입니까? 그냥 생각이야
ars265

1
@ ars265-Joel Muller는 많은 좋은 정보를 제공했으며 그에 대해 감사하고 싶습니다. 그러나 나는 궁극적으로 그의 직책에서 제안한 것과 다른 길을 갔다. 따라서 다른 게시물을 답변으로 표시하십시오.
James

답변:


201

페이지가 세션 변수를 수정하지 않으면 대부분의 잠금을 해제 할 수 있습니다.

<% @Page EnableSessionState="ReadOnly" %>

페이지에서 세션 변수를 읽지 않으면 해당 페이지에 대해이 잠금을 완전히 해제 할 수 있습니다.

<% @Page EnableSessionState="False" %>

페이지에서 세션 변수를 사용하지 않는 경우 web.config에서 세션 상태를 해제하십시오.

<sessionState mode="Off" />

궁금한 점이 있습니다. 잠금을 사용하지 않으면 "ThreadSafe 컬렉션"이 스레드로부터 안전 해지기 위해 어떻게 할 것이라고 생각하십니까?

편집 : 아마 "이 자물쇠의 대부분을 선택 해제"의 의미로 설명해야합니다. 지정된 세션에 대해 서로를 차단하지 않고 여러 읽기 전용 세션 또는 세션 없음 페이지를 동시에 처리 할 수 ​​있습니다. 그러나 읽기 / 쓰기 세션 페이지는 모든 읽기 전용 요청이 완료 될 때까지 처리를 시작할 수 없으며, 실행중인 동안 일관성을 유지하려면 해당 사용자의 세션에 독점적으로 액세스 할 수 있어야합니다. 한 페이지가 관련 값 집합을 그룹으로 변경하면 어떻게됩니까? 개별 값을 잠그면 작동하지 않습니다. 동시에 실행중인 다른 페이지가 사용자의 세션 변수를 일관되게 볼 수 있도록하려면 어떻게해야합니까?

가능하면 세션 변수가 일단 설정되면 수정을 최소화하는 것이 좋습니다. 이를 통해 대부분의 페이지를 읽기 전용 세션 페이지로 만들 수 있으므로 동일한 사용자의 여러 동시 요청이 서로 차단되지 않을 가능성이 높아집니다.


2
안녕 Joel이 답변에 시간을 내 주셔서 감사합니다. 이것들은 좋은 제안과 생각을위한 음식입니다. 세션의 모든 값이 전체 요청에서 독점적으로 잠겨 야한다고 말한 이유를 이해하지 못합니다. ASP.Net 캐시 값은 언제든지 스레드에 의해 변경 될 수 있습니다. 세션마다 왜 달라야합니까? 따로-내가 읽기 전용 옵션으로 가지고있는 한 가지 문제는 개발자가 읽기 전용 모드 일 때 세션에 값을 추가하면 자동으로 실패한다는 것입니다 (예외 없음). 실제로 그것은 나머지 요청에 대한 가치를 유지하지만 그 이상은 아닙니다.
제임스

5
@James-여기 디자이너의 동기를 추측하지만 사용하지 않거나 낮게 제거 할 수있는 캐시보다 단일 사용자 세션에서 여러 값이 서로 의존하는 것이 더 일반적이라고 생각합니다. 언제든지 메모리 이유. 한 페이지가 4 개의 관련 세션 변수를 설정하고 다른 페이지가 두 개만 수정 된 후 이들을 읽으면 진단하기 매우 어려운 버그가 발생할 수 있습니다. 디자이너들은 그런 이유로 "사용자 세션의 현재 상태"를 잠금 목적으로 단일 단위로 선택했다고 생각합니다.
Joel Mueller

2
그렇다면 잠금을 알아낼 수없는 최저 공통 분모 프로그래머를 수용 할 수있는 시스템을 개발 하시겠습니까? IIS 인스턴스간에 세션 저장소를 공유하는 웹 팜을 사용하는 것이 목적입니까? 세션 변수에 저장할 무언가의 예를 들어 줄 수 있습니까? 나는 아무것도 생각할 수 없다.
Jason Goemaat

2
예, 이것이 목적 중 하나입니다. 인프라에서로드 밸런싱 및 리던던시가 구현 될 때 다양한 시나리오를 재고하십시오. 사용자가 웹 페이지에서 작업 할 때, 즉 5 분 동안 양식에 데이터를 입력하면 웹팜에서 무언가 충돌이 발생합니다 (한 노드의 전원이 퍼프 됨). 사용자는이를 인식해서는 안됩니다. 자신의 작업자 프로세스가 더 이상 존재하지 않기 때문에 세션이 손실되어 세션에서 쫓겨날 수 없습니다. 이는 완벽한 균형 조정 / 중복성을 처리하기 위해 작업자 노드에서 세션을 외부화해야한다는 것을 의미합니다.
quetzalcoatl

6
또 다른 유용한 옵트 아웃 레벨은 <pages enableSessionState="ReadOnly" />web.config에 있으며 @Page를 사용하여 특정 페이지에만 쓸 수 있습니다.
MattW

84

자, 조엘 뮬러에게 그의 모든 의견에 대해 큰 제안을했습니다. 내 궁극적 인 해결책은이 MSDN 기사의 끝 부분에 자세히 설명 된 Custom SessionStateModule을 사용하는 것입니다.

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

이했다:

  • 구현이 매우 빠름 (실제로 공급자 경로를 이용하는 것보다 쉬웠습니다)
  • 많은 표준 ASP.Net 세션 처리를 SessionStateUtility 클래스를 통해 즉시 사용했습니다.

이것은 우리의 응용 프로그램에 대한 "스냅시 (snapiness)"느낌에 큰 차이를 가져 왔습니다. ASP.Net 세션의 사용자 지정 구현이 전체 요청에 대해 세션을 잠그는 것을 여전히 믿을 수 없습니다. 이것은 웹 사이트에 엄청난 양의 부진을 추가합니다. 내가해야 할 온라인 연구의 양 (그리고 실제로 경험이 많은 ASP.Net 개발자들과의 대화)을 고려할 때 많은 사람들이이 문제를 경험했지만 그 원인을 파악한 사람은 거의 없습니다. 아마도 Scott Gu에게 편지를 쓸 것입니다 ...

나는 이것이 소수의 사람들에게 도움이되기를 바랍니다!


19
그 참조는 흥미로운 발견이지만 몇 가지 사항에 대해주의를 기울여야합니다. 샘플 코드에는 몇 가지 문제가 있습니다. 먼저 ReaderWriterLock더 이상 사용되지 않아서 ReaderWriterLockSlim대신 사용해야합니다. 둘째, lock (typeof(...))더 이상 사용되지 않습니다. 대신 비공개 정적 객체 인스턴스를 잠 가야합니다. 셋째, "이 응용 프로그램은 동시 웹 요청이 동일한 세션 식별자를 사용하는 것을 막지 않습니다"라는 문구는 기능이 아니라 경고입니다.
Joel Mueller

3
이 작업을 수행 할 수 있다고 생각하지만 로드 할 때 재현하기 어려운 오류를 피 SessionStateItemCollection하려면 샘플 코드 의 사용법을 스레드 안전 클래스 (아마도 기반 ConcurrentDictionary)로 바꿔야합니다 .
Joel Mueller

3
방금 이것에 대해 조금 더 살펴 보았지만 불행히도 속성은 유형이 ISessionStateItemCollection필요합니다 . 공개 생성자가 없습니다. 고마워 매우 편리합니다. KeysSystem.Collections.Specialized.NameObjectCollectionBase.KeysCollection
Joel Mueller

2
좋아, 마침내 세션 작업의 전체 스레드 안전 비 읽기 잠금 구현이 있다고 생각합니다. 마지막 단계는 위의 주석에서 링크 된 MDSN 기사를 기반으로하는 사용자 정의 스레드 안전 SessionStateItem 콜렉션 구현과 관련이 있습니다. 이것으로 퍼즐의 마지막 조각은이 위대한 기사를 기반으로 스레드 안전 열거자를 작성하는 것입니다 : codeproject.com/KB/cs/safe_enumerable.aspx .
제임스

26
제임스-분명히 이것은 꽤 오래된 주제이지만 궁극적 인 솔루션을 공유 할 수 있는지 궁금합니다. 위의 주석 스레드를 사용하여 따라하려고했지만 지금까지는 작동하는 솔루션을 얻을 수 없었습니다. 잠금을 요구하는 세션의 제한된 사용에는 근본적인 것이 없다고 확신합니다.
bsiegel

31

AngiesList.Redis.RedisSessionStateModule을 사용하기 시작했습니다. 저장소 에 (매우 빠른) Redis 서버 를 사용하는 것 외에도 ( Windows 포트를 사용하고 있습니다 -MSOpenTech 포트 도 있지만 ) 세션에서 절대 잠금이 없습니다. .

내 의견으로는, 귀하의 응용 프로그램이 합리적인 방식으로 구성되어 있다면 이것은 문제가되지 않습니다. 실제로 세션의 일부로 고정 된 일관된 데이터가 필요한 경우에는 특별히 자체적으로 잠금 / 동시 검사를 구현해야합니다.

MS는 좋지 않은 응용 프로그램 디자인을 처리하기 위해 기본적으로 모든 ASP.NET 세션을 잠 가야한다고 결정하는 것은 잘못된 결정입니다. 특히 대부분의 개발자가 세션이 잠겨 있다는 사실을 알지 못하거나 알지 못하는 것처럼 보이기 때문에 앱을 구조화해야 할 필요는 없지만 가능한 한 읽기 전용 세션 상태를 최대한 많이 수행 할 수 있습니다 (가능한 경우 옵트 아웃) .


GitHub 링크가 404에 죽은 것으로 보입니다. library.io/github/angieslist/AL-Redis 가 새로운 URL 인 것 같습니다.
Uwe Keim

저자가 두 번째 링크에서도 라이브러리를 제거하려고 한 것 같습니다. 버려진 라이브러리를 사용하는 것을 주저하지만 여기에 포크가 있습니다 : github.com/PrintFleet/AL-Redis 및 여기에서 링크 된 대체 라이브러리 : stackoverflow.com/a/10979369/12534
Christian Davén

21

이 스레드에 게시 된 링크를 기반으로 라이브러리를 준비했습니다. MSDN 및 CodeProject의 예제를 사용합니다. 제임스에게 감사합니다.

나는 또한 Joel Mueller의 조언을 수정했습니다.

코드는 다음과 같습니다.

https://github.com/dermeister0/LockFreeSessionState

해시 테이블 모듈 :

Install-Package Heavysoft.LockFreeSessionState.HashTable

스케일 아웃 StateServer 모듈 :

Install-Package Heavysoft.LockFreeSessionState.Soss

맞춤 모듈 :

Install-Package Heavysoft.LockFreeSessionState.Common

Memcached 또는 Redis 지원을 구현하려면이 패키지를 설치하십시오. 그런 다음 LockFreeSessionStateModule 클래스를 상속하고 추상 메소드를 구현하십시오.

코드는 아직 프로덕션 환경에서 테스트되지 않았습니다. 또한 오류 처리를 개선해야합니다. 현재 구현에서는 예외가 발생하지 않습니다.

Redis를 사용하는 일부 잠금없는 세션 제공자 :


무료가 아닌 ScaleOut 솔루션의 라이브러리가 필요합니까?
Hoàng Long

1
예, SOSS 전용 구현을 만들었습니다. 언급 된 Redis 세션 제공자를 무료로 사용할 수 있습니다.
Der_Meister

어쩌면 Hoàng Long은 메모리 내 HashTable 구현과 ScaleOut StateServer 중 하나를 선택했다는 점을 놓쳤을 수 있습니다.
David De Sloovere

귀하의 기여에 감사드립니다.
Agustin Garzon

많은 사람들이 세션의 특정 항목에 대해 잠금을 얻는 것에 대해 언급 했으므로 백업 구현은 잠금을 활성화하기 위해 get 호출에서 세션 값에 대한 공통 참조를 반환해야 함을 지적하는 것이 좋습니다. 부하 분산 서버에서는 작동하지 않습니다). 세션 상태를 사용하는 방법에 따라 여기에 경쟁 조건이있을 수 있습니다. 또한 구현에 단일 읽기 또는 쓰기 호출 만 래핑하기 때문에 실제로 아무것도하지 않는 잠금 장치가있는 것 같습니다 (여기서 잘못하면 수정하십시오).
nw.

11

귀하의 응용 프로그램에 특별한 요구가 없다면, 두 가지 접근 방식이 있다고 생각합니다.

  1. 세션을 전혀 사용하지 마십시오
  2. joel이 언급 한대로 세션을 그대로 사용하고 미세 조정을 수행하십시오.

현재 요청이 완료 될 때까지 모든 세션 변수가 다른 활성 요청에서 변경되지 않음을 알 수있는 방식으로 세션은 스레드로부터 안전 할뿐만 아니라 상태에서 안전합니다. 이를 위해서는 현재 요청이 완료 될 때까지 세션이 잠기 도록 해야합니다 .

여러 가지 방법으로 동작과 같은 세션을 만들 수 있지만 현재 세션을 잠그지 않으면 '세션'이 아닙니다.

언급 한 특정 문제에 대해서는 HttpContext.Current.Response.IsClientConnected 확인해야한다고 생각합니다 . 이는 풀링 방식으로 만 비동기식으로 만 사용할 수 있기 때문에이 문제를 완전히 해결할 수는 없지만 불필요한 실행을 방지하고 클라이언트에서 대기하는 데 유용 할 수 있습니다.


10

업데이트 된 Microsoft.Web.RedisSessionStateProvider(에서 시작하여 3.0.2)을 사용하는 경우 web.config동시 세션을 허용하도록 이를 추가 할 수 있습니다 .

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

출처


이것이 왜 0인지 확실하지 않습니다. +1. 굉장히 유용하다.
Pangamma

클래식 모드 앱 풀에서 작동합니까? github.com/Azure/aspnet-redis-providers/issues/123
Rusty

기본 inProc 또는 세션 상태 서비스 제공 업체에서 작동합니까?
Nick Chan Abdullah

참고이 RedisSessionStateprovider를 사용하지만, 경우 포스터 참조 그것이 수도 또한 자신의 문서도 있기 때문에 (SQL과 우주에 대한) 이러한 새로운 AspNetSessionState 비동기 업체와 작업 : github.com/aspnet/AspNetSessionState 내 추측은 그것에서 일 것입니다 sessionStateProvider가 이미 클래식 모드에서 작동하는 경우 세션 상태 항목은 IIS가 아닌 ASP.Net 내부에서 발생할 수 있습니다. InProc를 사용하면 작동하지 않을 수도 있지만 절차가 아닌 시나리오에서 처리하는 리소스 경합 문제를 해결하기 때문에 문제가 적습니다.
madamission

4

ASPNET MVC의 경우 다음을 수행했습니다.

  1. 기본적 SessionStateBehavior.ReadOnly으로 모든 컨트롤러의 동작을 재정 의하여 설정 합니다DefaultControllerFactory
  2. 세션 상태에 쓰기가 필요한 컨트롤러 작업에서 속성으로 표시하여 설정하십시오. SessionStateBehavior.Required

사용자 정의 ControllerFactory를 작성하고 대체하십시오 GetControllerSessionBehavior.

    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");
    }

AcquisSessionLockAttribute

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

생성 된 컨트롤러 팩토리를 global.asax.cs

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

이제 하나의 세션 상태 read-onlyread-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세션 상태를 기록해야하는 컨트롤러의 동작에 주의를 기울여야합니다 .



3

컨트롤러의 세션 상태를 읽기 전용 또는 비활성화로 표시하면 문제가 해결됩니다.

다음 속성으로 컨트롤러를 장식하여 읽기 전용으로 표시 할 수 있습니다.

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

System.Web.SessionState.SessionStateBehavior의 열거 값은 다음과 같습니다

  • 기본
  • 비활성화
  • 읽기 전용
  • 필수

0

이 문제를 가진 사람을 돕기 위해 (같은 세션에서 다른 요청을 실행할 때 요청 잠금) ...

오늘이 문제를 해결하기 시작했으며 몇 시간의 연구 끝에 Global.asax 파일 Session_Start에서 방법을 비워서 해결했습니다 .

이것은 내가 테스트 한 모든 프로젝트에서 작동합니다.


IDK가 어떤 유형의 프로젝트에 있었지만 광산에는 Session_Start방법이없고 여전히 잠겨 있습니다.
Denis G. Labrecque

0

사용 가능한 모든 옵션으로 어려움을 겪은 후 JWT 토큰 기반 SessionStore 공급자를 작성했습니다 (세션은 쿠키 내에서 이동하며 백엔드 스토리지는 필요하지 않습니다).

http://www.drupalonwindows.com/en/content/token-sessionstate

장점 :

  • 드롭 인 교체, 코드를 변경할 필요가 없습니다
  • 세션 스토리지 백엔드가 필요하지 않으므로 다른 중앙 저장소보다 확장 성이 뛰어납니다.
  • 세션 스토리지에서 데이터를 검색 할 필요가 없으므로 다른 세션 스토리지보다 빠릅니다.
  • 세션 저장을위한 서버 리소스를 사용하지 않습니다.
  • 기본 비 차단 구현 : 동시 요청은 서로를 차단하지 않으며 세션에 대한 잠금을 유지합니다.
  • 응용 프로그램의 수평 확장 : 세션 데이터가 요청 자체와 함께 이동하므로 세션 공유에 대한 걱정없이 여러 웹 헤드를 가질 수 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.