feat(core): UnitService - работа с кэш
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Infrastructure.Redis.Helpers;
|
||||
using StackExchange.Redis;
|
||||
@@ -48,7 +49,7 @@ namespace PARR.Infrastructure.Redis
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Формат данных не байты.", ex);
|
||||
throw new InvalidOperationException("Формат данных не байты.", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -65,7 +66,7 @@ namespace PARR.Infrastructure.Redis
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Формат данных не строка.", ex);
|
||||
throw new InvalidOperationException("Формат данных не строка.", ex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -105,8 +106,8 @@ namespace PARR.Infrastructure.Redis
|
||||
if (useCompression)
|
||||
{
|
||||
// храним в байтах, если надо то сжимаем
|
||||
var jsonData = SerializeWithCompression(data);
|
||||
await cache.SetAsync(key, jsonData, options);
|
||||
var byteData = SerializeWithCompression(data);
|
||||
await cache.SetAsync(key, byteData, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,14 +188,24 @@ namespace PARR.Infrastructure.Redis
|
||||
|
||||
#region Нативные операции Redis, Redis Hash
|
||||
|
||||
public async Task SetHashFieldAsync<T>(string hashKey, string field, T value, TimeSpan? ttl = null)
|
||||
public async Task SetHashFieldAsync<T>(string hashKey, string field, T value, TimeSpan? ttl = null, bool useCompression = false)
|
||||
{
|
||||
// меняет одно поле в Hash
|
||||
|
||||
var jsonData = JsonSerializer.Serialize(value);
|
||||
|
||||
// true - поля не было, создалось новое. false - поле было, обновили значение
|
||||
var result = await redis.HashSetAsync(hashKey, field, jsonData);
|
||||
if (useCompression)
|
||||
{
|
||||
// храним в байтах, если надо то сжимаем
|
||||
var byteData = SerializeWithCompression(value);
|
||||
// true - поля не было, создалось новое. false - поле было, обновили значение
|
||||
var result = await redis.HashSetAsync(hashKey, field, byteData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// храним в строке
|
||||
var jsonData = JsonSerializer.Serialize(value);
|
||||
// true - поля не было, создалось новое. false - поле было, обновили значение
|
||||
var result = await redis.HashSetAsync(hashKey, field, jsonData);
|
||||
}
|
||||
|
||||
// если указан ttl, обновим для всего Hash
|
||||
// если не указан и ранее был создан hashKey, оставит его ttl; а если hashKey не было, то создаст его БЕССРОЧНЫМ!!!
|
||||
@@ -202,7 +213,7 @@ namespace PARR.Infrastructure.Redis
|
||||
await SetHashTtlAsync(hashKey, ttl.Value);
|
||||
}
|
||||
|
||||
public async Task<T?> GetHashFieldAsync<T>(string hashKey, string field)
|
||||
public async Task<T?> GetHashFieldAsync<T>(string hashKey, string field, bool useCompression = false)
|
||||
{
|
||||
// получить значение поля из Hash
|
||||
var value = await redis.HashGetAsync(hashKey, field);
|
||||
@@ -210,10 +221,80 @@ namespace PARR.Infrastructure.Redis
|
||||
if (value.IsNullOrEmpty)
|
||||
return default;
|
||||
|
||||
return JsonSerializer.Deserialize<T>(value);
|
||||
if (useCompression)
|
||||
{
|
||||
// хранили в байтах
|
||||
try
|
||||
{
|
||||
return DeserializeWithCompression<T>(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Формат данных не байты.", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// хранили в строке
|
||||
return JsonSerializer.Deserialize<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string Field, T? Value)?> GetFirstHashFieldAsync<T>(string hashKey)
|
||||
public async Task<List<T>> GetHashFieldsAsync<T>(List<(string HashKey, string FieldKey)> keys, bool useCompression = false)
|
||||
{
|
||||
var allItems = new List<T>(keys.Count);
|
||||
|
||||
// Делим на порции по 5000 (оптимально)
|
||||
foreach (var chunk in keys.Chunk(5000))
|
||||
{
|
||||
// Формируем пайплайн ТОЛЬКО для 5 000 элементов
|
||||
var tasks = new List<Task<RedisValue>>(chunk.Length);
|
||||
foreach (var key in chunk)
|
||||
{
|
||||
tasks.Add(redis.HashGetAsync(key.HashKey, key.FieldKey));
|
||||
}
|
||||
|
||||
logger.LogDebug("Сформировал {Count} одновременных запросов в кэш", tasks.Count);
|
||||
|
||||
// Ждем ответа от текущей порции
|
||||
RedisValue[] results = await Task.WhenAll(tasks);
|
||||
|
||||
// Парсим
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.HasValue)
|
||||
{
|
||||
if (useCompression)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = DeserializeWithCompression<T>((byte[])result);
|
||||
if (item != null)
|
||||
allItems.Add(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Формат данных не байты.", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var item = JsonSerializer.Deserialize<T>(result.ToString());
|
||||
if (item != null)
|
||||
allItems.Add(item);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
logger.LogDebug("Получил {Count} элементов из кэш.", allItems.Count);
|
||||
|
||||
return allItems;
|
||||
}
|
||||
|
||||
public async Task<(string Field, T? Value)?> GetFirstHashFieldAsync<T>(string hashKey, bool useCompression = false)
|
||||
{
|
||||
logger.LogDebug("Запрос первого поля из хеша '{HashKey}'", hashKey);
|
||||
|
||||
@@ -234,7 +315,24 @@ namespace PARR.Infrastructure.Redis
|
||||
continue;
|
||||
}
|
||||
|
||||
var deserialized = JsonSerializer.Deserialize<T>(entry.Value);
|
||||
T? deserialized = default;
|
||||
|
||||
if (useCompression)
|
||||
{
|
||||
// хранили в байтах
|
||||
try
|
||||
{
|
||||
deserialized = DeserializeWithCompression<T>(entry.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Формат данных не байты.", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deserialized = JsonSerializer.Deserialize<T>(entry.Value);
|
||||
}
|
||||
|
||||
logger.LogDebug("Получено поле '{Field}' из хеша '{HashKey}', тип: {Type}", entry.Name, hashKey, typeof(T).Name);
|
||||
|
||||
@@ -245,7 +343,7 @@ namespace PARR.Infrastructure.Redis
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, T>> GetAllHashFieldsAsync<T>(string hashKey)
|
||||
public async Task<Dictionary<string, T>> GetAllHashFieldsAsync<T>(string hashKey, bool useCompression = false)
|
||||
{
|
||||
// получить все записи из Hash
|
||||
var objs = await redis.HashGetAllAsync(hashKey);
|
||||
@@ -253,12 +351,20 @@ namespace PARR.Infrastructure.Redis
|
||||
if (objs.Length == 0)
|
||||
return new Dictionary<string, T>();
|
||||
|
||||
var result = objs.ToDictionary(
|
||||
try
|
||||
{
|
||||
var result = objs.ToDictionary(
|
||||
t => t.Name.ToString(),
|
||||
t => JsonSerializer.Deserialize<T>(t.Value)
|
||||
t => useCompression ? DeserializeWithCompression<T>(t.Value) : JsonSerializer.Deserialize<T>(t.Value)
|
||||
);
|
||||
|
||||
return result!;
|
||||
return result!;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Не смог десериализовать полученные данные", ex);
|
||||
//return new Dictionary<string, T>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteHashFieldAsync(string hashKey, string field)
|
||||
|
||||
Reference in New Issue
Block a user