Switch to GrpcClientIdentificationService and abolish Redis for Idents (#12)

* add GrpcClientIdentificationService

* remove unnecessary gauges

* set to no retry policy

* initialize metrics

Co-authored-by: Stanley Dimant <root.darkarchon@outlook.com>
This commit is contained in:
rootdarkarchon
2022-10-05 23:10:36 +02:00
committed by GitHub
parent 08b04e14d5
commit 17f26714ce
13 changed files with 373 additions and 209 deletions

View File

@@ -8,9 +8,7 @@ using MareSynchronosServer.Utils;
using MareSynchronosShared.Authentication; using MareSynchronosShared.Authentication;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models;
using MareSynchronosShared.Protos; using MareSynchronosShared.Protos;
using MareSynchronosShared.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
@@ -27,7 +25,7 @@ public partial class MareHub : Hub
private readonly FileService.FileServiceClient _fileServiceClient; private readonly FileService.FileServiceClient _fileServiceClient;
private readonly SystemInfoService _systemInfoService; private readonly SystemInfoService _systemInfoService;
private readonly IHttpContextAccessor _contextAccessor; private readonly IHttpContextAccessor _contextAccessor;
private readonly IClientIdentificationService _clientIdentService; private readonly GrpcClientIdentificationService _clientIdentService;
private readonly MareHubLogger _logger; private readonly MareHubLogger _logger;
private readonly MareDbContext _dbContext; private readonly MareDbContext _dbContext;
private readonly Uri _cdnFullUri; private readonly Uri _cdnFullUri;
@@ -38,7 +36,7 @@ public partial class MareHub : Hub
public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient, public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient,
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor, MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor,
IClientIdentificationService clientIdentService) GrpcClientIdentificationService clientIdentService)
{ {
_mareMetrics = mareMetrics; _mareMetrics = mareMetrics;
_authServiceClient = authServiceClient; _authServiceClient = authServiceClient;
@@ -118,9 +116,11 @@ public partial class MareHub : Hub
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)] [Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
public async Task<bool> CheckClientHealth() public async Task<bool> CheckClientHealth()
{ {
var needsReconnect = string.IsNullOrEmpty(await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false)); var serverId = await _clientIdentService.GetServerForUid(AuthenticatedUserId).ConfigureAwait(false);
if (needsReconnect) bool needsReconnect = false;
if (string.IsNullOrEmpty(serverId) || !string.Equals(serverId, _shardName, StringComparison.Ordinal))
{ {
needsReconnect = true;
_logger.LogCallWarning(Api.InvokeCheckClientHealth, needsReconnect); _logger.LogCallWarning(Api.InvokeCheckClientHealth, needsReconnect);
} }
return needsReconnect; return needsReconnect;

View File

@@ -0,0 +1,158 @@
using Grpc.Core;
using MareSynchronosShared.Metrics;
using MareSynchronosShared.Protos;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MareSynchronosServer.Services;
public class GrpcClientIdentificationService : IHostedService
{
private readonly string _shardName;
private readonly ILogger<GrpcClientIdentificationService> _logger;
private readonly IdentificationService.IdentificationServiceClient _grpcIdentClient;
private readonly MareMetrics _metrics;
protected ConcurrentDictionary<string, string> OnlineClients = new(StringComparer.Ordinal);
private bool _grpcIsFaulty = false;
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger, IdentificationService.IdentificationServiceClient gprcIdentClient, MareMetrics metrics, IConfiguration configuration)
{
var config = configuration.GetSection("MareSynchronos");
_shardName = config.GetValue("ServerName", "Main");
_logger = logger;
_grpcIdentClient = gprcIdentClient;
_metrics = metrics;
}
public async Task<string?> GetCharacterIdentForUid(string uid)
{
if (OnlineClients.TryGetValue(uid, out string ident))
{
return ident;
}
var result = await InvokeOnGrpc(_grpcIdentClient.GetIdentForUidAsync(new UidMessage { Uid = uid })).ConfigureAwait(false);
if (result == default(CharacterIdentMessage)) return null;
return result.Ident;
}
public async Task<string?> GetServerForUid(string uid)
{
if (OnlineClients.ContainsKey(uid))
{
return _shardName;
}
var result = await InvokeOnGrpc(_grpcIdentClient.GetIdentForUidAsync(new UidMessage { Uid = uid })).ConfigureAwait(false);
if (result == default(CharacterIdentMessage)) return null;
return result.ServerId;
}
public async Task<long> GetOnlineUsers()
{
var result = await InvokeOnGrpc(_grpcIdentClient.GetOnlineUserCountAsync(new ServerMessage())).ConfigureAwait(false);
if (result == default(OnlineUserCountResponse)) return OnlineClients.Count;
return result.Count;
}
public async Task<string?> GetUidForCharacterIdent(string characterIdent)
{
bool existsLocal = OnlineClients.Any(o => string.Equals(o.Value, characterIdent, StringComparison.Ordinal));
if (existsLocal)
{
return OnlineClients.First(c => string.Equals(c.Value, characterIdent, StringComparison.Ordinal)).Key;
}
var result = await InvokeOnGrpc(_grpcIdentClient.GetUidForCharacterIdentAsync(new CharacterIdentMessage { Ident = characterIdent, ServerId = string.Empty })).ConfigureAwait(false);
if (result == default(UidMessage)) return null;
return result.Uid;
}
public async Task MarkUserOffline(string uid)
{
OnlineClients.TryRemove(uid, out _);
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
await ExecuteOnGrpc(_grpcIdentClient.RemoveIdentForUidAsync(new RemoveIdentMessage() { ServerId = _shardName, Uid = uid })).ConfigureAwait(false);
}
public async Task MarkUserOnline(string uid, string charaIdent)
{
OnlineClients[uid] = charaIdent;
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
await ExecuteOnGrpc(_grpcIdentClient.SetIdentForUidAsync(new SetIdentMessage() { Ident = charaIdent, ServerId = _shardName, Uid = uid })).ConfigureAwait(false);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
}
private async Task<T> InvokeOnGrpc<T>(AsyncUnaryCall<T> toExecute)
{
try
{
var result = await toExecute.ConfigureAwait(false);
await CheckFaultStateAndResend().ConfigureAwait(false);
return result;
}
catch
{
SetGrpcFaulty();
return default;
}
}
private async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
{
try
{
await toExecute.ConfigureAwait(false);
await CheckFaultStateAndResend().ConfigureAwait(false);
}
catch
{
SetGrpcFaulty();
}
}
private async Task CheckFaultStateAndResend()
{
if (_grpcIsFaulty)
{
_logger.LogInformation("GRPC connection is restored, sending current server idents");
await _grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }).ConfigureAwait(false);
var msg = new ServerIdentMessage();
msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage()
{
Ident = c.Value,
Uid = c.Key,
ServerId = _shardName
}));
await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false);
_grpcIsFaulty = false;
}
}
private void SetGrpcFaulty()
{
if (!_grpcIsFaulty)
{
_grpcIsFaulty = true;
_logger.LogWarning("GRPC connection is faulty");
}
}
}

View File

@@ -6,7 +6,6 @@ using MareSynchronos.API;
using MareSynchronosServer.Hubs; using MareSynchronosServer.Hubs;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using MareSynchronosShared.Services;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -19,14 +18,14 @@ public class SystemInfoService : IHostedService, IDisposable
{ {
private readonly MareMetrics _mareMetrics; private readonly MareMetrics _mareMetrics;
private readonly IServiceProvider _services; private readonly IServiceProvider _services;
private readonly IClientIdentificationService _clientIdentService; private readonly GrpcClientIdentificationService _clientIdentService;
private readonly ILogger<SystemInfoService> _logger; private readonly ILogger<SystemInfoService> _logger;
private readonly IHubContext<MareHub> _hubContext; private readonly IHubContext<MareHub> _hubContext;
private Timer _timer; private Timer _timer;
private string _shardName; private string _shardName;
public SystemInfoDto SystemInfoDto { get; private set; } = new(); public SystemInfoDto SystemInfoDto { get; private set; } = new();
public SystemInfoService(MareMetrics mareMetrics, IConfiguration configuration, IServiceProvider services, IClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext) public SystemInfoService(MareMetrics mareMetrics, IConfiguration configuration, IServiceProvider services, GrpcClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
{ {
_mareMetrics = mareMetrics; _mareMetrics = mareMetrics;
_services = services; _services = services;
@@ -56,7 +55,7 @@ public class SystemInfoService : IHostedService, IDisposable
{ {
SystemInfoDto = new SystemInfoDto() SystemInfoDto = new SystemInfoDto()
{ {
OnlineUsers = _clientIdentService.GetOnlineUsers().Result, OnlineUsers = (int)_clientIdentService.GetOnlineUsers().Result,
}; };
_hubContext.Clients.All.SendAsync(Api.OnUpdateSystemInfo, SystemInfoDto); _hubContext.Clients.All.SendAsync(Api.OnUpdateSystemInfo, SystemInfoDto);

View File

@@ -20,7 +20,6 @@ using Prometheus;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using System.Collections.Generic; using System.Collections.Generic;
using MareSynchronosServer.Services; using MareSynchronosServer.Services;
using MareSynchronosShared.Services;
using System.Net.Http; using System.Net.Http;
using MareSynchronosServer.Utils; using MareSynchronosServer.Utils;
@@ -64,6 +63,12 @@ public class Startup
} }
}; };
var identMethodConfig = new MethodConfig
{
Names = { MethodName.Default },
RetryPolicy = null
};
services.AddSingleton(new MareMetrics(new List<string> services.AddSingleton(new MareMetrics(new List<string>
{ {
MetricsAPI.CounterInitializedConnections, MetricsAPI.CounterInitializedConnections,
@@ -77,7 +82,10 @@ public class Startup
MetricsAPI.GaugePairs, MetricsAPI.GaugePairs,
MetricsAPI.GaugePairsPaused, MetricsAPI.GaugePairsPaused,
MetricsAPI.GaugeAvailableIOWorkerThreads, MetricsAPI.GaugeAvailableIOWorkerThreads,
MetricsAPI.GaugeAvailableWorkerThreads MetricsAPI.GaugeAvailableWorkerThreads,
MetricsAPI.GaugeGroups,
MetricsAPI.GaugeGroupPairs,
MetricsAPI.GaugeGroupPairsPaused
})); }));
services.AddGrpcClient<AuthService.AuthServiceClient>(c => services.AddGrpcClient<AuthService.AuthServiceClient>(c =>
@@ -98,6 +106,20 @@ public class Startup
{ {
c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }; c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } };
}); });
services.AddGrpcClient<IdentificationService.IdentificationServiceClient>(c =>
{
c.Address = new Uri(mareConfig.GetValue<string>("ServiceAddress"));
}).ConfigureChannel(c =>
{
c.ServiceConfig = new ServiceConfig { MethodConfigs = { identMethodConfig } };
c.HttpHandler = new SocketsHttpHandler()
{
EnableMultipleHttp2Connections = true
};
});
services.AddSingleton<GrpcClientIdentificationService>();
services.AddHostedService(p => p.GetService<GrpcClientIdentificationService>());
services.AddDbContextPool<MareDbContext>(options => services.AddDbContextPool<MareDbContext>(options =>
{ {
@@ -135,19 +157,6 @@ public class Startup
{ {
options.Configuration.ChannelPrefix = "MareSynchronos"; options.Configuration.ChannelPrefix = "MareSynchronos";
}); });
services.AddStackExchangeRedisCache(opt =>
{
opt.Configuration = redis;
opt.InstanceName = "MareSynchronosCache:";
});
services.AddSingleton<IClientIdentificationService, DistributedClientIdentificationService>();
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
}
else
{
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
} }
services.AddHostedService(provider => provider.GetService<SystemInfoService>()); services.AddHostedService(provider => provider.GetService<SystemInfoService>());

View File

@@ -12,10 +12,10 @@ using Discord;
using Discord.Rest; using Discord.Rest;
using Discord.WebSocket; using Discord.WebSocket;
using MareSynchronosServices.Authentication; using MareSynchronosServices.Authentication;
using MareSynchronosServices.Identity;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models; using MareSynchronosShared.Models;
using MareSynchronosShared.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -24,11 +24,11 @@ using Microsoft.Extensions.Logging;
namespace MareSynchronosServices.Discord; namespace MareSynchronosServices.Discord;
public class DiscordBot : IHostedService internal class DiscordBot : IHostedService
{ {
private readonly CleanupService cleanupService; private readonly CleanupService cleanupService;
private readonly MareMetrics metrics; private readonly MareMetrics metrics;
private readonly IClientIdentificationService clientService; private readonly IdentityHandler identityHandler;
private readonly IServiceProvider services; private readonly IServiceProvider services;
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
private readonly ILogger<DiscordBot> logger; private readonly ILogger<DiscordBot> logger;
@@ -44,16 +44,15 @@ public class DiscordBot : IHostedService
private ConcurrentDictionary<ulong, DateTime> LastVanityChange = new(); private ConcurrentDictionary<ulong, DateTime> LastVanityChange = new();
private ConcurrentDictionary<string, DateTime> LastVanityGidChange = new(); private ConcurrentDictionary<string, DateTime> LastVanityGidChange = new();
private ulong vanityCommandId; private ulong vanityCommandId;
private ulong vanityGidCommandId;
private Task cleanUpUserTask = null; private Task cleanUpUserTask = null;
private SemaphoreSlim semaphore; private SemaphoreSlim semaphore;
public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IClientIdentificationService clientService, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger) public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IdentityHandler identityHandler, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger)
{ {
this.cleanupService = cleanupService; this.cleanupService = cleanupService;
this.metrics = metrics; this.metrics = metrics;
this.clientService = clientService; this.identityHandler = identityHandler;
this.services = services; this.services = services;
_configuration = configuration.GetRequiredSection("MareSynchronos"); _configuration = configuration.GetRequiredSection("MareSynchronos");
this.logger = logger; this.logger = logger;
@@ -804,7 +803,7 @@ public class DiscordBot : IHostedService
updateStatusCts = new(); updateStatusCts = new();
while (!updateStatusCts.IsCancellationRequested) while (!updateStatusCts.IsCancellationRequested)
{ {
var onlineUsers = await clientService.GetOnlineUsers(); var onlineUsers = identityHandler.GetOnlineUsers(string.Empty);
logger.LogInformation("Users online: " + onlineUsers); logger.LogInformation("Users online: " + onlineUsers);
await discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers + " Users")).ConfigureAwait(false); await discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers + " Users")).ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);

View File

@@ -0,0 +1,62 @@
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace MareSynchronosServices.Identity;
internal class IdentityHandler
{
private readonly ConcurrentDictionary<string, ServerIdentity> cachedIdentities = new();
internal Task<string> GetUidForCharacterIdent(string ident, string serverId)
{
var exists = cachedIdentities.Any(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId);
return Task.FromResult(exists ? cachedIdentities.FirstOrDefault(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId).Key : string.Empty);
}
internal Task<ServerIdentity> GetIdentForuid(string uid)
{
ServerIdentity result;
if (!cachedIdentities.TryGetValue(uid, out result))
{
result = new ServerIdentity();
}
return Task.FromResult(result);
}
internal void SetIdent(string uid, string serverId, string ident)
{
cachedIdentities[uid] = new ServerIdentity() { ServerId = serverId, CharacterIdent = ident };
}
internal void RemoveIdent(string uid, string serverId)
{
if (cachedIdentities.ContainsKey(uid) && cachedIdentities[uid].ServerId == serverId)
{
cachedIdentities.TryRemove(uid, out _);
}
}
internal int GetOnlineUsers(string serverId)
{
if (string.IsNullOrEmpty(serverId))
return cachedIdentities.Count;
return cachedIdentities.Count(c => c.Value.ServerId == serverId);
}
internal void ClearIdentsForServer(string serverId)
{
var serverIdentities = cachedIdentities.Where(i => i.Value.ServerId == serverId);
foreach (var identity in serverIdentities)
{
cachedIdentities.TryRemove(identity.Key, out _);
}
}
}
internal record ServerIdentity
{
public string ServerId { get; set; } = string.Empty;
public string CharacterIdent { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,69 @@
using Grpc.Core;
using MareSynchronosShared.Protos;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace MareSynchronosServices.Identity;
internal class IdentityService : IdentificationService.IdentificationServiceBase
{
private readonly ILogger<IdentityService> _logger;
private readonly IdentityHandler _handler;
public IdentityService(ILogger<IdentityService> logger, IdentityHandler handler)
{
_logger = logger;
_handler = handler;
}
public override Task<Empty> RemoveIdentForUid(RemoveIdentMessage request, ServerCallContext context)
{
_handler.RemoveIdent(request.Uid, request.ServerId);
return Task.FromResult(new Empty());
}
public override Task<Empty> SetIdentForUid(SetIdentMessage request, ServerCallContext context)
{
_handler.SetIdent(request.Uid, request.ServerId, request.Ident);
return Task.FromResult(new Empty());
}
public override async Task<CharacterIdentMessage> GetIdentForUid(UidMessage request, ServerCallContext context)
{
var result = await _handler.GetIdentForuid(request.Uid);
return new CharacterIdentMessage()
{
Ident = result.CharacterIdent,
ServerId = result.ServerId
};
}
public override async Task<UidMessage> GetUidForCharacterIdent(CharacterIdentMessage request, ServerCallContext context)
{
var result = await _handler.GetUidForCharacterIdent(request.Ident, request.ServerId);
return new UidMessage()
{
Uid = result
};
}
public override Task<OnlineUserCountResponse> GetOnlineUserCount(ServerMessage request, ServerCallContext context)
{
return Task.FromResult(new OnlineUserCountResponse() { Count = _handler.GetOnlineUsers(request.ServerId) });
}
public override Task<Empty> ClearIdentsForServer(ServerMessage request, ServerCallContext context)
{
_handler.ClearIdentsForServer(request.ServerId);
return Task.FromResult(new Empty());
}
public override Task<Empty> RecreateServerIdents(ServerIdentMessage request, ServerCallContext context)
{
foreach (var identMsg in request.Idents)
{
_handler.SetIdent(identMsg.Uid, identMsg.ServerId, identMsg.Ident);
}
return Task.FromResult(new Empty());
}
}

View File

@@ -10,7 +10,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Prometheus; using Prometheus;
using System.Collections.Generic; using System.Collections.Generic;
using MareSynchronosShared.Services; using MareSynchronosServices.Identity;
namespace MareSynchronosServices; namespace MareSynchronosServices;
@@ -45,29 +45,12 @@ public class Startup
})); }));
services.AddSingleton<SecretKeyAuthenticationHandler>(); services.AddSingleton<SecretKeyAuthenticationHandler>();
services.AddSingleton<IdentityHandler>();
services.AddSingleton<CleanupService>(); services.AddSingleton<CleanupService>();
services.AddTransient(_ => Configuration); services.AddTransient(_ => Configuration);
services.AddHostedService(provider => provider.GetService<CleanupService>()); services.AddHostedService(provider => provider.GetService<CleanupService>());
services.AddHostedService<DiscordBot>(); services.AddHostedService<DiscordBot>();
services.AddGrpc(); services.AddGrpc();
// add redis related options
var redis = Configuration.GetSection("MareSynchronos").GetValue("RedisConnectionString", string.Empty);
if (!string.IsNullOrEmpty(redis))
{
services.AddStackExchangeRedisCache(opt =>
{
opt.Configuration = redis;
opt.InstanceName = "MareSynchronosCache:";
});
services.AddSingleton<IClientIdentificationService, DistributedClientIdentificationService>();
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
}
else
{
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
}
} }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
@@ -80,6 +63,7 @@ public class Startup
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapGrpcService<AuthenticationService>(); endpoints.MapGrpcService<AuthenticationService>();
endpoints.MapGrpcService<IdentityService>();
}); });
} }
} }

View File

@@ -16,8 +16,50 @@ service FileService {
rpc DeleteFiles (DeleteFilesRequest) returns (Empty); rpc DeleteFiles (DeleteFilesRequest) returns (Empty);
} }
service IdentificationService {
rpc GetOnlineUserCount (ServerMessage) returns (OnlineUserCountResponse);
rpc RemoveIdentForUid (RemoveIdentMessage) returns (Empty);
rpc SetIdentForUid (SetIdentMessage) returns (Empty);
rpc GetUidForCharacterIdent (CharacterIdentMessage) returns (UidMessage);
rpc GetIdentForUid (UidMessage) returns (CharacterIdentMessage);
rpc ClearIdentsForServer (ServerMessage) returns (Empty);
rpc RecreateServerIdents (ServerIdentMessage) returns (Empty);
}
message Empty { } message Empty { }
message ServerIdentMessage {
repeated SetIdentMessage idents = 1;
}
message ServerMessage {
string server_id = 1;
}
message OnlineUserCountResponse {
int64 count = 1;
}
message RemoveIdentMessage {
string uid = 1;
string server_id = 2;
}
message SetIdentMessage {
string uid = 1;
string server_id = 2;
string ident = 3;
}
message CharacterIdentMessage {
string server_id = 1;
string ident = 2;
}
message UidMessage {
string uid = 1;
}
message UploadFileRequest { message UploadFileRequest {
string hash = 1; string hash = 1;
string uploader = 2; string uploader = 2;

View File

@@ -1,65 +0,0 @@
using System.Collections.Concurrent;
using MareSynchronosShared.Metrics;
namespace MareSynchronosShared.Services;
public abstract class BaseClientIdentificationService : IClientIdentificationService
{
private readonly MareMetrics metrics;
protected ConcurrentDictionary<string, string> OnlineClients = new();
protected BaseClientIdentificationService(MareMetrics metrics)
{
this.metrics = metrics;
}
public virtual Task<int> GetOnlineUsers()
{
return Task.FromResult(OnlineClients.Count);
}
public Task<string?> GetUidForCharacterIdent(string characterIdent)
{
var result = OnlineClients.SingleOrDefault(u =>
string.Compare(u.Value, characterIdent, StringComparison.InvariantCultureIgnoreCase) == 0);
return Task.FromResult(result.Equals(new KeyValuePair<string, string>()) ? null : result.Key);
}
public virtual Task<string?> GetCharacterIdentForUid(string uid)
{
if (!OnlineClients.TryGetValue(uid, out var result))
{
return Task.FromResult((string?)null);
}
return Task.FromResult(result);
}
public virtual Task MarkUserOnline(string uid, string charaIdent)
{
OnlineClients[uid] = charaIdent;
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
return Task.CompletedTask;
}
public virtual Task MarkUserOffline(string uid)
{
if (OnlineClients.TryRemove(uid, out _))
{
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
}
return Task.CompletedTask;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public virtual Task StopAsync(CancellationToken cancellationToken)
{
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, 0);
OnlineClients = new();
return Task.CompletedTask;
}
}

View File

@@ -1,71 +0,0 @@
using System.Text;
using MareSynchronosShared.Metrics;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
namespace MareSynchronosShared.Services;
public class DistributedClientIdentificationService : BaseClientIdentificationService
{
private readonly IDistributedCache distributedCache;
private readonly ILogger<DistributedClientIdentificationService> logger;
private readonly IConfiguration configuration;
private const string RedisPrefix = "uidcache:";
public DistributedClientIdentificationService(MareMetrics metrics, IDistributedCache distributedCache, IConfiguration configuration, ILogger<DistributedClientIdentificationService> logger) : base(metrics)
{
this.distributedCache = distributedCache;
this.logger = logger;
this.configuration = configuration.GetSection("MareSynchronos");
}
public override async Task<int> GetOnlineUsers()
{
try
{
var redis = configuration.GetValue<string>("RedisConnectionString");
var conn = await ConnectionMultiplexer.ConnectAsync(redis).ConfigureAwait(false);
var endpoint = conn.GetEndPoints().First();
return await conn.GetServer(endpoint).KeysAsync(pattern: "*" + RedisPrefix + "*").CountAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
logger.LogError(ex, "Error during GetOnlineUsers");
return 0;
}
}
public override async Task<string?> GetCharacterIdentForUid(string uid)
{
var localIdent = await base.GetCharacterIdentForUid(uid).ConfigureAwait(false);
if (localIdent != null) return localIdent;
var cachedIdent = await distributedCache.GetStringAsync(RedisPrefix + uid).ConfigureAwait(false);
return cachedIdent ?? null;
}
public override async Task MarkUserOffline(string uid)
{
await base.MarkUserOffline(uid).ConfigureAwait(false);
await distributedCache.RemoveAsync(RedisPrefix + uid).ConfigureAwait(false);
}
public override async Task MarkUserOnline(string uid, string charaIdent)
{
await base.MarkUserOnline(uid, charaIdent).ConfigureAwait(false);
await distributedCache.SetAsync(RedisPrefix + uid, Encoding.UTF8.GetBytes(charaIdent), new DistributedCacheEntryOptions()
{
AbsoluteExpiration = DateTime.Now.AddDays(7)
}).ConfigureAwait(false);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
foreach (var uid in OnlineClients)
{
distributedCache.Remove(RedisPrefix + uid.Key);
}
return base.StopAsync(cancellationToken);
}
}

View File

@@ -1,12 +0,0 @@
using Microsoft.Extensions.Hosting;
namespace MareSynchronosShared.Services;
public interface IClientIdentificationService : IHostedService
{
Task<int> GetOnlineUsers();
Task<string?> GetUidForCharacterIdent(string characterIdent);
Task<string?> GetCharacterIdentForUid(string uid);
Task MarkUserOnline(string uid, string charaIdent);
Task MarkUserOffline(string uid);
}

View File

@@ -1,10 +0,0 @@
using MareSynchronosShared.Metrics;
namespace MareSynchronosShared.Services;
public class LocalClientIdentificationService : BaseClientIdentificationService
{
public LocalClientIdentificationService(MareMetrics metrics) : base(metrics)
{
}
}