2

我的问题与这个问题非常相似:在大容量场景中使用 servicestack.redis 时出现协议错误、“没有更多数据”错误、“零长度响应”错误

我在使用 IIS 的 C# Web 应用程序中使用 ServiceStack v3.9.54.0。我可以看到 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 连接,但是我在我的单例实现中看不到问题PooledRedisClientManagerConfigs是一个存储连接信息的静态类):

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

}

所有的客户端都是通过pool获取的,如下:

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

我确定redisvar 不会在多个线程之间共享,据我所知,这段代码显示了一个正确的线程安全实现......

任何帮助将非常感激。


编辑:根据我使用的某些技术,我无法访问应用程序启动代码,也无法使用using块。所以,我像这样包装所有客户:

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

1 回答 1

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

从那时起,详细锁定只会增加开销,并且不会比直接访问 Singleton RedisManager 提供任何线程安全优势。

虽然解析客户端是 ThreadSafe:

var redis = RedisProvider.GetClient();

返回的 redis 客户端实例不是线程安全的(根据 .NET 约定)。因此,您需要确保您没有在多个线程之间共享同一个实例,您还需要确保客户端在使用后被释放。

为确保在同一个线程中访问和处理它,您应该将客户端使用情况包装在 using 语句中:

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

如果您在需要使用 RedisClient 时执行此操作,并且不在不同的后台线程、异步任务、并行代码等中共享相同的客户端实例,那么您应该不再有任何多线程问题。当您需要不同线程中的新客户端实例时,您应该使用相同的访问模式并从池中检索(和处置)一个单独的客户端实例。

于 2016-04-05T21:44:29.767 回答