Skip to content

Instantly share code, notes, and snippets.

@IntranetFactory
Last active August 17, 2024 20:26
Show Gist options
  • Select an option

  • Save IntranetFactory/09338b44aa3c6ad7b2cab0284acfe14c to your computer and use it in GitHub Desktop.

Select an option

Save IntranetFactory/09338b44aa3c6ad7b2cab0284acfe14c to your computer and use it in GitHub Desktop.
EFCoreSecondLevelCacheInterceptor Distributed cached provider using Redis sentinel
using adenin.Platform.Configuration;
using adenin.Platform.Serialization.MessagePack;
using EFCoreSecondLevelCacheInterceptor;
using JetBrains.Annotations;
using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
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(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 dependencyKeys = new HashSet<string>();
foreach (var item in redisDb.SortedSetScan(rootCacheKey))
{
_ = dependencyKeys.Add(item.Element);
}
if (dependencyKeys.Count > 0)
{
redisDb.KeyDelete([.. dependencyKeys]);
}
redisDb.KeyDelete(rootCacheKey);
}
}
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 byte[] Serialize<T>(T? obj)
{
return MessagePackSerializer.Serialize(obj, MessagePackSerializerOptions.Standard.WithResolver(CustomResolvers));
}
private static T? Deserialize<T>(byte[] data)
{
return MessagePackSerializer.Deserialize<T>(data, MessagePackSerializerOptions.Standard.WithResolver(CustomResolvers));
}
private static IFormatterResolver CustomResolvers => CompositeResolver.Create([DBNullFormatter.Instance],
[
NativeDateTimeResolver.Instance,
ContractlessStandardResolver.Instance,
StandardResolverAllowPrivate.Instance,
TypelessContractlessStandardResolver.Instance,
DynamicGenericResolver.Instance
]
);
}
@IntranetFactory
Copy link
Author

We switched to use MessagePack for serialization. I've updated the Gist to the version we use in prod without any problems since May.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment