Files
mapo-core/MP.RIOC/Services/RedisWeightProvider.cs
T

252 lines
9.0 KiB
C#

using MP.Core.DTO;
using StackExchange.Redis;
using ZiggyCreatures.Caching.Fusion;
namespace MP.RIOC.Services
{
public class RedisWeightProvider : IWeightProvider
{
#region Public Constructors
public RedisWeightProvider(
IConnectionMultiplexer mux,
IFusionCache cache,
IConfiguration config)
{
_cache = cache;
_config = config;
_db = mux.GetDatabase();
_mux = mux;
_defaultOld = config.GetValue<int>("RouteMan:DefaultWeightOld", 100);
_defaultNew = config.GetValue<int>("RouteMan:DefaultWeightNew", 0);
_redisBaseKey = config.GetValue<string>("ServerConf:RedisBaseKey") ?? "MP_IOC";
_keyPrefix = $"{_redisBaseKey}:route_weight:";
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Eliminazione esplicita cache data chiave
/// </summary>
/// <param name="method"></param>
public void EvictLocalCacheFor(string method)
{
if (string.IsNullOrEmpty(method)) return;
var cacheKey = $"weights:{method}";
// Rimuove la chiave dalla RAM (L1) dell'istanza corrente del Gateway
_cache.Remove(cacheKey);
}
public async Task<List<WeightDTO>> GetAllWeightsAsync()
{
var result = new List<WeightDTO>();
var server = _mux.GetServer(_mux.GetEndPoints().First());
if (server.IsReplica)
{
return result;
}
await foreach (var key in server.KeysAsync(pattern: $"{_keyPrefix}*"))
{
var methodName = KeyToString(key.ToString());
if (string.IsNullOrEmpty(methodName)) continue;
var oldVal = _db.HashGet(key, "old");
var newVal = _db.HashGet(key, "new");
int oldW = 100;
int newW = 0;
if (!oldVal.IsNull && int.TryParse(oldVal.ToString(), out var parsedOld))
oldW = Math.Clamp(parsedOld, 0, 100);
if (!newVal.IsNull && int.TryParse(newVal.ToString(), out var parsedNew))
newW = Math.Clamp(parsedNew, 0, 100);
result.Add(new WeightDTO { Method = methodName, OldWeight = oldW, NewWeight = newW });
}
// riordino desc x NEW poi alfabetico...
result = result
.OrderByDescending(x => x.NewWeight)
.ThenBy(x => x.Method)
.ToList();
return result;
}
/// <summary>
/// Ritorna (oldWeight, newWeight) per il metodo con FusionCache e poi con dati Redis.
/// Se non esiste, crea la chiave con i default value.
/// </summary>
public (int oldWeight, int newWeight) GetWeightsFor(string method)
{
if (string.IsNullOrEmpty(method)) method = "unknown";
var cacheKey = $"weights:{method}";
// FusionCache gestisce L1, L2 e se non trova nulla esegue la factory sotto
var weights = _cache.GetOrSet<RouteWeights>(
cacheKey,
_ =>
{
// FACTORY DI INIZIALIZZAZIONE (Viene eseguita solo se la cache è vuota ovunque)
var redisKey = _keyPrefix + method;
var oldVal = _db.HashGet(redisKey, "old");
var newVal = _db.HashGet(redisKey, "new");
if (oldVal.IsNull && newVal.IsNull)
{
_db.HashSet(redisKey, "old", _defaultOld, When.NotExists);
_db.HashSet(redisKey, "new", _defaultNew, When.NotExists);
oldVal = _defaultOld;
newVal = _defaultNew;
}
if (oldVal.IsNull) { _db.HashSet(redisKey, "old", _defaultOld, When.NotExists); oldVal = _defaultOld; }
if (newVal.IsNull) { _db.HashSet(redisKey, "new", _defaultNew, When.NotExists); newVal = _defaultNew; }
if (!int.TryParse(oldVal.ToString(), out var oldW)) oldW = _defaultOld;
if (!int.TryParse(newVal.ToString(), out var newW)) newW = _defaultNew;
return new RouteWeights(Math.Clamp(oldW, 0, 100), Math.Clamp(newW, 0, 100));
},
// Durata specifica per questa tipologia di dato
options => options.SetDuration(TimeSpan.FromMinutes(10))
);
return (weights.OldWeight, weights.NewWeight);
#if false
var key = _keyPrefix + method;
// Leggi entrambi i campi
var oldVal = _db.HashGet(key, "old");
var newVal = _db.HashGet(key, "new");
// Se entrambi mancanti, inizializza con default (usando HSet con When.NotExists per evitare overwrite)
if (oldVal.IsNull && newVal.IsNull)
{
// Imposta i campi singolarmente con When.NotExists per evitare overwrite
_db.HashSet(key, "old", _defaultOld, When.NotExists);
_db.HashSet(key, "new", _defaultNew, When.NotExists);
// Rileggi per essere sicuri
oldVal = _db.HashGet(key, "old");
newVal = _db.HashGet(key, "new");
}
// Se uno dei due manca, impostalo al default (non sovrascrive l'altro)
if (oldVal.IsNull)
{
_db.HashSet(key, "old", _defaultOld, When.NotExists);
oldVal = _defaultOld;
}
if (newVal.IsNull)
{
_db.HashSet(key, "new", _defaultNew, When.NotExists);
newVal = _defaultNew;
}
if (!int.TryParse(oldVal.ToString(), out var oldW)) oldW = _defaultOld;
if (!int.TryParse(newVal.ToString(), out var newW)) newW = _defaultNew;
// clamp 0..100
oldW = Math.Clamp(oldW, 0, 100);
newW = Math.Clamp(newW, 0, 100);
return (oldW, newW);
#endif
}
// API per aggiornare i pesi a runtime (opzionale)
public void SetWeights(string method, int oldWeight, int newWeight)
{
// 1. Scrittura su Redis
var key = _keyPrefix + (string.IsNullOrEmpty(method) ? "unknown" : method);
_db.HashSet(key, new HashEntry[] {
new HashEntry("old", Math.Clamp(oldWeight,0,100)),
new HashEntry("new", Math.Clamp(newWeight,0,100))
});
// 2. 🚀 COORDINAZIONE: Rimuovi la chiave da FusionCache.
// Grazie al Backplane Pub/Sub, la RAM verrà azzerata istantaneamente su TUTTI i server.
var cacheKey = $"weights:{method}";
_cache.Remove(cacheKey);
}
public bool UpsertWeight(WeightDTO updRecord)
{
if (updRecord == null || string.IsNullOrEmpty(updRecord.Method))
return false;
// 1. Scrivi su Redis (Sorgente dati reale)
var redisKey = _keyPrefix + updRecord.Method;
_db.HashSet(redisKey, new HashEntry[] {
new HashEntry("old", Math.Clamp(updRecord.OldWeight, 0, 100)),
new HashEntry("new", Math.Clamp(updRecord.NewWeight, 0, 100))
});
// 2. 🚀 COORDINAZIONE: Rimuovi la chiave da FusionCache.
// Grazie al Backplane Pub/Sub, la RAM verrà azzerata istantaneamente su TUTTI i server.
var cacheKey = $"weights:{updRecord.Method}";
_cache.Remove(cacheKey);
return true;
#if false
var key = _keyPrefix + updRecord.Method;
_db.HashSet(key, new HashEntry[] {
new HashEntry("old", Math.Clamp(updRecord.OldWeight, 0, 100)),
new HashEntry("new", Math.Clamp(updRecord.NewWeight, 0, 100))
});
return true;
#endif
}
#endregion Public Methods
#region Private Fields
private static string _keyPrefix = "route_weight:";
private static string _redisBaseKey = "";
private readonly IFusionCache _cache;
/// <summary>
/// Definizione Record leggero per la cache
/// </summary>
/// <param name="OldWeight"></param>
/// <param name="NewWeight"></param>
public record RouteWeights(int OldWeight, int NewWeight);
private readonly IConfiguration _config;
private readonly IDatabase _db;
private readonly int _defaultNew;
private readonly int _defaultOld;
private readonly IConnectionMultiplexer _mux;
#endregion Private Fields
#region Private Methods
private string KeyToString(string key)
{
if (string.IsNullOrEmpty(key)) return "";
var prefix = _keyPrefix ?? "";
if (key.StartsWith(prefix))
return key.Substring(prefix.Length);
return key;
}
#endregion Private Methods
}
}