一、什么是缓存?
☞ 缓存就是数据交换的缓冲区(称作:Cache),当某一硬件要读取数据时,会首先从缓存汇总查询数据,有则直接执行,不存在时从内存中获取。由于缓存的数据比内存快的多,所以缓存的作用就是帮助硬件更快的运行。
☞ 缓存往往使用的是RAM(断电既掉的非永久存储),所以在用完后还是会把文件送到硬盘等存储器中永久存储。电脑中最大缓存就是内存条,硬盘上也有16M或者32M的缓存。
☞ 高速缓存是用来协调CPU与主存之间存取速度的差异而设置的。一般CPU工作速度高,但内存的工作速度相对较低,为了解决这个问题,通常使用高速缓存,高速缓存的存取速度介于CPU与主存之间。系统将一些CPU在最近几个时间段经常访问的内容存在高速缓存,这样就在一定程度上缓解了由于主存速度低造成的CPU“停工待料”的情况。
☞ 缓存就是把一些外存上的数据保存在内存上而已,为什么保存在内存上,我们运行的所有程序里面的变量都是存放在内存中的,所以如果想将值放入内存上,可以通过变量的方式存储。在JAVA中一些缓存一般都是通过Map集合来实现的。
▁▂▃▅▆ :缓存在不同的场景下,作用是不一样的具体举例说明:
✔ 操作系统磁盘缓存 ——> 减少磁盘机械操作。
✔ 数据库缓存——>减少文件系统IO。
✔ 应用程序缓存——>减少对数据库的查询。
✔ Web服务器缓存——>减少应用服务器请求。
✔ 客户端浏览器缓存——>减少对网站的访问。
二、缓存穿透
什么是缓存穿透?发生的原因?
1、缓存穿透: 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候, 在缓存中找不到对应key的value,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次
无用的查询)。这样请求就绕过缓存直接查数据库
诱发原因:恶意攻击
2、有什么解决方案来防止缓存穿透?
-
缓存空值如果一个查询返回的数据为空(不管是数据不 存在,还是系统故障)我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库
-
采用布隆过滤器BloomFilter 优势占用内存空间很小,bit存储。性能特别高。 将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个bitmap 拦截掉,从而避免了对底层存储系统的查询压力
三、缓存击穿
什么是缓存击穿?发生原因?
上面提到的某个数据没有,然后好多请求查询数据库,可以归为缓存击穿的范畴:对于热点数据,当缓存失效的一瞬间,所有的请求都被下放到数据库去请求更新缓存,数据库被压垮。
解决方案:
- 一种思路是加全局锁,就是所有访问某个数据的请求都共享一个锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。
- 对即将过期的数据进行主动刷新,比如新起一个线程轮询数据,或者比如把所有的数据划分为不同的缓存区间,定期分区间刷新数据。该思路也是缓存雪崩的解决方案之一
四、缓存雪崩
什么是缓存雪崩?发生原因?
缓存雪崩是指当我们给所有的缓存设置了同样的过期时间,当某一时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被抛向了数据库,数据库就崩掉了。——实际上就是连锁式缓存穿透 导致性能急速下降
解决方案:
- 加锁排队 key: whiltList value:1000w个uid 指定setNx whiltList valuenullValuemutex互斥锁解决,Redis的SETNX去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法
- 数据预热 在系统上线后将相关数据加载到缓存系统,避免用户访问之间访问数据库而不是缓存
- 双层缓存策略 其实就是为你的缓存"买一个保险" 做个备份 当A缓存失效后 直接访问B缓存 注:A缓存失效的时间短 B(备份缓存)失效时间长
- 定时更新缓存
关于加锁 setnx 与setex 操作 :一般来说,setnx&setex 可以应对分布式锁的大部分需求场景 但是这样却会产生一个问题!!——那就是无法实现可重入锁 也就是说只能锁无法解锁
加锁流程
会产生问题
1、当qps 流量请求过多 导致项目服务宕机 导致Redis锁无法释放 其他机器无法获取锁
2、当缓存雪崩导致Redis宕机 获取不到锁
3、当setnx 和setex之间发生服务宕机 导致Redis锁永远不会过期
解决方案:
- 手动删除key
- 使用lua脚本 把setnx 和setex两个指令放到一起
五、缓存刷新
什么是缓存刷新?
既清空缓存 ,一般在insert、update、delete操作后就需要刷新缓存,如果不执行就会出现脏数据。但当缓存请求的系统蹦掉后,返回给缓存的值为null。