2

Моя проблема очень похожа на это: Protocol errors, "no more data" errors, "Zero length response" errors while using servicestack.redis in a high volume scenarioНеожиданный ответ на сценарии большого объема с использованием ServiceStack.Redis

Я использую ServiceStack v3.9.54.0 в C# веб-приложений, работающих на IIS. Я видел ошибки в версиях Redis 2.8.17 и 3.0.501.

Ошибки я получал следующие:

ServiceStack.Redis.RedisResponseException: Unexpected reply: +PONG, sPort: 65197, LastCommand: GET EX:KEY:230 
at ServiceStack.Redis.RedisNativeClient.CreateResponseError(String error) 
at ServiceStack.Redis.RedisNativeClient.ParseSingleLine(String r) 
at ServiceStack.Redis.RedisNativeClient.ReadData() 
at ServiceStack.Redis.RedisNativeClient.SendExpectData(Byte[][] cmdWithBinaryArgs) 
at ServiceStack.Redis.RedisNativeClient.GetBytes(String key) 
at ServiceStack.Redis.RedisNativeClient.Get(String key) 

И:

ServiceStack.Redis.RedisResponseException: Unknown reply on integer response: 43PONG, sPort: 59017, LastCommand: EXISTS EX:AnKey:Cmp6 
at ServiceStack.Redis.RedisNativeClient.CreateResponseError(String error) 
at ServiceStack.Redis.RedisNativeClient.ReadLong() 
at ServiceStack.Redis.RedisNativeClient.SendExpectLong(Byte[][] cmdWithBinaryArgs) 
at ServiceStack.Redis.RedisNativeClient.Exists(String key) 
at Redis.Documentos.RedisBaseType.Exists(String key) 

Первое, что я думал, что я делил соединение Redis через несколько потоков, но Я не вижу проблемы с моей одноэлементной реализацией PooledRedisClientManager (Configs - это статический класс, который хранит информацию о соединении):

public class RedisProvider 
{ 
    public PooledRedisClientManager Pool { get; set; } 
    private RedisProvider() 
    { 

     var srv = new List<string> { $"{Configs.Server}:{Configs.Port}" }; 

     Pool = new PooledRedisClientManager(srv, srv, null, 
      Configs.Database, Configs.PoolSize, Configs.PoolTimeout); 
    } 

    public IRedisClient GetClient() 
    { 
     try 
     { 
      var connection = (RedisClient)Pool.GetClient(); 
      return connection; 
     } 
     catch (TimeoutException) 
     { 
      return null; 
     } 
    } 

    private static RedisProvider _instance; 
    public static object _providerLock = new object(); 
    public static RedisProvider Provider 
    { 
     get 
     { 
      lock (_providerLock) 
      { 
       if (_instance == null) 
       { 
        var instance = new RedisProvider(); 
        _instance = instance; 
        return _instance; 
       } 
       else 
       { 

        return _instance; 
       } 
      } 
     } 
    } 

} 

Все клиенты получают через бассейн, следующим образом:

var redis = (RedisClient)RedisProvider.Provider.GetClient(); 

Я уверен, что redis вар не разделяется на несколько потоков, и, насколько я могу видеть, этот код показывает надлежащая потокобезопасная реализация ...

Любая помощь будет высоко оценена.


Edit: В соответствии с некоторыми технологиями, которые я использую, у меня нет доступа к коду приложения Startup не может использовать using блоки. Таким образом, я обернуть все клиенты так:

RedisClient redis; 
try { 
    redis = (RedisClient)RedisProvider.Provider.GetClient(); 
    // Do stuff 
} finally { 
    redis.Dispose(); 
} 

ответ

2

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

выше RedisProvider просто более подробная версия доступа обернута вокруг одноточечного, например:

public static class RedisProvider 
{ 
    public static IRedisClientManager Pool { get; set; } 

    public static RedisClient GetClient() 
    { 
     return (RedisClient)Pool.GetClient(); 
    } 
} 

RedisManager нужно только инициализировать один раз на App Запуск:

var srv = new List<string> { $"{Configs.Server}:{Configs.Port}" }; 
RedisProvider.Pool = new PooledRedisClientManager(srv, srv, null, 
    Configs.Database, Configs.PoolSize, Configs.PoolTimeout); 

С тех пор on, вербальная блокировка просто добавляет накладные расходы и не обеспечивает каких-либо преимуществ по обеспечению безопасности потоков при непосредственном доступе к Singleton RedisManager.

В то время как разрешение клиента является поточно:

var redis = RedisProvider.GetClient(); 

Redis клиента экземпляр возвращается не THREADSAFE (согласно конвенции .NET). В результате вам нужно убедиться, что вы не используете один и тот же экземпляр для нескольких потоков, вам также необходимо обеспечить, чтобы клиент был удален после использования.

Для того, чтобы она доступна и расположена в том же потоке, вы должны обернуть использование клиента в использовании высказыванием:

using (var redis = RedisProvider.GetClient()) 
{ 
    //... 
} 

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

+0

Спасибо, ответив на миф. Пожалуйста, взгляните на мое редактирование. Хотя я понимаю и согласен с вашими предложениями, могу ли я предположить, что реализация singleton как потокобезопасная, тогда как я помещаю клиент в блок finally try и гарантирую, что я не использую его для нескольких потоков? Есть ли еще одна возможная причина проблемы, помимо совместного использования клиента в нескольких потоках? – Dinei

+1

@ DineiA.Rockenbach Эта ошибка говорит, что не получает ожидаемого ответа, который всегда был признаком того, что один и тот же экземпляр клиента использовался для отправки новой команды в другой поток. Я не буду рассказывать, где в вашей базе кода проблема, но если вы можете собрать небольшое автономное репо (например, на GitHub), показывающее ошибку, я могу сообщить вам, в чем проблема. – mythz

+0

@mythz, где я должен использовать статический 'IRedisClientsManager'? Я вообще не использую рамки DI. Я создавал и удалял 'IRedisClientsManager' за вызов (с помощью' using' statement), а также 'IRedisClient'. Разве это не использовать singleton для менеджера клиентов? – Alisson

 Смежные вопросы

  • Нет связанных вопросов^_^