Storing user lists per server

This commit is contained in:
Pawel Kadluczka 2017-05-01 16:33:07 -07:00 committed by Pawel Kadluczka
parent 5d32407f24
commit 7300413d0c
1 changed files with 44 additions and 18 deletions

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -18,9 +17,11 @@ using StackExchange.Redis;
namespace ChatSample namespace ChatSample
{ {
public class RedisUserTracker<THub> : IUserTracker<THub> public class RedisUserTracker<THub> : IUserTracker<THub>, IDisposable
{ {
private readonly RedisKey UserIndexRedisKey = "UserIndex"; private readonly string ServerId = $"server:{Guid.NewGuid().ToString("D")}";
private readonly RedisKey ServerIndexRedisKey = "ServerIndex";
private readonly RedisKey UserIndexRedisKey;
private readonly int _redisDatabase; private readonly int _redisDatabase;
private readonly ConnectionMultiplexer _redisConnection; private readonly ConnectionMultiplexer _redisConnection;
private readonly ISubscriber _redisSubscriber; private readonly ISubscriber _redisSubscriber;
@ -36,17 +37,31 @@ namespace ChatSample
public RedisUserTracker(IOptions<RedisOptions> options, ILoggerFactory loggerFactory) public RedisUserTracker(IOptions<RedisOptions> options, ILoggerFactory loggerFactory)
{ {
UserIndexRedisKey = $"{ServerId}:users";
_logger = loggerFactory.CreateLogger<RedisUserTracker<THub>>(); _logger = loggerFactory.CreateLogger<RedisUserTracker<THub>>();
_redisDatabase = options.Value.Options.DefaultDatabase.GetValueOrDefault(); _redisDatabase = options.Value.Options.DefaultDatabase.GetValueOrDefault();
_redisConnection = ConnectToRedis(options.Value, _logger); _redisConnection = StartRedisConnection(options.Value);
_redisSubscriber = _redisConnection.GetSubscriber();
_redisSubscriber = _redisConnection.GetSubscriber();
_userAddedChannel = new RedisChannel(UserAddedChannelName, RedisChannel.PatternMode.Literal); _userAddedChannel = new RedisChannel(UserAddedChannelName, RedisChannel.PatternMode.Literal);
_userRemovedChannel = new RedisChannel(UserRemovedChannelName, RedisChannel.PatternMode.Literal); _userRemovedChannel = new RedisChannel(UserRemovedChannelName, RedisChannel.PatternMode.Literal);
_redisSubscriber.Subscribe(_userAddedChannel, (channel, value) => UserJoined(DeserializerUser(value))); _redisSubscriber.Subscribe(_userAddedChannel, (channel, value) => UserJoined(DeserializerUser(value)));
_redisSubscriber.Subscribe(_userRemovedChannel, (channel, value) => UserLeft(DeserializerUser(value))); _redisSubscriber.Subscribe(_userRemovedChannel, (channel, value) => UserLeft(DeserializerUser(value)));
} }
private ConnectionMultiplexer StartRedisConnection(RedisOptions options)
{
// TODO: handle connection failures
var redisConnection = ConnectToRedis(options, _logger);
// Register connection
var database = redisConnection.GetDatabase(_redisDatabase);
database.SetAdd(ServerIndexRedisKey, ServerId);
return redisConnection;
}
private static ConnectionMultiplexer ConnectToRedis(RedisOptions options, ILogger logger) private static ConnectionMultiplexer ConnectToRedis(RedisOptions options, ILogger logger)
{ {
var loggerTextWriter = new LoggerTextWriter(logger); var loggerTextWriter = new LoggerTextWriter(logger);
@ -63,6 +78,7 @@ namespace ChatSample
var configurationOptions = new ConfigurationOptions(); var configurationOptions = new ConfigurationOptions();
configurationOptions.EndPoints.Add(IPAddress.Loopback, 0); configurationOptions.EndPoints.Add(IPAddress.Loopback, 0);
configurationOptions.SetDefaultPorts(); configurationOptions.SetDefaultPorts();
return ConnectionMultiplexer.Connect(configurationOptions, loggerTextWriter); return ConnectionMultiplexer.Connect(configurationOptions, loggerTextWriter);
} }
@ -90,24 +106,34 @@ namespace ChatSample
public async Task<IEnumerable<UserDetails>> UsersOnline() public async Task<IEnumerable<UserDetails>> UsersOnline()
{ {
var database = _redisConnection.GetDatabase(_redisDatabase); var database = _redisConnection.GetDatabase(_redisDatabase);
var userIds = await database.SetMembersAsync(UserIndexRedisKey);
var users = await database.StringGetAsync(userIds.Select(id => (RedisKey)(string)id).ToArray()); var userIds = await database.ScriptEvaluateAsync(
return users.Select(user => DeserializerUser(user)); @"local keys = { }
for i, key in pairs(redis.call('smembers', KEYS[1])) do
table.insert(keys, key.. ':users')
end
return redis.call('sunion', unpack(keys))", new[] { ServerIndexRedisKey });
if (!userIds.IsNull)
{
var users = await database.StringGetAsync(((RedisValue[])userIds).Select(id => (RedisKey)(string)id).ToArray());
return users.Where(user => !user.IsNull).Select(user => DeserializerUser(user));
}
return Enumerable.Empty<UserDetails>();
} }
private static string GetUserRedisKey(Connection connection) private static string GetUserRedisKey(Connection connection) => $"user:{connection.ConnectionId}";
{
return $"user:{connection.ConnectionId}";
}
private static string SerializeUser(Connection connection) private static string SerializeUser(Connection connection) =>
{ $"{{ \"ConnectionID\": \"{connection.ConnectionId}\", \"Name\": \"{connection.User.Identity.Name}\" }}";
return $"{{ \"ConnectionID\": \"{connection.ConnectionId}\", \"Name\": \"{connection.User.Identity.Name}\" }}";
}
private static UserDetails DeserializerUser(string userJson) private static UserDetails DeserializerUser(string userJson) =>
JsonConvert.DeserializeObject<UserDetails>(userJson);
public void Dispose()
{ {
return JsonConvert.DeserializeObject<UserDetails>(userJson); _redisSubscriber.UnsubscribeAll();
} }
private class LoggerTextWriter : TextWriter private class LoggerTextWriter : TextWriter