.Net Core 缓存方式(二)分布式缓存的扩展方法的实现(4)
IDistributedCache 接口
看过 IDistributedCache.cs 了解到,所有分布式缓存都是通过实现 IDistributedCache,实现使用不同的缓存服务器,
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Caching.Distributed
{
/// <summary>
/// Represents a distributed cache of serialized values.
/// </summary>
public interface IDistributedCache
{
/// <summary>
/// Gets a value with the given key.
/// </summary>
/// <param name="key">A string identifying the requested value.</param>
/// <returns>The located value or null.</returns>
byte[] Get(string key);
/// <summary>
/// Gets a value with the given key.
/// </summary>
/// <param name="key">A string identifying the requested value.</param>
/// <param name="token">Optional. The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the located value or null.</returns>
Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken));
/// <summary>
/// Sets a value with the given key.
/// </summary>
/// <param name="key">A string identifying the requested value.</param>
/// <param name="value">The value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
void Set(string key, byte[] value, DistributedCacheEntryOptions options);
/// <summary>
/// Sets the value with the given key.
/// </summary>
/// <param name="key">A string identifying the requested value.</param>
/// <param name="value">The value to set in the cache.</param>
/// <param name="options">The cache options for the value.</param>
/// <param name="token">Optional. The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken));
/// <summary>
/// Refreshes a value in the cache based on its key, resetting its sliding expiration timeout (if any).
/// </summary>
/// <param name="key">A string identifying the requested calue.</param>
void Refresh(string key);
/// <summary>
/// Refreshes a value in the cache based on its key, resetting its sliding expiration timeout (if any).
/// </summary>
/// <param name="key">A string identifying the requested value.</param>
/// <param name="token">Optional. The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
Task RefreshAsync(string key, CancellationToken token = default(CancellationToken));
/// <summary>
/// Removes the value with the given key.
/// </summary>
/// <param name="key">A string identifying the requested value.</param>
void Remove(string key);
/// <summary>
/// Removes the value with the given key.
/// </summary>
/// <param name="key">A string identifying the requested value.</param>
/// <param name="token">Optional. The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
Task RemoveAsync(string key, CancellationToken token = default(CancellationToken));
}
}
默认的IDistributedCache只实现了一些很简单的方法,这些在实际开发中使用中并不常用,例如:
var currentTimeUTC = DateTime.UtcNow.ToString();
byte[] encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
var options = new DistributedCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(20));
cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
使用起来很麻烦,但是实际使用有类似 cache.SetString就再好不过了
IDistributedCache 的扩展方法 DistributedCacheExtensions.cs
微软官方提供了一些扩展方法
DistributedCacheExtensions.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Caching.Distributed
{
/// <summary>
/// Extension methods for setting data in an <see cref="IDistributedCache" />.
/// </summary>
public static class DistributedCacheExtensions
{
/// <summary>
/// Sets a sequence of bytes in the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to store the data in.</param>
/// <param name="value">The data to store in the cache.</param>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
public static void Set(this IDistributedCache cache, string key, byte[] value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
cache.Set(key, value, new DistributedCacheEntryOptions());
}
/// <summary>
/// Asynchronously sets a sequence of bytes in the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to store the data in.</param>
/// <param name="value">The data to store in the cache.</param>
/// <param name="token">Optional. A <see cref="CancellationToken" /> to cancel the operation.</param>
/// <returns>A task that represents the asynchronous set operation.</returns>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
public static Task SetAsync(this IDistributedCache cache, string key, byte[] value, CancellationToken token = default(CancellationToken))
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return cache.SetAsync(key, value, new DistributedCacheEntryOptions(), token);
}
/// <summary>
/// Sets a string in the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to store the data in.</param>
/// <param name="value">The data to store in the cache.</param>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
public static void SetString(this IDistributedCache cache, string key, string value)
{
cache.SetString(key, value, new DistributedCacheEntryOptions());
}
/// <summary>
/// Sets a string in the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to store the data in.</param>
/// <param name="value">The data to store in the cache.</param>
/// <param name="options">The cache options for the entry.</param>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
public static void SetString(this IDistributedCache cache, string key, string value, DistributedCacheEntryOptions options)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
cache.Set(key, Encoding.UTF8.GetBytes(value), options);
}
/// <summary>
/// Asynchronously sets a string in the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to store the data in.</param>
/// <param name="value">The data to store in the cache.</param>
/// <param name="token">Optional. A <see cref="CancellationToken" /> to cancel the operation.</param>
/// <returns>A task that represents the asynchronous set operation.</returns>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
public static Task SetStringAsync(this IDistributedCache cache, string key, string value, CancellationToken token = default(CancellationToken))
{
return cache.SetStringAsync(key, value, new DistributedCacheEntryOptions(), token);
}
/// <summary>
/// Asynchronously sets a string in the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to store the data in.</param>
/// <param name="value">The data to store in the cache.</param>
/// <param name="options">The cache options for the entry.</param>
/// <param name="token">Optional. A <see cref="CancellationToken" /> to cancel the operation.</param>
/// <returns>A task that represents the asynchronous set operation.</returns>
/// <exception cref="System.ArgumentNullException">Thrown when <paramref name="key"/> or <paramref name="value"/> is null.</exception>
public static Task SetStringAsync(this IDistributedCache cache, string key, string value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return cache.SetAsync(key, Encoding.UTF8.GetBytes(value), options, token);
}
/// <summary>
/// Gets a string from the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to get the stored data for.</param>
/// <returns>The string value from the stored cache key.</returns>
public static string GetString(this IDistributedCache cache, string key)
{
byte[] data = cache.Get(key);
if (data == null)
{
return null;
}
return Encoding.UTF8.GetString(data, 0, data.Length);
}
/// <summary>
/// Asynchronously gets a string from the specified cache with the specified key.
/// </summary>
/// <param name="cache">The cache in which to store the data.</param>
/// <param name="key">The key to get the stored data for.</param>
/// <param name="token">Optional. A <see cref="CancellationToken" /> to cancel the operation.</param>
/// <returns>A task that gets the string value from the stored cache key.</returns>
public static async Task<string> GetStringAsync(this IDistributedCache cache, string key, CancellationToken token = default(CancellationToken))
{
byte[] data = await cache.GetAsync(key, token).ConfigureAwait(false);
if (data == null)
{
return null;
}
return Encoding.UTF8.GetString(data, 0, data.Length);
}
}
}
通过DistributedCacheExtensions.cs 可以实现 cache.GetString(key) 这种写法
DistributedCacheEntryOptions.cs 实现
Microsoft.Extensions.Caching.Abstractions/src/DistributedCacheEntryOptions.cs
/// <summary>
/// Provides the cache options for an entry in <see cref="IDistributedCache"/>.
/// </summary>
public class DistributedCacheEntryOptions
{
private DateTimeOffset? _absoluteExpiration;
private TimeSpan? _absoluteExpirationRelativeToNow;
private TimeSpan? _slidingExpiration;
获取或设置缓存的绝对到期日期
/// <summary>
/// Gets or sets an absolute expiration date for the cache entry.
/// </summary>
public DateTimeOffset? AbsoluteExpiration
{
get
{
return _absoluteExpiration;
}
set
{
_absoluteExpiration = value;
}
}
获取或设置相对于现在的绝对到期时间
/// <summary>
/// Gets or sets an absolute expiration time, relative to now.
/// </summary>
public TimeSpan? AbsoluteExpirationRelativeToNow
{
get
{
return _absoluteExpirationRelativeToNow;
}
set
{
if (value <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(
nameof(AbsoluteExpirationRelativeToNow),
value,
"The relative expiration value must be positive.");
}
_absoluteExpirationRelativeToNow = value;
}
}
获取或设置缓存条目在被删除之前可以处于非活动状态(例如,未访问)的时间。这不会使条目的生存期超过绝对到期时间(如果已设置)。
/// <summary>
/// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed.
/// This will not extend the entry lifetime beyond the absolute expiration (if set).
/// </summary>
public TimeSpan? SlidingExpiration
{
get
{
return _slidingExpiration;
}
set
{
if (value <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(
nameof(SlidingExpiration),
value,
"The sliding expiration value must be positive.");
}
_slidingExpiration = value;
}
}
}
Microsoft.Extensions.Caching.Abstractions/src/DistributedCacheEntryExtensions.cs
public static class DistributedCacheEntryExtensions
{
/// <summary>
/// Sets an absolute expiration time, relative to now.
/// </summary>
/// <param name="options">The options to be operated on.</param>
/// <param name="relative">The expiration time, relative to now.</param>
public static DistributedCacheEntryOptions SetAbsoluteExpiration(
this DistributedCacheEntryOptions options,
TimeSpan relative)
{
options.AbsoluteExpirationRelativeToNow = relative;
return options;
}
/// <summary>
/// Sets an absolute expiration date for the cache entry.
/// </summary>
/// <param name="options">The options to be operated on.</param>
/// <param name="absolute">The expiration time, in absolute terms.</param>
public static DistributedCacheEntryOptions SetAbsoluteExpiration(
this DistributedCacheEntryOptions options,
DateTimeOffset absolute)
{
options.AbsoluteExpiration = absolute;
return options;
}
/// <summary>
/// Sets how long the cache entry can be inactive (e.g. not accessed) before it will be removed.
/// This will not extend the entry lifetime beyond the absolute expiration (if set).
/// </summary>
/// <param name="options">The options to be operated on.</param>
/// <param name="offset">The sliding expiration time.</param>
public static DistributedCacheEntryOptions SetSlidingExpiration(
this DistributedCacheEntryOptions options,
TimeSpan offset)
{
options.SlidingExpiration = offset;
return options;
}
}
new DistributedCacheEntryOptions() 后 DateTimeOffset? _absoluteExpiration TimeSpan? _absoluteExpirationRelativeToNow TimeSpan? _slidingExpiration均为null
过期时间判断
1. void Set(string key, byte[] value, DistributedCacheEntryOptions options)
var creationTime = DateTimeOffset.UtcNow;
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options);
var result = _cache.ScriptEvaluate(SetScript, new RedisKey[] { _instance + key },
new RedisValue[]
{
absoluteExpiration?.Ticks ?? NotPresent,
options.SlidingExpiration?.Ticks ?? NotPresent,
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent,
value
});
2.GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options)
private static DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options)
{
if (options.AbsoluteExpiration.HasValue && options.AbsoluteExpiration <= creationTime)
{
throw new ArgumentOutOfRangeException(
nameof(DistributedCacheEntryOptions.AbsoluteExpiration),
options.AbsoluteExpiration.Value,
"The absolute expiration value must be in the future.");
}
var absoluteExpiration = options.AbsoluteExpiration;
if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
absoluteExpiration = creationTime + options.AbsoluteExpirationRelativeToNow;
}
return absoluteExpiration;
}
private static long? GetExpirationInSeconds(DateTimeOffset creationTime, DateTimeOffset? absoluteExpiration, DistributedCacheEntryOptions options)
{
if (absoluteExpiration.HasValue && options.SlidingExpiration.HasValue)
{
return (long)Math.Min(
(absoluteExpiration.Value - creationTime).TotalSeconds,
options.SlidingExpiration.Value.TotalSeconds);
}
else if (absoluteExpiration.HasValue)
{
return (long)(absoluteExpiration.Value - creationTime).TotalSeconds;
}
else if (options.SlidingExpiration.HasValue)
{
return (long)options.SlidingExpiration.Value.TotalSeconds;
}
return null;
}
3.const string SetScript
private const string SetScript = (@"
redis.call('HMSET', KEYS[1], 'absexp', ARGV[1], 'sldexp', ARGV[2], 'data', ARGV[4])
if ARGV[3] ~= '-1' then
redis.call('EXPIRE', KEYS[1], ARGV[3])
end
return 1");
4.StackExchange.Redis Scripting
db.ScriptEvaluate
const string Script = "redis.call('set', @key, @value)";
using (ConnectionMultiplexer conn = /* init code */)
{
var db = conn.GetDatabase(0);
var prepared = LuaScript.Prepare(Script);
db.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 });
}