Browse Source

Merge pull request #20024 from abpframework/abprediscache

Enhance AbpRedisCahce
pull/20033/head
maliming 2 years ago
committed by GitHub
parent
commit
f567851d17
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 169
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs
  2. 16
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisExtensions.cs

169
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
@ -19,6 +20,8 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
protected static readonly string SlidingExpirationKey; protected static readonly string SlidingExpirationKey;
protected static readonly string DataKey; protected static readonly string DataKey;
protected static readonly long NotPresent; protected static readonly long NotPresent;
protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData;
protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration;
private readonly static FieldInfo SetScriptField; private readonly static FieldInfo SetScriptField;
private readonly static FieldInfo RedisDatabaseField; private readonly static FieldInfo RedisDatabaseField;
@ -27,29 +30,29 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
private readonly static MethodInfo MapMetadataMethod; private readonly static MethodInfo MapMetadataMethod;
private readonly static MethodInfo GetAbsoluteExpirationMethod; private readonly static MethodInfo GetAbsoluteExpirationMethod;
private readonly static MethodInfo GetExpirationInSecondsMethod; private readonly static MethodInfo GetExpirationInSecondsMethod;
private readonly static MethodInfo OnRedisErrorMethod;
protected IDatabase RedisDatabase => GetRedisDatabase()!; protected RedisKey InstancePrefix { get; }
private IDatabase? _redisDatabase;
protected string Instance { get; }
static AbpRedisCache() static AbpRedisCache()
{ {
var type = typeof(RedisCache); var type = typeof(RedisCache);
RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField))!; RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField));
SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField))!; SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField));
ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod))!; ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod));
ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod))!; ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod));
MapMetadataMethod = Check.NotNull(type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(MapMetadataMethod))!; MapMetadataMethod = Check.NotNull(type.GetMethod("MapMetadata", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(MapMetadataMethod));
GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod))!; GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod));
GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod))!; GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod));
OnRedisErrorMethod = Check.NotNull(type.GetMethod("OnRedisError", BindingFlags.Instance | BindingFlags.NonPublic), nameof(OnRedisErrorMethod));
AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
@ -58,32 +61,29 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To<int>(); NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To<int>();
HashMembersAbsoluteExpirationSlidingExpirationData = [AbsoluteExpirationKey, SlidingExpirationKey, DataKey];
HashMembersAbsoluteExpirationSlidingExpiration = [AbsoluteExpirationKey, SlidingExpirationKey];
} }
public AbpRedisCache(IOptions<RedisCacheOptions> optionsAccessor) public AbpRedisCache(IOptions<RedisCacheOptions> optionsAccessor)
: base(optionsAccessor) : base(optionsAccessor)
{ {
Instance = optionsAccessor.Value.InstanceName ?? string.Empty; var instanceName = optionsAccessor.Value.InstanceName;
} if (!string.IsNullOrEmpty(instanceName))
protected virtual void Connect()
{
if (GetRedisDatabase() != null)
{ {
return; InstancePrefix = (RedisKey)Encoding.UTF8.GetBytes(instanceName);
} }
}
ConnectMethod.Invoke(this, Array.Empty<object>()); protected virtual IDatabase Connect()
{
return (IDatabase)ConnectMethod.Invoke(this, Array.Empty<object>())!;
} }
protected virtual async ValueTask<IDatabase> ConnectAsync(CancellationToken token = default) protected virtual async ValueTask<IDatabase> ConnectAsync(CancellationToken token = default)
{ {
var redisDatabase = GetRedisDatabase();
if (redisDatabase != null)
{
return redisDatabase;
}
return await (ValueTask<IDatabase>)ConnectAsyncMethod.Invoke(this, new object[] { token })!; return await (ValueTask<IDatabase>)ConnectAsyncMethod.Invoke(this, new object[] { token })!;
} }
@ -108,9 +108,17 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
IEnumerable<KeyValuePair<string, byte[]>> items, IEnumerable<KeyValuePair<string, byte[]>> items,
DistributedCacheEntryOptions options) DistributedCacheEntryOptions options)
{ {
Connect(); var cache = Connect();
Task.WaitAll(PipelineSetMany(items, options)); try
{
Task.WaitAll(PipelineSetMany(cache, items, options));
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
} }
public async Task SetManyAsync( public async Task SetManyAsync(
@ -120,9 +128,17 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
await ConnectAsync(token); var cache = await ConnectAsync(token);
await Task.WhenAll(PipelineSetMany(items, options)); try
{
await Task.WhenAll(PipelineSetMany(cache, items, options));
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
} }
public void RefreshMany( public void RefreshMany(
@ -146,9 +162,17 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{ {
keys = Check.NotNull(keys, nameof(keys)); keys = Check.NotNull(keys, nameof(keys));
Connect(); var cache = Connect();
RedisDatabase.KeyDelete(keys.Select(key => (RedisKey)(Instance + key)).ToArray()); try
{
cache.KeyDelete(keys.Select(key => InstancePrefix.Append(key)).ToArray());
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
} }
public async Task RemoveManyAsync(IEnumerable<string> keys, CancellationToken token = default) public async Task RemoveManyAsync(IEnumerable<string> keys, CancellationToken token = default)
@ -156,33 +180,40 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
keys = Check.NotNull(keys, nameof(keys)); keys = Check.NotNull(keys, nameof(keys));
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
await ConnectAsync(token); var cache = await ConnectAsync(token);
await RedisDatabase.KeyDeleteAsync(keys.Select(key => (RedisKey)(Instance + key)).ToArray()); try
{
await cache.KeyDeleteAsync(keys.Select(key => InstancePrefix.Append(key)).ToArray());
}
catch (Exception ex)
{
OnRedisError(ex, cache);
throw;
}
} }
protected virtual byte[]?[] GetAndRefreshMany( protected virtual byte[]?[] GetAndRefreshMany(
IEnumerable<string> keys, IEnumerable<string> keys,
bool getData) bool getData)
{ {
Connect(); var cache = Connect();
var keyArray = keys.Select(key => Instance + key).ToArray(); var keyArray = keys.Select(key => InstancePrefix.Append( key)).ToArray();
RedisValue[][] results; byte[]?[] bytes;
if (getData) try
{ {
results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, var results = cache.HashMemberGetMany(keyArray, GetHashFields(getData));
SlidingExpirationKey, DataKey);
Task.WaitAll(PipelineRefreshManyAndOutData(cache, keyArray, results, out bytes));
} }
else catch (Exception ex)
{ {
results = RedisDatabase.HashMemberGetMany(keyArray, AbsoluteExpirationKey, OnRedisError(ex, cache);
SlidingExpirationKey); throw;
} }
Task.WaitAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes));
return bytes; return bytes;
} }
@ -193,29 +224,28 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
await ConnectAsync(token); var cache = await ConnectAsync(token);
var keyArray = keys.Select(key => Instance + key).ToArray(); var keyArray = keys.Select(key => InstancePrefix.Append(key)).ToArray();
RedisValue[][] results; byte[]?[] bytes;
if (getData) try
{ {
results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, var results = await cache.HashMemberGetManyAsync(keyArray, GetHashFields(getData));
SlidingExpirationKey, DataKey); await Task.WhenAll(PipelineRefreshManyAndOutData(cache, keyArray, results, out bytes));
} }
else catch (Exception ex)
{ {
results = await RedisDatabase.HashMemberGetManyAsync(keyArray, AbsoluteExpirationKey, OnRedisError(ex, cache);
SlidingExpirationKey); throw;
} }
await Task.WhenAll(PipelineRefreshManyAndOutData(keyArray, results, out var bytes));
return bytes; return bytes;
} }
protected virtual Task[] PipelineRefreshManyAndOutData( protected virtual Task[] PipelineRefreshManyAndOutData(
string[] keys, IDatabase cache,
RedisKey[] keys,
RedisValue[][] results, RedisValue[][] results,
out byte[]?[] bytes) out byte[]?[] bytes)
{ {
@ -242,7 +272,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
expr = sldExpr; expr = sldExpr;
} }
tasks[i] = RedisDatabase.KeyExpireAsync(keys[i], expr); tasks[i] = cache.KeyExpireAsync(keys[i], expr);
} }
else else
{ {
@ -264,6 +294,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
} }
protected virtual Task[] PipelineSetMany( protected virtual Task[] PipelineSetMany(
IDatabase cache,
IEnumerable<KeyValuePair<string, byte[]>> items, IEnumerable<KeyValuePair<string, byte[]>> items,
DistributedCacheEntryOptions options) DistributedCacheEntryOptions options)
{ {
@ -277,14 +308,13 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
for (var i = 0; i < itemArray.Length; i++) for (var i = 0; i < itemArray.Length; i++)
{ {
tasks[i] = RedisDatabase.ScriptEvaluateAsync(GetSetScript(), new RedisKey[] { Instance + itemArray[i].Key }, tasks[i] = cache.ScriptEvaluateAsync(GetSetScript(), new RedisKey[] { InstancePrefix.Append(itemArray[i].Key) },
new RedisValue[] [
{ absoluteExpiration?.Ticks ?? NotPresent,
absoluteExpiration?.Ticks ?? NotPresent,
options.SlidingExpiration?.Ticks ?? NotPresent, options.SlidingExpiration?.Ticks ?? NotPresent,
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
itemArray[i].Value itemArray[i].Value
}); ]);
} }
return tasks; return tasks;
@ -317,14 +347,21 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{ {
return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options }); return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options });
} }
private IDatabase? GetRedisDatabase() protected virtual void OnRedisError(Exception ex, IDatabase cache)
{ {
return _redisDatabase ??= RedisDatabaseField.GetValue(this) as IDatabase; OnRedisErrorMethod.Invoke(this, [ex, cache]);
} }
private string GetSetScript() private string GetSetScript()
{ {
return SetScriptField.GetValue(this)!.ToString()!; return SetScriptField.GetValue(this)!.ToString()!;
} }
private static RedisValue[] GetHashFields(bool getData)
{
return getData
? HashMembersAbsoluteExpirationSlidingExpirationData
: HashMembersAbsoluteExpirationSlidingExpiration;
}
} }

16
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisExtensions.cs

@ -8,16 +8,15 @@ public static class AbpRedisExtensions
{ {
public static RedisValue[][] HashMemberGetMany( public static RedisValue[][] HashMemberGetMany(
this IDatabase cache, this IDatabase cache,
string[] keys, RedisKey[] keys,
params string[] members) RedisValue[] fields)
{ {
var tasks = new Task<RedisValue[]>[keys.Length]; var tasks = new Task<RedisValue[]>[keys.Length];
var fields = members.Select(member => (RedisValue)member).ToArray();
var results = new RedisValue[keys.Length][]; var results = new RedisValue[keys.Length][];
for (var i = 0; i < keys.Length; i++) for (var i = 0; i < keys.Length; i++)
{ {
tasks[i] = cache.HashGetAsync((RedisKey)keys[i], fields); tasks[i] = cache.HashGetAsync(keys[i], fields);
} }
for (var i = 0; i < tasks.Length; i++) for (var i = 0; i < tasks.Length; i++)
@ -28,17 +27,16 @@ public static class AbpRedisExtensions
return results; return results;
} }
public static async Task<RedisValue[][]> HashMemberGetManyAsync( public async static Task<RedisValue[][]> HashMemberGetManyAsync(
this IDatabase cache, this IDatabase cache,
string[] keys, RedisKey[] keys,
params string[] members) RedisValue[] fields)
{ {
var tasks = new Task<RedisValue[]>[keys.Length]; var tasks = new Task<RedisValue[]>[keys.Length];
var fields = members.Select(member => (RedisValue)member).ToArray();
for (var i = 0; i < keys.Length; i++) for (var i = 0; i < keys.Length; i++)
{ {
tasks[i] = cache.HashGetAsync((RedisKey)keys[i], fields); tasks[i] = cache.HashGetAsync(keys[i], fields);
} }
return await Task.WhenAll(tasks); return await Task.WhenAll(tasks);

Loading…
Cancel
Save