Skip to content

Commit 61e3bb8

Browse files
authored
Merge pull request #838 from yileicn/RefactorCache
refactor cache
2 parents a9b1449 + 4a68bca commit 61e3bb8

File tree

16 files changed

+182
-160
lines changed

16 files changed

+182
-160
lines changed

src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(TargetFramework)</TargetFramework>
@@ -37,7 +37,7 @@
3737
<PackageReference Include="System.Text.Json" Version="8.0.5" />
3838
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
3939
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
40-
<PackageReference Include="Rougamo.Fody" Version="4.0.0" />
40+
<PackageReference Include="Rougamo.Fody" Version="4.0.4" />
4141
<PackageReference Include="AspectInjector" Version="2.8.2" />
4242
</ItemGroup>
4343

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace BotSharp.Abstraction.Infrastructures.Enums
8+
{
9+
public enum CacheType
10+
{
11+
MemoryCache,
12+
RedisCache
13+
}
14+
}

src/Infrastructure/BotSharp.Abstraction/Infrastructures/ICacheService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ public interface ICacheService
66
Task<object> GetAsync(string key, Type type);
77
Task SetAsync<T>(string key, T value, TimeSpan? expiry);
88
Task RemoveAsync(string key);
9+
Task ClearCacheAsync(string prefix);
910
}

src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,37 @@
66

77
namespace BotSharp.Core.Infrastructures;
88

9-
public class SharpCacheAttribute : MoAttribute
9+
public class SharpCacheAttribute : AsyncMoAttribute
1010
{
1111
public static IServiceProvider Services { get; set; } = null!;
12+
private static readonly object NullMarker = new { __is_null = "$_is_null" };
1213

13-
private int _minutes;
14+
private readonly int _minutes;
15+
private readonly bool _perInstanceCache;
16+
private readonly ICacheService _cache;
17+
private readonly SharpCacheSettings _settings;
1418

15-
public SharpCacheAttribute(int minutes = 60)
19+
public SharpCacheAttribute(int minutes = 60, bool perInstanceCache = false)
1620
{
1721
_minutes = minutes;
22+
_perInstanceCache = perInstanceCache;
23+
_cache = Services.GetRequiredService<ICacheService>();
24+
_settings = Services.GetRequiredService<SharpCacheSettings>();
1825
}
1926

20-
public override void OnEntry(MethodContext context)
27+
public override async ValueTask OnEntryAsync(MethodContext context)
2128
{
22-
var settings = Services.GetRequiredService<SharpCacheSettings>();
23-
if (!settings.Enabled)
29+
if (!_settings.Enabled)
2430
{
2531
return;
2632
}
2733

28-
var cache = Services.GetRequiredService<ICacheService>();
29-
30-
var key = GetCacheKey(settings, context);
31-
var value = cache.GetAsync(key, context.TaskReturnType).Result;
34+
var key = GetCacheKey(context);
35+
var value = await _cache.GetAsync(key, context.TaskReturnType);
3236
if (value != null)
3337
{
3438
// check if the cache is out of date
35-
var isOutOfDate = IsOutOfDate(context, value).Result;
39+
var isOutOfDate = await IsOutOfDate(context, value);
3640

3741
if (!isOutOfDate)
3842
{
@@ -41,10 +45,9 @@ public override void OnEntry(MethodContext context)
4145
}
4246
}
4347

44-
public override void OnSuccess(MethodContext context)
48+
public override async ValueTask OnSuccessAsync(MethodContext context)
4549
{
46-
var settings = Services.GetRequiredService<SharpCacheSettings>();
47-
if (!settings.Enabled)
50+
if (!_settings.Enabled)
4851
{
4952
return;
5053
}
@@ -57,12 +60,10 @@ public override void OnSuccess(MethodContext context)
5760
return;
5861
}
5962

60-
var cache = Services.GetRequiredService<ICacheService>();
61-
6263
if (context.ReturnValue != null)
6364
{
64-
var key = GetCacheKey(settings, context);
65-
cache.SetAsync(key, context.ReturnValue, new TimeSpan(0, _minutes, 0)).Wait();
65+
var key = GetCacheKey(context);
66+
await _cache.SetAsync(key, context.ReturnValue, new TimeSpan(0, _minutes, 0));
6667
}
6768
}
6869

@@ -71,25 +72,45 @@ public virtual Task<bool> IsOutOfDate(MethodContext context, object value)
7172
return Task.FromResult(false);
7273
}
7374

74-
private string GetCacheKey(SharpCacheSettings settings, MethodContext context)
75+
76+
private string GetCacheKey(MethodContext context)
7577
{
76-
var key = settings.Prefix + ":" + context.Method.Name;
77-
foreach (var arg in context.Arguments)
78+
var prefixKey = GetPrefixKey(context.Method.Name);
79+
var argsKey = string.Join("_", context.Arguments.Select(arg => GetCacheKeyByArg(arg)));
80+
81+
if (_perInstanceCache && context.Target != null)
7882
{
79-
if (arg is null)
80-
{
81-
key += "-" + "<NULL>";
82-
}
83-
else if (arg is ICacheKey withCacheKey)
84-
{
85-
key += "-" + withCacheKey.GetCacheKey();
86-
}
87-
else
88-
{
89-
key += "-" + arg.ToString();
90-
}
83+
return $"{prefixKey}-{context.Target.GetHashCode()}_{argsKey}";
9184
}
85+
else
86+
{
87+
return $"{prefixKey}_{argsKey}";
88+
}
89+
}
90+
91+
private string GetPrefixKey(string name)
92+
{
93+
return _settings.Prefix + ":" + name;
94+
}
95+
96+
private string GetCacheKeyByArg(object? arg)
97+
{
98+
if (arg is null)
99+
{
100+
return NullMarker.GetHashCode().ToString();
101+
}
102+
else if (arg is ICacheKey withCacheKey)
103+
{
104+
return withCacheKey.GetCacheKey();
105+
}
106+
else
107+
{
108+
return arg.GetHashCode().ToString();
109+
}
110+
}
92111

93-
return key;
112+
public async Task ClearCacheAsync()
113+
{
114+
await _cache.ClearCacheAsync(_settings.Prefix);
94115
}
95116
}

src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheSettings.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ namespace BotSharp.Abstraction.Infrastructures;
22

33
public class SharpCacheSettings
44
{
5-
public bool Enabled { get; set; } = false;
6-
public string Prefix { get; set; } = "cache";
5+
public bool Enabled { get; set; } = true;
6+
public CacheType CacheType { get; set; } = Enums.CacheType.MemoryCache;
7+
public string Prefix { get; set; } = "cache";
78
}

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace BotSharp.Core.Agents.Services;
55
public partial class AgentService
66
{
77
#if !DEBUG
8-
[MemoryCache(10 * 60, perInstanceCache: true)]
8+
[SharpCache(10, perInstanceCache: true)]
99
#endif
1010
public async Task<PagedItems<Agent>> GetAgents(AgentFilter filter)
1111
{
@@ -27,7 +27,7 @@ public async Task<PagedItems<Agent>> GetAgents(AgentFilter filter)
2727
}
2828

2929
#if !DEBUG
30-
[MemoryCache(10 * 60, perInstanceCache: true)]
30+
[SharpCache(10, perInstanceCache: true)]
3131
#endif
3232
public async Task<Agent> GetAgent(string id)
3333
{

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public partial class AgentService
77
{
88
public static ConcurrentDictionary<string, Dictionary<string, string>> AgentParameterTypes = new();
99

10-
[MemoryCache(10 * 60, perInstanceCache: true)]
10+
[SharpCache(10, perInstanceCache: true)]
1111
public async Task<Agent> LoadAgent(string id, bool loadUtility = true)
1212
{
1313
if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString())

src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,13 @@
188188
</ItemGroup>
189189

190190
<ItemGroup>
191-
<PackageReference Include="Aspects.Cache" Version="2.0.4" />
192191
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
193192
<PackageReference Include="EntityFrameworkCore.BootKit" Version="8.8.0" />
194193
<PackageReference Include="Fluid.Core" Version="2.11.1" />
195194
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
196195
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
197196
<PackageReference Include="Nanoid" Version="3.1.0" />
197+
<PackageReference Include="Rougamo.Fody" Version="4.0.4" />
198198
</ItemGroup>
199199

200200
<ItemGroup>

src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using BotSharp.Core.Roles.Services;
1515
using BotSharp.Abstraction.Templating;
1616
using BotSharp.Core.Templating;
17+
using BotSharp.Abstraction.Infrastructures.Enums;
1718

1819
namespace BotSharp.Core;
1920

@@ -34,22 +35,32 @@ public static IServiceCollection AddBotSharpCore(this IServiceCollection service
3435
services.AddScoped<IUserService, UserService>();
3536
services.AddScoped<ProcessorFactory>();
3637

37-
// Register cache service
38-
var cacheSettings = new SharpCacheSettings();
39-
config.Bind("SharpCache", cacheSettings);
40-
services.AddSingleton(x => cacheSettings);
41-
services.AddSingleton<ICacheService, RedisCacheService>();
42-
4338
AddRedisEvents(services, config);
44-
45-
services.AddMemoryCache();
39+
// Register cache service
40+
AddCacheServices(services, config);
4641

4742
RegisterPlugins(services, config);
4843
AddBotSharpOptions(services, configOptions);
4944

5045
return services;
5146
}
5247

48+
private static void AddCacheServices(IServiceCollection services, IConfiguration config)
49+
{
50+
services.AddMemoryCache();
51+
var cacheSettings = new SharpCacheSettings();
52+
config.Bind("SharpCache", cacheSettings);
53+
services.AddSingleton(x => cacheSettings);
54+
services.AddSingleton<MemoryCacheService>();
55+
services.AddSingleton<RedisCacheService>();
56+
services.AddSingleton<ICacheService>(sp =>
57+
cacheSettings.CacheType switch
58+
{
59+
CacheType.RedisCache => sp.GetRequiredService<RedisCacheService>(),
60+
_ => sp.GetRequiredService<MemoryCacheService>(),
61+
});
62+
}
63+
5364
public static IServiceCollection UsingSqlServer(this IServiceCollection services, IConfiguration config)
5465
{
5566
services.AddScoped<IBotSharpRepository>(sp =>

src/Infrastructure/BotSharp.Core/Infrastructures/MemoryCacheService.cs renamed to src/Infrastructure/BotSharp.Core/Infrastructures/Cache/MemoryCacheService.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
using BotSharp.Abstraction.Infrastructures;
22
using Microsoft.Extensions.Caching.Memory;
3+
using Microsoft.Extensions.Options;
34

45
namespace BotSharp.Core.Infrastructures;
56

67
public class MemoryCacheService : ICacheService
78
{
8-
private static IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions
9-
{
10-
});
11-
private readonly BotSharpDatabaseSettings _settings;
9+
private static readonly MemoryCache _cache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions()));
1210

13-
public MemoryCacheService(BotSharpDatabaseSettings settings)
11+
public MemoryCacheService()
1412
{
15-
_settings = settings;
1613
}
1714

1815
public async Task<T?> GetAsync<T>(string key)
@@ -37,4 +34,9 @@ public async Task RemoveAsync(string key)
3734
{
3835
_cache.Remove(key);
3936
}
37+
38+
public async Task ClearCacheAsync(string prefix)
39+
{
40+
_cache.Compact(1.0);
41+
}
4042
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using BotSharp.Abstraction.Infrastructures;
2+
using Newtonsoft.Json;
3+
using StackExchange.Redis;
4+
using System.Linq;
5+
6+
namespace BotSharp.Core.Infrastructures;
7+
8+
public class RedisCacheService : ICacheService
9+
{
10+
private IConnectionMultiplexer _redis = null!;
11+
12+
public RedisCacheService(IConnectionMultiplexer redis)
13+
{
14+
_redis = redis;
15+
}
16+
17+
public async Task<T?> GetAsync<T>(string key)
18+
{
19+
var db = _redis.GetDatabase();
20+
var value = await db.StringGetAsync(key);
21+
22+
if (value.HasValue)
23+
{
24+
return JsonConvert.DeserializeObject<T>(value);
25+
}
26+
27+
return default;
28+
}
29+
30+
public async Task<object> GetAsync(string key, Type type)
31+
{
32+
var db = _redis.GetDatabase();
33+
var value = await db.StringGetAsync(key);
34+
35+
if (value.HasValue)
36+
{
37+
return JsonConvert.DeserializeObject(value, type);
38+
}
39+
40+
return default;
41+
}
42+
43+
44+
public async Task SetAsync<T>(string key, T value, TimeSpan? expiry)
45+
{
46+
var db = _redis.GetDatabase();
47+
await db.StringSetAsync(key, JsonConvert.SerializeObject(value), expiry);
48+
}
49+
50+
public async Task RemoveAsync(string key)
51+
{
52+
var db = _redis.GetDatabase();
53+
await db.KeyDeleteAsync(key);
54+
}
55+
56+
public async Task ClearCacheAsync(string prefix)
57+
{
58+
var db = _redis.GetDatabase();
59+
var server = _redis.GetServer(_redis.GetEndPoints().First());
60+
const int pageSize = 1000;
61+
var keys = server.Keys(pattern: $"{prefix}*", pageSize: pageSize).ToList();
62+
63+
for (int i = 0; i < keys.Count; i += pageSize)
64+
{
65+
var batch = keys.Skip(i).Take(pageSize).ToArray();
66+
await db.KeyDeleteAsync(batch);
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)