From 8324222e73ef2f00a75e5c46bcaa070e38b3d93d Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Mon, 29 Dec 2025 18:42:57 +0100 Subject: [PATCH 1/2] Reduces memory allocation via pooling and fixes race condition with distributed cache --- .../SSRAG/Datory/DistributedCache.Public.cs | 88 ++++++++----------- core/src/SSRAG/Datory/DistributedCache.cs | 5 +- core/src/SSRAG/SSRAG.csproj | 1 + 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/core/src/SSRAG/Datory/DistributedCache.Public.cs b/core/src/SSRAG/Datory/DistributedCache.Public.cs index 457e236f..fdb27b1a 100644 --- a/core/src/SSRAG/Datory/DistributedCache.Public.cs +++ b/core/src/SSRAG/Datory/DistributedCache.Public.cs @@ -39,42 +39,33 @@ public async Task GetOrCreateAsync(string key, Func> factory) // 2. 获取或创建锁,防止缓存击穿 var lockKey = $"lock:{key}"; - var semaphore = _locks.GetOrAdd(lockKey, _ => new SemaphoreSlim(1, 1)); + using var _ = await _locks.LockAsync(lockKey).ConfigureAwait(false); - await semaphore.WaitAsync(); - try + // 3. 再次检查本地缓存(可能在等待期间已被其他线程填充) + if (_memoryCache.TryGetValue(key, out value)) { - // 3. 再次检查本地缓存(可能在等待期间已被其他线程填充) - if (_memoryCache.TryGetValue(key, out value)) - { - return value; - } - - // 4. 从Redis获取 - - var redisValue = await StringGetAsync(key); - if (!string.IsNullOrEmpty(redisValue)) - { - value = TranslateUtils.JsonDeserialize(redisValue); - _memoryCache.Set(key, value, TimeSpan.FromMinutes(Constants.DefaultMemoryExpireMinutes)); - return value; - } - - // 5. 如果Redis中也没有,则调用工厂方法生成数据 - value = await factory(); - if (value == null) return default; - - // 6. 同时写入本地缓存和Redis - _memoryCache.Set(key, value, TimeSpan.FromMinutes(Constants.DefaultMemoryExpireMinutes)); - await StringSetAsync(key, TranslateUtils.JsonSerialize(value)); - return value; } - finally + + // 4. 从Redis获取 + + var redisValue = await StringGetAsync(key); + if (!string.IsNullOrEmpty(redisValue)) { - semaphore.Release(); - _locks.TryRemove(lockKey, out _); + value = TranslateUtils.JsonDeserialize(redisValue); + _memoryCache.Set(key, value, TimeSpan.FromMinutes(Constants.DefaultMemoryExpireMinutes)); + return value; } + + // 5. 如果Redis中也没有,则调用工厂方法生成数据 + value = await factory(); + if (value == null) return default; + + // 6. 同时写入本地缓存和Redis + _memoryCache.Set(key, value, TimeSpan.FromMinutes(Constants.DefaultMemoryExpireMinutes)); + await StringSetAsync(key, TranslateUtils.JsonSerialize(value)); + + return value; } public async Task RemoveAsync(string key) @@ -103,33 +94,24 @@ public async Task GetStringAsync(string key) // 2. 获取或创建锁,防止缓存击穿 var lockKey = $"lock:{key}"; - var semaphore = _locks.GetOrAdd(lockKey, _ => new SemaphoreSlim(1, 1)); + using var _ = await _locks.LockAsync(lockKey).ConfigureAwait(false); - await semaphore.WaitAsync(); - try + // 3. 再次检查本地缓存(可能在等待期间已被其他线程填充) + if (_memoryCache.TryGetValue(key, out value)) { - // 3. 再次检查本地缓存(可能在等待期间已被其他线程填充) - if (_memoryCache.TryGetValue(key, out value)) - { - return value; - } - - // 4. 从Redis获取 - var redisValue = await StringGetAsync(key); - if (!string.IsNullOrEmpty(redisValue)) - { - value = redisValue; - _memoryCache.Set(key, value, TimeSpan.FromMinutes(Constants.DefaultMemoryExpireMinutes)); - return value; - } - - return string.Empty; + return value; } - finally + + // 4. 从Redis获取 + var redisValue = await StringGetAsync(key); + if (!string.IsNullOrEmpty(redisValue)) { - semaphore.Release(); - _locks.TryRemove(lockKey, out _); + value = redisValue; + _memoryCache.Set(key, value, TimeSpan.FromMinutes(Constants.DefaultMemoryExpireMinutes)); + return value; } + + return string.Empty; } public async Task SetStringAsync(string key, string value, int minutes = 0) @@ -232,4 +214,4 @@ public async Task ClearAsync() ClearKeys(); } } -} \ No newline at end of file +} diff --git a/core/src/SSRAG/Datory/DistributedCache.cs b/core/src/SSRAG/Datory/DistributedCache.cs index b59bb909..f27a21de 100644 --- a/core/src/SSRAG/Datory/DistributedCache.cs +++ b/core/src/SSRAG/Datory/DistributedCache.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Threading; using System.Collections.Generic; +using AsyncKeyedLock; using SSRAG.Datory.Utils; namespace SSRAG.Datory @@ -13,7 +14,7 @@ public partial class DistributedCache : IDistributedCache private readonly string _prefix; private readonly MemoryCache _memoryCache; private readonly Lazy _lazyConnection; - private static readonly ConcurrentDictionary _locks = new(); + private static readonly AsyncKeyedLocker _locks = new(); public DistributedCache(string connectionString) { @@ -41,4 +42,4 @@ public DistributedCache(string connectionString) private ConnectionMultiplexer GetConnection() => _lazyConnection.Value; } -} \ No newline at end of file +} diff --git a/core/src/SSRAG/SSRAG.csproj b/core/src/SSRAG/SSRAG.csproj index ec0ac059..629b594e 100644 --- a/core/src/SSRAG/SSRAG.csproj +++ b/core/src/SSRAG/SSRAG.csproj @@ -26,6 +26,7 @@ + From e82265cee35e1bddade63b6d267b96978d5f7c41 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Fri, 2 Jan 2026 10:27:42 +0100 Subject: [PATCH 2/2] Bump AsyncKeyedLock to 8.0.0 --- core/src/SSRAG/SSRAG.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/SSRAG/SSRAG.csproj b/core/src/SSRAG/SSRAG.csproj index 629b594e..374344a2 100644 --- a/core/src/SSRAG/SSRAG.csproj +++ b/core/src/SSRAG/SSRAG.csproj @@ -26,7 +26,7 @@ - +