我对内存缓存的看法

· 2 分钟阅读

我一直在研究一些处理大量数据的东西。在这样做的过程中,我意识到其中很大一部分永远不会改变,或者至少在一段固定的时间内不会改变。

所以我认为创建个人缓存存储库会很有用,当然这不是什么新鲜事,几周前我在 StackOverflow 的 post 中读到了由 Nick Craver 撰写的关于他们如何管理应用程序缓存的内容。

另外,我一直想使用缓存,它是如何工作的,逻辑以及如何让它工作,所以…为什么不!

流量

下面是一个快速流程,展示了当用户请求一个值时,拥有缓存控制权的类将如何表现。

图片来自 Gyazo

实施

在我们开始之前

我知道,我知道。 System.Runtime.Caching 处理内存缓存。但我决定自己构建它,如果您想使用该类,请查看此处以获取操作方法。

缓存项

第一步是拥有一个存储对象值和到期日期的类。可能有更好的方法来做到这一点,但这就是我的想法,所以你可以:

public class CacheItem
{
    public string Identifier { get; set; }
    public object Value { get; set; }
    public DateTime ValidUntil { get; set; }

    public CacheItem(string identifier, object value, TimeSpan valid)
    {
        Identifier = identifier;
        Value = value;
        ValidUntil = DateTime.UtcNow.Add(valid);
    }
}

缓存存储库

然后我们需要一个类来处理对象并将它们保存在某个地方(如 CacheItem)。我喜欢处理具有后缀 Repository 的类中的所有数据/模型,但您不必,所以让我们构建一个用于缓存。

public class CacheRepository
{
    private static Dictionary<string, string> Cache = new Dictionary<string, string>();
    private static T Set<T>(string key, Func<T> lookup, TimeSpan durationMinutes)
    {
        var item = new Models.Item(key, lookup(), durationMinutes);
        return Save<T>(key, item);
    }
    private static T Save<T>(string key, Item item)
    {
        Cache[key] = JsonConvert.SerializeObject(item);
        return (T)item.Value;
    }
    private static T Get<T>(string key)
    {
        var cached = Cache.FirstOrDefault(x => x.Key == key).Value;
        var item = string.IsNullOrEmpty(cached) ? null : JsonConvert.DeserializeObject<Item>(cached);
        return (item == null) 
        ? default : (item.ValidUntil > DateTime.UtcNow) 
        ? JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(item.Value)) : default;
    }
    public static T GetOrSet<T>(string key, Func<T> lookup, TimeSpan durationMinutes)
    {
        var cache = Get<T>(key);
        return cache == null ? Set(key, lookup, durationMinutes) : cache;
    }
}

到目前为止,我们有一个名为 Cache 的静态字典,其中将存储所有项目。请记住,这只会在应用程序运行时持续,因此这个教程标题中有内存缓存。

请记住,一旦加载 CacheRepository 类,Cache 项就会被初始化。

调用 CacheRepository 类时唯一可用的方法是 GetOrSet(string key, Func<T> lookup, TimeSpan durationMinutes),它需要三个参数:

  1. key:要保存的对象的标识符。
  2. lookup:缓存过期或为空时的回调函数。
  3. durationMinutes:将添加到当前时间(UTC 时间)的持续时间(以分钟为单位)。

缓存时间

现在,使用我们的缓存存储库从某处获取一些数据。

为了使所有这些都有意义,让我们创建一个具有一些属性的示例对象,然后创建一个存储库来获取并填充该对象的列表。

public class User 
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
public class UserRepository 
{
    public List<User> Get()
    {
        // Code to get users
    }
}

由于我们有填充 User 列表的方法,所以让我们使用 CacheRepository 类。

private UserRepository _userRepository = new UserRepository();
private List<User> _user;
public List<User> User
{
    get
    {
        _user = CacheRepository.GetOrSet($"users", usersRepo.Get, TimeSpan.FromMinutes(10));
        return _user;
    }
}

就像这样,每次访问变量 User 时,它都会向 CacheRepository 询问具有键 users 的对象的值。

如果该密钥存在,它将检查到期日期。如果这些条件之一为 false,它将使用回调来设置对象的值(使用 usersRepo.Get),将其保存到缓存,并将到期日期设置为 DateTime.UtcNow + TimeSpan.FromMinutes(10) 并返回它。