缓存
为了减少数据库的访问次数,以浪费内存为代价,提高查询效率,所以引入缓存
- 优化顺序: 内存>网络>磁盘
- 问题:双写问题
缓存常见的实现方案
- memcached
- redis 11万读/8万写
Redis缓存
-
使用场景
- 降低后端负载
- 加速请求响应
- 大量写合并为批量写
-
Redis缓存策略
- LRU、LFU、FIFO
- 超时剔除
- 主动更新
-
缓存粒度控制
- 通用性:全量属性更好
- 占用空间:部分属性更好
- 代码维护:表面上全量属性最好
-
缓存穿透问题
- 现象:请求访问redis,redis没有缓存,然后再访问mysql,但是mysql中也没有,下次在请求的时候,redis中也没有,这样就是缓存穿透的问题
- 解决方法
- 缓存空对象
- 布隆过滤器拦截
-
无底洞问题:更多的集器不代表更好的性能
- 命令行的优化,减少慢查询,比如:keys *,hgetall bigkey
- 减少网络通信次数
- 降低接入成本
-
热点key的重建
-
减少重缓存的次数
-
数据尽可能一致
-
减少潜在危险
-
解决方案
- 互斥锁:获取缓存的时候加锁,重建缓存完成之后再解锁
- 永不过期:逻辑上设置键是永不过期的
-
springboot使用redis缓存
spring针对持久化操作引入了spring data
- 针对RDBMS引入了spring data jpa
- 针对不同的NoSQL数据库提供了对应的实现
- spring-data-redis采用的是RedisTemplate方式
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--如果需要使用连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
全局配置
spring:
cache:
type: redis # 声明所使用的缓存类型
# 针对redis链接的配置
redis:
host: localhost
port: 6379
# 针对lettuce配置,如果没有优化配置参数则使用默认值
lettuce:
pool:
max-active: 8
max-wait: 10000ms
max-idle: 2
在主类上添加注解启用缓存
@EnableCaching
public class Springboot11Application
@EnableCaching注解是spring framework中的注解驱动的缓存管理功能。自spring版本3.1起加入了该注解。
- 如果你使用了这个注解,那么你就不需要在XML文件中配置cache manager了
- 当你在配置类(@Configuration)上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的bean执行处理。
@Cacheable:作用是主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
- value :缓存的名称,在 spring 配置文件中定义,必须指定至少一个,
例如:@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}。 - key :缓存的 key,可以为空,
如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合,
例如:@Cacheable(value=”testcache”,key=”#userName”)。 - condition:指定符合条件的情况下才缓存;如condition="#id>0"
- unless:否定缓存,当unless指定的条件为true,方法的返回值不会被缓存,可以获取到结果进行判断;如unless="#result==null";
@RestController //复合注解
@RequestMapping("/users")
public class UserController {
@Autowired
private IUserServ userService;
@GetMapping("/{id}")
@Cacheable(value="users",key = "#p0",unless = "#result==null")
public User load(@PathVariable("id") Long id){
return userService.getById(id);
}
}
@CachePut作用是主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实查询
参数配置和@Cacheable一样
@RestController //复合注解
@RequestMapping("/users")
public class UserController {
@Autowired
private IUserServ userService;
@CachePut(value="users",key = "#result.id",condition = "#result!=null")
@PostMapping
public User create(User user) {
boolean bb = userService.create(user);
if (bb)
return user;
return null;
}
}
@CacheEvict:作用是主要针对方法配置,能够根据一定的条件对缓存进行清空
- value , key 和 condition 参数配置和@Cacheable一样
- allEntries :是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
- beforeInvocation是否在方法执行前就清空,缺省为 false。如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存,
@DeleteMapping("/{id}")
@CacheEvict(value = "users",allEntries = true)
public void remove(@PathVariable Long id){
userService.remove(id);
}
@Caching注解将其他注解方式融合在一起了,可以根据需求来自定义注解,并将前面三个注解应用在一起。@Caching注解可以在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
@Caching(
put = {
@CachePut(value = "users", key = "#result.id", condition = "#result!=null")
}, evict = {
@CacheEvict(value = "users", key = "'all'")
}
)
@PostMapping
public User create(User user) {
boolean bb = userService.create(user);
if (bb)
return user;
return null;
}
@CacheConfig(cacheNames=“users”)用于标注在类上,可以存放该类中所有缓存的公有属性,比如设置缓存的名字
redis的客户端
Redis的Java客户端很多,官方推荐的有三种:Jedis、Redisson和lettuce。
Jedis/jie’dai/:
- 轻量,简洁,便于集成和改造,线程不安全的
- 支持连接池
- 支持pipelining、事务、LUA Scripting、Redis Sentinel、Redis Cluster
- 不支持读写分离,需要自己实现
- 文档差(真的很差,几乎没有……)
lettuce/lai’tei’si/是一个线程安全的redis客户端。提供同步,异步和reactive的APIs.。如果可以避开阻塞和事务型的操作比如BLPOP
和MULTI
/EXEC
,多个线程可以分享同一个连接。多个连接被NIO框架netty有效的管理。SpringBoot推荐使用
jedis和lettuce的区别
Jedis 和 Lettuce 是 Java 操作 Redis 的客户端。在 Spring Boot 1.x 版本默认使用的是 jedis ,而在 Spring Boot 2.x 版本默认使用的就是Lettuce。关于 Jedis 跟 Lettuce 的区别如下:
- Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
- Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
redis配置类
在默认的情况下,两者整合后便可将数据缓存到redis,但是缓存进去的对象类型数据是经过jdk默认序列化过后的,在可视化工具中是HEX格式,看起来非常不方便,所以需要进行格式化
@EnableCaching
@Configuration
public class RedisConfig {
@Bean
public CacheManager cacheManager(LettuceConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
cacheManager.afterPropertiesSet();
return cacheManager;
}
}
Redis编程使用
与使用注解方式不同,注解方式可以零配置,只需引入依赖并在启动类上加上@EnableCaching 注解就可以使用;而使用 RedisTemplate 方式麻烦些,需要做一些配置。
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
RedisTemplate 对五种数据结构分别定义了操作
- redisTemplate.opsForValue();操作字符串
- redisTemplate.opsForHash();操作hash
- redisTemplate.opsForList();操作list
- redisTemplate.opsForSet();操作set
- redisTemplate.opsForZSet();操作有序set