缓存概述
各级缓存,分别就是以下各节点
浏览器缓存原理
请求过第一次,就有部分数据缓存在硬盘或内存
DNS&&反向代理
其实原理和浏览器缓存也很相似,都是在请求头,响应头里面进行控制,不过这两个缓存都是面向一群用户的
具体细节比较艰深晦涩,主要还是看服务器缓存和浏览器缓存吧
服务器缓存
这里才是主要学习的大头
首先自定义写个缓存来深入理解
自定义缓存
缓存:为了快速获取结果,在第一次获取数据后存起来,下次直接用
缓存究竟用在哪里?
/// 1 会重复请求
/// 2 数据相对稳定
/// 3 耗时/耗资源
/// 4 体积不大
/// 配置文件;菜单-权限;省市区;类别数据;
/// 热搜(二八原则);公告;技能/属性;数据字典;
/// 分页(只要数据不是经常变)
public class CustomCache
{
/// <summary>
/// private 保护数据
/// static 全局唯一 不释放
/// Dictionary 保存多项数据
/// </summary>
private static Dictionary<string, KeyValuePair<object, DateTime>> CustomCacheDictionary;
//private static System.Collections.Concurrent.ConcurrentDictionary
/// <summary>
/// 主动清理:只要是过期,最多超过10分钟,一定会被清理
/// </summary>
static CustomCache()
{
CustomCacheDictionary = new Dictionary<string, KeyValuePair<object, DateTime>>();
Console.WriteLine($"{DateTime.Now.ToString("MM-dd HH:mm:ss fff")}初始化缓存");
//缓存依托内存,系统重启后,缓存会重启,日志
Task.Run(() =>
{
while (true)
{
LockAction(new Action(() =>
{
List<string> list = new List<string>();
foreach (var key in CustomCacheDictionary.Keys)
{
var valueTime = CustomCacheDictionary[key];
if (valueTime.Value > DateTime.Now)//没有过期
{
//没过期
}
else
{
list.Add(key);
}
}
list.ForEach(key => CustomCacheDictionary.Remove(key));
}));
Thread.Sleep(1000 * 60 * 10);//10分钟来一遍 CPU影响很小
}
});
}
/// <summary>
/// 添加数据 key重复会异常的
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Add(string key, object value, int second = 1800)
{
LockAction(new Action(() =>
{
CustomCacheDictionary.Add(key, new KeyValuePair<object, DateTime>(value, DateTime.Now.AddSeconds(second)));
}));
}
private static readonly object CustomCache_Lock = new object();
private static void LockAction(Action action)
{
lock (CustomCache_Lock)
{
action.Invoke();
}
}
/// <summary>
/// 保存数据,有就覆盖 没有就新增
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public static void SaveOrUpdate(string key, object value, int second = 1800)
{
LockAction(new Action(() =>
{
CustomCacheDictionary[key] = new KeyValuePair<object, DateTime>(value, DateTime.Now.AddSeconds(second));
}));
}
/// <summary>
/// 获取数据 没有会异常的
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public static T Get<T>(string key)
{
return (T)CustomCacheDictionary[key].Key;
}
/// <summary>
/// 检查是否存在
///
/// 清理一下,除非我们去访问这条缓存,才会去清理 被动清理,任何过期的数据,都不可以被查到
/// 可能有垃圾留在缓存里面
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Exsit(string key)
{
if (CustomCacheDictionary.ContainsKey(key))
{
var valueTime = CustomCacheDictionary[key];
if (valueTime.Value > DateTime.Now)//没有过期
{
return true;
}
else
{
LockAction(new Action(() =>
{
CustomCacheDictionary.Remove(key);//清理一下
}));
return false;
}
}
else
{
return false;
}
}
public static void Remove(string key)
{
LockAction(new Action(() =>
{
CustomCacheDictionary.Remove(key);
}));
}
public static void RemoveAll()
{
LockAction(new Action(() =>
{
CustomCacheDictionary.Clear();
}));
}
public static void RemoveCondition(Func<string, bool> func)
{
LockAction(new Action(() =>
{
List<string> list = new List<string>();
foreach (var key in CustomCacheDictionary.Keys)
{
if (func.Invoke(key))
{
list.Add(key);
}
}
list.ForEach(key => CustomCacheDictionary.Remove(key));
}));
}
public static T Find<T>(string key, Func<T> func, int second = 1800)
{
T t = default(T);
if (!Exsit(key))
{
t = func.Invoke();
CustomCache.Add(key, t, second);
}
else
{
t = Get<T>(key);
}
return t;
}
}
}
调用方式:
//for (int i = 0; i < 5; i++)
//{
// Console.WriteLine($"获取{nameof(DBHelper)} {i}次 {DateTime.Now.ToString("yyyyMMdd HHmmss.fff")}");
// List<Program> programList = null;
// string key = $"{nameof(DBHelper)}_Query_{nameof(Program)}_{123}";//1 能精确描述这次操作 2 能够重现
// //if (!CustomCache.Exsit(key))
// //{
// // programList = DBHelper.Query<Program>(123);
// // //CustomCache.SaveOrUpdate(key, programList);
// // CustomCache.Add(key, programList);
// //}
// //else
// //{
// // programList = CustomCache.Get<List<Program>>(key);
// //}
// programList = CustomCache.Find<List<Program>>(key, () => DBHelper.Query<Program>(123));
// Console.WriteLine($"{i}次获取的数据为{programList.GetHashCode()}");
//}
现在缓存的效果已经有了但是这里的数据一直是用之前的,如果结果有变怎么办呢?
就比如一个用户权限----用户-角色-菜单 查询比较麻烦,也很频繁,相对稳定 所以为每个用户缓存一个
//1 假如修改了Eleven用户的角色,缓存数据的其实失效了,
只对Eleven用户的权限数据失效,只有一条数据失效了 就直接Remove
是Remove不是Update:缓存只是一个临时存储,不是数据源,也许更新了还用不上的,所以直接删除重新添加
//2 如果删除一个菜单,会影响一大批的用户
菜单–角色–用户,然后拼装出全部的key,然后遍历删除? 不对! 成本太高,缓存应该是提示性能,而不是负担
全部删除 还会误伤别的缓存,造成缓存穿透
解决这个问题可以在key的名字上做文章
比如
string key = “Eleven_Privage_Menu”;//只要缓存跟菜单有关的数据,都加上_Menu_
CustomCache.RemoveCondition(s => s.Contains(“Menu”));
//3 private static Dictionary<string, KeyValuePair<object, DateTime>> CustomCacheDictionary;
这里为啥是KeyValuePair<object, DateTime>而不是直接一个object,这就是我们的第三种场景了
//定时作业更新了数据–远程接口更新了数据,更多的时候是我们不知道的情况下,数据变了
给系统提供一个接口,更新数据也来通知下系统(能解决少量场景)
缓存,是沿用上一次的结果,根本就不会去数据源的-----肯定有误差
可以做一下过期,加一个时间有效性, 数据延迟来换取性能,降低压力,需要抉择
过期的就要清除,所以又分成主动清理(在构造方法中新建一个线程每隔一段时间进行)
被动清理(每一次请求的时候才检测)
//4 多线程并发了,插入数据/删除/查询,会不会冲突? 肯定会的!
首先弄清这个字典数据结构,其实里面是数组,然后多线程并发导致的种种问题
加锁,基本全部环节都需要lock的 可以测试一下,100线程-插入数据/获取数据/清除数据
操作并不是很耗时间,锁倒是很耗时间
还有一种系统内置加锁的字典private static System.Collections.Concurrent.ConcurrentDictionary
线程安全的字典ConcurrentDictionary 多线程操作时候不冲突,内部实现了锁的
/// 如果要线程安全,对一块儿内存的操作,必须是单线程的,
以上就是自定义缓存的写法,微软有memoryCache可以直接用的
Memorycache原理其实也差不多,但是比较老了,而且只能存key-value
不如redis