Java:Redis分布式缓存

1、Redis作为缓存

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>>(){});
    }
    return listMap;

总结:当前高高并发中会造成缓存穿透

2、高并发-缓存穿透

缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中,将查询数据库,但是数据库也无此记录。
我们没有将查询的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来解决缓存穿透问题,但是还是会遇到高并发导致服务器雪崩问题

3、高并发-缓存雪崩

缓存雪崩:指我们设置的缓存时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);
}

总结:解决了缓存穿透和缓存雪崩问题,但是还是存在某个时间点出现的缓存击穿问题

4、高并发-缓存击穿

缓存击穿:对于一些设置过期时间的key,如何这些key可能会在某些时间点被高并发访问,是一个非常热点的数据,如果这个key在大量请求同时进来钱正好失效,那么所有的key数据查询到落到存储层DB。我们称为缓存击穿。
解决方法: 加锁,大量并发只让一个去查,其他人等待,查到以后释放锁。其他人获取到锁,先查询缓存,就有数据了,不用去存储层查询。

a、本地锁

本地锁只锁当前进程,无法完全锁住高并发。

synchronized (this){
//保证锁的原子性
}

Java:Redis分布式缓存_第1张图片

b、分布式锁

加锁保证原子性:

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);

Java:Redis分布式缓存_第2张图片

c、Redisson分布式锁

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);
    }
}

具体如何使用可以参考官网。

d、SpringCache

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就足够了。写模式下可以添加缓存过期时间来满足。

你可能感兴趣的:(分布式架构,Redis,JAVA,redis,缓存)