Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(caching): HashIncrementAsync, HashDecrementAsync support sliding expiration #314

Merged
merged 6 commits into from
Oct 31, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,14 @@ public override Task RemoveAsync<T>(string key, Action<CacheOptions>? action = n
public abstract Task<long> HashIncrementAsync(
string key,
long value = 1,
Action<CacheOptions>? action = null);
Action<CacheOptions>? action = null,
CacheEntryOptions? options = null);

public abstract Task<long> HashDecrementAsync(string key,
public abstract Task<long?> HashDecrementAsync(string key,
long value = 1L,
long defaultMinVal = 0L,
Action<CacheOptions>? action = null);
Action<CacheOptions>? action = null,
CacheEntryOptions? options = null);

public virtual bool KeyExpire(string key, TimeSpan? absoluteExpirationRelativeToNow)
=> KeyExpire(key, new CacheEntryOptions(absoluteExpirationRelativeToNow));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,18 +119,27 @@ public interface IDistributedCacheClient : ICacheClient
/// <param name="key">cache key</param>
/// <param name="value">incremental increment, must be greater than 0</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<long> HashIncrementAsync(string key, long value = 1L, Action<CacheOptions>? action = null);
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
/// <returns>Returns the field value after the increment operation</returns>
Task<long> HashIncrementAsync(string key,
long value = 1L,
Action<CacheOptions>? action = null,
CacheEntryOptions? options = null);

/// <summary>
/// Descending Hash
/// </summary>
/// <param name="key">cache key</param>
/// <param name="value">decrement increment, must be greater than 0</param>
/// <param name="defaultMinVal">critical value, must be greater than or equal to 0</param>
/// <param name="defaultMinVal">critical value</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <returns></returns>
Task<long> HashDecrementAsync(string key, long value = 1L, long defaultMinVal = 0L, Action<CacheOptions>? action = null);
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
/// <returns>Returns null on failure, and returns the field value after the decrement operation on success</returns>
Task<long?> HashDecrementAsync(string key,
long value = 1L,
long defaultMinVal = 0L,
Action<CacheOptions>? action = null,
CacheEntryOptions? options = null);

/// <summary>
/// Set cache lifetime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,39 +386,97 @@ public override Task SubscribeAsync<T>(string channel, Action<SubscribeOptions<T

#region Hash

public override Task<long> HashIncrementAsync(string key, long value = 1, Action<CacheOptions>? action = null)
/// <summary>
/// Increment Hash
/// </summary>
/// <param name="key">cache key</param>
/// <param name="value">incremental increment, must be greater than 0</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
/// <returns>Returns the field value after the increment operation</returns>
public override async Task<long> HashIncrementAsync(
string key,
long value = 1,
Action<CacheOptions>? action = null,
CacheEntryOptions? options = null)
{
if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than 0");
CheckParametersByHashIncrementOrHashDecrement(value);

var script = $@"
local exist = redis.call('EXISTS', KEYS[1])
if(exist ~= 1) then
redis.call('HMSET', KEYS[1], KEYS[3], ARGV[1], KEYS[4], ARGV[2])
if ARGV[3] ~= '-1' then
redis.call('EXPIRE', KEYS[1], ARGV[3])
end
end
return redis.call('HINCRBY', KEYS[1], KEYS[2], {value})";

var formattedKey = FormatCacheKey<long>(key, action);
var result = (long)await Db.ScriptEvaluateAsync(script,
new RedisKey[]
{ formattedKey, Const.DATA_KEY, Const.ABSOLUTE_EXPIRATION_KEY, Const.SLIDING_EXPIRATION_KEY },
GetRedisValues(options));

await RefreshAsync(formattedKey);

return Db.HashIncrementAsync(FormatCacheKey<long>(key, action), Const.DATA_KEY, value);
return result;
}

private static void CheckParametersByHashIncrementOrHashDecrement(long value = 1)
{
if (value < 1) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than 0");
}

public override async Task<long> HashDecrementAsync(
/// <summary>
/// Descending Hash
/// </summary>
/// <param name="key">cache key</param>
/// <param name="value">decrement increment, must be greater than 0</param>
/// <param name="defaultMinVal">critical value</param>
/// <param name="action">Cache configuration, used to change the global cache configuration information</param>
/// <param name="options">Configure the cache life cycle, which is consistent with the default configuration when it is empty (is only initialized if the configuration does not exist)</param>
/// <returns>Returns null on failure, and returns the field value after the decrement operation on success</returns>
public override async Task<long?> HashDecrementAsync(
string key,
long value = 1L,
long defaultMinVal = 0L,
Action<CacheOptions>? action = null)
Action<CacheOptions>? action = null,
CacheEntryOptions? options = null)
{
CheckParametersByHashDecrement(value, defaultMinVal);
CheckParametersByHashIncrementOrHashDecrement(value);

var script = $@"
local exist = redis.call('EXISTS', KEYS[1])
if(exist ~= 1) then
redis.call('HMSET', KEYS[1], KEYS[3], ARGV[1], KEYS[4], ARGV[2])
if ARGV[3] ~= '-1' then
redis.call('EXPIRE', KEYS[1], ARGV[3])
end
end

local result = redis.call('HGET', KEYS[1], KEYS[2])
if result then
else
result = 0
end
if tonumber(result) > {defaultMinVal} then
result = redis.call('HINCRBY', KEYS[1], KEYS[2], {0 - value})
return result
else
return -1
return nil
end";
var result = (long)await Db.ScriptEvaluateAsync(script, new RedisKey[] { FormatCacheKey<long>(key, action), Const.DATA_KEY });

return result;
}
var formattedKey = FormatCacheKey<long>(key, action);
var result = await Db.ScriptEvaluateAsync(
script,
new RedisKey[] { formattedKey, Const.DATA_KEY , Const.ABSOLUTE_EXPIRATION_KEY, Const.SLIDING_EXPIRATION_KEY },
GetRedisValues(options));
await RefreshAsync(formattedKey);

private static void CheckParametersByHashDecrement(long value = 1, long defaultMinVal = 0L)
{
if (value < 1) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than 0");
if (result.IsNull)
return null;

if (defaultMinVal < 0) throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(value)} must be greater than or equal to 0");
return (long)result;
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,17 +531,6 @@ await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async ()
=> await _distributedCacheClient.HashDecrementAsync(key, value));
}

[DataTestMethod]
[DataRow("hash_test", -1)]
[DataRow("hash_test", -2)]
public async Task TestHashDecrementAndMinalValueLessThan0Async(string key, long minVal)
{
await _distributedCacheClient.RemoveAsync(key);

await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async ()
=> await _distributedCacheClient.HashDecrementAsync(key, 1, minVal));
}

[DataTestMethod]
[DataRow("test_expire")]
public void TestKeyExpireAndSpecialTimeSpan(string key)
Expand Down