Skip to content

Commit c2ccc4a

Browse files
Support removing cache keys by pattern (#401)
* Support removing cache keys by pattern * Fix redis port * Handle removing cache by pattern in OnMessage
1 parent 14bbda4 commit c2ccc4a

29 files changed

+1179
-239
lines changed

src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,33 @@ public override async Task BaseRemoveByPrefixAsync(string prefix, CancellationTo
299299
await Task.WhenAll(tasks);
300300
}
301301

302+
/// <summary>
303+
/// Removes the by pattern async.
304+
/// </summary>
305+
/// <returns>The by pattern async.</returns>
306+
/// <param name="pattern">Pattern.</param>
307+
/// <param name="cancellationToken">CancellationToken</param>
308+
public override async Task BaseRemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default)
309+
{
310+
ArgumentCheck.NotNullOrWhiteSpace(pattern, nameof(pattern));
311+
312+
pattern = this.HandleKeyPattern(pattern);
313+
314+
if (_options.EnableLogging)
315+
_logger?.LogInformation($"RemoveByPatternAsync : pattern = {pattern}");
316+
317+
var redisKeys = this.SearchRedisKeys(pattern);
318+
319+
var tasks = new List<Task<long>>();
320+
321+
foreach (var item in redisKeys)
322+
{
323+
tasks.Add(_cache.DelAsync(item));
324+
}
325+
326+
await Task.WhenAll(tasks);
327+
}
328+
302329
/// <summary>
303330
/// Sets all async.
304331
/// </summary>

src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,22 @@ private string HandlePrefix(string prefix)
275275

276276
return prefix;
277277
}
278+
279+
/// <summary>
280+
/// Handles the pattern of CacheKey.
281+
/// </summary>
282+
/// <param name="pattern">Pattern of CacheKey.</param>
283+
private string HandleKeyPattern(string pattern)
284+
{
285+
// Forbid
286+
if (pattern.Equals("*"))
287+
throw new ArgumentException("the pattern should not equal to *");
288+
289+
if (!string.IsNullOrWhiteSpace(_cache.Nodes?.Values?.FirstOrDefault()?.Prefix))
290+
pattern = _cache.Nodes?.Values?.FirstOrDefault()?.Prefix + pattern;
291+
292+
return pattern;
293+
}
278294

279295
/// <summary>
280296
/// Searchs the redis keys.
@@ -401,6 +417,27 @@ public override void BaseRemoveByPrefix(string prefix)
401417
}
402418
}
403419

420+
/// <summary>
421+
/// Remove cached value by pattern
422+
/// </summary>
423+
/// <param name="pattern">The pattern of cache key</param>
424+
public override void BaseRemoveByPattern(string pattern)
425+
{
426+
ArgumentCheck.NotNullOrWhiteSpace(pattern, nameof(pattern));
427+
428+
pattern = this.HandleKeyPattern(pattern);
429+
430+
if (_options.EnableLogging)
431+
_logger?.LogInformation($"RemoveByPattern : pattern = {pattern}");
432+
433+
var redisKeys = this.SearchRedisKeys(pattern);
434+
435+
foreach (var item in redisKeys)
436+
{
437+
_cache.Del(item);
438+
}
439+
}
440+
404441
/// <summary>
405442
/// Set the specified cacheKey, cacheValue and expiration.
406443
/// </summary>

src/EasyCaching.Core/Bus/EasyCachingMessage.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,11 @@ public class EasyCachingMessage
2525
/// </summary>
2626
/// <value><c>true</c> if is prefix; otherwise, <c>false</c>.</value>
2727
public bool IsPrefix { get; set; }
28+
29+
/// <summary>
30+
/// Gets or sets a value indicating whether this <see cref="T:EasyCaching.Core.Bus.EasyCachingMessage"/> is pattern.
31+
/// </summary>
32+
/// <value><c>true</c> if is pattern; otherwise, <c>false</c>.</value>
33+
public bool IsPattern { get; set; }
2834
}
2935
}

src/EasyCaching.Core/EasyCachingAbstractProvider.cs

Lines changed: 152 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@ namespace EasyCaching.Core
55
using System;
66
using System.Collections.Generic;
77
using System.Diagnostics;
8-
using System.Linq;
9-
using System.Threading;
10-
using System.Threading.Tasks;
11-
using EasyCaching.Core.Configurations;
8+
using System.Linq;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using EasyCaching.Core.Configurations;
1212
using EasyCaching.Core.Diagnostics;
1313

1414
public abstract class EasyCachingAbstractProvider : IEasyCachingProvider
1515
{
1616
protected static readonly DiagnosticListener s_diagnosticListener =
17-
new DiagnosticListener(EasyCachingDiagnosticListenerExtensions.DiagnosticListenerName);
18-
19-
private readonly IDistributedLockFactory _lockFactory;
20-
private readonly BaseProviderOptions _options;
21-
17+
new DiagnosticListener(EasyCachingDiagnosticListenerExtensions.DiagnosticListenerName);
18+
19+
private readonly IDistributedLockFactory _lockFactory;
20+
private readonly BaseProviderOptions _options;
21+
2222
protected string ProviderName { get; set; }
2323
protected bool IsDistributedProvider { get; set; }
2424
protected int ProviderMaxRdSecond { get; set; }
@@ -36,13 +36,13 @@ public abstract class EasyCachingAbstractProvider : IEasyCachingProvider
3636

3737
protected EasyCachingAbstractProvider() { }
3838

39-
protected EasyCachingAbstractProvider(IDistributedLockFactory lockFactory, BaseProviderOptions options)
40-
{
41-
_lockFactory = lockFactory;
42-
_options = options;
43-
}
44-
45-
public abstract object BaseGetDatabse();
39+
protected EasyCachingAbstractProvider(IDistributedLockFactory lockFactory, BaseProviderOptions options)
40+
{
41+
_lockFactory = lockFactory;
42+
_options = options;
43+
}
44+
45+
public abstract object BaseGetDatabse();
4646
public abstract bool BaseExists(string cacheKey);
4747
public abstract Task<bool> BaseExistsAsync(string cacheKey, CancellationToken cancellationToken = default);
4848
public abstract void BaseFlush();
@@ -64,6 +64,8 @@ protected EasyCachingAbstractProvider(IDistributedLockFactory lockFactory, BaseP
6464
public abstract Task BaseRemoveAsync(string cacheKey, CancellationToken cancellationToken = default);
6565
public abstract void BaseRemoveByPrefix(string prefix);
6666
public abstract Task BaseRemoveByPrefixAsync(string prefix, CancellationToken cancellationToken = default);
67+
public abstract void BaseRemoveByPattern(string pattern);
68+
public abstract Task BaseRemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default);
6769
public abstract void BaseSet<T>(string cacheKey, T cacheValue, TimeSpan expiration);
6870
public abstract void BaseSetAll<T>(IDictionary<string, T> values, TimeSpan expiration);
6971
public abstract Task BaseSetAllAsync<T>(IDictionary<string, T> values, TimeSpan expiration, CancellationToken cancellationToken = default);
@@ -185,30 +187,30 @@ public CacheValue<T> Get<T>(string cacheKey, Func<T> dataRetriever, TimeSpan exp
185187
var operationId = s_diagnosticListener.WriteGetCacheBefore(new BeforeGetRequestEventData(CachingProviderType.ToString(), Name, nameof(Get), new[] { cacheKey }, expiration));
186188
Exception e = null;
187189
try
188-
{
190+
{
189191
if (_lockFactory == null) return BaseGet<T>(cacheKey, dataRetriever, expiration);
190192

191193
var value = BaseGet<T>(cacheKey);
192-
if (value.HasValue) return value;
193-
194-
using (var @lock = _lockFactory.CreateLock(Name, $"{cacheKey}_Lock"))
195-
{
194+
if (value.HasValue) return value;
195+
196+
using (var @lock = _lockFactory.CreateLock(Name, $"{cacheKey}_Lock"))
197+
{
196198
if (!@lock.Lock(_options.SleepMs)) throw new TimeoutException();
197199

198200
value = BaseGet<T>(cacheKey);
199-
if (value.HasValue) return value;
200-
201-
var item = dataRetriever();
202-
if (item != null || _options.CacheNulls)
203-
{
204-
BaseSet(cacheKey, item, expiration);
205-
206-
return new CacheValue<T>(item, true);
207-
}
208-
else
209-
{
210-
return CacheValue<T>.NoValue;
211-
}
201+
if (value.HasValue) return value;
202+
203+
var item = dataRetriever();
204+
if (item != null || _options.CacheNulls)
205+
{
206+
BaseSet(cacheKey, item, expiration);
207+
208+
return new CacheValue<T>(item, true);
209+
}
210+
else
211+
{
212+
return CacheValue<T>.NoValue;
213+
}
212214
}
213215
}
214216
catch (Exception ex)
@@ -316,36 +318,36 @@ public async Task<CacheValue<T>> GetAsync<T>(string cacheKey, Func<Task<T>> data
316318
if (_lockFactory == null) return await BaseGetAsync<T>(cacheKey, dataRetriever, expiration, cancellationToken);
317319

318320
var value = await BaseGetAsync<T>(cacheKey);
319-
if (value.HasValue) return value;
320-
321-
var @lock = _lockFactory.CreateLock(Name, $"{cacheKey}_Lock");
322-
try
323-
{
321+
if (value.HasValue) return value;
322+
323+
var @lock = _lockFactory.CreateLock(Name, $"{cacheKey}_Lock");
324+
try
325+
{
324326
if (!await @lock.LockAsync(_options.SleepMs)) throw new TimeoutException();
325327

326328
value = await BaseGetAsync<T>(cacheKey, cancellationToken);
327-
if (value.HasValue) return value;
328-
329-
var task = dataRetriever();
330-
if (!task.IsCompleted &&
331-
await Task.WhenAny(task, Task.Delay(_options.LockMs)) != task)
332-
throw new TimeoutException();
333-
334-
var item = await task;
335-
if (item != null || _options.CacheNulls)
336-
{
337-
await BaseSetAsync(cacheKey, item, expiration, cancellationToken);
338-
339-
return new CacheValue<T>(item, true);
340-
}
341-
else
342-
{
343-
return CacheValue<T>.NoValue;
344-
}
345-
}
346-
finally
347-
{
348-
await @lock.DisposeAsync();
329+
if (value.HasValue) return value;
330+
331+
var task = dataRetriever();
332+
if (!task.IsCompleted &&
333+
await Task.WhenAny(task, Task.Delay(_options.LockMs)) != task)
334+
throw new TimeoutException();
335+
336+
var item = await task;
337+
if (item != null || _options.CacheNulls)
338+
{
339+
await BaseSetAsync(cacheKey, item, expiration, cancellationToken);
340+
341+
return new CacheValue<T>(item, true);
342+
}
343+
else
344+
{
345+
return CacheValue<T>.NoValue;
346+
}
347+
}
348+
finally
349+
{
350+
await @lock.DisposeAsync();
349351
}
350352
}
351353
catch (Exception ex)
@@ -473,8 +475,8 @@ public async Task<IDictionary<string, CacheValue<T>>> GetByPrefixAsync<T>(string
473475
public int GetCount(string prefix = "")
474476
{
475477
return BaseGetCount(prefix);
476-
}
477-
478+
}
479+
478480
public async Task<int> GetCountAsync(string prefix = "", CancellationToken cancellationToken = default)
479481
{
480482
return await BaseGetCountAsync(prefix, cancellationToken);
@@ -636,6 +638,62 @@ public async Task RemoveByPrefixAsync(string prefix, CancellationToken cancellat
636638
}
637639
}
638640

641+
public void RemoveByPattern(string pattern)
642+
{
643+
var operationId = s_diagnosticListener.WriteRemoveCacheBefore(
644+
new BeforeRemoveRequestEventData(CachingProviderType.ToString(), Name, nameof(RemoveByPattern),
645+
new[] { pattern }));
646+
Exception e = null;
647+
try
648+
{
649+
BaseRemoveByPattern(pattern);
650+
}
651+
catch (Exception ex)
652+
{
653+
e = ex;
654+
throw;
655+
}
656+
finally
657+
{
658+
if (e != null)
659+
{
660+
s_diagnosticListener.WriteRemoveCacheError(operationId, e);
661+
}
662+
else
663+
{
664+
s_diagnosticListener.WriteRemoveCacheAfter(operationId);
665+
}
666+
}
667+
}
668+
669+
public async Task RemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default)
670+
{
671+
var operationId = s_diagnosticListener.WriteRemoveCacheBefore(
672+
new BeforeRemoveRequestEventData(CachingProviderType.ToString(), Name, nameof(RemoveByPatternAsync),
673+
new[] { pattern }));
674+
Exception e = null;
675+
try
676+
{
677+
await BaseRemoveByPatternAsync(pattern, cancellationToken);
678+
}
679+
catch (Exception ex)
680+
{
681+
e = ex;
682+
throw;
683+
}
684+
finally
685+
{
686+
if (e != null)
687+
{
688+
s_diagnosticListener.WriteRemoveCacheError(operationId, e);
689+
}
690+
else
691+
{
692+
s_diagnosticListener.WriteRemoveCacheAfter(operationId);
693+
}
694+
}
695+
}
696+
639697
public void Set<T>(string cacheKey, T cacheValue, TimeSpan expiration)
640698
{
641699
var operationId = s_diagnosticListener.WriteSetCacheBefore(new BeforeSetRequestEventData(CachingProviderType.ToString(), Name, nameof(Set), new Dictionary<string, object> { { cacheKey, cacheValue } }, expiration));
@@ -804,7 +862,37 @@ public async Task<TimeSpan> GetExpirationAsync(string cacheKey, CancellationToke
804862

805863
public ProviderInfo GetProviderInfo()
806864
{
807-
return BaseGetProviderInfo();
865+
return BaseGetProviderInfo();
866+
}
867+
868+
protected SearchKeyPattern ProcessSearchKeyPattern(string pattern)
869+
{
870+
var postfix = pattern.StartsWith("*");
871+
var prefix = pattern.EndsWith("*");
872+
873+
var contains = postfix && prefix;
874+
875+
if (contains)
876+
{
877+
return SearchKeyPattern.Contains;
878+
}
879+
880+
if (postfix)
881+
{
882+
return SearchKeyPattern.Postfix;
883+
}
884+
885+
if (prefix)
886+
{
887+
return SearchKeyPattern.Prefix;
888+
}
889+
890+
return SearchKeyPattern.Exact;
808891
}
892+
893+
protected string HandleSearchKeyPattern(string pattern)
894+
{
895+
return pattern.Replace("*", string.Empty);
896+
}
809897
}
810898
}

0 commit comments

Comments
 (0)