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

Loading…
Cancel
Save