返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C# .NET 中的缓存实现详情
  • 779
分享到

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

2024-04-02 19:04:59 779人浏览 独家记忆
摘要

目录一、缓存的基本概念二、缓存三、进程内缓存早期做法四、更好的解决方案1、 Microsoft.Extensions.Caching.Memory 2、具有驱逐策略的 IM

一、缓存的基本概念

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

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

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

二、缓存

有 3 种类型的缓存:

  • In-Memory Cache: 用于在单个进程中实现缓存。当进程终止时,缓存也随之终止。如果您在多台服务器上运行相同的进程,您将为每台服务器提供一个单独的缓存。
  •  持久性进程内缓存: 是指在进程内存之外备份缓存。它可能在文件中,也可能在数据库中。这比较困难,但如果您的进程重新启动,缓存不会丢失。最适合在获取缓存项的情况下使用范围广泛,并且您的进程往往会重新启动很多。
  • 分布式缓存: 是指您希望为多台机器共享缓存。通常,它将是多个服务器。使用分布式缓存,它存储在外部服务中。这意味着如果一台服务器保存了一个缓存项,其他服务器也可以使用它。像 Redis [1] 这样的服务非常适合这一点。

我们将只讨论 进程内缓存

三、进程内缓存早期做法

让我们用 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[] ) 保存在进程内存中。对头像的所有后续请求都将从内存中提取,从而节省时间和资源。

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

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

  • 缓存会占用大量内存,最终导致内存不足异常和崩溃。
  • 高内存消耗会导致 GC 压力 (又名内存压力)。在这种状态下,垃圾收集器的工作量超出其应有的水平,从而损害了性能。
  •  如果数据发生变化,可能需要刷新缓存。我们的缓存基础设施应该支持这种能力。

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

  • 无论如何, 绝对过期 策略将在固定时间后从缓存中删除项目。
  • 如果在固定的时间段内未 访问 某个项目,则 滑动过期 策略将从缓存中删除该项目。因此,如果我将过期时间设置为 1 分钟,只要我每 30 秒使用一次,该项目就会一直保留在缓存中。一旦我超过一分钟不使用它,该物品就会被驱逐。
  • 大小限制 策略将限制缓存内存大小。

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

四、更好的解决方案

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

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

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;
    }
}
  • SizeLimit 被添加到 MemoryCacheOptions . 这为我们的缓存容器添加了基于大小的策略。大小没有单位。相反,我们需要在每个缓存条目上设置大小数量。在这种情况下,我们每次将金额设置为 1 SetSize(1) 。这意味着缓存限制为 1024 个项目。
  •   当我们达到大小限制时,应该删除哪个缓存项?您实际上可以使用 .SetPriority(CacheItemPriority.High) . 级别为 Low、NORMal、High 和 NeverRemove
  • SetSlidingExpiration(TimeSpan.FromSeconds(2)) 添加了,它将 滑动过期 时间设置为 2 秒。这意味着如果一个项目在 2 秒内未被访问,它将被删除。
  •   SetAbsoluteExpiration(TimeSpan.FromSeconds(10)) 添加了,将 绝对过期 时间设置为 10 秒。这意味着该项目将在 10 秒内被驱逐,如果它还没有。
  • 除了示例中的选项之外,您还可以设置一个 ReGISterPostEvictionCallback 委托,该委托将在项目被驱逐时调用。
  • 这是一个非常全面的功能集。它让你想知道是否还有什么要添加的。实际上有几件事。

3、问题和缺失的功能

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

  • 虽然您可以设置大小限制,但缓存实际上并不监控 gc 压力。如果真的监测,压力大的时候可以收紧政策,压力小的时候可以放松政策。
  • 当多个线程同时请求同一个项目时,请求不会等待第一个完成。该项目将被创建多次。例如,假设我们正在缓存头像,从数据库中获取头像需要 10 秒。如果我们在第一次请求后 2 秒请求头像,它将检查头像是否已缓存(尚未缓存),并开始另一次访问数据库。

事实上,这是一个 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 中的缓存实现内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C# .NET 中的缓存实现详情

本文链接: https://lsjlt.com/news/135480.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C# .NET 中的缓存实现详情
    目录一、缓存的基本概念二、缓存三、进程内缓存早期做法四、更好的解决方案1、 Microsoft.Extensions.Caching.Memory 2、具有驱逐策略的 IM...
    99+
    2024-04-02
  • .NET 6开发中怎么实现缓存
    小编今天带大家了解.NET 6开发中怎么实现缓存,文中知识点介绍的非常详细。觉得有帮助的朋友可以跟着小编一起浏览文章的内容,希望能够帮助更多想解决这个问题的朋友找到问题的答案,下面跟着小编一起深入学习“.NET 6开发中...
    99+
    2023-06-26
  • Android中巧妙的实现缓存详解
    前言 缓存有很多的实现方式,技巧性还有坑都很多,今天我给大家介绍一些非通用的方法,可以巧妙地帮大家简单实现一些内存缓存。 Supplier和Memoize SQLite是An...
    99+
    2022-06-06
    缓存 Android
  • C语言中的内存管理详情
    目录1.malloc2.内存泄露3.内存池4.理论5.代码数据结构6.代码7.blk->begin8.总结内容提要: 大家写C程序时,手工申请过内存吗?每次需要存储空间时都向操...
    99+
    2024-04-02
  • 一文掌握.Net core中的缓存
    目录1 Net Framewoke的缓存1.1 System.Web.Caching1.2 System.Runtime.Caching2 Net core的缓存介绍2.1 Memo...
    99+
    2024-04-02
  • Redis 缓存淘汰策略和事务实现乐观锁详情
    目录缓存淘汰策略标题LRU原理标题Redis缓存淘汰策略设置最大缓存淘汰策略Redis事务Redis事务介绍MULTIEXECDISCARDWATCHRedis 不支持事务回滚(为什么呢)Redis乐观锁Redis乐观锁...
    99+
    2022-07-21
    Redis 缓存淘汰策略 Redis 事务实现乐观锁
  • Redis 缓存淘汰策略和事务实现乐观锁详情
    目录缓存淘汰策略标题LRU原理标题Redis缓存淘汰策略设置最大缓存淘汰策略Redis事务Redis事务介绍MULTIEXECDISCARDWATCHRedis 不支持事务回滚(为什...
    99+
    2024-04-02
  • 详解C#中普通缓存的使用
    目录一、首先,新建控制台程序(.NET Core)、以下为项目结构二、编写缓存类三、编写有缓存和没有缓存方法四、控制台上端调用总结一下哈:一、首先,新建控制台程序(.NET Core...
    99+
    2024-04-02
  • vue中详情跳转至列表页实现列表页缓存的示例分析
    小编给大家分享一下vue中详情跳转至列表页实现列表页缓存的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!提了一个需求,希...
    99+
    2024-04-02
  • Java实现LRU缓存的实例详解
    Java实现LRU缓存的实例详解1.CacheCache对于代码系统的加速与优化具有极大的作用,对于码农来说是一个很熟悉的概念。可以说,你在内存中new 了一个一段空间(比方说数组,list)存放一些冗余的结果数据,并利用这些数据完成了以空...
    99+
    2023-05-31
    java lru缓存 ava
  • c++模拟实现string类详情
    目录一、string类简介二、模拟实现成员变量成员函数迭代器重载运算符[ ]三、几种常见函数reserve()resize()push_back()append()重载+=inser...
    99+
    2024-04-02
  • C++多态实现方式详情
    注:文章转自公众号:Coder梁(ID:Coder_LT) 在我们之前介绍的继承的情况当中,派生类调用基类的方法都是不做任何改动的调用。 但有的时候会有一些特殊的情况,我们会希望同一...
    99+
    2024-04-02
  • C++ 实现LRU 与 LFU 的缓存算法
    目录一、LRU (Least Recently Used) 缓存 二、LFU (Least Frequently Used) 缓存一、LRU (Least Recently Used...
    99+
    2024-04-02
  • 详解vue computed的缓存实现原理
    目录初始化 computed依赖收集派发更新总结一下本文围绕下面这个例子,讲解一下computed初始化及更新时的流程,来看看计算属性是怎么实现的缓存,及依赖是怎么被收集的。 &...
    99+
    2024-04-02
  • java中LRU缓存实现
    LRU是Least Recently Used 的缩写,翻译过来就是“最近最少使用”,LRU缓存就是使用这种原理实现,简单的说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉。比如我们缓存10000条数据,当数据小于1000...
    99+
    2016-01-05
    java基础 java
  • C++中的数组详情
    目录1、数组2、数组的使用2.1 元素访问2.2 初始化3、C++11 的初始化方式1、数组 数组其实也是一种数据格式,不过是一种复合类型,它可以存储多个同类型的值。 使用数组可以将...
    99+
    2024-04-02
  • Redis作为缓存应用的情形详细分析
    目录为什么使用缓存应用场景使用缓存的收益和成本缓存不一致业务场景先更新数据库值再更新缓存值删除缓存值再更新数据库值先更新数据库值在删除缓存值方案的详细设计订阅binlog总结缓存问题...
    99+
    2023-01-28
    Redis作为缓存 Redis作为缓存应用 Redis缓存
  • NestJS+Redis实现缓存步骤详解
    NestJS的缓存模块天生支持Redis等缓存机制。以下通过一个示例,说明如何在NestJS中操作Redis。步骤如下: 先安装运行Redis服务,步骤参见链接 新建nestjs项目...
    99+
    2024-04-02
  • C#中怎么使用Couchbase实现分布式缓存
    C#中怎么使用Couchbase实现分布式缓存,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、简介 目前C#业界使用得最多的 Cache 系统主要是 Memcached和...
    99+
    2023-06-17
  • C#中的Explicit和Implicit详情
    目录 一、Implicit和Explicit1、Implicit2、、Explicit先上一段奇怪的代码: if (dto.Payment == null) conti...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作