すべてのASP.Net Webサイトが遅い理由を発見しました。どうすればよいのかを考えています。


275

ASP.Net Webアプリケーションのすべてのリクエストがリクエストの最初にセッションロックを取得し、リクエストの最後にそれを解放することを発見しました!

最初に私がそうであったように、これの影響があなたに失われた場合、これは基本的に以下を意味します:

  • ASP.Net Webページの読み込みに時間がかかる(データベースの呼び出しが遅いなどの理由で)場合があり、ユーザーは待機に飽き飽きしているため、別のページに移動することを決定します。ASP.Netセッションロックにより、新しいページ要求は、元の要求が非常に遅いロードを終了するまで強制的に待機します。ああ。

  • UpdatePanelの読み込みが遅く、ユーザーがUpdatePanelの更新が完了する前に別のページに移動することにした場合...できません。ASP.netセッションロックにより、新しいページ要求は、元の要求が非常に遅いロードを完了するまで強制的に待機します。ダブルアレ!

それで、オプションは何ですか?これまでのところ私は思いつきました:

  • ASP.NetがサポートするカスタムSessionStateDataStoreを実装します。私はコピーするほど多くを見つけていません、そしてそれは一種のリスクが高く、簡単に失敗するようです。
  • 進行中のすべてのリクエストを追跡し、同じユーザーからリクエストが届いた場合は、元のリクエストをキャンセルします。ちょっと極端に思えますが、うまくいくと思います。
  • セッションを使用しないでください!ユーザーに何らかの種類の状態が必要な場合は、代わりにキャッシュを使用し、認証されたユーザー名のキーアイテムなどを使用できます。再び極端なようです。

ASP.Net Microsoftチームがバージョン4.0のフレームワークにこのような巨大なパフォーマンスのボトルネックを残したとは本当に信じられません!明らかなものがないのですか?セッションにThreadSafeコレクションを使用するのはどれほど難しいでしょうか。


40
このサイトは.NETの上に構築されていることをご存じでしょう。そうは言っても、私はそれがかなりうまくスケーリングすると思います。
ウィーティー

7
わかりました、それで私は私のタイトルで少し面倒でした。それでも、すぐに使えるセッションの実装が課すパフォーマンスの圧迫感は驚くべきものです。また、スタックオーバーフローの人たちは、彼らが達成したパフォーマンスとスケーラビリティを得るために、高度なカスタム開発をかなり行わなければならなかったに違いないと思います。最後に、Stack OverflowはMVC APPであり、WebFormsではありません。これは確かに役立ちます(確かに、これはまだ同じセッションインフラストラクチャを使用しています)。
James


4
Joel Muellerが問題を修正するための情報を提供した場合、彼の答えを正しい答えとしてマークしないのはなぜですか?ちょっとした考え。
ars265 2012年

1
@ ars265-Joel Mullerはたくさんの良い情報を提供してくれました、そして私は彼にそれを感謝したいと思いました。しかし、私は最終的に彼の投稿で提案されたものとは異なるルートを使いました。したがって、別の投稿を回答としてマークします。
James

回答:


201

ページがセッション変数を変更しない場合は、このロックの大部分をオプトアウトできます。

<% @Page EnableSessionState="ReadOnly" %>

ページがセッション変数を読み取らない場合は、そのページに対して、このロックを完全に無効にすることができます。

<% @Page EnableSessionState="False" %>

どのページもセッション変数を使用していない場合は、web.configでセッション状態をオフにします。

<sessionState mode="Off" />

ロックを使用しない場合、「ThreadSafeコレクション」がスレッドセーフになるにはどうしたらよいと思いますか。

編集:「このロックのほとんどをオプトアウトする」とはどういう意味かを説明する必要があります。任意の数の読み取り専用セッションページまたは非セッションページを、互いにブロックすることなく、特定のセッションで同時に処理できます。ただし、読み取り/書き込みセッションページは、すべての読み取り専用リクエストが完了するまで処理を開始できません。実行中は、一貫性を維持するために、そのユーザーのセッションに排他的にアクセスする必要があります。1つのページが関連する値のセットをグループとして変更した場合はどうなるのか、個々の値のロックは機能しません。同時に実行されている他のページがユーザーのセッション変数の一貫したビューを確実に取得するようにするにはどうすればよいですか?

可能であれば、一度設定したセッション変数の変更を最小限に抑えることをお勧めします。これにより、ページの大部分を読み取り専用セッションのページにすることができ、同じユーザーからの複数の同時リクエストが互いにブロックしなくなる可能性が高くなります。


2
こんにちはジョエルこの回答にお時間をいただきありがとうございます。これらは、いくつかの良い提案といくつかの思考の糧です。セッションのすべての値はリクエスト全体で排他的にロックする必要があると言った理由を理解できません。ASP.Netキャッシュの値は、任意のスレッドによっていつでも変更できます。なぜこれはセッションで異なるのですか?余談ですが、私が読み取り専用オプションで抱えている問題の1つは、セッションが読み取り専用モードのときに開発者がセッションに値を追加した場合、暗黙的に失敗することです(例外はありません)。実際、それは残りのリクエストの値を保持しますが、それ以降は保持しません。
ジェームズ

5
@ジェームズ-私はここでデザイナーの動機を推測しているだけですが、使用の欠如または低いためにパージできるキャッシュ内よりも、単一のユーザーのセッションで複数の値が互いに依存している方が一般的だと思いますいつでもメモリの理由。1つのページが4つの関連するセッション変数を設定し、2つだけが変更された後に別のセッションがそれらを読み取る場合、いくつかの非常に診断が難しいバグに簡単につながる可能性があります。そのため、デザイナーが「ユーザーのセッションの現在の状態」を単一の単位としてロック目的で表示することを選択したことを想像します。
Joel Mueller

2
それで、ロックを理解することができない最も一般的な分母プログラマーに応えるシステムを開発しますか?IISインスタンス間でセッションストアを共有するWebファームを有効にする目的はありますか?セッション変数に格納するものの例を挙げられますか?何も考えられない。
Jason Goemaat、2012年

2
はい、これは目的の1つです。負荷分散と冗長性がインフラストラクチャに実装されている場合のさまざまなシナリオを考え直してください。ユーザーがWebページで作業しているとき、つまり、たとえば5分間フォームにデータを入力しているときに、Webファーム内の何かがクラッシュした場合-1つのノードの電源がパフになった場合-ユーザーはそれに気付かないはずです。彼のセッションが失われたからといって、彼のワーカープロセスがもう存在しないからといって、彼をセッションから追い出すことはできません。この完璧なバランス/冗長性を処理するために、セッションはワーカーノードから外部化されなければならないことを意味...
ケツァルコアトル

7
オプトアウトのもう1つの有用なレベルは<pages enableSessionState="ReadOnly" />web.configにあり、@ Pageを使用して特定のページのみへの書き込みを有効にします。
MattW 2014年

84

承知しました。JoelMuller氏のすべての情報提供に対して、非常に大きなプロップです。私の最終的な解決策は、このMSDN記事の最後に詳述されているカスタムSessionStateModuleを使用することでした。

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

これは。。。でした:

  • 実装が非常に速い(実際にはプロバイダーのルートに行くよりも簡単に見えた)
  • 多くの標準ASP.Netセッション処理をそのまま使用しました(SessionStateUtilityクラス経由)。

これにより、アプリケーションの「スナップ感」に大きな違いが生じました。ASP.Netセッションのカスタム実装がリクエスト全体のセッションをロックすることはまだ信じられません。これは、ウェブサイトにそのような非常に遅い速度を加えます。私がしなければならなかったオンライン調査の量(および実際に経験豊富なASP.Net開発者との会話)から判断すると、多くの人がこの問題を経験しましたが、原因を突き止めた人はほとんどいません。多分私はスコット・グーに手紙を書くでしょう...

これが数人の人に役立つことを願っています!


19
その参照は興味深い発見ですが、いくつかの点について注意する必要があります-サンプルコードにはいくつかの問題があります:まず、ReaderWriterLock非推奨になりましたReaderWriterLockSlim-代わりにそれを使用する必要があります。次に、lock (typeof(...))非推奨になりました-代わりにプライベート静的オブジェクトインスタンスをロックする必要があります。第3に、「このアプリケーションは同時Webリクエストが同じセッション識別子を使用することを妨げない」という句は警告であり、機能ではありません。
Joel Mueller

3
これでうまくいくと思いますが、負荷がかかった状態で再現が難しいエラーを回避したい場合SessionStateItemCollectionは、サンプルコードのの使用をスレッドセーフクラス(おそらくに基づくConcurrentDictionary)に置き換える必要があります。
Joel Mueller

3
私はこれをもう少し詳しく調べたところ、残念ながらプロパティは型であるISessionStateItemCollection必要がありKeysますSystem.Collections.Specialized.NameObjectCollectionBase.KeysCollection-これにはパブリックコンストラクターがありません。ねえ、みんなありがとう。とても便利です。
Joel Mueller

2
OK、私はようやく、完全にスレッドセーフで、セッションの非読み取りロック実装が機能していると思います。最後のステップは、上記のコメントでリンクされたMDSN記事に基づいたカスタムのスレッドセーフSessionStateItemコレクションの実装に関係していました。これに関するパズルの最後のピースは、この素晴らしい記事codeproject.com/KB/cs/safe_enumerable.aspxに基づいてスレッドセーフな列挙子を作成することでした
James

26
ジェームズ-明らかにこれはかなり古いトピックですが、最終的な解決策を共有できるかどうか疑問に思いましたか?上記のコメントのスレッドを使用してフォローしようとしましたが、これまでのところ、有効な解決策を得ることができませんでした。セッションの限られた使用には、ロックを必要とするような基本的なことは何もないことはかなり確実です。
bsiegel

31

AngiesList.Redis.RedisSessionStateModuleの使用を開始しました。これは、ストレージに(非常に高速な)Redisサーバーを使用する以外に(Windowsポートを使用していますが、MSOpenTechポートもあります))とは、セッションをロックしません。 。

私の意見では、アプリケーションが合理的な方法で構成されている場合、これは問題ではありません。セッションの一部として実際にロックされた一貫性のあるデータが必要な場合は、独自にロック/同時実行チェックを実装する必要があります。

MSは、貧弱なアプリケーション設計を処理するためだけにすべてのASP.NETセッションをデフォルトでロックする必要があると決定することは、私の意見では悪い決定です。特に、ほとんどの開発者はセッションがロックされていることに気付いていない、または知らないように思われるため、可能な限り読み取り専用のセッション状態を実行できるようにアプリを構造化する必要があることは言うまでもありません(可能な場合はオプトアウト) 。


あなたのGitHubリンクは404デッドのようです。libraries.io/github/angieslist/AL-Redisが新しいURLのようです?
Uwe Keim 2017

作成者が2番目のリンクからでも、ライブラリを削除したかったようです。私は放棄されたライブラリを使用するのをためらいますが、ここにフォークがあります:github.com/PrintFleet/AL-Redisおよびここからリンクされている代替ライブラリ:stackoverflow.com/a/10979369/12534
クリスチャンダベン

21

このスレッドに投稿されたリンクを元にライブラリを用意しました。MSDNとCodeProjectの例を使用しています。ジェームスに感謝します。

Joel Muellerのアドバイスを受けて修正も加えました。

コードはここにあります:

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を使用する一部のロックフリーセッションプロバイダー:


無料ではない、ScaleOutソリューションのライブラリが必要ですか?
ホアンロング

1
はい、SOSSのみの実装を作成しました。上記のRedisセッションプロバイダーは無料で使用できます。
Der_Meister 2016年

おそらくHoàngLongは、メモリ内HashTable実装とScaleOut StateServerのどちらかを選択できるという点を逃しました。
David De Sloovere 2016年

あなたの貢献に感謝します:)私はそれが私たちが関与している私たちが持っているいくつかのユースケースにどのように作用するかを見てみます。
Agustin Garzon

多くの人がセッションの特定のアイテムでロックを取得することについて言及しているため、ロックを有効にするために、バッキング実装がget呼び出し全体でセッション値への共通参照を返す必要があることを指摘するのは良いことかもしれません(さらに負荷分散されたサーバーでは機能しません)。セッション状態の使用方法によっては、ここで競合状態が発生する可能性があります。また、単一の読み取りまたは書き込み呼び出ししかラップしていないため、実際には何もしないロックが実装にあるようです(ここで間違っている場合は修正してください)。
nw。

11

アプリケーションに特別な必要がない限り、2つのアプローチがあると思います。

  1. セッションをまったく使用しない
  2. セッションをそのまま使用し、ジョエルが述べたように微調整を実行します。

セッションはスレッドセーフであるだけでなく状態セーフでもあり、現在のリクエストが完了するまで、すべてのセッション変数が別のアクティブなリクエストから変更されることはありません。これが発生するためには、セッションがロックされることを確認する必要があります、現在のリクエストが完了するまでれる。

動作のようなセッションはさまざまな方法で作成できますが、現在のセッションをロックしない場合、「セッション」にはなりません。

あなたが言及した特定の問題については、HttpContext.Current.Response.IsClientConnectedを確認する必要があると思います。これは、この問題を完全に解決できるわけではありませんが、プールの方法でのみ使用でき、非同期では使用できないため、クライアントでの不要な実行と待機を防ぐのに役立ちます。


10

更新されたMicrosoft.Web.RedisSessionStateProvider(から始まる3.0.2)を使用している場合は、これをに追加して、web.config同時セッションを許可できます。

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

ソース


なぜこれが0だったのかわかりません。+ 1。非常に便利。
パンガンマ

これはクラシックモードのアプリプールで機能しますか?github.com/Azure/aspnet-redis-providers/issues/123
Rusty

これはデフォルトのinProcまたはセッション状態サービスプロバイダーで機能しますか?
Nick Chan Abdullah

(注)RedisSessionStateproviderを使用しますが、されている場合は、ポスターが参照することかもしれない、それが彼らのドキュメントでもありますので(SQLやコスモスなど)これらの新しいAspNetSessionState非同期プロバイダーとの仕事:github.com/aspnet/AspNetSessionState私の推測では、それが中に働くだろうということですクラシックモードSessionStateProviderが既にクラシックモードで動作している場合、おそらくセッションステートはIIS(IISではなく)ASP.Net内で発生します。InProcを使用すると、機能しない可能性がありますが、Proc以外のシナリオで対処するリソース競合の問題を解決するため、問題は少なくなります。
マダミッション

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

AcquireSessionLockAttribute

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

作成したコントローラーファクトリを global.asax.cs

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

これで、1つのに両方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

この問題で誰かを助けるために(同じセッションから別のものを実行するときにリクエストをロックする)...

今日、私はこの問題の解決を開始し、数時間の調査の後Session_StartGlobal.asaxファイルからメソッドを(空であっても)削除することで解決しました。

これは、私がテストしたすべてのプロジェクトで機能します。


IDKは、これがどのタイプのプロジェクトに参加していたかを知りましたが、私にはSession_Startメソッドがなく、ロックされています
Denis G.Labrecque

0

利用可能なすべてのオプションに取り組んだ後、私は最終的にJWTトークンベースのSessionStoreプロバイダーを記述しました(セッションはCookie内を移動するため、バックエンドストレージは必要ありません)。

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

利点:

  • ドロップイン置換、コードの変更は不要
  • セッションストレージバックエンドが不要なため、他の集中型ストアよりも優れた拡張性。
  • セッションストレージからデータを取得する必要がないため、他のセッションストレージよりも高速
  • セッションストレージ用のサーバーリソースを消費しません。
  • デフォルトのノンブロッキング実装:同時リクエストはお互いをブロックせず、セッションをロックしません
  • アプリケーションを水平方向にスケーリングします。セッションデータはリクエスト自体と共に移動するため、セッションの共有を気にすることなく、複数のWebヘッドを使用できます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.