同时读取请求的 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();
    }
}

但我正在努力解决的是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 能够在同一集上"同时"处理大量请求,但显然我做错了。:)

顺便说一句:Redis 2.8.12 运行在 Windows Server 2008 机器上(即将在 2012 年)。

感谢您的任何建议!

错误消息表示跨多个线程使用 RedisClient 的非线程安全实例,因为它正在接收对意外/发送的请求的响应。

为了确保您正确使用,我只会传入线程安全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 服务器连接,因此可以安全地保留为单例。

最新更新