web-dev-qa-db-ja.com

私はちょうどすべてのASP.Net Webサイトが遅い理由を発見しました、そして私はそれについて何をすべきかを解決しようとしています

ASP.Net Webアプリケーションのすべてのリクエストが、リクエストの開始時にセッションロックを取得し、リクエストの終了時にセッションロックを解除することを発見しました!

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

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

  • UpdatePanelの読み込みが遅いときはいつでも、ユーザーはUpdatePanelの更新が完了する前に別のページに移動することにします...できません! ASP.netセッションロックは、元のリクエストが非常に遅いロードを完了するまで、新しいページリクエストを強制的に待機させます。ダブルアラー!

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

  • ASP.NetがサポートするカスタムSessionStateDataStoreを実装します。コピーするものがあまりにも多くないことを発見しましたが、それは一種の高リスクで混乱しやすいようです。
  • 進行中のすべてのリクエストを追跡し、同じユーザーからリクエストが来たら、元のリクエストをキャンセルします。一種の極端に思えますが、うまくいくでしょう(私は思う)。
  • セッションを使用しないでください!ユーザーに何らかの状態が必要な場合は、代わりにCacheを使用し、認証されたユーザー名のキーアイテムなどを使用できます。繰り返しますが、一種の極端なようです。

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

269
James

OK、ジョエル・ミュラーのすべての入力に対して非常に大きな小道具。私の究極のソリューションは、このMSDN記事の最後に詳述されているカスタムSessionStateModuleを使用することでした。

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

これは。。。でした:

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

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

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

82
James

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

<% @Page EnableSessionState="ReadOnly" %>

ページがセッション変数を読み取らない場合、そのページに対してこのロックを完全にオプトアウトできます。

<% @Page EnableSessionState="False" %>

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

<sessionState mode="Off" />

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

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

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

199
Joel Mueller

AngiesList.Redis.RedisSessionStateModule の使用を開始しました。これは、(非常に高速) Redisサーバー の使用とは別に( windowsポート)を使用しています - MSOpenTechポート )もありますが、セッションのロックは絶対に行われません。

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

私の意見では、不適切なアプリケーション設計を処理するためだけに、すべてのASP.NETセッションをデフォルトでロックする必要があると判断したのは、悪い判断です。特に、ほとんどの開発者はセッションがロックされていることを認識していない/認識していないように見えるため、アプリは明らかに読み取り専用のセッション状態を可能な限り構成する必要があるようです(可能な場合はオプトアウト) 。

31
gregmac

このスレッドに投稿されたリンクに基づいてライブラリを準備しました。 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を使用するロックフリーセッションプロバイダー:

20
Der_Meister

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

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

セッションはスレッドセーフであるだけでなく、状態セーフでもあります。つまり、現在のリクエストが完了するまで、すべてのセッション変数は別のアクティブリクエストから変更されることはありません。これを行うには、現在のリクエストが完了するまでensureそのセッションWILL BE LOCKEDする必要があります。

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

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

11

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

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

ソース

6
rabz100

ASPNET MVCの場合、次のことを行いました。

  1. デフォルトでは、DefaultControllerFactoryをオーバーライドして、すべてのコントローラーのアクションにSessionStateBehavior.ReadOnlyを設定します
  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));

これで、単一のControllerread-onlyread-writeの両方のセッション状態を含めることができます。

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
Misterhex

コントローラのセッション状態をreadonlyまたはdisabledとしてマークすると、問題が解決します。

次の属性を使用してコントローラーを装飾し、読み取り専用としてマークできます。

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

System.Web.SessionState.SessionStateBehavior enumには次の値があります。

  • Default
  • 無効
  • 読み取り専用
  • 必須
2
Michael King

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

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

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

0
graduenz