【问题标题】:.NET Fault tolerant StateServer.NET 容错状态服务器
【发布时间】:2014-01-12 23:41:15
【问题描述】:

我们使用 StateServer 来处理 Session 以获得已知的好处(网络农场、IIS 回收)。

但是我试图弄清楚如何使这个容错。我们存储在 Session 中的任何内容都不是关键的,它只是用于性能。因此,如果 StateServer 不可用,我们很乐意从磁盘重新加载。

但是似乎无法检测 StateServer 是否在线,因此即使 StateServer 关闭,以下代码也可以正常运行

try
{
    //It is not NULL as it has been configured
    if (HttpContext.Current.Session != null)
        Session["Test"] = "value";
}
// No exception is thrown
catch (Exception)
{
    throw new Exception();
}

现在对我来说没有抛出异常是有道理的。如果必须在每次写入时检查状态,会话处理将不会非常高效。所以我猜测会发生什么,它会在写入响应时写入所有会话变量。

这就是问题所在,当它尝试写入 Session 时,它会失败并出现 500 错误,而且我不知道如何拦截该错误并进行处理。

无法向会话状态服务器发出会话状态请求。 请确保 ASP.NET 状态服务已启动并且 客户端和服务器端口相同。

我希望发生的是,写入只是静默失败(或记录错误)并且客户端不受影响。正如现在所写的那样,由于这个单点故障,整个站点都崩溃了。

任何想法 - 我是否遗漏了一些明显的东西?

【问题讨论】:

  • 您是否尝试过 ELMAH 或 global.asax 中的 OnError 事件来捕获这些错误?
  • 是的,这可能有效 - 如果这是错误类型,我可能会使用 Server.ClearError()。让我玩。
  • 如果会话提供者由于StateServer服务停止而无法初始化,那么它将中断请求过程,并在global.asax中执行OnError事件,但之后您将无法恢复请求处理. Server.ClearError 将禁止将此异常发布到 EventLog。它不会恢复处理。我看到的唯一方法是像我的答案一样编写自定义会话提供程序。
  • 嗨,谢尔盖 - 我已经做了一些测试并清除错误似乎对我来说没问题。也许我错过了其他东西?
  • 如果你需要记录这个错误,那应该没问题。但是,如果您不想继续处理当前页面但没有会话,那么它可能取决于您在请求中使用会话的频率。至于对会话的每次访问,它都可以抛出 NullReference 异常。你想达到什么目标?

标签: asp.net .net session session-state fault-tolerance


【解决方案1】:

嗯,这可能很难。 asp.net 使用 session 紧密,所以如果 session 存储失败,asp.net 在 session 模块初始化的时候也会失败。您可以编写自己的session state provider,它将包装现有的,如果失败,它将返回空的会话项目,但很难使用它,因为会话行为可能是不可预测的。

您可以查看内置的SQL session state provider,如果您的 SQL 服务器有复制,它具有故障转移功能。

更新1

这是默认会话提供者的包装器示例

public class SessionProviderWrapper : SessionStateStoreProviderBase
{
    private readonly SessionStateStoreProviderBase _provider;

    private static Func<SessionStateStoreProviderBase> _createProvider;

    static SessionProvider()
    {
        _createProvider = InitializerProvider();
    }

    private static Func<SessionStateStoreProviderBase> InitializerProvider()
    {
        if (_createProvider != null)
            return _createProvider;

        var sessionType = "stateserver"; // you can switch to another session provider

        Type type;
        switch (sessionType)
        {
            case "inproc":
                type = Type.GetType("System.Web.SessionState.InProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                break;

            case "sql":
                type = Type.GetType("System.Web.SessionState.SqlSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                break;

            case "stateserver":
                type = Type.GetType("System.Web.SessionState.OutOfProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
                break;

            default:
                throw new ConfigurationErrorsException("Unknow session type: " + sessionType);
        }

        if (type == null)
        {
            throw new InvalidOperationException("Failed to find session provider for " + sessionType);
        }

        _createProvider = GenerateConstructorCall(type);

        return _createProvider;
    }

    private static Func<SessionStateStoreProviderBase> GenerateConstructorCall(Type type)
    {
        // we are searching for public constructor
        var constructor = type.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0);
        if (constructor == null)
        {
            // otherwise for internal. SQL session provider has internal constructor, but we don't care
            constructor = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Length == 0);
        }

        var node = Expression.New(constructor);
        var lambda = Expression.Lambda<Func<SessionStateStoreProviderBase>>(node, null);
        var func = lambda.Compile();
        return func;
    }

    public SessionProvider()
    {
        var createProvider = InitializerProvider();

        _provider = createProvider();
    }

    public override void Initialize(string name, NameValueCollection config)
    {
        _provider.Initialize(name, config);
    }

    public override string Name
    {
        get { return _provider.Name; }
    }

    public override string Description
    {
        get { return _provider.Description; }
    }

    public override void Dispose()
    {
        _provider.Dispose();
    }

    public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
    {
        return _provider.SetItemExpireCallback(expireCallback);
    }

    public override void InitializeRequest(HttpContext context)
    {
        _provider.InitializeRequest(context);
    }

    public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
                                         out SessionStateActions actions)
    {
        try
        {
            return _provider.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
        }
        catch (Exception ex)
        {
            locked = false;
            lockAge = TimeSpan.Zero;
            lockId = null;
            actions = SessionStateActions.None;
            // log ex
            return new SessionStateStoreData(new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 10);
        }
    }

    public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
                                                  out SessionStateActions actions)
    {
        return _provider.GetItemExclusive(context, id, out locked, out lockAge, out lockId, out actions);
    }

    public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
    {
        _provider.ReleaseItemExclusive(context, id, lockId);
    }

    public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
    {
        _provider.SetAndReleaseItemExclusive(context, id, item, lockId, newItem);
    }

    public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
    {
        _provider.RemoveItem(context, id, lockId, item);
    }

    public override void ResetItemTimeout(HttpContext context, string id)
    {
        _provider.ResetItemTimeout(context, id);
    }

    public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
    {
        return _provider.CreateNewStoreData(context, timeout);
    }

    public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
    {
        _provider.CreateUninitializedItem(context, id, timeout);
    }

    public override void EndRequest(HttpContext context)
    {
        _provider.EndRequest(context);
    }
}

基本上你可以像 GetItem 方法一样对每个方法进行 try\catch,如果出现错误,你可以返回空会话对象。如果它在 try\catch 应用程序中失败,它仍然会存在。但是性能会降低,因为每个请求都会在 Get\Release 上抛出几个异常,这些异常将在 catch 部分处理。但无论如何,这些异常会稍微降低性能

【讨论】:

    【解决方案2】:

    我愿意接受 tgolisch 的回答作为适合我的解决方案。

    • 在 Global.asax 中,我们将在 Application_Error 事件中查找缺少的 StateServer 错误
    • 如果我们找到它,我们将使用 Server.ClearError() 并记录错误
    • 我们还将使用它来记录错误并可能发出警报

    谢谢大家!

    【讨论】:

      猜你喜欢
      • 2021-06-23
      • 1970-01-01
      • 1970-01-01
      • 2018-02-19
      • 1970-01-01
      • 2019-12-24
      • 2019-04-28
      • 2017-07-30
      • 1970-01-01
      相关资源
      最近更新 更多