SpringBoot进阶-缓存击穿,缓存穿透,缓存雪崩(八)

redis很重要的一个应用就是缓存,缓存是提升系统性能的有效手段,今天我们就来聊聊缓存那些事。

缓存实现

来看一个缓存实现的代码,比较经典了,没啥难度

@GetMapping("getById")
@ApiOperation(value = "根据id获取用户")
public Result<User> getById(int id) {
    String key = USER_KEY + id;
    //先从缓存中取
    User user = (User) redisTemplate.opsForValue().get(key);
    if (user == null) {
        //user不存在则从数据库取出并放入缓存,并且设置过期时间
        user = userService.getById(id);
        redisTemplate.opsForValue().set(key, user, Duration.ofHours(6));
    }
    return resultOk(user);
}

@ApiOperation(value = "修改用户")
@PutMapping
public Result update(@RequestBody User user) {
    //修改和删除时需要同时删除缓存,防止缓存中存在脏数据
    String key = USER_KEY + user.getId();
    redisTemplate.delete(key);
    userService.updateById(user);
    return resultOk();
}

Spring Cache

上面的代码还是有点啰嗦的,有没有偷懒的办法呢?那就用Spring Cache吧

第一步,导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

第二步,开启cahe并自定义存储格式

@Configuration
@EnableCaching // 开启缓存支持
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化(解决乱码的问题),过期时间30分钟
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

}

第三步,使用缓存

@GetMapping("getByIdSpringCache")
@ApiOperation(value = "根据id获取用户SpringCache")
@Cacheable(value = "USER", key = "'USER_INFO:' +#p0")
public Result<User> getByIdSpringCache(int id) {
    User user = userService.getById(id);
    return resultOk(user);
}

@ApiOperation(value = "修改用户SpringCache")
@PutMapping("updateSpringCache")
@CacheEvict(value = "USER", key = "'USER_INFO:' +#p0.id")
public Result updateSpringCache(@RequestBody User user) {
    userService.updateById(user);
    return resultOk();
}

可以看到代码比之前简洁多了,一个注解就搞定了。其中#p0是springEL表达式,代表第一个参数值。@Cacheable表示放入缓存,@CacheEvict表示删除缓存

如果并发量很高,就要考虑缓存的三大坑了,缓存击穿,缓存穿透,缓存雪崩。

缓存击穿

缓存击穿是原来有缓存,由于缓存失效,造成缓存被击穿了,然后大量请求涌入到数据库,造成系统卡死。

解决方法是热点key不设置失效时间,或者加锁。下面看加锁方案

private final static String GET_BY_ID_LOCK = "GET_BY_ID_LOCK";

@GetMapping("getById")
@ApiOperation(value = "根据id获取用户")
public Result<User> getById(int id) {
    String key = USER_KEY + id;
    //先从缓存中取
    User user = (User) redisTemplate.opsForValue().get(key);
    if (user == null) {
        //此时有1W个请求到这,不加锁的话1W个请求会直接打到数据库
        synchronized (GET_BY_ID_LOCK) {
            //高并发下需要双重检查,如果还是空说明真的没有缓存
            user = (User) redisTemplate.opsForValue().get(key);
            if (user == null) {
                //user不存在则从数据库取出并放入缓存,并且设置过期时间
                user = userService.getById(id);
                redisTemplate.opsForValue().set(key, user, Duration.ofHours(6));
            }
        }
    }
    return resultOk(user);
}

缓存穿透

缓存穿透指缓存和数据库都没有数据,请求直接穿透了。解决方案是空对象缓存或者布隆过滤器。

空对象缓存的意思是比如查id是1的对象返回null,那就直接把null也放入缓存,但判断缓存中存不存在就要用到exists了,比较麻烦。

还有一种布隆过滤器,以前面试要是能答出这个来瞬间逼格拉满,现在似乎是很平常的八股文了(真是越来越卷了o(╥﹏╥)o)。

布隆过滤器有点像hashmap,由一个大数组和多个hash函数构成,它会把hash值取模后的位置置为1,由于hash冲突,如果他判断元素不存在则一定不存在,如果他判断元素存在则元素有可能存在,会有一定的误判。

不了解也没事,只要记住一句话,布隆过滤器就是用来快速判断某个数据是否存在的。

SpringBoot集成Redisson

SpringBoot集成Redisson有两种方式,一种是手动集成,一种是自动配置

方式一

导入依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.5.0</version>
</dependency>

 创建redissonClient的bean

@Configuration
public class RedissonManager {
    @Value("${redisson.address}")
    private String addressUrl;

    @Bean
    public RedissonClient getRedisson() throws Exception{
        RedissonClient redisson = null;
        Config config = new Config();
        config.useSingleServer()
                .setAddress(addressUrl);
        redisson = Redisson.create(config);
        return redisson;
    }
}

方式二

导入依赖,可以看到redisson的自动配置底层使用的是spring-data-redis,所以版本要与spring-data-redis的版本一致,比如我的springBoot版本是2.3.X就要用spring-data-23,更详细的可以看官方文档:https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter#spring-boot-starter

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-25</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <!-- for Spring Data Redis v.2.3.x -->
    <artifactId>redisson-spring-data-23</artifactId>
    <version>3.16.1</version>
</dependency>

 配置就可以直接使用spring-data-redis的配置了,

spring:
  redis:
    database: 
    host:
    port:
    password:
    ssl: 
    timeout:
    cluster:
      nodes:
    sentinel:
      master:
      nodes:

 使用布隆过滤器解决缓存穿透

第一步,需要创建一个过滤器,预估数据量和可接受的错误率,然后把所有id放进去,后面新增数据的时候也需要放入id,https://krisives.github.io/bloom-calculator/,这个网站可以看指定数据量和错误率的情况下所需要的空间。

@GetMapping("init")
@ApiOperation(value = "初始化")
public Result init() {
    RBloomFilter bloomFilter = redissonClient.getBloomFilter("userIdFilter");
    //预估数据量和可接受的错误率
    bloomFilter.tryInit(1000000, 0.001);
    List<User> users = userService.list();
    for (User user : users) {
        //放入布隆过滤器
        bloomFilter.add(user.getId());
    }
    return resultOk();
}

接下去请求的时候判断id存不存在,不存在直接返回,解决了缓存穿透问题

@GetMapping("getById")
@ApiOperation(value = "根据id获取用户")
public Result<User> getById(int id) {
    RBloomFilter bloomFilter = redissonClient.getBloomFilter("userIdFilter");
    //判断该id是否存在
    if (!bloomFilter.contains(id)) {
        //布隆过滤器特点,判断结果为不存在的时候则一定不存在,可放心返回
        return resultOk();
    }
    //存在的情况布隆过滤器有一定误判可能,
    // 但没关系,错误率为0.001的情况下1W个请求只会有10个打到数据库,可以接受
    String key = USER_KEY + id;
    //先从缓存中取
    User user = (User) redisTemplate.opsForValue().get(key);
    if (user == null) {
        //user不存在则从数据库取出并放入缓存,并且设置过期时间
        user = userService.getById(id);
        redisTemplate.opsForValue().set(key, user, Duration.ofHours(6));
    }
    return resultOk(user);
}

缓存雪崩

redis挂了或者大量热点数据同时过期,导致大量请求 打到数据库引发系统奔溃。

解决方法是1. redis高可用-Redis Cluster,2. 使用sentinel进行限流和降级,3. 开启redis持久化机制aof/rdb,尽快恢复缓存集群。

如果是热点数据过期可参考缓存击穿解决方案。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot中整合Ehcache缓存,需要进行以下几个步骤: 1. 添加Ehcache依赖 在pom.xml文件中添加Ehcache依赖: ``` <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>${ehcache.version}</version> </dependency> ``` 2. 配置Ehcache缓存 在application.yml(或application.properties)文件中配置Ehcache缓存: ``` spring: cache: type: ehcache ehcache: config: classpath:ehcache.xml ``` 其中,`spring.cache.type`属性指定使用的缓存类型为Ehcache,`ehcache.config`属性指定Ehcache配置文件的路径(在classpath下)。 3. 编写Ehcache配置文件 在classpath下创建`ehcache.xml`文件,配置Ehcache缓存的相关信息: ``` <ehcache> <diskStore path="java.io.tmpdir"/> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" transactionalMode="off"> </defaultCache> <cache name="userCache" maxEntriesLocalHeap="1000" maxEntriesLocalDisk="10000" eternal="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off"> </cache> </ehcache> ``` 其中,`defaultCache`为默认缓存配置,`cache`为自定义缓存配置。 4. 在代码中使用缓存 在需要使用缓存的方法上添加`@Cacheable`注解,指定缓存名称和缓存key: ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @Cacheable(value = "userCache", key = "#id") public User getUserById(Integer id) { return userDao.getUserById(id); } // ... } ``` 其中,`value`属性指定缓存名称,`key`属性为缓存key表达式,可以使用Spring表达式语言(SpEL)进行动态表达。 以上就是在Spring Boot中整合Ehcache缓存的步骤,通过使用Ehcache缓存可以有效地提高应用程序的性能和响应速度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值