纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

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

michaelscoding   2021-09-13 我要评论
想了解C# .NET 中的缓存实现详情的相关内容吗michaelscoding在本文为您仔细讲解C# .NET 中的缓存实现的相关知识和一些Code实例欢迎阅读和指正我们先划重点:C#,.NET,中的缓存实现,C#,.NET,缓存下面大家一起来学习吧

一、缓存的基本概念

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

例如要获取一个人的头像您可能需要访问数据库我们不会每次都执行那次旅行而是将 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 压力缓存太少会导致性能问题而分布式缓存这是一个需要探索的全新世界软件开发职业就这样总是有新的东西要学习


相关文章

猜您喜欢

  • Java反射 Java反射之深入理解

    想了解Java反射之深入理解的相关内容吗威斯布鲁克.猩猩在本文为您仔细讲解Java反射的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Java反射,Java反射机制下面大家一起来学习吧..
  • python opencv文字颜色识别 python+opencv实现文字颜色识别与标定功能

    想了解python+opencv实现文字颜色识别与标定功能的相关内容吗AI炮灰在本文为您仔细讲解python opencv文字颜色识别的相关知识和一些Code实例欢迎阅读和指正我们先划重点:python,opencv文字颜色识别,python,opencv文字识别下面大家一起来学习吧..

网友评论

Copyright 2020 www.sopisoft.net 【绿软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式