mirror of
https://github.com/ncblakely/GiantsTools
synced 2024-11-21 21:55:38 +01:00
Move caching for servers to store layer.
This commit is contained in:
parent
d8a107b5ca
commit
e44d685d55
@ -16,24 +16,16 @@
|
||||
private readonly ILogger<ServerRegistryService> logger;
|
||||
private readonly IServerRegistryStore registryStore;
|
||||
private readonly IConfiguration configuration;
|
||||
private readonly IDateTimeProvider dateTimeProvider;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
private readonly TimeSpan timeoutPeriod;
|
||||
private readonly int maxServerCount;
|
||||
|
||||
public ServerRegistryService(
|
||||
ILogger<ServerRegistryService> logger,
|
||||
IServerRegistryStore registryStore,
|
||||
IConfiguration configuration,
|
||||
IDateTimeProvider dateTimeProvider,
|
||||
IMemoryCache memoryCache)
|
||||
IConfiguration configuration)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.registryStore = registryStore;
|
||||
this.configuration = configuration;
|
||||
this.dateTimeProvider = dateTimeProvider;
|
||||
this.memoryCache = memoryCache;
|
||||
this.timeoutPeriod = TimeSpan.FromMinutes(Convert.ToDouble(this.configuration["ServerTimeoutPeriodInMinutes"]));
|
||||
this.maxServerCount = Convert.ToInt32(this.configuration["MaxServerCount"]);
|
||||
}
|
||||
|
||||
@ -48,41 +40,13 @@
|
||||
throw new ArgumentException($"Unsupported game name {serverInfo.GameName}", nameof(serverInfo));
|
||||
}
|
||||
|
||||
// Check cache before we write to store
|
||||
ConcurrentDictionary<string, ServerInfo> allServerInfo = await this.memoryCache.GetOrCreateAsync(CacheKeys.ServerInfo, this.PopulateCache);
|
||||
|
||||
if (allServerInfo.ContainsKey(serverInfo.HostIpAddress))
|
||||
{
|
||||
if (allServerInfo[serverInfo.HostIpAddress].Equals(serverInfo))
|
||||
{
|
||||
this.logger.LogInformation("State for {IPAddress} is unchanged. Skipping write to store.", serverInfo.HostIpAddress);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.LogInformation("State for {IPAddress} has changed. Committing update to store.", serverInfo.HostIpAddress);
|
||||
}
|
||||
|
||||
}
|
||||
await this.registryStore.UpsertServerInfo(serverInfo);
|
||||
|
||||
this.logger.LogInformation("Updating cache for request from {IPAddress}.", serverInfo.HostIpAddress);
|
||||
|
||||
if (allServerInfo.ContainsKey(serverInfo.HostIpAddress))
|
||||
{
|
||||
allServerInfo[serverInfo.HostIpAddress] = serverInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
allServerInfo.TryAdd(serverInfo.HostIpAddress, serverInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ServerInfo>> GetAllServers()
|
||||
{
|
||||
ConcurrentDictionary<string, ServerInfo> serverInfo = await this.memoryCache.GetOrCreateAsync(CacheKeys.ServerInfo, this.PopulateCache);
|
||||
|
||||
return serverInfo.Values;
|
||||
return (await this.registryStore.GetServerInfos())
|
||||
.Take(this.maxServerCount);
|
||||
}
|
||||
|
||||
public async Task DeleteServer(string ipAddress)
|
||||
@ -96,17 +60,5 @@
|
||||
await this.registryStore.DeleteServer(serverInfo.id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ConcurrentDictionary<string, ServerInfo>> PopulateCache(ICacheEntry entry)
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
|
||||
|
||||
IDictionary<string, ServerInfo> serverInfo = (await this.registryStore
|
||||
.GetServerInfos(whereExpression: c => c.LastHeartbeat > this.dateTimeProvider.UtcNow - this.timeoutPeriod))
|
||||
.Take(this.maxServerCount)
|
||||
.ToDictionary(x => x.HostIpAddress, y => y);
|
||||
|
||||
return new ConcurrentDictionary<string, ServerInfo>(serverInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,14 @@
|
||||
namespace Giants.Services
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Giants.Services.Core;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@ -12,21 +16,41 @@
|
||||
{
|
||||
private readonly ILogger<CosmosDbServerRegistryStore> logger;
|
||||
private readonly IConfiguration configuration;
|
||||
private readonly IMemoryCache memoryCache;
|
||||
private readonly IDateTimeProvider dateTimeProvider;
|
||||
private readonly TimeSpan timeoutPeriod;
|
||||
private CosmosDbClient client;
|
||||
|
||||
public CosmosDbServerRegistryStore(
|
||||
ILogger<CosmosDbServerRegistryStore> logger,
|
||||
IConfiguration configuration)
|
||||
IConfiguration configuration,
|
||||
IMemoryCache memoryCache,
|
||||
IDateTimeProvider dateTimeProvider)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.configuration = configuration;
|
||||
this.memoryCache = memoryCache;
|
||||
this.dateTimeProvider = dateTimeProvider;
|
||||
this.timeoutPeriod = TimeSpan.FromMinutes(Convert.ToDouble(this.configuration["ServerTimeoutPeriodInMinutes"]));
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ServerInfo>> GetServerInfos(
|
||||
Expression<Func<ServerInfo, bool>> whereExpression = null,
|
||||
string partitionKey = null)
|
||||
{
|
||||
return await this.client.GetItems<ServerInfo>(whereExpression);
|
||||
ConcurrentDictionary<string, ServerInfo> serverInfo = await this.memoryCache.GetOrCreateAsync(CacheKeys.ServerInfo, this.PopulateCache);
|
||||
|
||||
IQueryable<ServerInfo> serverInfoQuery = serverInfo
|
||||
.Values
|
||||
.AsQueryable();
|
||||
|
||||
if (whereExpression != null)
|
||||
{
|
||||
serverInfoQuery = serverInfoQuery.Where(whereExpression);
|
||||
}
|
||||
|
||||
return serverInfoQuery.Where(c => c.LastHeartbeat > this.dateTimeProvider.UtcNow - this.timeoutPeriod)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TSelect>> GetServerInfos<TSelect>(
|
||||
@ -34,18 +58,40 @@
|
||||
Expression<Func<ServerInfo, bool>> whereExpression = null,
|
||||
string partitionKey = null)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(selectExpression, nameof(selectExpression));
|
||||
ConcurrentDictionary<string, ServerInfo> serverInfo = await this.memoryCache.GetOrCreateAsync(CacheKeys.ServerInfo, this.PopulateCache);
|
||||
|
||||
return await this.client.GetItems<ServerInfo, TSelect>(
|
||||
selectExpression: selectExpression,
|
||||
whereExpression: whereExpression,
|
||||
partitionKey: partitionKey);
|
||||
IQueryable<ServerInfo> serverInfoQuery = serverInfo
|
||||
.Values
|
||||
.AsQueryable();
|
||||
|
||||
if (serverInfoQuery != null)
|
||||
{
|
||||
serverInfoQuery = serverInfoQuery.Where(whereExpression);
|
||||
}
|
||||
|
||||
return serverInfoQuery.Where(c => c.LastHeartbeat > this.dateTimeProvider.UtcNow - this.timeoutPeriod)
|
||||
.Select(selectExpression)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<ServerInfo> GetServerInfo(string ipAddress)
|
||||
{
|
||||
ArgumentUtility.CheckStringForNullOrEmpty(ipAddress, nameof(ipAddress));
|
||||
|
||||
ConcurrentDictionary<string, ServerInfo> serverInfo = await this.memoryCache.GetOrCreateAsync(CacheKeys.ServerInfo, this.PopulateCache);
|
||||
if (serverInfo.ContainsKey(ipAddress))
|
||||
{
|
||||
try
|
||||
{
|
||||
return serverInfo[ipAddress];
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.logger.LogInformation("Cached server for {IPAddress} no longer found: {Exception}", ipAddress, e.ToString());
|
||||
// May have been removed from the cache by another thread. Ignore and query DB instead.
|
||||
}
|
||||
}
|
||||
|
||||
return await this.client.GetItemById<ServerInfo>(ipAddress);
|
||||
}
|
||||
|
||||
@ -53,14 +99,45 @@
|
||||
{
|
||||
ArgumentUtility.CheckForNull(serverInfo, nameof(serverInfo));
|
||||
|
||||
// Check cache before we write to store
|
||||
ConcurrentDictionary<string, ServerInfo> allServerInfo = await this.memoryCache.GetOrCreateAsync(CacheKeys.ServerInfo, this.PopulateCache);
|
||||
|
||||
if (allServerInfo.ContainsKey(serverInfo.HostIpAddress))
|
||||
{
|
||||
if (allServerInfo[serverInfo.HostIpAddress].Equals(serverInfo))
|
||||
{
|
||||
this.logger.LogInformation("State for {IPAddress} is unchanged. Skipping write to store.", serverInfo.HostIpAddress);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.logger.LogInformation("State for {IPAddress} has changed. Committing update to store.", serverInfo.HostIpAddress);
|
||||
}
|
||||
}
|
||||
|
||||
await this.client.UpsertItem(
|
||||
item: serverInfo,
|
||||
partitionKey: new PartitionKey(serverInfo.DocumentType));
|
||||
|
||||
this.logger.LogInformation("Updating cache for request from {IPAddress}.", serverInfo.HostIpAddress);
|
||||
|
||||
if (allServerInfo.ContainsKey(serverInfo.HostIpAddress))
|
||||
{
|
||||
allServerInfo[serverInfo.HostIpAddress] = serverInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
allServerInfo.TryAdd(serverInfo.HostIpAddress, serverInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteServer(string id, string partitionKey = null)
|
||||
{
|
||||
await this.client.DeleteItem<ServerInfo>(id, partitionKey);
|
||||
|
||||
// Remove from cache
|
||||
ConcurrentDictionary<string, ServerInfo> allServerInfo = await this.memoryCache.GetOrCreateAsync(CacheKeys.ServerInfo, this.PopulateCache);
|
||||
allServerInfo.TryRemove(id, out ServerInfo _);
|
||||
}
|
||||
|
||||
public async Task DeleteServers(IEnumerable<string> ids, string partitionKey = null)
|
||||
@ -71,7 +148,7 @@
|
||||
{
|
||||
this.logger.LogInformation("Deleting server for host IP {IPAddress}", id);
|
||||
|
||||
await this.client.DeleteItem<ServerInfo>(id, partitionKey);
|
||||
await this.DeleteServer(id, partitionKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,5 +162,16 @@
|
||||
|
||||
await this.client.Initialize();
|
||||
}
|
||||
|
||||
private async Task<ConcurrentDictionary<string, ServerInfo>> PopulateCache(ICacheEntry entry)
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
|
||||
|
||||
IDictionary<string, ServerInfo> serverInfo =
|
||||
(await this.client.GetItems<ServerInfo>())
|
||||
.ToDictionary(x => x.HostIpAddress, y => y);
|
||||
|
||||
return new ConcurrentDictionary<string, ServerInfo>(serverInfo);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
@ -8,11 +7,11 @@
|
||||
"sslPort": 44304
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "weatherforecast",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
@ -21,10 +20,10 @@
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "weatherforecast",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user