最常用的软件开发模式之一是缓存。这是一个简单但非常有效的概念。我们的想法是重用操作的结果。执行繁重的操作时,我们会将结果存储在缓存容器中。下次我们需要结果。当我们将它从缓存容器中取出时,而不是再次执行繁重的工作。
例如,要获取某人的头像,您可能需要转到数据库。我们不是每次都执行查询,而是将结果保存在缓存中,并在每次需要时将它们从内存中删除。
缓存非常适合不经常更改的数据,甚至不会更改。不断变化的数据不适合缓存,例如当前机器时间不应缓存,否则会得到错误的结果。
进程内缓存,持久性缓存和分布式缓存
当进程内缓存用于在单个进程中实现缓存时,缓存会在进程终止时消失。如果在多个服务器上运行相同的进程,则每个服务器都有一个单独的缓存。
持久性缓存是指在进程内存之外备份缓存,进程内存可以位于文件或数据库中。这很难实现,但如果重新启动进程,缓存不会丢失。
分布式缓存意味着您为多台计算机提供共享缓存,通常是使用分布式缓存的多台服务器,分布式缓存存储在外部服务中。这意味着如果服务器保存缓存条目,则其他服务器可以使用它。像Redis这样的服务非常适合这种情况。
单线程缓存
这个简单的代码解决了一个关键问题。要获取test的值,只有第一个请求才会实际执行数据库操作,然后将数据保存在进程内存中。稍后,将从内存中提取测试请求。节省时间和资源。
然而,就像编程中的大多数事情一样,没有什么是如此简单由于许多原因,上述解决方案并不好。首先,这种实现不是线程安全的。使用多个线程时可能会发生异常。此外,缓存的项目将永远保留在内存中,这实际上非常糟糕。
例如:
运行结果7234859,运行数据丢失
这就是我们应该从缓存中删除项目的原因:
缓存可能占用大量内存,最终导致内存不足和崩溃。高内存消耗可能导致GC压力(也称为内存压力)。在此状态下,垃圾收集器的工作负载超出预期并可能影响性能。
如果数据发生更改,则可能需要刷新缓存,我们的缓存基础结构应支持此功能。
为了解决这些问题,缓存框架具有逐出策略(即删除策略),该规则是基于某些逻辑从缓存中删除项目的规则。常见的驱逐政策是:
绝对过期策略将在一段固定时间后从缓存中删除项目。
如果在固定的时间内未访问该项,则滑动到期策略将从缓存中删除该项。因此,如果我将到期时间设置为1分钟,只要我每隔30秒使用一次,该项目将保留在缓存中,一旦我不使用它超过一分钟,该项目将被逐出。
大小限制策略将限制缓存大小。
现在我们知道了我们需要什么,让我们继续寻找更好的解决方案。
改进计划
令我非常失望的是,作为一名博主,微软已经创建了一个很棒的缓存实现,让我无法创建类似的实现,但至少我写这篇博文的工作量较少。
我将向您展示Microsoft的解决方案如何工作,如何有效地使用它,以及如何在某些情况下改进它。
System.Runtime.Caching/MemoryCache和Microsoft.Extensions.Caching.Memory
微软有两个解决方案,两个不同的NuGet包用于缓存,这两个包都很棒。根据微软的建议,微软更喜欢使用Microsoft.Extensions.Caching.Memory,因为它与Asp集成得更好。它可以很容易地注入到Asp .NET Core的依赖注入机制中。
这是Microsoft.Extensions.Caching.Memory的基本示例:
这与我自己的NaiveCache非常相似,所以改变了什么?嗯,首先,这是一个线程安全的实现。您可以安全地从多个线程一次调用它。
具有驱逐政策的IMemoryCache:
让我们分析一下新的东西:
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委托。
这是一个非常全面的功能集。它让你想知道是否还有其他东西需要添加,实际上还有一些东西。
问题和缺失的功能
这个实现中有几个重要的缺失部分。
虽然您可以设置大小限制,但缓存实际上并不监视gc压力。如果我们对其进行监控,我们可以在遇到压力时收紧政策,在压力下放松政策。
当同时请求具有多个线程的相同项目时,请求不等待第一次完成,该项目将被多次创建。例如,假设我们正在缓存头像,从数据库获取头像需要10秒钟。如果我们在第一次请求后2秒请求头像,它将检查是否缓存了头像(它还没有)并开始另一次访问数据库。
原始英文文本中有描述,但不是很好,也没有翻译。