.NET中的缓存实现
软件开发中最常用的模式之一是缓存,这是一个简单但非常有效的概念,想法是重用操作结果,执行繁重的操作时,我们会将结果保存在缓存容器中,下次我们需要该结果时,我们将从缓存容器中取出它,而不是再次执行繁重的操作。
例如,要获得某人的头像,您可能需要前往数据库。我们不会每次都执行那次查询,而是将结果保存在缓存中,每次需要时都将其从内存中删除。
缓存非常适合不经常更改的数据,甚至永远不会改变。不断变化的数据不适合缓存,如当前机器的时间不应缓存,否则您将得到错误的结果。
进程内缓存,持久化缓存和分布式缓存
- 进程内缓存用于在单个进程中实现缓存时,当进程终止时,缓存会随之消失。如果您在多个服务器上运行相同的进程,则每个服务器都有一个单独的缓存。
- 持久化缓存是指在进程内存之外备份缓存,它可能位于文件中,也可能位于数据库中。这实现比较困难,但如果重新启动进程,缓存不会丢失。
- 分布式缓存是指您为多台计算机提供共享缓存,通常它将是几个服务器,使用分布式缓存,它存储在外部服务中。这意味着如果一台服务器保存了缓存项,其他服务器也可以使用它。像Redis这样的服务非常适合这种情况。
单线程的缓存
public class NaiveCache<T>
{
private static Dictionary<object, T> _cache = new Dictionary<object, T>();
public static T GetOrCreate(object key, Func<T> createItem)
{
T cacheEntry;
if (!_cache.TryGetValue(key, out cacheEntry))
{
cacheEntry = createItem();
_cache.Add(key, cacheEntry);
} return cacheEntry;
}
} //用法
NaiveCache<string>.GetOrCreate("test", () => { return "test123"; });
这个简单的代码解决了一个关键问题,要获取test的值,只有第一个请求才会实际执行数据库操作,然后将数据保存在进程存储器中,以后有关test的请求都将从内存中提取,从而节省时间和资源。
但是,作为编程中的大多数事情,没有什么是如此简单。由于许多原因,上述解决方案并不好。首先,这种实现不是线程安全的,多个线程使用时可能会发生异常,除此之外,缓存的项目将永远留在内存中,这实际上非常糟糕。
例如:
List<Task> t1 = new List<Task>(); foreach (var item in list)
{
var a = Task.Run(() =>
{
Console.Write($"{NaiveCache<string>.GetOrCreate(item, () => { return item.ToString(); })}");
});
t1.Add(a);
} try
{
Task.WaitAll(t1.ToArray());
}
catch { }
运行结果7234859,运行 的数据丢失了
这就是为什么我们应该从Cache中删除项目:
- 缓存可能占用大量内存,最终导致内存不足异常和崩溃。
- 高内存消耗可导致GC压力(又称内存压力)。在这种状态下,垃圾收集器的工作量超出预期,会影响性能。
- 如果数据发生更改,可能需要刷新缓存,我们的缓存基础架构应该支持这种能力。
为了处理这些问题,缓存框架具有驱逐策略(即删除策略),这些是根据某些逻辑从缓存中删除项目的规则,常见的驱逐政策是:
- 绝对过期策略将在一段固定的时间后从缓存中删除一个项目。
- 如果未在固定的时间内访问项目,则滑动过期策略将从缓存中删除项目。因此,如果我将到期时间设置为1分钟,只要我每隔30秒使用一次,该项目就会保持在缓存中,一旦我不使用它超过一分钟,该项目被驱逐。
- 大小限制策略将限制高速缓存大小。
现在我们知道了我们需要什么,让我们继续寻找更好的解决方案。
改善方案
令我非常沮丧的是,作为博主,微软已经创建了一个很棒的缓存实现,这剥夺了我自己创建类似实现的乐趣,但至少我写这篇博文的工作较少。
我将向您展示Microsoft的解决方案,如何有效地使用它,以及如何在某些情况下改进它。
System.Runtime.Caching / MemoryCache与Microsoft.Extensions.Caching.Memory
微软有2个解决方案,2个不同的NuGet包用于缓存,两者都很棒,根据微软的建议,更喜欢使用Microsoft.Extensions.Caching.Memory因为它与Asp更好地集成.NET核心。它可以很容易地注入到Asp .NET Core的依赖注入机制中。
这是一个基本的例子Microsoft.Extensions.Caching.Memory:
/// <summary>
/// 利用微软的库写的缓存
/// </summary>
/// <typeparam name="T"></typeparam>
public class SimpleMemoyCache<T>
{
private static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions()); public static T GetOrCreate(object key, Func<T> createItem) {
T cacheEntry;
if (!_cache.TryGetValue(key, out cacheEntry)) {
cacheEntry = createItem();
_cache.Set(key, cacheEntry);
} return cacheEntry;
}
}
用法:
SimpleMemoyCache<string>.GetOrCreate("test", () => { return "test123"; });
这与我自己非常相似NaiveCache,所以改变了什么?嗯,首先,这是一个线程安全的实现。您可以安全地从多个线程一次调用它。
带有逐出政策的IMemoryCache:
/// <summary>
/// 带有策略的缓存
/// </summary>
/// <typeparam name="T"></typeparam>
public class MemoryCacheWithPolicy<T>
{
/// <summary>
/// 增加设置缓存大小
/// </summary>
private static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions() { SizeLimit = }); public static T GetOrCreate(object key, Func<T> createItem) {
T cacheEntry;
if (!_cache.TryGetValue(key, out cacheEntry)) {
cacheEntry = createItem();
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSize()
.SetPriority(CacheItemPriority.High) //设置优先级
.SetSlidingExpiration(TimeSpan.FromSeconds()) //2s没有访问删除
.SetAbsoluteExpiration(TimeSpan.FromSeconds()); //10s过期 _cache.Set(key, cacheEntry, cacheEntryOptions);
} return cacheEntry;
}
}
让我们分析一下新增内容:
SizeLimit加入了MemoryCacheOptions,这会将基于大小的策略添加到缓存容器中。相反,我们需要在每个缓存条目上设置大小,在这种情况下,我们每次设置为1SetSize(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秒请求头像,它将检查头像是否被缓存(它还没有),并开始另一次访问数据库。
英文原文中有说明,但是觉得不太好,再次没有翻译。
英文原文地址:
代码与所写有所修改,但是大致意思一样,如果感兴趣,可以看看英文。
.NET中的缓存实现的更多相关文章
- [原创]关于mybatis中一级缓存和二级缓存的简单介绍
关于mybatis中一级缓存和二级缓存的简单介绍 mybatis的一级缓存: MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候 ...
- 清除oracle中的缓存(具体细节未知, 慎用)
oracle中的缓存主要是指SGA中的:1.share pool2.database buffer cache清空命令如下:首先要登录到sqlplus命令下,输入如下命令即可:SQL> alte ...
- AngularJS中的缓存
欢迎大家指导与讨论 : ) 缓存篇 一个缓存就是一个组件,它可以透明地储存数据,以便以后可以更快地服务于请求.多次重复地获取资源可能会导致数据重复,消耗时间.因此缓存适用于变化性不大的一些数据,缓存能 ...
- 如何在 apache 中设置缓存有效时间
今天学习了下如何在 apache 中设置缓存时间,记之以备忘. 在 http 报文头中,与缓存时间有关的两个字段是 Expires 以及 Cache-Control 中的 max-age,Expire ...
- 谈谈MVC项目中的缓存功能设计的相关问题
本文收集一些关于项目中为什么需要使用缓存功能,以及怎么使用等,在实际开发中对缓存的设计的考虑 为什么需要讨论缓存呢? 缓存是一个中大型系统所必须考虑的问题.为了避免每次请求都去访问后台的资源(例如数据 ...
- iOS中dyld缓存的实现原理是怎样的?
在iOS开发中,为了提升系统的安全性,很多系统库文件都被打包到一个缓存的文件当中即dyld缓存,那大家对dyld缓存了解多少呢?今天小编将和大家分享的就是一位iOS大神对dyld缓存的使用分析,一起来 ...
- angular中$cacheFactory缓存的使用
最近在学习使用angular,慢慢从jquery ui转型到用ng开发,发现了很多不同点,继续学习吧: 首先创建一个服务,以便在项目中的controller中引用,服务有几种存在形式,factory( ...
- 如何在 Linux 中清除缓存(Cache)
如何在 Linux 中清除缓存(Cache) 方法一: http://mp.weixin.qq.com/s?__biz=MjM5ODAzODgyMQ==&am ...
- 菜鸟-手把手教你把Acegi应用到实际项目中(7)-缓存用户信息
首先讲讲EhCache.在默认情况下,即在用户未提供自身配置文件ehcache.xml或ehcache-failsafe.xml时,EhCache会依据其自身Jar存档包含的ehcache-fails ...
- HTTP请求中浏览器缓存
本文导读:浏览器缓存机制,其实主要就是HTTP协议定义的缓存机制.客户端缓存是否需要是可以在服务端代码上控制的.那就是响应头.响应头告诉缓存器不要保留缓存,缓存器就不会缓存相应内容:如果请求信息是需要 ...
随机推荐
- 【Codeforces1139D_CF1139D】Steps to One (Mobius_DP)
Problem: Codeforces 1139D Analysis: After ACing E, I gave up D and spent the left 30 minutes chattin ...
- 记忆化搜索(DFS+DP) URAL 1223 Chernobyl’ Eagle on a Roof
题目传送门 /* 记忆化搜索(DFS+DP):dp[x][y] 表示x个蛋,在y楼扔后所需要的实验次数 ans = min (ans, max (dp[x][y-i], dp[x-1][i-1]) + ...
- HDU 5808 Price List Strike Back bitset优化的背包。。水过去了
http://acm.hdu.edu.cn/showproblem.php?pid=5808 用bitset<120>dp,表示dp[0] = true,表示0出现过,dp[100] = ...
- Invitation Cards POJ 1511 SPFA || dij + heap
http://poj.org/problem?id=1511 求解从1去其他顶点的最短距离之和. 加上其他顶点到1的最短距离之和. 边是单向的. 第一种很容易,直接一个最短路, 然后第二个,需要把边反 ...
- Mysql读写分离操作之mysql-proxy
常见的读写方式 基于程序代码内部实现 在代码中根据select.insert进行选择分类:这类方法也是生产常用的,效率最高,但是对开发人员比较麻烦.架构不能灵活调整 基于中间件的读写分离: mysql ...
- 【学习笔记】深入理解js原型和闭包系列学习笔记——精华
深入理解js原型和闭包笔记: 1.“一切皆是对象”,对象是属性的集合. 丨 函数也是对象,但是使用typeof时为什么函数返回function而 丨 不是object呢,js为何要对函数做这样的区分 ...
- MyBatis使用懒加载mybatis-config.xml配置
在mybatis-config.xml添加如下配置 <settings> <!--要使延迟加载生效必须配置下面两个属性--> <setting name="la ...
- 跟随鼠标指针跑的div拖拽效果
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...
- 推荐一款功能强大的Tomcat 管理监控工具,可替代Tomcat Manager
我们在本地启动Tomcat服务器后,用localhost:访问: 再点Manager App,即可进入Tomcat自带的Manager这个应用,此处可以单独部署/卸载每一个应用.可以看到在Manage ...
- DBMS的工作模式
数据库管理系统(DBMS)是指数据库系统中对数据进行管理的软件系统,它是数据库系统的核心组成部分,对数据库的一切操作(增删改查)都是通过DBMS进行的 DBMS的工作模式如下: 1>接受应用程序 ...