使用 ServiceStack.Redis 对大量场景的意外回复
Unexpected reply on high volume scenario using 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 连接,但我看不到 PooledRedisClientManager
(Configs
是一个单例实现的问题static class 存储连接信息):
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;
}
}
}
}
}
所有client都是通过pool获取的,如下:
var redis = (RedisClient)RedisProvider.Provider.GetClient();
我确定 redis
var 不会在多个线程之间共享,据我所知,这段代码显示了正确的线程安全实现...
如有任何帮助,我们将不胜感激。
编辑: 根据我使用的某些技术,我无法访问应用程序启动代码,也无法使用 using
块。所以,我这样包装所有客户:
RedisClient redis;
try {
redis = (RedisClient)RedisProvider.Provider.GetClient();
// Do stuff
} finally {
redis.Dispose();
}
此错误消息表明同一个 redis 客户端实例正在跨多个线程共享,所提供的源代码未提供任何证明它不是的验证。
上面的 RedisProvider
只是围绕单例的更详细的访问版本,例如:
public static class RedisProvider
{
public static IRedisClientManager Pool { get; set; }
public static RedisClient GetClient()
{
return (RedisClient)Pool.GetClient();
}
}
RedisManager 只需要在应用程序启动时初始化一次:
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 时执行此操作并且不在不同的后台线程、异步任务、并行代码等中共享相同的客户端实例,您应该不再有任何多线程问题。当您在不同的线程中需要一个新的客户端实例时,您应该使用相同的访问模式并从池中检索(和处置)一个单独的客户端实例。
我的问题与这个非常相似:
我在 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 连接,但我看不到 PooledRedisClientManager
(Configs
是一个单例实现的问题static class 存储连接信息):
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;
}
}
}
}
}
所有client都是通过pool获取的,如下:
var redis = (RedisClient)RedisProvider.Provider.GetClient();
我确定 redis
var 不会在多个线程之间共享,据我所知,这段代码显示了正确的线程安全实现...
如有任何帮助,我们将不胜感激。
编辑: 根据我使用的某些技术,我无法访问应用程序启动代码,也无法使用 using
块。所以,我这样包装所有客户:
RedisClient redis;
try {
redis = (RedisClient)RedisProvider.Provider.GetClient();
// Do stuff
} finally {
redis.Dispose();
}
此错误消息表明同一个 redis 客户端实例正在跨多个线程共享,所提供的源代码未提供任何证明它不是的验证。
上面的 RedisProvider
只是围绕单例的更详细的访问版本,例如:
public static class RedisProvider
{
public static IRedisClientManager Pool { get; set; }
public static RedisClient GetClient()
{
return (RedisClient)Pool.GetClient();
}
}
RedisManager 只需要在应用程序启动时初始化一次:
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 时执行此操作并且不在不同的后台线程、异步任务、并行代码等中共享相同的客户端实例,您应该不再有任何多线程问题。当您在不同的线程中需要一个新的客户端实例时,您应该使用相同的访问模式并从池中检索(和处置)一个单独的客户端实例。