Commit a12743e8 authored by 阿星Plus's avatar 阿星Plus

RedisCache

parent 86f50128
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
namespace Plus.Extensions.Serialization
{
public class DataTimeConverter : IsoDateTimeConverter
{
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(DateTime) || objectType == typeof(DateTime?))
{
return true;
}
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
DateTime? dateTime = ReadJson(reader, objectType, existingValue, serializer) as DateTime?;
if (dateTime.HasValue)
{
return dateTime.Value;
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DateTime? dateTime = value as DateTime?;
WriteJson(writer, dateTime ?? value, serializer);
}
public DataTimeConverter()
{
}
}
}
\ No newline at end of file
using Newtonsoft.Json;
using Plus.Extensions.Serialization;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
......@@ -81,4 +83,33 @@ public static class Extensions
StringReader textReader = new StringReader(@this);
return (T)xmlSerializer.Deserialize(textReader);
}
public static string SerializeWithType(this object obj)
{
return SerializeWithType(obj, obj.GetType());
}
public static string SerializeWithType(this object obj, Type type)
{
return $"{type.AssemblyQualifiedName}{'|'}{obj.SerializeToJson()}";
}
public static T DeserializeWithType<T>(this string serializedObj)
{
return (T)DeserializeWithType(serializedObj);
}
public static object DeserializeWithType(this string serializedObj)
{
int typeSeperatorIndex = serializedObj.IndexOf('|');
var type = Type.GetType(serializedObj.Substring(0, typeSeperatorIndex));
string value = serializedObj.Substring(typeSeperatorIndex + 1);
var settings = new JsonSerializerSettings();
settings.Converters.Insert(0, new DataTimeConverter());
return JsonConvert.DeserializeObject(value, type, settings);
}
}
\ No newline at end of file
using Plus.Dependency;
using StackExchange.Redis;
using System;
namespace Plus.RedisCache
{
/// <summary>
/// DefaultRedisCacheSerializer
/// </summary>
public class DefaultRedisCacheSerializer : IRedisCacheSerializer, ITransientDependency
{
/// <summary>
/// Deserialize
/// </summary>
/// <param name="objbyte"></param>
/// <returns></returns>
public virtual object Deserialize(RedisValue objbyte)
{
var serializedObj = objbyte.ToString();
return serializedObj.DeserializeWithType();
}
/// <summary>
/// Serialize
/// </summary>
/// <param name="value"></param>
/// <param name="type"></param>
/// <returns></returns>
public virtual string Serialize(object value, Type type)
{
return value.SerializeWithType(type);
}
}
}
\ No newline at end of file
using Plus.Configuration;
namespace Plus.RedisCache
{
/// <summary>
/// DefaultRedisCacheSettings
/// </summary>
public class DefaultRedisCacheSettings : SettingsBase
{
public DefaultRedisCacheSettings()
{
}
/// <summary>
/// DatabaseId
/// </summary>
/// <returns></returns>
public int DefaultDatabaseId => Config.GetSection("RedisCache")["DatabaseId"].ToInt();
/// <summary>
/// ConnectionString
/// </summary>
/// <returns></returns>
public string DefaultConnectionString => Config.GetSection("RedisCache")["ConnectionString"].ToString();
}
}
\ No newline at end of file
using StackExchange.Redis;
namespace Plus.RedisCache
{
/// <summary>
/// 用于获取 <see cref="IDatabase"/> Redis cache。
/// </summary>
public interface IPlusRedisCacheDatabaseProvider
{
/// <summary>
/// 获取数据连接
/// </summary>
/// <returns></returns>
IDatabase GetDatabase();
}
}
\ No newline at end of file
using StackExchange.Redis;
using System;
namespace Plus.RedisCache
{
/// <summary>
/// (De)Serialize
/// 来自Redis缓存的对象
/// </summary>
public interface IRedisCacheSerializer
{
/// <summary>
/// Deserialize
/// </summary>
/// <param name="objbyte"></param>
/// <returns></returns>
object Deserialize(RedisValue objbyte);
/// <summary>
/// Serialize
/// </summary>
/// <param name="value"></param>
/// <param name="type"></param>
/// <returns></returns>
string Serialize(object value, Type type);
}
}
\ No newline at end of file
......@@ -4,4 +4,13 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.0.601" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plus.Extensions.Serialization\Plus.Extensions.Serialization.csproj" />
<ProjectReference Include="..\Plus\Plus.csproj" />
</ItemGroup>
</Project>
using Plus.Domain.Entities;
using Plus.Runtime.Caching;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Plus.RedisCache
{
/// <summary>
/// 用于在Redis服务器中存储缓存
/// </summary>
public class PlusRedisCache : CacheBase
{
private readonly IDatabase _database;
private readonly IRedisCacheSerializer _serializer;
public PlusRedisCache(
string name,
IPlusRedisCacheDatabaseProvider redisCacheDatabaseProvider,
IRedisCacheSerializer redisCacheSerializer)
: base(name)
{
_database = redisCacheDatabaseProvider.GetDatabase();
_serializer = redisCacheSerializer;
}
public override object GetOrDefault(string key)
{
var objbyte = _database.StringGet(GetLocalizedRedisKey(key));
return objbyte.HasValue ? Deserialize(objbyte) : null;
}
public override object[] GetOrDefault(string[] keys)
{
var redisKeys = keys.Select(GetLocalizedRedisKey);
var redisValues = _database.StringGet(redisKeys.ToArray());
var objbytes = redisValues.Select(obj => obj.HasValue ? Deserialize(obj) : null);
return objbytes.ToArray();
}
public override async Task<object> GetOrDefaultAsync(string key)
{
var objbyte = await _database.StringGetAsync(GetLocalizedRedisKey(key));
return objbyte.HasValue ? Deserialize(objbyte) : null;
}
public override async Task<object[]> GetOrDefaultAsync(string[] keys)
{
var redisKeys = keys.Select(GetLocalizedRedisKey);
var redisValues = await _database.StringGetAsync(redisKeys.ToArray());
var objbytes = redisValues.Select(obj => obj.HasValue ? Deserialize(obj) : null);
return objbytes.ToArray();
}
public override void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
if (value == null)
{
throw new PlusException("Can not insert null values to the cache!");
}
_database.StringSet(
GetLocalizedRedisKey(key),
Serialize(value, GetSerializableType(value)),
absoluteExpireTime ?? slidingExpireTime ?? DefaultAbsoluteExpireTime ?? DefaultSlidingExpireTime
);
}
public override async Task SetAsync(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
if (value == null)
{
throw new PlusException("Can not insert null values to the cache!");
}
await _database.StringSetAsync(
GetLocalizedRedisKey(key),
Serialize(value, GetSerializableType(value)),
absoluteExpireTime ?? slidingExpireTime ?? DefaultAbsoluteExpireTime ?? DefaultSlidingExpireTime
);
}
public override void Set(KeyValuePair<string, object>[] pairs, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
if (pairs.Any(p => p.Value == null))
{
throw new PlusException("Can not insert null values to the cache!");
}
var redisPairs = pairs.Select(p => new KeyValuePair<RedisKey, RedisValue>
(GetLocalizedRedisKey(p.Key), Serialize(p.Value, GetSerializableType(p.Value)))
);
if (slidingExpireTime.HasValue || absoluteExpireTime.HasValue)
{
Logger.WarnFormat("{0}/{1} is not supported for Redis bulk insert of key-value pairs", nameof(slidingExpireTime), nameof(absoluteExpireTime));
}
_database.StringSet(redisPairs.ToArray());
}
public override async Task SetAsync(KeyValuePair<string, object>[] pairs, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
{
if (pairs.Any(p => p.Value == null))
{
throw new PlusException("Can not insert null values to the cache!");
}
var redisPairs = pairs.Select(p => new KeyValuePair<RedisKey, RedisValue>
(GetLocalizedRedisKey(p.Key), Serialize(p.Value, GetSerializableType(p.Value)))
);
if (slidingExpireTime.HasValue || absoluteExpireTime.HasValue)
{
Logger.WarnFormat("{0}/{1} is not supported for Redis bulk insert of key-value pairs", nameof(slidingExpireTime), nameof(absoluteExpireTime));
}
await _database.StringSetAsync(redisPairs.ToArray());
}
public override void Remove(string key)
{
_database.KeyDelete(GetLocalizedRedisKey(key));
}
public override async Task RemoveAsync(string key)
{
await _database.KeyDeleteAsync(GetLocalizedRedisKey(key));
}
public override void Remove(string[] keys)
{
var redisKeys = keys.Select(GetLocalizedRedisKey);
_database.KeyDelete(redisKeys.ToArray());
}
public override async Task RemoveAsync(string[] keys)
{
var redisKeys = keys.Select(GetLocalizedRedisKey);
await _database.KeyDeleteAsync(redisKeys.ToArray());
}
public override void Clear()
{
_database.KeyDeleteWithPrefix(GetLocalizedRedisKey("*"));
}
protected virtual Type GetSerializableType(object value)
{
var type = value.GetType();
if (EntityHelper.IsEntity(type) && type.GetAssembly().FullName.Contains("EntityFrameworkDynamicProxies"))
{
type = type.GetTypeInfo().BaseType;
}
return type;
}
protected virtual string Serialize(object value, Type type)
{
return _serializer.Serialize(value, type);
}
protected virtual object Deserialize(RedisValue objbyte)
{
return _serializer.Deserialize(objbyte);
}
protected virtual RedisKey GetLocalizedRedisKey(string key)
{
return GetLocalizedKey(key);
}
protected virtual string GetLocalizedKey(string key)
{
return "n:" + Name + ",c:" + key;
}
}
}
\ No newline at end of file
using StackExchange.Redis;
using System;
namespace Plus.RedisCache
{
/// <summary>
/// PlusRedisCacheDatabaseProvider
/// </summary>
public class PlusRedisCacheDatabaseProvider : IPlusRedisCacheDatabaseProvider
{
private readonly PlusRedisCacheOptions _options;
private readonly Lazy<ConnectionMultiplexer> _connectionMultiplexer;
/// <summary>
/// 初始化 <see cref="PlusRedisCacheDatabaseProvider"/> 一个实例
/// </summary>
/// <param name="options"></param>
/// <param name="connectionMultiplexer"></param>
public PlusRedisCacheDatabaseProvider(PlusRedisCacheOptions options, Lazy<ConnectionMultiplexer> connectionMultiplexer)
{
_options = options;
_connectionMultiplexer = connectionMultiplexer;
}
/// <summary>
/// 获取数据连接
/// </summary>
/// <returns></returns>
public IDatabase GetDatabase()
{
return _connectionMultiplexer.Value.GetDatabase(_options.DatabaseId);
}
}
}
\ No newline at end of file
using Plus.Dependency;
using Plus.Runtime.Caching;
using Plus.Runtime.Caching.Configuration;
namespace Plus.RedisCache
{
/// <summary>
/// PlusRedisCacheManager
/// </summary>
public class PlusRedisCacheManager : CacheManagerBase
{
public PlusRedisCacheManager(IIocManager iocManager, ICachingConfiguration configuration)
: base(iocManager, configuration)
{
IocManager.RegisterIfNot<PlusRedisCache>(DependencyLifeStyle.Transient);
}
protected override ICache CreateCacheImplementation(string name)
{
return IocManager.Resolve<PlusRedisCache>();
}
}
}
\ No newline at end of file
using Plus.Configuration.Startup;
using System;
namespace Plus.RedisCache
{
/// <summary>
/// PlusRedisCacheOptions
/// </summary>
public class PlusRedisCacheOptions
{
public IPlusStartupConfiguration PlusStartupConfiguration { get; }
public string ConnectionString { get; set; }
public int DatabaseId { get; set; }
public PlusRedisCacheOptions(IPlusStartupConfiguration plusStartupConfiguration)
{
PlusStartupConfiguration = plusStartupConfiguration;
DatabaseId = GetDefaultDatabaseId();
ConnectionString = GetDefaultConnectionString();
}
private static int GetDefaultDatabaseId()
{
try
{
var defaultRedisCacheSettings = PlusEngine.Instance.Resolve<DefaultRedisCacheSettings>();
int databaseId = defaultRedisCacheSettings.DefaultDatabaseId;
return databaseId;
}
catch (Exception ex)
{
throw new PlusException(ex.Message);
}
}
private static string GetDefaultConnectionString()
{
try
{
var defaultRedisCacheSettings = PlusEngine.Instance.Resolve<DefaultRedisCacheSettings>();
string connectionString = defaultRedisCacheSettings.DefaultConnectionString;
return connectionString;
}
catch (Exception ex)
{
throw new PlusException(ex.Message);
}
}
}
}
\ No newline at end of file
using Plus.Dependency;
using Plus.Runtime.Caching;
using Plus.Runtime.Caching.Configuration;
using System;
namespace Plus.RedisCache
{
/// <summary>
/// RedisCacheConfigurationExtensions
/// </summary>
public static class RedisCacheConfigurationExtensions
{
public static void UseRedis(this ICachingConfiguration cachingConfiguration)
{
cachingConfiguration.UseRedis(options => { });
}
public static void UseRedis(this ICachingConfiguration cachingConfiguration, Action<PlusRedisCacheOptions> optionsAction)
{
var iocManager = cachingConfiguration.PlusConfiguration.IocManager;
iocManager.RegisterIfNot<ICacheManager, PlusRedisCacheManager>();
optionsAction(iocManager.Resolve<PlusRedisCacheOptions>());
}
}
}
\ No newline at end of file
using StackExchange.Redis;
using System;
namespace Plus.RedisCache
{
/// <summary>
/// RedisDatabaseExtensions
/// </summary>
internal static class RedisDatabaseExtensions
{
public static void KeyDeleteWithPrefix(this IDatabase database, string prefix)
{
if (database == null)
{
throw new ArgumentException("Database cannot be null", nameof(database));
}
if (string.IsNullOrWhiteSpace(prefix))
{
throw new ArgumentException("Prefix cannot be empty", nameof(database));
}
database.ScriptEvaluate(@"
local keys = redis.call('keys', ARGV[1])
for i=1,#keys,5000 do
redis.call('del', unpack(keys, i, math.min(i+4999, #keys)))
end", values: new RedisValue[] { prefix });
}
public static int KeyCount(this IDatabase database, string prefix)
{
if (database == null)
{
throw new ArgumentException("Database cannot be null", nameof(database));
}
if (string.IsNullOrWhiteSpace(prefix))
{
throw new ArgumentException("Prefix cannot be empty", nameof(database));
}
var retVal = database.ScriptEvaluate("return table.getn(redis.call('keys', ARGV[1]))", values: new RedisValue[] { prefix });
if (retVal.IsNull)
{
return 0;
}
return (int)retVal;
}
}
}
\ No newline at end of file
using Microsoft.Extensions.Configuration;
using Plus.Dependency;
using System.IO;
namespace Plus.Configuration
{
/// <summary>
/// SettingsBase
/// </summary>
public class SettingsBase : ISingletonDependency
{
public IConfigurationRoot _config;
public IConfigurationRoot Config => _config;
public SettingsBase()
{
IConfigurationBuilder configurationBuilder = JsonConfigurationExtensions.AddJsonFile(FileConfigurationExtensions.SetBasePath(new ConfigurationBuilder(), Directory.GetCurrentDirectory()), "appsettings.json", true, true);
_config = configurationBuilder.Build();
}
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
......@@ -8,6 +8,9 @@
<PackageReference Include="Castle.Core" Version="4.4.0" />
<PackageReference Include="Castle.Windsor" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Nito.AsyncEx.Coordination" Version="5.0.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
......@@ -17,4 +20,8 @@
<ProjectReference Include="..\Plus.Extensions\Plus.Extensions.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Utils\" />
</ItemGroup>
</Project>
......@@ -8,7 +8,7 @@ using System.Linq;
namespace Plus.Runtime.Caching
{
/// <summary>
/// Base class for cache managers.
/// 缓存管理
/// </summary>
public abstract class CacheManagerBase : ICacheManager, ISingletonDependency
{
......@@ -18,11 +18,6 @@ namespace Plus.Runtime.Caching
protected readonly ConcurrentDictionary<string, ICache> Caches;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="iocManager"></param>
/// <param name="configuration"></param>
protected CacheManagerBase(IIocManager iocManager, ICachingConfiguration configuration)
{
IocManager = iocManager;
......@@ -67,10 +62,10 @@ namespace Plus.Runtime.Caching
}
/// <summary>
/// Used to create actual cache implementation.
/// 用于创建实际的缓存实现
/// </summary>
/// <param name="name">Name of the cache</param>
/// <returns>Cache object</returns>
/// <param name="name">缓存名称</param>
/// <returns></returns>
protected abstract ICache CreateCacheImplementation(string name);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment