1、缓存的读模式:
先读取缓存中的数据,如果有返回结果,如果没有查询数据库,放入缓存中,返回结果
2、缓存的写模式:
双写模式
改了数据库的数据的时候,同时改了redis缓存中的数据
问题:A改了数据库数据,因为网络问题导致了没能及时写入缓存,而此时B改了数据库数据,立马把缓存中的数据改了,这时候A的修改的缓存数据为B的写入缓存数据之上
解决:为缓存数据设置过期时间,但会出现暂时性的脏数据问题,实现最终一致性
失效模式
改了数据库的数据的时候,把redis缓存中的数据删了,等待下次主动查询进行更新
问题:A改了数据库,执行完了,删除缓存,B也改数据库,花的时间比较长,此时C网络快,查缓存发现没数据,读取了A修改的数据库老数据,然后C要更新缓存,此时因为网络问题更新慢,此时B写完了数据库立马把缓存删了,C此时把读取A的老数据写到了缓存中,最新的数据B没能放入缓存中
解决:加锁解决(笨重)
解决方案:
无论双写还是失效都会导致缓存不一致问题
1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加 上过期时间,每隔一段时间触发读的主动更新即可
2、如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
4、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心 脏数据,允许临时脏数据可忽略);
总结:
- 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保 证每天拿到当前最新数据即可。
- 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
SpringCache
1、整合springCache简化缓存开发
1)、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2)、写配置
(1)、自动配置了哪些
自动配好了缓存过滤器RedisCacheConfiguration
自动配好了缓存过滤器RedisCacheManager
(2)、配置redis作为缓存
(3)、测试使用缓存
1)、开启缓存功能@Enablecaching
2)、只需要使用注解就能完成缓存操作
在application.priperties配置cache
#配置缓存类型使用redis
spring.cache.type=redis
#指定缓存的名字以后缓存名字全部按配置的来,不配置的话系统中用到哪些缓存自动创建出来
spring.cache.cache-name=qq
#设置存活时间,毫秒为单位
spring.cache.redis.time-to-live=3600000
#如果指定了前缀就用我们指定的前缀,如果没有就没人使用缓存的名字作为前缀
spring.cache.redis.key-prefix=CACHE_
spring.cache.redis.use-key-prefix=false
#是否缓存空值。防止缓存穿透
spring.cache.redis.cache-null-values=true
测试使用缓存
@Cacheable:触发将数据保存到缓存的操作
@CacheEvict:触发将数据从缓存删除的操作
@CahePut:不影响方法执行更新缓存
@Caching:组合以上多个操作
@CacheConfig:在类级别共享缓存的相同配置
开启缓存
1)、开启缓存功能
开启缓存功能使用**@Enablecaching**,在启动类上加上开启
只需要使用注解就能完成缓存操作
如:
配置Config
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching //开启缓存
public class MyCacheConfig {
// @Autowired
// CacheProperties cacheProperties;
/**
* 配置文件中的东西没有用上:
* 1、原来和配置文件绑定的配置类是这样子的
*
* 2、要让他生效
*
* @return
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();//默认配置
// config =config.entryTtl();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
//将配置文件中的所有配置都生效
return config;
}
}