缓存是提升接口响应速度和降低数据库压力的重要手段。
Redis |
通过Socket访问到缓存服务,效率比EnCache低,对集群和分布式支持友好。 |
EnCache |
纯Java的进程缓存,直接在JVM中进行缓存,速度快、效率高,但对分布式集群的支持不太好。 |
ConcurrentHashMap Manager |
Spring Boot 默认提供的缓存管理器,线程安全,官方明确不建议在生产环境使用。 |
表 Java缓存方案对比
1 缓存注解
Spring Boot 提供了缓存抽象层,并结合JSR-107标准,提供了多个缓存注解。
@Cacheable |
查询缓存。 |
@CachePut |
更新缓存。 |
@CacheEvict |
删除缓存。 |
表 常用的缓存注解
这些注解作用于方法级别,在不侵入业务逻辑的前提下,实现缓存管理。它们是基于AOP实现的,有以下注意项:
- 只能用于public方法上。
- 只有当调用方通过Spring代理调用该方法时才生效。(同一个类中直接调用不生效)
- 需要启用缓存功能。@EnableCaching。
1.1 缓存key 生成策略
缓存注解keyGenerator字段用于指定该方法缓存key的生成策略。
图 key 生成策略需实现的接口
@Bean
public KeyGenerator paramsKeyGenerator() {
return ((target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName());
sb.append("::");
sb.append(method.getName());
for (Object obj : params) {
sb.append("::");
sb.append(obj.toString());
}
return sb.toString();
});
}
2 Redis 缓存策略
maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
缓存策略配置:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues() // 避免缓存穿透
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
2.1 redis 连接池
Spring Boot 2.x 默认使用Lettuce作为Redis客户端。
Lettuce |
基于Netty的异步非阻塞通信;原生线程安全;长连接Lettuce性能优势更明显;CPU利用率更高效。 |
Jedis |
同步阻塞通信;非线程安全; |
表 Lettuce 与 Jedis对比
2.3 Redis 缓存问题
1 缓存穿透:查询一个根本不存在的数据,导致每次查询都会访问数据库。
使用布隆过滤器(检索元素是否存在集合中)。它内存占用小。判断元素存在有误差,但是判断元素不存在绝对准确。
2 缓存击穿:某个热点key突然实现,大量请求同时访问时,会击穿缓存直接访问数据库。
使用互斥锁或逻辑过期。也可以设置热点key永不过期或是后台刷新热点key缓存。
3 缓存雪崩:大量缓存key同时失效,大量请求直接访问数据库。发生场景有:1)缓存服务器重启;2)大量缓存设置相同过期时间。
缓存key的过期时间随机化。