我有带有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.
}
}
,如果是,那么线程安全吗?如果没有,您是否可以提出一种适用于负载平衡的更好的解决方案?
答案 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);
}
如果您能找到更好的方法,将很高兴听到。努力查找可扩展并具有数据保留和共享功能的任何文档。