缓存cache(擦车)
第一次接触到cache的时候,是在webform中,第一次接触,我就再也没能忘记,cache(擦车,的拼音)
客户端浏览器缓存
cdn缓存原理
阿里云cdn开启设置
有句话叫做,系统性能优化的第一步,就是使用缓存,所以,缓存真的很重要
缓存:
实际上是一种效果&目标,就是获取数据点时候,第一次获取之后找个地方存起来,后面直接用,这样一来可以提升后面每次获取数据的效率。读取配置文件的时候把信息放在静态字段,这个就是缓存。缓存是无处不在的。
我们来请求一个网站,打开开发人员工具
客户端缓存的好处:
1、缩短网络路径,加快响应速度
2、减少请求,降低服务器压力
浏览器缓存究竟是怎么做到的?
打开一个网页,浏览器-----请求---服务器---处理请求会发响应------浏览器展示
http协议,数据传输的格式(协议,就像是两人交流,都用什么语言)
信息是否缓存,一定是服务器控制的。responseheader--cache---control来指定下缓存策略,浏览器看到了这个,就去存储一下。
第一次请求服务器:
再一次请求服务器
dns是互联网的第一跳,dns缓存就是cdn,内容分发网络,cdn就是加速缓存的
没有用cdn的请求:
使用了cdn缓存
反向代理:
1、隔离网络,保护服务器(节约公共ip)
2、网络加速,反向代理双网卡
3、负载均衡
4、缓存(跟cdn,也是识别一下header,压缩到一个物理路径/内存)
为什么叫反向代理?因为他就是一个代理,一般的代理,是客户端和服务器之间,有一个代理,去做处理的。但是这个代理是安装在服务器端的。
几种缓存套路相同,但是位置不同,影响的范围也不同。
客户端缓存:只影响当前用户
cdn缓存:针对一批用户
反向代理缓存:针对全部用户。
客户端缓存,存在内存或者硬盘,下次直接用。cookie,存在内存或者硬盘,浏览器每次请求服务器都会带上的信息。
什么时候用缓存?
1、重复请求,100人访问首页,每个人其实做的都一样,不就是重复
2、耗时好资源
3、结果没变的
下面有一个第三方数据存储和获取的地方:
/// <summary> /// 第三方数据存储和获取的地方 /// </summary> public class customcache { /// <summary> /// private:私有一下数据容器,安全 /// static:不被gc /// 字典:读写效率高 /// </summary> private static dictionary<string, object> customcachedictionary = new dictionary<string, object>(); public static void add(string key, object ovaule) { customcachedictionary.add(key, ovaule); } /// <summary> /// 要求在get前做exists检测 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="key"></param> /// <returns></returns> public static t get<t>(string key) { return (t)customcachedictionary[key]; } public static bool exists(string key) { return customcachedictionary.containskey(key); } public static t gett<t>(string key, func<t> func) { t t = default(t); if (!customcache.exists(key)) { t = func.invoke(); customcache.add(key, t); } else { t = customcache.get<t>(key); } return t; } }
存取数据的唯一标识:1 唯一的 2 能重现
for (int i = 0; i < 5; i++) { console.writeline($"获取{nameof(dbhelper)} {i}次 {datetime.now.tostring("yyyymmdd hhmmss.fff")}"); //list<program> programlist = dbhelper.query<program>(123); list<program> programlist = null; string key = $"{nameof(dbhelper)}_query_{123}"; //存取数据的唯一标识:1 唯一的 2 能重现 //if (!customcache.exists(key)) //{ // programlist = dbhelper.query<program>(123); // customcache.add(key, programlist); //} //else //{ // programlist = customcache.get<list<program>>(key); //} programlist = customcache.gett<list<program>>(key, () => dbhelper.query<program>(123)); }
/// <summary> /// 数据库查询 /// </summary> public class dbhelper { /// <summary> /// 1 耗时耗资源 /// 2 参数固定时,结果不变 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="index"></param> /// <returns></returns> public static list<t> query<t>(int index) { console.writeline("this is {0} query", typeof(dbhelper)); long lresult = 0; for (int i = index; i < 1000000000; i++) { lresult += i; } list<t> tlist = new list<t>(); for (int i = 0; i < index % 3; i++) { tlist.add(default(t)); } return tlist; } }
缓存优化性能,核心就是结果重用,下次请求还是上一次的结果。如果数据库中有变化,岂不是用了一个错误的数据?是的,缓存是难免的,缓存难免会有脏数据,当然了,我们也会分门别类的去尽量减少脏数据。
用户--角色--菜单,用户权限查的多+比较耗资源+相对稳定,非常适合缓存,缓存方式应该是用户id为key,菜单列表作为value。
string name = "bingle"; list<string> menu = new list<string>(); if (!customcache.exists(name)) { menu = new list<string>() { "123", "125553", "143", "123456" }; customcache.add(name, menu); } else { menu = customcache.get<list<string>>(name); }
假如bingle的权限变化了,缓存应该失效。数据更新影响单挑缓存,常规做法是remove而不是更新,因为缓存只是用来提升效率的,而不是数据保存的,因此不需要更新,只需要删除就好,如果真的下次用上了,到时候再去初始化。
customcache类增加删除缓存的方法:
public static void remove(string key) { customcachedictionary.remove(key); }
string name = "bingle"; customcache.remove(name); list<string> menu = new list<string>(); if (!customcache.exists(name)) { menu = new list<string>() { "123", "125553", "143" }; customcache.add(name, menu); } else { menu = customcache.get<list<string>>(name); }
删除了某个菜单,影响了一大批用户。根据菜单--昭觉寺---找用户---每一个拼装key然后去remove(最准确)。但是这种方式不行,为了缓存增加数据库的任务,最大的问题是数据量的问题,缓存是二八原则,只有20%的热点用户才缓存,这样做的成本太高。
可以选择加上一个removeall的方法
public static void removeall() { customcachedictionary.clear(); }
或者,菜单删除了,能不能只影响一部分的缓存数据呢?
1、添加缓存时,key带上规则,比如权限包含_menu_
2、清理时,就只删除key含_menu_的
/// <summary> /// 按条件删除 /// </summary> /// <param name="func"></param> public static void removecondition(func<string, bool> func) { list<string> keylist = new list<string>(); lock (customcache_lock) foreach (var key in customcachedictionary.keys) { if (func.invoke(key)) { keylist.add(key); } } keylist.foreach(s => remove(s)); }
第三方修改了数据,缓存并不知道,这个就没办法了
a 可以调用接口清理缓存,b系统修改数据,调用c西永通知下缓存更新,b就只能容忍了,容忍脏数据,但是可以加上时间限制,减少影响时间。
时间,过期策略:
永久有效----目前就是
绝对过期:
有个时间点,超过就过期了
滑动过期:
多久之后过期,如果期间更新/查询/检查存在,就再次延长多久。
/// <summary> /// 主动清理 /// </summary> static customcache() { task.run(() => { while (true) { try { list<string> keylist = new list<string>(); lock (customcache_lock) { foreach (var key in customcachedictionary.keys) { datamodel model = (datamodel)customcachedictionary[key]; if (model.obslotetype != obslotetype.never && model.deadline < datetime.now) { keylist.add(key); } } keylist.foreach(s => remove(s)); } thread.sleep(1000 * 60 * 10); } catch (exception ex) { console.writeline(ex.message); continue; } } }); }
多线程问题:
list<task> tasklist = new list<task>(); for (int i = 0; i < 110000; i++) { int k = i; tasklist.add(task.run(() => customcache.add($"testkey_{k}", $"testvalue_{k}", 10))); } for (int i = 0; i < 100; i++) { int k = i; tasklist.add(task.run(() => customcache.remove($"testkey_{k}"))); } for (int i = 0; i < 100; i++) { int k = i; tasklist.add(task.run(() => customcache.exists($"testkey_{k}"))); } //thread.sleep(10*1000); task.waitall(tasklist.toarray());
多线程操作非现场安全的容器,会造成冲突
1、线程安全容器concurrentdictionary
2、用lock---add/remove/遍历,可以解决问题,但是性能呢?
怎么降低影响,提升性能呢?多个数据容器,多个锁,容器之间可以并发
为了解决多线程问题,customcache 类最终修改成如下:
public class customcache { //concurrentdictionary private static readonly object customcache_lock = new object(); /// <summary> /// 主动清理 /// </summary> static customcache() { task.run(() => { while (true) { try { list<string> keylist = new list<string>(); lock (customcache_lock) { foreach (var key in customcachedictionary.keys) { datamodel model = (datamodel)customcachedictionary[key]; if (model.obslotetype != obslotetype.never && model.deadline < datetime.now) { keylist.add(key); } } keylist.foreach(s => remove(s)); } thread.sleep(1000 * 60 * 10); } catch (exception ex) { console.writeline(ex.message); continue; } } }); } /// <summary> /// private:私有一下数据容器,安全 /// static:不被gc /// 字典:读写效率高 /// </summary> //private static dictionary<string, object> customcachedictionary = new dictionary<string, object>(); private static dictionary<string, object> customcachedictionary = new dictionary<string, object>(); public static void add(string key, object ovaule) { lock (customcache_lock) customcachedictionary.add(key, new datamodel() { value = ovaule, obslotetype = obslotetype.never, }); } /// <summary> /// 绝对过期 /// </summary> /// <param name="key"></param> /// <param name="ovaule"></param> /// <param name="timeoutsecond"></param> public static void add(string key, object ovaule, int timeoutsecond) { lock (customcache_lock) customcachedictionary.add(key, new datamodel() { value = ovaule, obslotetype = obslotetype.absolutely, deadline = datetime.now.addseconds(timeoutsecond) }); } /// <summary> /// 相对过期 /// </summary> /// <param name="key"></param> /// <param name="ovaule"></param> /// <param name="duration"></param> public static void add(string key, object ovaule, timespan duration) { lock (customcache_lock) customcachedictionary.add(key, new datamodel() { value = ovaule, obslotetype = obslotetype.relative, deadline = datetime.now.add(duration), duration = duration }); } /// <summary> /// 要求在get前做exists检测 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="key"></param> /// <returns></returns> public static t get<t>(string key) { return (t)(((datamodel)customcachedictionary[key]).value); } /// <summary> /// 被动清理,请求了数据,才能清理 /// </summary> /// <param name="key"></param> /// <returns></returns> public static bool exists(string key) { if (customcachedictionary.containskey(key)) { datamodel model = (datamodel)customcachedictionary[key]; if (model.obslotetype == obslotetype.never) { return true; } else if (model.deadline < datetime.now)//现在已经超过你的最后时间 { lock (customcache_lock) customcachedictionary.remove(key); return false; } else { if (model.obslotetype == obslotetype.relative)//没有过期&是滑动 所以要更新 { model.deadline = datetime.now.add(model.duration); } return true; } } else { return false; } } /// <summary> /// 删除key /// </summary> /// <param name="key"></param> public static void remove(string key) { lock (customcache_lock) customcachedictionary.remove(key); } public static void removeall() { lock (customcache_lock) customcachedictionary.clear(); } /// <summary> /// 按条件删除 /// </summary> /// <param name="func"></param> public static void removecondition(func<string, bool> func) { list<string> keylist = new list<string>(); lock (customcache_lock) foreach (var key in customcachedictionary.keys) { if (func.invoke(key)) { keylist.add(key); } } keylist.foreach(s => remove(s)); } public static t gett<t>(string key, func<t> func) { t t = default(t); if (!customcache.exists(key)) { t = func.invoke(); customcache.add(key, t); } else { t = customcache.get<t>(key); } return t; } } /// <summary> /// 缓存的信息 /// </summary> internal class datamodel { public object value { get; set; } public obslotetype obslotetype { get; set; } public datetime deadline { get; set; } public timespan duration { get; set; } //数据清理后出发事件 public event action dataclearevent; } public enum obslotetype { never, absolutely, relative }
public class customcachenew { //动态初始化多个容器和多个锁 private static int cpunumer = 0;//获取系统的cpu数 private static list<dictionary<string, object>> dictionarylist = new list<dictionary<string, object>>(); private static list<object> locklist = new list<object>(); static customcachenew() { cpunumer = 4; for (int i = 0; i < cpunumer; i++) { dictionarylist.add(new dictionary<string, object>()); locklist.add(new object()); } task.run(() => { while (true) { thread.sleep(1000 * 60 * 10); try { for (int i = 0; i < cpunumer; i++) { list<string> keylist = new list<string>(); lock (locklist[i])//减少锁的影响范围 { foreach (var key in dictionarylist[i].keys) { datamodel model = (datamodel)dictionarylist[i][key]; if (model.obslotetype != obslotetype.never && model.deadline < datetime.now) { keylist.add(key); } } keylist.foreach(s => dictionarylist[i].remove(s)); } } } catch (exception ex) { console.writeline(ex.message); continue; } } }); }
缓存究竟哪里用?满足哪些特点适合用缓存?
1、访问频繁
2、耗时耗资源
3、相对稳定
4、体积不那么大的
不是说严格满足,具体的还要看情况,存一次能查三次,就值得缓存(大型想换标准)
下面应该用缓存
1、字典数据
2、省市区
3、配置文件
4、网站公告信息
5、部门权限,菜单权限
6、热搜
7、类别列表/产品列表
8、用户,其实session也是缓存的一种表现
股票信息价格/彩票开奖信息,这些不能用缓存,即时性要求很高。图片/视频,这些也不行,太大了。商品评论,这个可以用缓存的,虽然评论汇编,但是这个不重要,我们不一定非要看到最新的,而且第一页一般不变。
可以测试下customcache的性能,十万/百万/千万 插入/获取/删除的性能。
推荐阅读