feat(core, infrastructure): IRedisCacheService - данные теперь можно архивировать
This commit is contained in:
@@ -5,30 +5,31 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRedisCacheService
|
public interface IRedisCacheService
|
||||||
{
|
{
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Получить кэшированные данные
|
///// Получить кэшированные данные
|
||||||
/// </summary>
|
///// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
///// <typeparam name="T"></typeparam>
|
||||||
/// <param name="key"></param>
|
///// <param name="key"></param>
|
||||||
/// <returns></returns>
|
///// <returns></returns>
|
||||||
T? GetCachedData<T>(string key);
|
//T? GetCachedData<T>(string key);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить кэшированные данные асинхронно
|
/// Получить кэшированные данные асинхронно
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
|
/// <param name="useCompression">Разрешить сжатие данных</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<T?> GetCachedDataAsync<T>(string key);
|
Task<T?> GetCachedDataAsync<T>(string key, bool useCompression = false);
|
||||||
|
|
||||||
/// <summary>
|
///// <summary>
|
||||||
/// Добавить в кэш данные
|
///// Добавить в кэш данные
|
||||||
/// </summary>
|
///// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
///// <typeparam name="T"></typeparam>
|
||||||
/// <param name="key"></param>
|
///// <param name="key"></param>
|
||||||
/// <param name="data"></param>
|
///// <param name="data"></param>
|
||||||
/// <param name="cacheDuration"></param>
|
///// <param name="cacheDuration"></param>
|
||||||
void SetCachedData<T>(string key, T data, TimeSpan cacheDuration);
|
//void SetCachedData<T>(string key, T data, TimeSpan cacheDuration);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Добавить в кэш данные асинхронно
|
/// Добавить в кэш данные асинхронно
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
/// <param name="data"></param>
|
/// <param name="data"></param>
|
||||||
/// <param name="cacheDuration"></param>
|
/// <param name="cacheDuration"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task SetCachedDataAsync<T>(string key, T data, TimeSpan cacheDuration);
|
Task SetCachedDataAsync<T>(string key, T data, TimeSpan cacheDuration, bool useCompression = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Удалить кэшированные данные
|
/// Удалить кэшированные данные
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NodaTime;
|
|
||||||
using PARR.Core.Common.Interfaces;
|
using PARR.Core.Common.Interfaces;
|
||||||
using PARR.Core.Repositories.Interfaces;
|
using PARR.Core.Repositories.Interfaces;
|
||||||
using PARR.Core.Services.Shortcodes;
|
using PARR.Core.Services.Shortcodes;
|
||||||
@@ -308,7 +307,7 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
{
|
{
|
||||||
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
|
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
|
||||||
|
|
||||||
return await redisCacheService.GetCachedDataAsync<WorkloadReport>(key);
|
return await redisCacheService.GetCachedDataAsync<WorkloadReport>(key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -325,7 +324,7 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
|
public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
|
||||||
{
|
{
|
||||||
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
|
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
|
||||||
await redisCacheService.SetCachedDataAsync(key, report, cacheWorkloadTtl);
|
await redisCacheService.SetCachedDataAsync(key, report, cacheWorkloadTtl, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -377,11 +376,5 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
|
|
||||||
return keyStr;
|
return keyStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
PARR.Infrastructure/Redis/Helpers/CompressionHelper.cs
Normal file
51
PARR.Infrastructure/Redis/Helpers/CompressionHelper.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
|
namespace PARR.Infrastructure.Redis.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Архивация/разарзивация данных
|
||||||
|
/// </summary>
|
||||||
|
internal static class CompressionHelper
|
||||||
|
{
|
||||||
|
// Не сжимать маленькие значения
|
||||||
|
private const int MinCompressionSize = 1024; // 1Кб
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сжать
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static byte[] Compress(byte[] data)
|
||||||
|
{
|
||||||
|
if (data.Length < MinCompressionSize)
|
||||||
|
return data;
|
||||||
|
|
||||||
|
using var output = new MemoryStream();
|
||||||
|
using (var gzip = new GZipStream(output, CompressionLevel.Optimal))
|
||||||
|
{
|
||||||
|
gzip.Write(data, 0, data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Разархивировать если это GZip
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static byte[] Decompress(byte[] data)
|
||||||
|
{
|
||||||
|
// Проверяем сигнатуру GZip (0x1F, 0x8B)
|
||||||
|
if (data.Length < 2 || data[0] != 0x1F || data[1] != 0x8B)
|
||||||
|
return data; // Не сжато
|
||||||
|
|
||||||
|
using var input = new MemoryStream(data);
|
||||||
|
using var gzip = new GZipStream(input, CompressionMode.Decompress);
|
||||||
|
using var output = new MemoryStream();
|
||||||
|
gzip.CopyTo(output);
|
||||||
|
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using PARR.Core.Common.Interfaces;
|
using PARR.Core.Common.Interfaces;
|
||||||
|
using PARR.Infrastructure.Redis.Helpers;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
@@ -8,6 +9,11 @@ namespace PARR.Infrastructure.Redis
|
|||||||
{
|
{
|
||||||
internal class RedisCacheService : IRedisCacheService
|
internal class RedisCacheService : IRedisCacheService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Минимальный размер для сжатия
|
||||||
|
/// </summary>
|
||||||
|
private const int compressionTreshold = 10 * 1024; // 10Kb
|
||||||
|
|
||||||
private readonly IDistributedCache cache;
|
private readonly IDistributedCache cache;
|
||||||
private readonly IConnectionMultiplexer connectionMultiplexer;
|
private readonly IConnectionMultiplexer connectionMultiplexer;
|
||||||
private readonly ILogger<RedisCacheService> logger;
|
private readonly ILogger<RedisCacheService> logger;
|
||||||
@@ -27,49 +33,87 @@ namespace PARR.Infrastructure.Redis
|
|||||||
|
|
||||||
#region Распределенный кэш IDistributedCache
|
#region Распределенный кэш IDistributedCache
|
||||||
|
|
||||||
public async Task<T?> GetCachedDataAsync<T>(string key)
|
public async Task<T?> GetCachedDataAsync<T>(string key, bool useCompression = false)
|
||||||
{
|
{
|
||||||
var jsonData = await cache.GetStringAsync(key);
|
if (useCompression)
|
||||||
|
{
|
||||||
|
// хранили в байтах
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = await cache.GetAsync(key);
|
||||||
|
if (data == null)
|
||||||
|
return default(T);
|
||||||
|
|
||||||
if (jsonData == null)
|
return DeserializeWithCompression<T>(data);
|
||||||
return default(T);
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception("Формат данных не байты.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// хранили в строке
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonData = await cache.GetStringAsync(key);
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<T>(jsonData);
|
if (jsonData == null)
|
||||||
|
return default(T);
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<T>(jsonData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception("Формат данных не строка.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public T? GetCachedData<T>(string key)
|
//public T? GetCachedData<T>(string key)
|
||||||
{
|
//{
|
||||||
var jsonData = cache.GetString(key);
|
// var jsonData = cache.GetString(key);
|
||||||
|
|
||||||
if (jsonData == null)
|
// if (jsonData == null)
|
||||||
return default(T);
|
// return default(T);
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<T>(jsonData);
|
// return JsonSerializer.Deserialize<T>(jsonData);
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
public void SetCachedData<T>(string key, T data, TimeSpan cacheDuration)
|
//public void SetCachedData<T>(string key, T data, TimeSpan cacheDuration)
|
||||||
|
//{
|
||||||
|
// var options = new DistributedCacheEntryOptions
|
||||||
|
// {
|
||||||
|
// AbsoluteExpirationRelativeToNow = cacheDuration
|
||||||
|
// };
|
||||||
|
|
||||||
|
// var jsonData = JsonSerializer.Serialize(data);
|
||||||
|
// cache.SetString(key, jsonData, options);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task SetCachedDataAsync<T>(string key, T data, TimeSpan cacheDuration, bool useCompression = false)
|
||||||
{
|
{
|
||||||
var options = new DistributedCacheEntryOptions
|
var options = new DistributedCacheEntryOptions
|
||||||
{
|
{
|
||||||
AbsoluteExpirationRelativeToNow = cacheDuration
|
AbsoluteExpirationRelativeToNow = cacheDuration
|
||||||
};
|
};
|
||||||
|
|
||||||
var jsonData = JsonSerializer.Serialize(data);
|
if (useCompression)
|
||||||
cache.SetString(key, jsonData, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task SetCachedDataAsync<T>(string key, T data, TimeSpan cacheDuration)
|
|
||||||
{
|
|
||||||
var options = new DistributedCacheEntryOptions
|
|
||||||
{
|
{
|
||||||
AbsoluteExpirationRelativeToNow = cacheDuration
|
// храним в байтах, если надо то сжимаем
|
||||||
};
|
var jsonData = SerializeWithCompression(data);
|
||||||
|
await cache.SetAsync(key, jsonData, options);
|
||||||
var jsonData = JsonSerializer.Serialize(data);
|
}
|
||||||
await cache.SetStringAsync(key, jsonData, options);
|
else
|
||||||
|
{
|
||||||
|
// храним в строке
|
||||||
|
var jsonData = JsonSerializer.Serialize(data);
|
||||||
|
await cache.SetStringAsync(key, jsonData, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -250,5 +294,28 @@ namespace PARR.Infrastructure.Redis
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private byte[] SerializeWithCompression<T>(T data)
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.SerializeToUtf8Bytes(data);
|
||||||
|
|
||||||
|
logger.LogDebug("Сериализовано: {Size} байт, тип: {Type}", json.Length, typeof(T).Name);
|
||||||
|
|
||||||
|
if (json.Length < compressionTreshold)
|
||||||
|
return json;
|
||||||
|
|
||||||
|
var compressed = CompressionHelper.Compress(json);
|
||||||
|
logger.LogDebug("После сжатия: {Size} байт (экономия: {Percent}%)", compressed.Length, Math.Round((1 - (double)compressed.Length / json.Length) * 100, 1));
|
||||||
|
|
||||||
|
return compressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T? DeserializeWithCompression<T>(byte[] data)
|
||||||
|
{
|
||||||
|
var json = CompressionHelper.Decompress(data);
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<T>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user