1.缓存
2.缓存算法
- LRU(least recentlty used,最近最少使用)算法
- LFU(least Frequently used,最不经常使用)
- FIFO(first in first out,先进先出)
3.缓存穿透
存储穿透:是指查询一个一定不存在的数据,由于缓存是不命中时被动写,并且处于容错考虑,如果从DB查不到数据就不写入缓存,这将导致这个不存在的数据一直去请求DB,是去缓存的意义。
如何解决?
-
方案一,缓存空对象。
-
方案二,BloomFilter布隆过滤器
在缓存服务的基础上,构建BloomFilter数据结构。在BloomFilter中存储对应的key是否存在,如果存在,再说明该key的值不为空,逻辑如下:
为什么BloomFilter不存储Key是不存在的情况?
缓存空对象 BloomFilter 使用场景 1.数据命中率不高;2.保证一致性 1.数据命中不高。2数据相对固定,实时性低 维护成本 1.代码维护简单;2.需要过多的缓存空间;3.数据不一致 1.代码维护复杂;2.缓存空间占用少
4.缓存雪崩
概念
缓存雪崩,是指缓存由于某些原因无法提供服务(比如缓存挂了),所有的请求到达DB,导致DB负荷增大,最终挂掉的情况。
如何解决
-
缓存高可用
-
本地缓存
-
请求DB限流
- 有一部分用户任然可以使用该系统。
- 缓存服务恢复后,立即可以使用。不用再去管理DB服务
被限流的请求,我们可以通过服务降级,提供一些默认的值,比如空白页面、友情提示等到。我们可以通过Sentinel、Hystrix等来实现。
引入本地缓存的问题
- 本地缓存实时性如何保证
- 引入消息队列。在数据更新时,发布数据更新的消息;而进程中有相应的消费者消费该消息,从而更新本地缓存;
- 设置较短的过期时长,请求从DB拉取数据;
- 通过手动过期;
- 每个进程可能会在本地缓存相同的数据,导致资源浪费?
- 需要配置本地的缓存过期策略和缓存数量上限。
5.缓存击穿
概念
如何解决
-
方案一,使用互斥锁
- 1获取分布式锁,直到成功或者超时。如果超时则抛出异常,成功,继续向下执行
- 2、获取缓存,如果存在值,直接返回,如果不存在,继续。
- 3、查询DB,更新到缓存,返回值。
-
方案二,手动过期
- 1、获取缓存,通过value中的时间来比较是否过期。如果未过期,直接返回,如果过期,继续向下执行。
- 2、通过一个后台的异步线程进行缓存的构建,也就是“手动过期”。通过后台的异步线程,保证只有一个线程查询DB。
- 3、同时,虽然Value还是过期,但还是直接返回。通过这样的方式保证了服务的可用性,但是损失了一定的实效性。
6.缓存与DB一致性保持
产生不一致的原因
-
并发场景下,导致老的DB数据写入到缓存中。
缓存和DB的操作不在同一个事物中,可能DB操作成功,二cache操作失败,这样会导致不一致
解决方案
-
将缓存可能出现的并行写,实现串行写
-
在写请求前,先淘汰缓存之前,先获取分布式锁。
写-->获取锁-->delete cache-->update db ---> write cache
-
在读请求时,如果缓存不存在,先去获取分布式锁
读-->读cache,如果null-->获取锁-->查询cache,如果null-->查db-->write cache
-
-
实现缓存的最终一致性。
-
先淘汰缓存,在写数据库。
-
先写数据库,在更新缓存
基于定时任务来实现
- 首先,写入数据库
- 然后在写入数据库所在的事物中,插入一个记录的任务表,该记录表存储需要存入cache中的key和value
- 【异步】遍历任务表,写入cache
基于消息队列来实现
- 首先,写入数据库
- 然后发送带我缓存key和value的事物消息。
- 【异步】消费者消费该消息,写入缓存
基于数据库的binlog日志
- 应用直接将数据写入数据库
- 数据库会更新binlog日志
- 利用Cannal中间件读取binlog日志
- Cannal借助于限流主键按频率将数据发送到MQ中
- 应用监控MQ渠道,将MQ的数据缓存到cache中
-
7.缓存预热
启动时,先将热点数据缓存到缓存中
如何实现
- 数据量不大的时候,项目启动,自动进行初始化
- 通过修复脚本,执行脚本
- 写管理页面,手动操作
7.缓存淘汰策略
- 定时清理
- 当用户请求过来的时候,去判断是否过期,过期的话就去底层系统获取数据,进行更新