初次发布于我的个人文档
参考:缓存雪崩,缓存击穿,缓存穿透
Caffeine本地缓存
在一些场景下可以引入缓存加速,利用redis实现缓存通常是一个不错的选择,但有时为了避免系统变得复杂可以使用本地缓存。
Caffeine就是一个高效的本地缓存组件。使用方式如下:
1.安装依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.2</version>
</dependency>
2.创建用于缓存的key-value键值对
Cache<String, String> answerCacheMap = Caffeine.newBuilder()
//初始化缓存键值对容量
.initialCapacity(1024)
// 设置缓存有效期为一天
.expireAfterAccess(1L, TimeUnit.DAYS)
.build();
利用缓存是为了提高速度,但是缓存的数据并不会及时更新,所以需要设置有效期,即
超时是为了保证数据的有效。缓存有效期需要根据业务的不同自行设置。
3.新增和读取缓存
// 从刚刚的缓存键值对中读取缓存的cacheKey对应的数据
String cache = answerCacheMap.getIfPresent(cacheKey);
// 如果没有命中缓存,则取出的cache字符串为空
if (StringUtils.isEmpty(cache)) {
// 未命中缓存,走正常的业务逻辑
cache = work();
// 缓存结果
answerCacheMap.put(cacheKey, json);
}
4.缓存的问题
无论用什么方式实现缓存都需要注意以下几个问题:
- 缓存击穿
缓存击穿指在某一个时间有大量同一个key对应的缓存键值对过期或redis、caffeine等缓存中间件故障,与此同时客户端直接向业务系统(如数据库)发起请求,从而导致业务系统接着崩溃。
解决方法有:
预防性缓存更新:在热点数据即将过期时,提前异步刷新缓存。通过检测热点数据的访问频率,当即将过期时触发自动更新操作,避免过期瞬间的击穿问题。
双缓存机制:可以采用双层缓存策略:一个主要缓存层负责缓存大部分数据,另一个次缓存层保存上次的缓存数据。在主要缓存失效时,可以直接从次缓存层读取数据,避免直接打到业务系统。
加锁保证同时只有少量请求能够构建缓存和访问业务系统
- 缓存雪崩
缓存雪崩指在某一个时间有大量不同的key对应的缓存键值对过期或redis、caffeine等缓存中间件故障,与此同时客户端直接向业务系统(如数据库)发起请求,从而导致业务系统接着崩溃。
解决方法有:
将过期时间进行一定范围内的随机化
使用多级缓存
加锁保证同时只有少量请求能够构建缓存和访问业务系统
使用redis高可用集群等确保缓存尽量少得故障。
- 缓存穿透
缓存穿透指用户恶意查询业务系统中本不可能存在的key导致每次请求都直接穿过缓存机制访问业务系统,如果恶意用户进行大量这样的查询则会导致业务系统因压力过大而崩溃。
解决方法:
缓存空结果:如果查询的某个键在业务系统中不应该存在,则将该键的查询结果(如 null 或空值)缓存起来,并设定一个较短的过期时间,防止该键反复查询打到业务系统。
阻止非法请求(使用黑名单机制):在查询请求进入业务系统前,进行严格的参数校验和过滤,避免不合法的请求查询业务系统(避免黑名单内的请求进入)。
使用白名单机制:使用布隆过滤器对所有可能存在的数据进行标记(设为白名单),所有请求先经过布隆过滤器进行校验,只有布隆过滤器认为存在的数据(白名单的数据),才会去查询缓存或数据库。这样可以有效拦截掉绝大多数不存在的请求,防止这些请求绕过缓存直接打到数据库。