2

Mein Problem besteht darin, sehr ähnlich wie diese: Protocol errors, "no more data" errors, "Zero length response" errors while using servicestack.redis in a high volume scenariounerwartete Antwort auf hochvolumige Szenario ServiceStack.Redis

I ServiceStack v3.9.54.0 in einer C# Web-Anwendung bin mit IIS arbeiten. Ich konnte die Fehler in beiden Redis Versionen 2.8.17 und 3.0.501 sehen.

Die Fehler Ich habe erhalten, sind die folgenden:

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) 

Und:

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) 

Das erste, was ich dachte, war, dass ich die Redis Verbindung über mehrere Threads teilte, aber ich kann das Problem nicht auf meiner Singleton Umsetzung der PooledRedisClientManager sehen (Configs ist eine statische Klasse, die die Verbindungsinformationen speichert):

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

} 

Alle Kunden werden durch den Pool erhalten, wie folgt:

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

Ich bin sicher, dass die redis var nicht über mehrere Threads gemeinsam genutzt wird und, soweit ich sehen kann, dieser Code zeigt ein richtige Thread-sichere Implementierung ...

Jede Hilfe würde sehr geschätzt werden.


Edit: Gemäß einiger Technologien, die ich benutze, ich keinen Zugriff auf den App-Startcode noch using Blöcke verwenden kann. Also, ich alle Kunden so wickeln:

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

Antwort

2

Diese Fehlermeldung eine Anzeige ist die gleiche redis Client-Instanz über mehrere Threads gemeinsam genutzt wird, wird der Quellcode zur Verfügung gestellt bietet keine Überprüfung, dass es nicht.

Die obige RedisProvider ist nur eine ausführlichere Version des Zugangs um einen Singleton gewickelt, zum Beispiel:

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

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

Die RedisManager muss nur einmal im App Startup initialisiert werden:

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

Fortan on, das ausführliche Sperren fügt nur Overhead hinzu und bietet keine Thread-Sicherheitsvorteile gegenüber dem direkten Zugriff auf den Singleton RedisManager.

während der Client-Lösung ist THREAD:

var redis = RedisProvider.GetClient(); 

redis Der Client-Instanz zurückgegeben wird, ist nicht THREAD (gemäß .NET Konventionen). Daher müssen Sie sicherstellen, dass Sie nicht dieselbe Instanz über mehrere Threads hinweg verwenden. Sie müssen außerdem sicherstellen, dass der Client nach der Verwendung entsorgt wird.

Um sicherzustellen, dass auf sie zugegriffen und im selben Thread angeordnet ist, sollten Sie die Client-Nutzung in einer using-Anweisung wickeln:

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

Wenn Sie dies tun, wenn Sie die RedisClient verwenden müssen und dies nicht tun teilen Sie die gleiche Clientinstanz in einem anderen Hintergrundthread, Async-Task, parallelisiertem Code usw. Sie sollten keine Multithreading-Probleme mehr haben. Wenn Sie eine neue Clientinstanz in einem anderen Thread benötigen, sollten Sie dasselbe Zugriffsmuster verwenden und eine separate Clientinstanz aus dem Pool abrufen (und entsorgen).

+0

Dank Ihrer Antwort mythz. Bitte sehen Sie sich meine Bearbeitung an. Während ich Ihre Vorschläge verstehe und stimme, kann ich davon ausgehen, dass Singleton-Implementierung als Thread-sicher ist, während ich den Client in den Versuch bringe, endlich zu blockieren und sicherzustellen, dass ich ihn nicht über mehrere Threads teile? Gibt es eine andere mögliche Ursache für das Problem, abgesehen davon, dass der Client in Multi-Threads geteilt wird? – Dinei

+1

@ DineiA.Rockenbach Dieser Fehler besagt, dass es nicht die erwartete Antwort erhält, die immer ein Hinweis darauf war, dass dieselbe Clientinstanz zum Senden eines neuen Befehls in einem anderen Thread verwendet wurde. Ich werde nicht in der Lage sein zu sagen, wo in Ihrer Code-Basis das Problem ist, aber wenn Sie eine kleine eigenständige Repo (z. B. auf GitHub) zusammenstellen können, die den Fehler zeigt, kann ich Sie wissen lassen, was das Problem ist. – mythz

+0

@mythz Wo sollte ich den statischen "IRedisClientsManager" haben? Ich benutze überhaupt kein DI-Framework. Ich habe "IRedisClientsManager" pro Aufruf (mit einer 'using' Anweisung) sowie den 'IRedisClient' erstellt und disponiert. Ist es in Ordnung, kein Singleton für den Client-Manager zu verwenden? – Alisson