将SignalR集线器与Redis一起使用时如何跨负载均衡器服务器跨越ConcurrentDictionary

时间:2019-01-02 05:50:22

标签: asp.net-core redis signalr asp.net-core-signalr concurrentdictionary

我有带有Redis的SignalR横向扩展的ASP.NET Core Web应用程序设置。 使用内置组工作正常:

Clients.Group("Group_Name");

,并且可以承受多个负载均衡器。我假设SignalR自动将这些组保留在Redis中,以便所有服务器都知道我们拥有哪些组以及谁订阅了这些组。

但是,在我的情况下,我不能仅依靠组(或用户),因为无法将connectionId(在重载OnDisconnectedAsync且仅知道连接ID的情况下)映射回其组,并且您始终需要Group_Name来标识组。我需要识别该组的哪个部分在线,因此,OnDisconnectedAsync被叫时,我知道此人属于哪个组,以及他在对话的哪一边。

我已经做过一些研究,他们都建议(包括Microsoft Docs)使用类似这样的东西:

static readonly ConcurrentDictionary<string, ConversationInformation> connectionMaps;

在集线器本身中。

现在,这是一个很好的解决方案(并且是线程安全的),只不过它仅存在于一个负载均衡服务器的内存中,而其他服务器具有此字典的不同实例。

问题是,我是否必须手动坚持connectionMaps?以Redis为例?

类似的东西:

public class ChatHub : Hub
{
    static readonly ConcurrentDictionary<string, ConversationInformation> connectionMaps;

    ChatHub(IDistributedCache distributedCache)
    {

        connectionMaps = distributedCache.Get("ConnectionMaps");
       /// I think connectionMaps should not be static any more.
    }
}

,如果是,那么线程安全吗?如果没有,您是否可以提出一种适用于负载平衡的更好的解决方案?

1 个答案:

答案 0 :(得分:1)

为此一直在同一个问题作斗争。我想出的是在使用StackExchange.Redis.IDatabaseAsync和锁来处理并发性的同时,将集合保存在redis缓存中。 不幸的是,这使整个过程同步,但还没有办法解决。

这是我正在做的事情的核心,它获得了一个锁并从缓存中返回了反序列化的集合


    private async Task<ConcurrentDictionary<int, HubMedia>> GetMediaAttributes(bool requireLock)
        {
            if(requireLock)
            {
                var retryTime = 0;
                try
                {
                    while (!await _redisDatabase.LockTakeAsync(_mediaAttributesLock, _lockValue, _defaultLockDuration))
                    {
                        //wait till we can get a lock on the data, 100ms by default
                        await Task.Delay(100);
                        retryTime += 10;
                        if (retryTime > _defaultLockDuration.TotalMilliseconds)
                        {
                            _logger.LogError("Failed to get Media Attributes");
                            return null;
                        }
                    }
                }
                catch(TaskCanceledException e)
                {
                    _logger.LogError("Failed to take lock within the default 5 second wait time " + e);
                    return null;
                }

            }
            var mediaAttributes = await _redisDatabase.StringGetAsync(MEDIA_ATTRIBUTES_LIST);
            if (!mediaAttributes.HasValue)
            {
                return new ConcurrentDictionary<int, HubMedia>();
            }
            return JsonConvert.DeserializeObject<ConcurrentDictionary<int, HubMedia>>(mediaAttributes);
        }

在操作完集合后像这样更新集合

        private async Task<bool> UpdateCollection(string redisCollectionKey, object collection, string lockKey)
        {
            var success = false;
            try
            {
                success = await _redisDatabase.StringSetAsync(redisCollectionKey, JsonConvert.SerializeObject(collection, new JsonSerializerSettings
                {
                    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                }));
            }
            finally
            {
                await _redisDatabase.LockReleaseAsync(lockKey, _lockValue);
            }
            return success;
        }

完成后,我只是确保释放了锁,以供其他实例抓取和使用


private async Task ReleaseLock(string lockKey)
        {
            await _redisDatabase.LockReleaseAsync(lockKey, _lockValue);
        }

如果您能找到更好的方法,将很高兴听到。努力查找可扩展并具有数据保留和共享功能的任何文档。