【问题标题】:How to delete a key or keys from the memory cache basis on particular pattern?如何根据特定模式从内存缓存中删除一个或多个键?
【发布时间】:2020-10-11 04:24:20
【问题描述】:

我有一个方法可以生成我们的缓存键,它是一个字符串,如下所示:

private static readonly HashSet<string> CacheKeys = new HashSet<string>();

public string Generate(int clientId, int processId)
{
    StringBuilder sb = new StringBuilder();
    sb.Append("data:").Append("c:").Append(clientId).Append("::");
    sb.Append("pi::").Append(processId);

    // do I need to store everything in my CacheKeys?
    // or this can be improved so that I can fullfill below 3 requirements
    // CacheKeys.Add(sb.ToString());
    
    return sb.ToString();
}

然后这个字符串和它们的值一起被缓存在MemoryCache 中(这里没有显示,因为它不相关)。

_memoryCache.Set<T>(cacheKey, value, memoryCacheOptions);

我有一个要求,我需要根据以下要求从内存缓存中删除特定的 keykeys -

  • 我需要从内存缓存中删除特定的processId + clientId 键。
  • 无论processId 是什么,我都需要从内存缓存中删除与特定clientId 键匹配的所有键。
  • 我还需要从内存缓存中删除与特定 processId 键匹配的所有键,无论 clientId 是什么。

现在在阅读了 SO 和 MemoryCache 教程之后,似乎没有办法遍历所有键(或找到匹配特定正则表达式模式的键)并将其从缓存中删除。

问题陈述

在经历了各种link 之后,看起来我需要将我的缓存键存储在单独的数据结构中,然后使用它来删除它。

  • 我应该如何在上述方法中以这种方式存储我的cacheKey,以便我可以通过迭代此数据结构或任何线程安全数据结构轻松删除上述 3 个要求的关键基础?
  • 此外,指定要删除哪些键(遵循上述 3 模式)的输入结构来自其他一些服务,并且也没有定义,我非常愿意以任何可以帮助我做到这一点的格式表示高效地完成任务。

基本上,我正在尝试为上述所有三种模式提供一个示例,通过该示例我可以有效地从使用RemoveByPatternRemove 方法的MemoryCache 中删除键。到目前为止,我无法弄清楚如何为所有这三个案例准备一个示例或测试案例,这些案例可以使用我的 RemoveByPatternRemove 方法并有效地清除密钥。

public void RemoveByPattern(MemoryCache memoryCache, string pattern)
{
    var keysToRemove = CacheKeys
        .Where(k => Regex.IsMatch(k, pattern, RegexOptions.IgnoreCase))
        .ToArray();

    foreach (var ktr in keysToRemove)
        Remove(memoryCache, ktr);
}

public void Remove(MemoryCache memoryCache, string key)
{
    memoryCache.Remove(key);
    CacheKeys.Remove(key);
}

如何以能够理解我的上述三个要求的方式表示我的输入结构,然后轻松地将其提供给RemoveByPatternRemove 方法?

【问题讨论】:

    标签: c# regex asp.net-core data-structures memorycache


    【解决方案1】:

    我认为你是在正确的轨道上。目标是使CacheKeys 中的键与MemoryCache 中的键一致。

    案例 1:添加新密钥时

    您需要将新创建的密钥添加到MemoryCacheCacheKeys

    _memoryCache.Set<T>(cacheKey, value, memoryCacheOptions);
    CacheKeys.Add(cacheKey);
    

    案例 2:删除现有密钥时

    你的实现已经足够好了。

    案例 3:密钥过期时

    当密钥从MemoryCache 过期时,MemoryCacheCacheKeys 之间的密钥集不一致。你需要在后台实现一个定时运行的定时任务来检查和清理CacheKeysMemoryCache是真相的来源)。

    private void ConsolidateKeys()
    {
        var invalidKeys = new List<string>();
        foreach (var key in CacheKeys)
        {
            if (!_memoryCache.TryGetValue(key, out var _))
            {
                invalidKeys.Add(key);
            }
        }
        foreach (var key in invalidKeys)
        {
            CacheKeys.Remove(key);
        }
    }
    

    注意

    您需要考虑访问CacheKeys 的线程安全性,因为这是一个多线程场景。关于CacheKeys的数据结构,推荐ConcurrentDictionary&lt;TKey, TValue&gt;优于HashSet&lt;T&gt;


    更新

    为了有效地移除键(假设您的意思是速度),我将以空间换取速度。

    有点复杂,不过可以定义一组processId,命名为ProcessIdMap。密钥是processId。该值是一组clientId,在生成缓存键时具有相同的processIdClientIdMap 类似。 (伪代码)

    ProcessIdMap = Map<processId, Set<clientId>>
    ClientIdMap = Map<clientId, Set<cacheKey>>
    

    案例 1:添加新密钥时

    private static readonly HashSet<string> CacheKeys = new HashSet<string>();
    private static readonly Dictionary<string, HashSet<string>> ProcessIdMap = new Dictionary<string, HashSet<string>>();
    private static readonly Dictionary<string, HashSet<string>> ClientIdMap = new Dictionary<string, HashSet<string>>();
    
    private string BuildCacheKey(int clientId, int processId)
    {
        var sb = new StringBuilder();
        sb.Append("data:").Append("c:").Append(clientId).Append("::");
        sb.Append("pi::").Append(processId);
        return sb.ToString();
    }
    
    public string AddToCache(int clientId, int processId)
    {
        // Populate processId map
        if (!ProcessIdMap.TryGetValue(processId, out var clientIdSet))
        {
            clientIdSet = new HashSet<string>();
        }
        clientIdSet.Add(clientId);
    
        // Populate clientId map
        if (!ClientIdMap.TryGetValue(clientId, out var processIdSet))
        {
            processIdSet = new HashSet<string>();
        }
        processIdSet.Add(processId);
    
        var cacheKey = BuildCacheKey(clientId, processId);
        
        // Populate cache key store
        CacheKeys.Add(cacheKey);
    
        // Populate memory cache
        _memoryCache.Set<T>(cacheKey, value, memoryCacheOptions);
    
        return cacheKey;
    }
    

    案例 2:删除现有密钥时RemoveClientId 方法类似)

    public void RemoveProcessId(string processId)
    {
        if (ProcessIdMap.TryGetValue(processId, out var clientIds))
        {
            foreach (var clientId in clientIds)
            {
                var cacheKey = BuildCacheKey(clientId, processId);
    
                // Remove from cache key store
                CacheKeys.Remove(cacheKey);
    
                // Remove from memory cache
                _memoryCache.Remove(cacheKey);
    
                // Remove from clientId map
                if (ClientIdMap.TryGetValue(clientId, out var processIdSet))
                {
                    processIdSet.Remove(processId);
                }
            }
    
            // Remove from processId map
            ProcessIdMap.Remove(processId);
        }
    }
    

    【讨论】:

    • 建议使用ConcurrentDictionary。我会调查的。
    • 感谢您的建议,但我的困惑是我将如何使用 RemoveByPatternRemove 方法来实现 CacheKeys 设置数据结构或 ConcurrentDictionary 数据结构的这 3 种情况?您是否认为您可以提供一个示例来演示如何有效地使用这些方法来实现这三个场景?到目前为止,我无法弄清楚如何为所有这 3 个可以使用我的 RemoveByPatternRemove 方法的案例准备示例或测试用例。
    • 例如:如何以能够理解我以上三个要求的方式表示我的输入结构,然后轻松地将其提供给RemoveByPatternRemove 方法。
    • 你还在吗?关于如何为这三种模式设置示例以演示如何有效删除键的任何想法?
    • @user1950349 - 帖子已更新。基本上,性能(速度方面)将通过两个额外的地图得到改善。希望对您有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    • 1970-01-01
    相关资源
    最近更新 更多