Created
April 27, 2024 10:13
-
-
Save IntranetFactory/09338b44aa3c6ad7b2cab0284acfe14c to your computer and use it in GitHub Desktop.
EFCoreSecondLevelCacheInterceptor Distributed cached provider using Redis sentinel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using adenin.Platform.Configuration; | |
using EFCoreSecondLevelCacheInterceptor; | |
using JetBrains.Annotations; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.Options; | |
using Newtonsoft.Json; | |
using StackExchange.Redis; | |
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
namespace adenin.Platform.Caching; | |
// ReSharper disable once InconsistentNaming | |
public class EFRedisCacheProvider : IEFCacheServiceProvider | |
{ | |
public const string CacheName = "PlatformEFFusionCache"; | |
private readonly IServiceProvider _serviceProvider; | |
private readonly ILogger<EFRedisCacheProvider> _redisCacheProviderLogger; | |
private readonly ConcurrentDictionary<string, ConnectionMultiplexer> _redisConnections = new(StringComparer.Ordinal); | |
public EFRedisCacheProvider( | |
IOptions<EFCoreSecondLevelCacheSettings> cacheSettings, | |
IServiceProvider serviceProvider, | |
ILogger<EFRedisCacheProvider> redisCacheProviderLogger) | |
{ | |
ArgumentNullException.ThrowIfNull(cacheSettings); | |
ArgumentNullException.ThrowIfNull(serviceProvider); | |
_serviceProvider = serviceProvider; | |
_redisCacheProviderLogger = redisCacheProviderLogger; | |
} | |
public void InsertValue(EFCacheKey cacheKey, [CanBeNull] EFCachedData value, EFCachePolicy cachePolicy) | |
{ | |
ArgumentNullException.ThrowIfNull(cacheKey); | |
ArgumentNullException.ThrowIfNull(cachePolicy); | |
value ??= new EFCachedData { IsNull = true }; | |
var redisDb = GetRedisConnection().GetDatabase(); | |
var keyHash = cacheKey.KeyHash; | |
foreach (var rootCacheKey in cacheKey.CacheDependencies) | |
{ | |
if (string.IsNullOrWhiteSpace(rootCacheKey)) | |
{ | |
continue; | |
} | |
var expiryTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + cachePolicy.CacheTimeout.TotalMilliseconds; | |
redisDb.SortedSetAdd(SortedSetCacheKey(rootCacheKey), keyHash, expiryTime); | |
} | |
var data = Serialize(value); | |
redisDb.StringSet(keyHash, data, cachePolicy.CacheTimeout); | |
} | |
public void ClearAllCachedEntries() | |
{ | |
_redisCacheProviderLogger.LogWarning("EFFusionCacheProvider.ClearAllCachedEntries was called"); | |
} | |
public EFCachedData? GetValue(EFCacheKey cacheKey, EFCachePolicy cachePolicy) | |
{ | |
ArgumentNullException.ThrowIfNull(cacheKey); | |
var redisDb = GetRedisConnection().GetDatabase(); | |
var maybeValue = redisDb.StringGet(cacheKey.KeyHash); | |
return maybeValue.HasValue ? Deserialize<EFCachedData>(maybeValue) : null; | |
} | |
public void InvalidateCacheDependencies(EFCacheKey cacheKey) | |
{ | |
ArgumentNullException.ThrowIfNull(cacheKey); | |
var redisDb = GetRedisConnection().GetDatabase(); | |
foreach (var rootCacheKey in cacheKey.CacheDependencies) | |
{ | |
if (string.IsNullOrWhiteSpace(rootCacheKey)) | |
{ | |
continue; | |
} | |
var setKey = SortedSetCacheKey(rootCacheKey); | |
var dependencyKeys = new HashSet<string>(); | |
foreach (var item in redisDb.SortedSetScan(setKey)) | |
{ | |
_ = dependencyKeys.Add(item.Element); | |
} | |
if (dependencyKeys.Count > 0) | |
{ | |
redisDb.KeyDelete([.. dependencyKeys]); | |
} | |
redisDb.KeyDelete(setKey); | |
} | |
} | |
private ConnectionMultiplexer GetRedisConnection() | |
{ | |
return _redisConnections.GetOrAdd("redis", _ => | |
{ | |
var config = _serviceProvider.GetRequiredService<IWebHostEnvironment>(); | |
var redisSetting = config.GetAppConfiguration().GetValue<string>("RedisConnectionString"); | |
var hosts = redisSetting.Split(','); | |
if (hosts.Length <= 1) | |
{ | |
return ConnectionMultiplexer.Connect(redisSetting); | |
} | |
var endpoints = new EndPointCollection(); | |
foreach (var host in hosts) | |
{ | |
endpoints.Add(host, 26379); | |
} | |
var redisOptions = new ConfigurationOptions | |
{ | |
EndPoints = endpoints, | |
ServiceName = "mymaster", | |
TieBreaker = "" | |
}; | |
return ConnectionMultiplexer.Connect(redisOptions); | |
}); | |
} | |
private static string SortedSetCacheKey(string rootCacheKey) => $"{rootCacheKey}:Sets"; | |
private static string Serialize<T>(T? obj) | |
{ | |
return JsonConvert.SerializeObject(obj); | |
} | |
private static T? Deserialize<T>(string data) | |
{ | |
return JsonConvert.DeserializeObject<T>(data); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment