From e72a1f1613ede7c87441bf29896da2d819973f3c Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Tue, 21 Jan 2025 22:43:38 +0800 Subject: [PATCH] refactor cache --- .../BotSharp.Abstraction.csproj | 4 +- .../Infrastructures/Enums/CacheType.cs | 15 +++ .../Infrastructures/SharpCacheAttribute.cs | 48 +++++----- .../Infrastructures/SharpCacheSettings.cs | 3 +- .../BotSharp.Core/BotSharp.Core.csproj | 1 + .../BotSharp.Core/BotSharpCoreExtensions.cs | 29 ++++-- .../Cache/HybridCacheService.cs | 66 +++++++++++++ .../{ => Cache}/MemoryCacheService.cs | 9 +- .../Cache/RedisCacheService.cs | 54 +++++++++++ .../Infrastructures/RedisCacheService.cs | 95 ------------------- 10 files changed, 189 insertions(+), 135 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/CacheType.cs create mode 100644 src/Infrastructure/BotSharp.Core/Infrastructures/Cache/HybridCacheService.cs rename src/Infrastructure/BotSharp.Core/Infrastructures/{ => Cache}/MemoryCacheService.cs (74%) create mode 100644 src/Infrastructure/BotSharp.Core/Infrastructures/Cache/RedisCacheService.cs delete mode 100644 src/Infrastructure/BotSharp.Core/Infrastructures/RedisCacheService.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj index 9b31ae6f..b5dfa8e5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj +++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj @@ -1,4 +1,4 @@ - + $(TargetFramework) @@ -37,7 +37,7 @@ - + diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/CacheType.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/CacheType.cs new file mode 100644 index 00000000..af2c07ed --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/Enums/CacheType.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Abstraction.Infrastructures.Enums +{ + public enum CacheType + { + MemoryCache, + RedisCache, + HybridCache + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs index c8f6a674..1c5b00d1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs @@ -6,9 +6,10 @@ using Rougamo.Context; namespace BotSharp.Core.Infrastructures; -public class SharpCacheAttribute : MoAttribute +public class SharpCacheAttribute : AsyncMoAttribute { public static IServiceProvider Services { get; set; } = null!; + private static readonly object NullMarker = new { __is_null = "$_is_null" }; private int _minutes; @@ -17,8 +18,9 @@ public class SharpCacheAttribute : MoAttribute _minutes = minutes; } - public override void OnEntry(MethodContext context) + public override async ValueTask OnEntryAsync(MethodContext context) { + var settings = Services.GetRequiredService(); if (!settings.Enabled) { @@ -26,9 +28,8 @@ public class SharpCacheAttribute : MoAttribute } var cache = Services.GetRequiredService(); - var key = GetCacheKey(settings, context); - var value = cache.GetAsync(key, context.TaskReturnType).Result; + var value = await cache.GetAsync(key, context.TaskReturnType); if (value != null) { // check if the cache is out of date @@ -41,7 +42,7 @@ public class SharpCacheAttribute : MoAttribute } } - public override void OnSuccess(MethodContext context) + public override async ValueTask OnSuccessAsync(MethodContext context) { var settings = Services.GetRequiredService(); if (!settings.Enabled) @@ -62,7 +63,7 @@ public class SharpCacheAttribute : MoAttribute if (context.ReturnValue != null) { var key = GetCacheKey(settings, context); - cache.SetAsync(key, context.ReturnValue, new TimeSpan(0, _minutes, 0)).Wait(); + await cache.SetAsync(key, context.ReturnValue, new TimeSpan(0, _minutes, 0)); } } @@ -71,25 +72,26 @@ public class SharpCacheAttribute : MoAttribute return Task.FromResult(false); } + private string GetCacheKey(SharpCacheSettings settings, MethodContext context) { - var key = settings.Prefix + ":" + context.Method.Name; - foreach (var arg in context.Arguments) - { - if (arg is null) - { - key += "-" + ""; - } - else if (arg is ICacheKey withCacheKey) - { - key += "-" + withCacheKey.GetCacheKey(); - } - else - { - key += "-" + arg.ToString(); - } - } + var prefixKey = settings.Prefix + ":" + context.Method.Name; + return $"{prefixKey}_{string.Join("_", context.Arguments.Select(arg => GetCacheKey(arg)))}"; + } - return key; + private string GetCacheKey(object? arg) + { + if (arg is null) + { + return NullMarker.GetHashCode().ToString(); + } + else if (arg is ICacheKey withCacheKey) + { + return withCacheKey.GetCacheKey(); + } + else + { + return arg.GetHashCode().ToString(); + } } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheSettings.cs index 2fc266d2..7413d021 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheSettings.cs @@ -3,5 +3,6 @@ namespace BotSharp.Abstraction.Infrastructures; public class SharpCacheSettings { public bool Enabled { get; set; } = false; - public string Prefix { get; set; } = "cache"; + public CacheType CacheType { get; set; } = Enums.CacheType.MemoryCache; + public string Prefix { get; set; } = "cache"; } diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index d1232c5e..bedcd52c 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -195,6 +195,7 @@ + diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index f588b4d1..b69c09fb 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -14,6 +14,7 @@ using BotSharp.Core.Infrastructures.Events; using BotSharp.Core.Roles.Services; using BotSharp.Abstraction.Templating; using BotSharp.Core.Templating; +using BotSharp.Abstraction.Infrastructures.Enums; namespace BotSharp.Core; @@ -34,15 +35,9 @@ public static class BotSharpCoreExtensions services.AddScoped(); services.AddScoped(); - // Register cache service - var cacheSettings = new SharpCacheSettings(); - config.Bind("SharpCache", cacheSettings); - services.AddSingleton(x => cacheSettings); - services.AddSingleton(); - AddRedisEvents(services, config); - - services.AddMemoryCache(); + // Register cache service + AddCacheServices(services, config); RegisterPlugins(services, config); AddBotSharpOptions(services, configOptions); @@ -50,6 +45,24 @@ public static class BotSharpCoreExtensions return services; } + private static void AddCacheServices(IServiceCollection services, IConfiguration config) + { + services.AddMemoryCache(); + var cacheSettings = new SharpCacheSettings(); + config.Bind("SharpCache", cacheSettings); + services.AddSingleton(x => cacheSettings); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(sp => + cacheSettings.CacheType switch + { + CacheType.HybridCache => sp.GetRequiredService(), + CacheType.RedisCache => sp.GetRequiredService(), + _ => sp.GetRequiredService(), + }); + } + public static IServiceCollection UsingSqlServer(this IServiceCollection services, IConfiguration config) { services.AddScoped(sp => diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/HybridCacheService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/HybridCacheService.cs new file mode 100644 index 00000000..d610c730 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/HybridCacheService.cs @@ -0,0 +1,66 @@ +using BotSharp.Abstraction.Infrastructures; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Core.Infrastructures +{ + public class HybridCacheService : ICacheService + { + private readonly MemoryCacheService _memoryCache; + private readonly RedisCacheService _redisCache; + + public HybridCacheService( + MemoryCacheService memoryCache, + RedisCacheService redisCache) + { + _memoryCache = memoryCache; + _redisCache = redisCache; + } + + public async Task GetAsync(string key) + { + var result = await _memoryCache.GetAsync(key); + if (result != null) + { + return result; + } + result = await _redisCache.GetAsync(key); + if (result != null) + { + await SetAsync(key, result, TimeSpan.FromMinutes(5)); + } + return result; + } + + public async Task GetAsync(string key, Type type) + { + var result = await _memoryCache.GetAsync(key, type); + if (result != null) + { + return result; + } + result = await _redisCache.GetAsync(key, type); + if (result != null) + { + await SetAsync(key, result, TimeSpan.FromMinutes(5)); + } + + return result; + } + + public async Task SetAsync(string key, T value, TimeSpan? expiry) + { + await _memoryCache.SetAsync(key, value, expiry); + await _redisCache.SetAsync(key, value, expiry); + } + + public async Task RemoveAsync(string key) + { + await _memoryCache.RemoveAsync(key); + await _redisCache.RemoveAsync(key); + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/MemoryCacheService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/MemoryCacheService.cs similarity index 74% rename from src/Infrastructure/BotSharp.Core/Infrastructures/MemoryCacheService.cs rename to src/Infrastructure/BotSharp.Core/Infrastructures/Cache/MemoryCacheService.cs index a775ab6a..ede8a380 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/MemoryCacheService.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/MemoryCacheService.cs @@ -5,14 +5,11 @@ namespace BotSharp.Core.Infrastructures; public class MemoryCacheService : ICacheService { - private static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions - { - }); - private readonly BotSharpDatabaseSettings _settings; + private readonly IMemoryCache _cache; - public MemoryCacheService(BotSharpDatabaseSettings settings) + public MemoryCacheService(IMemoryCache memoryCache) { - _settings = settings; + this._cache = memoryCache; } public async Task GetAsync(string key) diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/RedisCacheService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/RedisCacheService.cs new file mode 100644 index 00000000..78c62acd --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/Cache/RedisCacheService.cs @@ -0,0 +1,54 @@ +using BotSharp.Abstraction.Infrastructures; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace BotSharp.Core.Infrastructures; + +public class RedisCacheService : ICacheService +{ + private IConnectionMultiplexer _redis = null!; + + public RedisCacheService(IConnectionMultiplexer redis) + { + _redis = redis; + } + + public async Task GetAsync(string key) + { + var db = _redis.GetDatabase(); + var value = await db.StringGetAsync(key); + + if (value.HasValue) + { + return JsonConvert.DeserializeObject(value); + } + + return default; + } + + public async Task GetAsync(string key, Type type) + { + var db = _redis.GetDatabase(); + var value = await db.StringGetAsync(key); + + if (value.HasValue) + { + return JsonConvert.DeserializeObject(value, type); + } + + return default; + } + + + public async Task SetAsync(string key, T value, TimeSpan? expiry) + { + var db = _redis.GetDatabase(); + await db.StringSetAsync(key, JsonConvert.SerializeObject(value), expiry); + } + + public async Task RemoveAsync(string key) + { + var db = _redis.GetDatabase(); + await db.KeyDeleteAsync(key); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/RedisCacheService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/RedisCacheService.cs deleted file mode 100644 index bde2708e..00000000 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/RedisCacheService.cs +++ /dev/null @@ -1,95 +0,0 @@ -using BotSharp.Abstraction.Infrastructures; -using Newtonsoft.Json; -using StackExchange.Redis; - -namespace BotSharp.Core.Infrastructures; - -public class RedisCacheService : ICacheService -{ - private readonly BotSharpDatabaseSettings _settings; - private static ConnectionMultiplexer redis = null!; - - public RedisCacheService(BotSharpDatabaseSettings settings) - { - _settings = settings; - } - - public async Task GetAsync(string key) - { - if (string.IsNullOrEmpty(_settings.Redis)) - { - return default; - } - - if (redis == null) - { - redis = ConnectionMultiplexer.Connect(_settings.Redis); - } - - var db = redis.GetDatabase(); - var value = await db.StringGetAsync(key); - - if (value.HasValue) - { - return JsonConvert.DeserializeObject(value); - } - - return default; - } - - public async Task GetAsync(string key, Type type) - { - if (string.IsNullOrEmpty(_settings.Redis)) - { - return default; - } - - if (redis == null) - { - redis = ConnectionMultiplexer.Connect(_settings.Redis); - } - - var db = redis.GetDatabase(); - var value = await db.StringGetAsync(key); - - if (value.HasValue) - { - return JsonConvert.DeserializeObject(value, type); - } - - return default; - } - - - public async Task SetAsync(string key, T value, TimeSpan? expiry) - { - if (string.IsNullOrEmpty(_settings.Redis)) - { - return; - } - - if (redis == null) - { - redis = ConnectionMultiplexer.Connect(_settings.Redis); - } - - var db = redis.GetDatabase(); - await db.StringSetAsync(key, JsonConvert.SerializeObject(value), expiry); - } - - public async Task RemoveAsync(string key) - { - if (string.IsNullOrEmpty(_settings.Redis)) - { - return; - } - - if (redis == null) - { - redis = ConnectionMultiplexer.Connect(_settings.Redis); - } - - var db = redis.GetDatabase(); - await db.KeyDeleteAsync(key); - } -}