ServiceStack Redis 同时读取请求的问题
ServiceStack Redis problems with simultaneous read requests
我正在使用 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();
}
}
但我正在努力解决的是 Excel PowerQuery 所做的 OData 过滤。我知道我还没有进行任何服务器端过滤,但目前这并不重要。当我筛选任何属性并单击刷新时,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 能够在同一组上处理大量请求 "simultaneously",但显然我做错了。 :)
顺便说一句:Redis 2.8.12 运行 在 Windows Server 2008 机器上(很快 2012)。
感谢任何建议!
错误消息表明在多个线程中使用了 RedisClient 的非线程安全实例,因为它正在获取对请求的响应,但它没有 expect/send。
为了确保您正确使用,我只会传入线程安全 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 服务器连接,因此作为单例保持是安全的。
我正在使用 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();
}
}
但我正在努力解决的是 Excel PowerQuery 所做的 OData 过滤。我知道我还没有进行任何服务器端过滤,但目前这并不重要。当我筛选任何属性并单击刷新时,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 能够在同一组上处理大量请求 "simultaneously",但显然我做错了。 :)
顺便说一句:Redis 2.8.12 运行 在 Windows Server 2008 机器上(很快 2012)。
感谢任何建议!
错误消息表明在多个线程中使用了 RedisClient 的非线程安全实例,因为它正在获取对请求的响应,但它没有 expect/send。
为了确保您正确使用,我只会传入线程安全 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 服务器连接,因此作为单例保持是安全的。