.NET Отказоустойчивый StateServer

Мы используем StateServer для обработки сеанса с известными преимуществами (веб-ферма, переработка IIS).

Однако я пытаюсь выяснить, как сделать эту отказоустойчивой. Ничто из того, что мы храним на Сессии, не является критичным, оно просто используется для повышения производительности Поэтому, если 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();
}

Теперь для меня имеет смысл, чтобы не было исключений. Обработка сеанса не будет очень производительной, если она будет проверять состояние при каждой записи. Итак, я предполагаю, что при записи ответа он записывает все переменные сеанса.

Проблема заключается в том, что когда он пытается записать сеанс, он завершается с ошибкой 500, и я не знаю, как перехватить эту ошибку и обработать ее.

Не удалось отправить запрос состояния сеанса на сервер состояний сеанса. Убедитесь, что служба состояния ASP.NET запущена и порты клиента и сервера совпадают.

То, что я хотел бы случиться, - то, что запись просто терпит неудачу тихо (или регистрирует ошибку), и клиенты не затронуты. Как сейчас пишут, весь сайт выходит из строя из-за этой единственной точки отказа.

Есть идеи - я что-то упускаю из виду?

2 ответа

Решение

Я хотел бы принять ответ tgolisch как решение, которое работает для меня.

  • В Global.asax мы будем искать отсутствующую ошибку StateServer в событии Application_Error
  • Если мы найдем его, мы будем использовать Server.ClearError() и зарегистрировать ошибку
  • Мы также будем использовать это, чтобы зарегистрировать ошибку и, возможно, отправить предупреждение

Спасибо всем!

Ну, это может быть сложно. Asp.net использует сессию плотно, поэтому, если хранилище сессии не удается, asp.net также не будет работать во время инициализации модуля сессии. Вы можете написать собственный поставщик состояния сеанса, который обернет существующий, и в случае сбоя он вернет пустые элементы сеанса, но это может быть сложно использовать, потому что поведение сеанса может быть непредсказуемым.

Вы можете взглянуть на встроенный поставщик состояния сеанса SQL, который имеет аварийное переключение на случай, если на вашем SQL-сервере есть репликация.

Update1

Вот пример оболочки для поставщиков сеансов по умолчанию

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

По сути, вы можете сделать try\catch для каждого метода, как в методе GetItem, и в случае ошибки вы можете вернуть пустой объект сеанса. Если это не удастся в приложении try\catch, оно останется в живых. Но производительность будет снижаться, так как для каждого запроса он генерирует пару исключений в Get\Release, которые будут обрабатываться в секции catch. Но в любом случае эти исключения немного снизят производительность

Другие вопросы по тегам