Redis是一款内存高速缓存数据库;
数据模型为:key - value,非关系型数据库使用的存储数据的格式;
可持久化:将内存数据在写入之后按照一定格式存储在磁盘文件中,宕机、断电后可以重启redis时读取磁盘中文件恢复缓存数据;
分布式:当前任务被多个节点切分处理,叫做分布式处理一个任务。单个服务器内存,磁盘空间有限,无法处理海量的缓存数据,必须支持分布式的结构;
SpringBoot 2.x引入Redis缓存
org.springframework.boot
spring-boot-starter-data-redis
样例:采用Redis缓存。
Map> listMap = null;
String category= (String) redisUtils.get("Category");
if(StringUtils.isEmpty(category)){
listMap = getCategoryJsonFromDb();
String s = JSON.toJSONString(listMap);
redisUtils.set("Category", s);
}else {
listMap = JSON.parseObject(category, new TypeReference
总结:当前高高并发中会造成缓存穿透
缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中,将查询数据库,但是数据库也无此记录。
我们没有将查询的null写入缓存,导致不存在的数据每次请求数据存储层,失去缓存的意义。
风险:
利用不存在的数据进行攻击,数据库压力瞬时增大,最终导致崩溃
解决方法:
null结果缓存,并加入短暂过期时间
对上诉样例改造:
if(listMap != null){
String s = JSON.toJSONString(listMap);
redisUtils.set("CatalogJson", s, 24*60*60);
}
else{
redisUtils.set("CatalogJson", null, 24*60*60);
}
总结:通过设置过期时间和缓存null来解决缓存穿透问题,但是还是会遇到高并发导致服务器雪崩问题
缓存雪崩:指我们设置的缓存时key采用相同的过期时间,导致缓存在某一时刻同时失效,请求全部转换到存储层DB,存储层瞬间压力过重导致雪崩
解决方法:原有的失效时间基础上增加一个随机值,比如1-10分钟随机,这样每一个缓存的过期时间的重复率就会降低,就能大概率避免集体失效的事件。
对上诉代码改造:
int randomTime = (int)(1+Math.random()*(10-1+1));//随机数要自己改造
if(listMap != null){
String s = JSON.toJSONString(listMap);
redisUtils.set("CatalogJson", s, 24*60*60 + randomTime);
}
else{
redisUtils.set("CatalogJson", null, 24*60*60 + randomTime);
}
总结:解决了缓存穿透和缓存雪崩问题,但是还是存在某个时间点出现的缓存击穿问题
缓存击穿:对于一些设置过期时间的key,如何这些key可能会在某些时间点被高并发访问,是一个非常热点的数据,如果这个key在大量请求同时进来钱正好失效,那么所有的key数据查询到落到存储层DB。我们称为缓存击穿。
解决方法: 加锁,大量并发只让一个去查,其他人等待,查到以后释放锁。其他人获取到锁,先查询缓存,就有数据了,不用去存储层查询。
本地锁只锁当前进程,无法完全锁住高并发。
synchronized (this){
//保证锁的原子性
}
加锁保证原子性:
String uuid = UUID.randomUUID().toString();
boolean lock = redisTemplate.opsForValue().setIfAbsent("category_lock", uuid, 120, TimeUnit.SECONDS);
删锁保证原子性:采用lua脚本
String script ="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Long lockInt = redisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList("category_lock"), uuid);
Redission 为 Redis 官网分布式解决方案
官网: https://redisson.org/
github: https://github.com/redisson/redisson#quick-start
pom.xml引入redisson maven关联
org.redisson
redisson
3.16.0
添加redisson config
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}
具体如何使用可以参考官网。
SpringCache 常用注解为:@Cacheable、@CachePut、@CacheEvit
@Cacheable 的作用:适用于获取数据,如果缓存中有数据,不在调用注解方法;
@CachePut 的作用:适合于插入数据和更新数据。一定会调用真实方法,再将方法返回值保存到缓存;
@CachEvict 的作用:适合于删除数据。
引入pom.xml
org.springframework.boot
spring-boot-starter-cache
解决缓存穿透方式:添加允许缓存空值的配置
spring.cache.redis.cache-null-values=true
解决缓存雪崩方式:添加同步锁
@Cacheable(value = {"category"},key = "#root.method.name", sync = true)
解决缓存击穿方式:加上过期时间和随机时间配置
spring.cache.redis.time-to-live=360000
总结:读多写少,即时性和一致性要求不高,使用springcache就足够了。写模式下可以添加缓存过期时间来满足。