Неожиданный ответ в сценарии большого объема с использованием ServiceStack.Redis
Моя проблема очень похожа на эту: ошибки протокола, ошибки "больше нет данных", ошибки "ответ нулевой длины" при использовании 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
var не распределяется между несколькими потоками, и, насколько я вижу, этот код показывает правильную поточно-ориентированную реализацию...
Любая помощь приветствуется.
Изменить: Согласно некоторым технологиям, которые я использую, я не имею доступа к коду запуска приложения и не могу использовать using
блоки. Итак, я обертываю всех клиентов так:
RedisClient redis;
try {
redis = (RedisClient)RedisProvider.Provider.GetClient();
// Do stuff
} finally {
redis.Dispose();
}
1 ответ
Это сообщение об ошибке указывает на то, что один и тот же экземпляр клиента 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 не является Thread-Safe (согласно соглашениям.NET). В результате вам нужно убедиться, что вы не используете один и тот же экземпляр в нескольких потоках, а также убедиться, что клиент удаляется после использования.
Чтобы убедиться, что к нему обращаются и располагают в одном потоке, вы должны заключить использование клиента в оператор using:
using (var redis = RedisProvider.GetClient())
{
//...
}
Если вы делаете это всякий раз, когда вам нужно использовать RedisClient и не использовать один и тот же экземпляр клиента в другом фоновом потоке, асинхронной задаче, распараллеленном коде и т. Д., У вас больше не должно быть проблем с многопоточностью. Когда вам нужен новый экземпляр клиента в другом потоке, вы должны использовать тот же шаблон доступа и извлечь (и утилизировать) отдельный экземпляр клиента из пула.