再论缓存删除 - 用正则匹配 CacheKey 批量删除缓存

接上次的话题《缓存方式扩展,缓存 Tag(命名空间) 的实现》,给 cache item 打上了 tag 标签,这个方式是不错,今天再来一个更好的方法。
这个方式是模仿 Ruby on Rails 里面自带的功能

在 Ruby on Rails 里面,我们可以:

1
2
3
4
5
6
7
8
# 写入将 page1,page2 的数据写入缓存
Rails.cache.write('posts/page/1',@page1)
Rails.cache.write('posts/page/2',@page2)
Rails.cache.write('posts/index',@index)
# 用 posts/page/* 来删除 posts/page/1,posts/page/2
Rails.cache.delete('posts/page/*')
# 用 posts/* 来删除 posts/index,posts/page/1,posts/page/2
Rails.cache.delete('posts/*')

从上面的代码中看出这种删除实在是非常的实用,我们可以根据类别层次级为缓存起 key 的名称,如

  • home/index - 首页的 index 视图
  • posts/show/1 - 查看文章页面 ID 为 1 的文章

然后根据层次级来删除缓存。

这种按 * 号匹配删除一组缓存的方式在实际的应用中需求非常频繁,如《缓存方式扩展,缓存 Tag(命名空间) 的实现》里面提到的列表缓存清除,你不确定具体有多少页,但这些页面都得清除,哪就得用Tag正则匹配的方式来清除了。

如图:

如上图所示,我们将所有的 Cache Key 存入 CacheKeys 中,CacheKeys 以静态的方式存放于进程内存里面,当 DeleteByMatch 调用的时候,用正则的从 CacheKeys 里面匹配出相关的 Keys 列表,再通过循环删除 Memcached 中的缓存。

.NET 里面实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
 * Delete cache by regex match
 * Author: Jason Lee
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Memcached;

public class Caches
{
    ///
    /// 用于存放所有 cache keys,以便于用正则的方式删除缓存
    /// 如:place/home/page/1,以后用 place/home/page/* 来删除 place/home/page/1,place/home/page/2 ... place/home/page/n
    ///
    private static List<string> cacheKeys = new List<string>();
    ///
    /// 将 Key 存入 cacheKeyStore
    ///
    ///
    private static void saveKeyToCacheKeys(string key)
    {
        if (!cacheKeys.Exists(c => c == key))
        {
            cacheKeys.Add(key);
        }
    }

    ///
    /// 删缓存
    ///
    ///
    /// 是否用正则匹配
    public static void Remove(string key, bool match)
    {
        if (!match)
        {
            // Memcached 虚构的自已实现一个
            Memcached.Remove(key);
            // 写 Cached 操作日志
            Debug.Log("Cache remove " + key);
        }
        else
        {
            Regex reg = new Regex(key, RegexOptions.IgnoreCase);
            List<string> matchedKeys = cacheKeys.FindAll(c => reg.IsMatch(c));
            foreach (var k in matchedKeys)
            {
                Memcached.Remove(k);
                Debug.Log("Cache remove " + key);
            }

        }
    }
}

其实整个过程还是比较简单的,这里有个细节要注意!

我在示例代码中所提供的方式是直接静态的方式存放于进程缓存的,这种方式效率很高。但有两个缺陷:

  • 空间问题,当 Cache Key 过多时就需要考虑换地方存放这个列表了,不然 IIS 进程内存超支哪就要 crash 掉。
  • 多个网站同步问题,当有两个 Web 应用程序使用相同的缓存时,如:news.tmitter.com,share.tmitter.com,IIS 两个网站之间内存不是共享的,这里就需要考虑缓存同步的问题了。

以上两个问题目前我已有解决办法,不在本文讨论范围内,以后再说。