ASP.Net Webアプリケーションのすべてのリクエストが、リクエストの開始時にセッションロックを取得し、リクエストの終了時にセッションロックを解除することを発見しました!
最初は私にとってそうであったように、この意味があなたに失われた場合、これは基本的に以下を意味します:
ASP.Net Webページの読み込みに時間がかかる(データベース呼び出しが遅いなどの理由による)場合はいつでも、ユーザーは待機にうんざりしているため、別のページに移動することを決定します。 ASP.Netセッションロックは、元のリクエストが非常に遅いロードを完了するまで、新しいページリクエストを強制的に待機させます。ああ。
UpdatePanelの読み込みが遅いときはいつでも、ユーザーはUpdatePanelの更新が完了する前に別のページに移動することにします...できません! ASP.netセッションロックは、元のリクエストが非常に遅いロードを完了するまで、新しいページリクエストを強制的に待機させます。ダブルアラー!
それでは、オプションは何ですか?これまでのところ、私は思いついた:
ASP.Net Microsoftチームがバージョン4.0のフレームワークにこのような大きなパフォーマンスボトルネックを残していたとは本当に信じられません。明らかな何かが欠けていますか?セッションにThreadSafeコレクションを使用するのはどれくらい難しいでしょうか?
OK、ジョエル・ミュラーのすべての入力に対して非常に大きな小道具。私の究極のソリューションは、このMSDN記事の最後に詳述されているカスタムSessionStateModuleを使用することでした。
http://msdn.Microsoft.com/en-us/library/system.web.sessionstate.sessionstateutility.aspx
これは。。。でした:
これにより、アプリケーションの「スナップ」感に大きな違いが生じました。 ASP.Net Sessionのカスタム実装がリクエスト全体のセッションをロックするとはまだ信じられません。これにより、Webサイトに膨大な量の遅延が追加されます。私がしなければならなかったオンライン調査の量(および実際に経験を積んだASP.Net開発者との会話)から判断すると、多くの人がこの問題を経験していますが、原因を突き止めた人はほとんどいません。たぶん私はスコット・グーに手紙を書くでしょう...
これが数人の人々に役立つことを願っています!
ページがセッション変数を変更しない場合、このロックのほとんどをオプトアウトできます。
<% @Page EnableSessionState="ReadOnly" %>
ページがセッション変数を読み取らない場合、そのページに対してこのロックを完全にオプトアウトできます。
<% @Page EnableSessionState="False" %>
セッション変数を使用するページがない場合は、web.configでセッション状態をオフにします。
<sessionState mode="Off" />
ロックを使用しない場合、「ThreadSafeコレクション」がスレッドセーフになるにはどうすればよいと思いますか?
編集:おそらく「このロックのほとんどをオプトアウトする」という意味で説明する必要があります。任意の数の読み取り専用セッションまたはセッションなしのページを、互いにブロックすることなく、特定のセッションで同時に処理できます。ただし、読み取り/書き込みセッションページは、すべての読み取り専用要求が完了するまで処理を開始できません。実行中は、一貫性を維持するために、そのユーザーのセッションへの排他的アクセスが必要です。 1つのページがグループとして関連する値のセットを変更するとどうなるのか、個々の値のロックは機能しません。同時に実行されている他のページがユーザーのセッション変数の一貫したビューを取得することをどのように保証しますか?
セッション変数が設定されたら、可能な限り変更を最小限に抑えることをお勧めします。これにより、ページの大半を読み取り専用セッションページにすることができ、同じユーザーからの複数の同時リクエストが互いにブロックしない可能性が高くなります。
AngiesList.Redis.RedisSessionStateModule の使用を開始しました。これは、(非常に高速) Redisサーバー の使用とは別に( windowsポート)を使用しています - MSOpenTechポート )もありますが、セッションのロックは絶対に行われません。
私の意見では、アプリケーションが合理的な方法で構成されていれば、これは問題ではありません。セッションの一部としてロックされた一貫性のあるデータが実際に必要な場合は、独自にロック/並行性チェックを具体的に実装する必要があります。
私の意見では、不適切なアプリケーション設計を処理するためだけに、すべてのASP.NETセッションをデフォルトでロックする必要があると判断したのは、悪い判断です。特に、ほとんどの開発者はセッションがロックされていることを認識していない/認識していないように見えるため、アプリは明らかに読み取り専用のセッション状態を可能な限り構成する必要があるようです(可能な場合はオプトアウト) 。
このスレッドに投稿されたリンクに基づいてライブラリを準備しました。 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を使用するロックフリーセッションプロバイダー:
アプリケーションに特別なニーズがない限り、2つのアプローチがあると思います。
セッションはスレッドセーフであるだけでなく、状態セーフでもあります。つまり、現在のリクエストが完了するまで、すべてのセッション変数は別のアクティブリクエストから変更されることはありません。これを行うには、現在のリクエストが完了するまでensureそのセッションWILL BE LOCKEDする必要があります。
多くの方法でビヘイビアのようなセッションを作成できますが、現在のセッションをロックしない場合、「セッション」にはなりません。
あなたが言及した特定の問題については、HttpContext.Current.Response.IsClientConnectedを確認する必要があると思います。これは不要な実行とクライアントでの待機を防ぐのに役立ちますが、非同期ではなくプーリング方法でのみ使用できるため、この問題を完全に解決することはできません。
更新されたMicrosoft.Web.RedisSessionStateProvider
(3.0.2
から開始)を使用している場合、これをweb.config
に追加して、同時セッションを許可できます。
<appSettings>
<add key="aspnet:AllowConcurrentRequestsPerSession" value="true"/>
</appSettings>
ASPNET MVCの場合、次のことを行いました。
DefaultControllerFactory
をオーバーライドして、すべてのコントローラーのアクションにSessionStateBehavior.ReadOnly
を設定します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));
これで、単一のController
にread-only
とread-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
をマークするように注意する必要がありますセッション状態の書き込みが必要です。
コントローラのセッション状態をreadonlyまたはdisabledとしてマークすると、問題が解決します。
次の属性を使用してコントローラーを装飾し、読み取り専用としてマークできます。
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
System.Web.SessionState.SessionStateBehavior enumには次の値があります。
この問題を抱えている人を助けるために(同じセッションから別のリクエストを実行するときにリクエストをロックする)...
今日、私はこの問題の解決に着手し、数時間の調査の後、Global.asaxファイルからSession_Start
メソッド(空であっても)を削除することで解決しました。
これは、テストしたすべてのプロジェクトで機能します。