文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

C# .NET 中的缓存实现详情

2024-04-02 19:55

关注

一、缓存的基本概念

缓存 。这是一个简单但非常有效的概念,这个想法的核心是记录过程数据,重用操作结果。当执行繁重的操作时,我们会将结果保存在我们的 缓存容器中 。下次我们需要该结果时,我们将从缓存容器中拉出它,而不是再次执行繁重的操作。

例如,要获取一个人的头像,您可能需要访问数据库。我们不会每次都执行那次旅行,而是将 Avatar 保存在缓存中,每次需要时从内存中提取它。

缓存非常适用于不经常更改的数据。或者甚至更好,永远不会改变。不断变化的数据,比如当前机器的时间不应该被缓存,否则你会得到错误的结果。

二、缓存

有 3 种类型的缓存:

我们将只讨论 进程内缓存

三、进程内缓存早期做法

让我们用 C# 创建一个非常简单的缓存实现:


public class NaiveCache<TItem>


{


    Dictionary<object, TItem> _cache = new Dictionary<object, TItem>();

 

 

    public TItem GetOrCreate(object key, Func<TItem> createItem)


    {


        if (!_cache.ContainsKey(key))


        {


            _cache[key] = createItem();


        }


        return _cache[key];


    }


}

用法:


var _avatarCache = new NaiveCache<byte[]>();


// ...


var myAvatar = _avatarCache.GetOrCreate(userId, () => _database.GetAvatar(userId));

这个简单的代码解决了一个关键问题。要获取用户的头像,只有第一个请求才会真正执行到数据库的访问。然后将头像数据 ( byte[] ) 保存在进程内存中。对头像的所有后续请求都将从内存中提取,从而节省时间和资源。

但是,正如编程中的大多数事情一样,没有什么是那么简单的。由于多种原因,上述解决方案并不好。一方面,这个实现 不是线程安全的 。从多个线程使用时可能会发生异常。除此之外,缓存的项目将永远留在内存中,这实际上非常糟糕。

这就是我们应该从缓存中删除项目的原因:

为了处理这些问题,缓存框架具有 驱逐策略 (又名 移除策略 )。这些是根据某些逻辑从缓存中删除项目的规则。常见的驱逐政策有:

现在我们知道我们需要什么,让我们继续寻找更好的解决方案。

四、更好的解决方案

作为一名博主,令我非常沮丧的是,微软已经创建了一个很棒的缓存实现。这剥夺了我自己创建类似实现的乐趣,但至少我写这篇博文的工作量减少了。

我将向您展示微软的解决方案,如何有效地使用它,然后在某些场景中如何改进它。

System.Runtime.Caching/MemoryCache 与 Microsoft.Extensions.Caching.Memory

Microsoft 有 2 个解决方案 2 个不同的 NuGet 包用于缓存。两者都很棒。根据 Microsoft 的 建议 ,更喜欢使用, Microsoft.Extensions.Caching.Memory 因为它与 Asp.NET Core 集成得更好。它可以很 容易地注入 到 Asp .NET Core 的依赖注入机制中。

1、 Microsoft.Extensions.Caching.Memory

这是一个基本示例 Microsoft.Extensions.Caching.Memory :


public class SimpleMemoryCache<TItem>


{

    private MemoryCache _cache = new MemoryCache(new MemoryCacheOptions()); 

    public TItem GetOrCreate(object key, Func<TItem> createItem)


    {


        TItem cacheEntry;


        if (!_cache.TryGetValue(key, out cacheEntry))// Look for cache key.


        {


            // Key not in cache, so get data.


            cacheEntry = createItem();


            // Save data in cache.


            _cache.Set(key, cacheEntry);


        }


        return cacheEntry;
    }
}

用法:


var _avatarCache = new SimpleMemoryCache<byte[]>();


// ...


var myAvatar = _avatarCache.GetOrCreate(userId, () => _database.GetAvatar(userId));

这和我自己的非常相似 NaiveCache ,所以有什么改变?嗯,一方面,这是一个 线程安全的 实现。您可以一次从多个线程安全地调用它。

第二件事是 MemoryCache 允许我们之前谈到的所有 驱逐政策 。

下面是一个例子:

2、具有驱逐策略的 IMemoryCache


public class MemoryCacheWithPolicy<TItem>
{
    private MemoryCache _cache = new MemoryCache(new MemoryCacheOptions()

    {
        SizeLimit = 1024

    });

    public TItem GetOrCreate(object key, Func<TItem> createItem)

  {
        TItem cacheEntry;
        if (!_cache.TryGetValue(key, out cacheEntry))// Look for cache key.

        {
            // Key not in cache, so get data.
            cacheEntry = createItem();

            var cacheEntryOptions = new MemoryCacheEntryOptions()
             .SetSize(1)//Size amount
             //Priority on removing when reaching size limit (memory pressure)

                .SetPriority(CacheItemPriority.High)

            // Keep in cache for this time, reset time if accessed.

             .SetSlidingExpiration(TimeSpan.FromSeconds(2))

              // Remove from cache after this time, regardless of sliding expiration
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(10));

            // Save data in cache.
            _cache.Set(key, cacheEntry, cacheEntryOptions);
        }
        return cacheEntry;
    }
}

3、问题和缺失的功能

在这个实现中有几个重要的缺失部分。

事实上,这是一个 MemoryCache 完全解决它的实现:


public class WaitToFinishMemoryCache<TItem>

{

    private MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
    private ConcurrentDictionary<object, SemaphoreSlim> _locks = new ConcurrentDictionary<object, SemaphoreSlim>();

    public async Task<TItem> GetOrCreate(object key, Func<Task<TItem>> createItem)

    {

        TItem cacheEntry;

        if (!_cache.TryGetValue(key, out cacheEntry))// Look for cache key.
        {
            SemaphoreSlim mylock = _locks.GetOrAdd(key, k => new SemaphoreSlim(1, 1));

            await mylock.WaitAsync();
            try
            {
                if (!_cache.TryGetValue(key, out cacheEntry))
                {
                    // Key not in cache, so get data.
                    cacheEntry = await createItem();

                    _cache.Set(key, cacheEntry);
                }
            }
            finally
            {
                mylock.Release();
            }

        }

        return cacheEntry;
    }

}

用法:


var _avatarCache = new WaitToFinishMemoryCache<byte[]>();
// ...
var myAvatar = 

 await _avatarCache.GetOrCreate(userId, async () => await _database.GetAvatar(userId));

4、代码说明

此实现锁定项目的创建。锁是特定于钥匙的。例如,如果我们正在等待获取 Alex Avatar,我们仍然可以在另一个线程上获取 John Sarah 的缓存值。

字典 _locks 存储了所有的锁。常规锁不适用于 async/await ,因此我们需要使用 SemaphoreSlim [5] .

如果 (!_cache.TryGetValue(key, out cacheEntry)),有 2 次检查以查看该值是否已被缓存。锁内的那个是确保只有一个创建的那个。锁外面的那个是为了优化。

五、何时使用 WaitToFinishMemoryCache

这个实现显然有一些开销。让我们考虑什么时候甚至有必要。

在以下情况下使用 WaitToFinishMemoryCache:

在以下情况下不要使用 WaitToFinishMemoryCache:

概括:

缓存是一种非常强大的模式,它也很危险,并且有其自身的复杂性。缓存太多,可能会导致 GC 压力,缓存太少会导致性能问题。而分布式缓存,这是一个需要探索的全新世界。软件开发职业就这样,总是有新的东西要学习。

到此这篇关于C# .NET 中的缓存实现详情的文章就介绍到这了,更多相关C# .NET 中的缓存实现内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯