ServiceStack Redis проблемы с одновременными запросами на чтение
Я использую реализацию ServiceStack.Redis для кэширования событий, доставляемых через интерфейс Web API. Эти события должны быть вставлены в кеш и автоматически удалены через некоторое время (например, через 3 дня):
private readonly IRedisTypedClient<CachedMonitoringEvent> _eventsCache;
public EventMonitorCache([NotNull]IRedisTypedClient<CachedMonitoringEvent> eventsCache)
{
_eventsCache = eventsCache;
}
public void Dispose()
{
//Release connections again
_eventsCache.Dispose();
}
public void AddOrUpdate(MonitoringEvent monitoringEvent)
{
if (monitoringEvent == null)
return;
try
{
var cacheExpiresAt = DateTime.Now.Add(CacheExpirationDuration);
CachedMonitoringEvent cachedEvent;
string eventKey = CachedMonitoringEvent.CreateUrnId(monitoringEvent);
if (_eventsCache.ContainsKey(eventKey))
{
cachedEvent = _eventsCache[eventKey];
cachedEvent.SetExpiresAt(cacheExpiresAt);
cachedEvent.MonitoringEvent = monitoringEvent;
}
else
cachedEvent = new CachedMonitoringEvent(monitoringEvent, cacheExpiresAt);
_eventsCache.SetEntry(eventKey, cachedEvent, CacheExpirationDuration);
}
catch (Exception ex)
{
Log.Error("Error while caching MonitoringEvent", ex);
}
}
public List<MonitoringEvent> GetAll()
{
IList<CachedMonitoringEvent> allEvents = _eventsCache.GetAll();
return allEvents
.Where(e => e.MonitoringEvent != null)
.Select(e => e.MonitoringEvent)
.ToList();
}
Реестр StructureMap 3 выглядит следующим образом:
public class RedisRegistry : Registry
{
private readonly static RedisConfiguration RedisConfiguration = Config.Feeder.Redis;
public RedisRegistry()
{
For<IRedisClientsManager>().Singleton().Use(BuildRedisClientsManager());
For<IRedisTypedClient<CachedMonitoringEvent>>()
.AddInstances(i => i.ConstructedBy(c => c.GetInstance<IRedisClientsManager>()
.GetClient().GetTypedClient<CachedMonitoringEvent>()));
}
private static IRedisClientsManager BuildRedisClientsManager()
{
return new PooledRedisClientManager(RedisConfiguration.Host + ":" + RedisConfiguration.Port);
}
}
Первый сценарий - извлечь все кэшированные события (несколько сотен) и передать их через ODataV3 и ODataV4 в Excel PowerTools для визуализации. Это работает как ожидалось:
public class MonitoringEventsODataV3Controller : EntitySetController<MonitoringEvent, string>
{
private readonly IEventMonitorCache _eventMonitorCache;
public MonitoringEventsODataV3Controller([NotNull]IEventMonitorCache eventMonitorCache)
{
_eventMonitorCache = eventMonitorCache;
}
[ODataRoute("MonitoringEvents")]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public override IQueryable<MonitoringEvent> Get()
{
var allEvents = _eventMonitorCache.GetAll();
return allEvents.AsQueryable();
}
}
Но я борюсь с фильтрацией OData, которую выполняет Excel PowerQuery. Я осознаю тот факт, что я пока не делаю никакой фильтрации на стороне сервера, но это не имеет значения в настоящее время. Когда я фильтрую какое-либо свойство и нажимаю кнопку "Обновить", PowerQuery отправляет несколько запросов (я видел до трех) одновременно. Я полагаю, что он сначала получает весь набор данных, а затем выполняет следующие запросы с фильтрами. Это приводит к различным исключениям для ServiceStack.Redis:
An exception of type 'ServiceStack.Redis.RedisResponseException' occurred in ServiceStack.Redis.dll but was not handled in user code
С дополнительной информацией, такой как:
Additional information: Unknown reply on multi-request: 117246333|company|osdmonitoringpreinst|2014-12-22|113917, sPort: 54980, LastCommand:
Или же
Additional information: Invalid termination, sPort: 54980, LastCommand:
Или же
Additional information: Unknown reply on multi-request: 57, sPort: 54980, LastCommand:
Или же
Additional information: Type definitions should start with a '{', expecting serialized type 'CachedMonitoringEvent', got string starting with: u259447|company|osdmonitoringpreinst|2014-12-18|1
Все эти исключения происходят на _eventsCache.GetAll()
,
Там должно быть что-то, что я скучаю. Я уверен, что Redis способен обрабатывать много запросов "одновременно" на одном наборе, но, очевидно, я делаю это неправильно.:)
Кстати: Redis 2.8.12 работает на компьютере с Windows Server 2008 (скоро 2012).
Спасибо за любой совет!
1 ответ
Сообщения об ошибках указывают на использование не поточно-безопасного экземпляра RedisClient в нескольких потоках, поскольку он получает ответы на запросы, которые он не ожидал / отправил.
Чтобы обеспечить правильное использование, я бы только перешел в Thread-Safe IRedisClientsManager
синглтон, например:
public EventMonitorCache([NotNull]IRedisClientsManager redisManager)
{
this.redisManager = redisManager;
}
Затем явным образом разрешите и утилизируйте клиент Redis в ваших методах, например:
public void AddOrUpdate(MonitoringEvent monitoringEvent)
{
if (monitoringEvent == null)
return;
try
{
using (var redis = this.redisManager.GetClient())
{
var _eventsCache = redis.As<CachedMonitoringEvent>();
var cacheExpiresAt = DateTime.Now.Add(CacheExpirationDuration);
CachedMonitoringEvent cachedEvent;
string eventKey = CachedMonitoringEvent.CreateUrnId(monitoringEvent);
if (_eventsCache.ContainsKey(eventKey))
{
cachedEvent = _eventsCache[eventKey];
cachedEvent.SetExpiresAt(cacheExpiresAt);
cachedEvent.MonitoringEvent = monitoringEvent;
}
else
cachedEvent = new CachedMonitoringEvent(monitoringEvent, cacheExpiresAt);
_eventsCache.SetEntry(eventKey, cachedEvent, CacheExpirationDuration);
}
}
catch (Exception ex)
{
Log.Error("Error while caching MonitoringEvent", ex);
}
}
И в GetAll():
public List<MonitoringEvent> GetAll()
{
using (var redis = this.redisManager.GetClient())
{
var _eventsCache = redis.As<CachedMonitoringEvent>();
IList<CachedMonitoringEvent> allEvents = _eventsCache.GetAll();
return allEvents
.Where(e => e.MonitoringEvent != null)
.Select(e => e.MonitoringEvent)
.ToList();
}
}
Это будет работать независимо от того, какой срок службы вашего EventMonitorCache
Зависимость регистрируется как, например, безопасно хранить как одиночный, так как EventMonitorCache
больше не держит соединение с сервером Redis.