赞
踩
今天给大家介绍一下如何在SpringBoot中解决Redis的缓存穿透、缓存击穿、缓存雪崩的问题。
缓存穿透指的是一个缓存系统无法缓存某个查询的数据,从而导致这个查询每一次都要访问数据库。
常见的Redis缓存穿透场景包括:
我们可以使用Guava在内存中维护一个布隆过滤器。具体步骤如下:
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <version>29.0-jre</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- 复制代码
- public class BloomFilterUtil {
- // 布隆过滤器的预计容量
- private static final int expectedInsertions = 1000000;
- // 布隆过滤器误判率
- private static final double fpp = 0.001;
- private static BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);
- /**
- * 向Bloom Filter中添加元素
- */
- public static void add(String key){
- bloomFilter.put(key);
- }
- /**
- * 判断元素是否存在于Bloom Filter中
- */
- public static boolean mightContain(String key){
- return bloomFilter.mightContain(key);
- }
- }
-
- 复制代码
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
-
- @GetMapping("/user/{id}")
- public User getUserById(@PathVariable Long id){
- // 先从布隆过滤器中判断此id是否存在
- if(!BloomFilterUtil.mightContain(id.toString())){
- return null;
- }
- // 查询缓存数据
- String userKey = "user_"+id.toString();
- User user = (User) redisTemplate.opsForValue().get(userKey);
- if(user == null){
- // 查询数据库
- user = userRepository.findById(id).orElse(null);
- if(user != null){
- // 将查询到的数据加入缓存
- redisTemplate.opsForValue().set(userKey, user, 300, TimeUnit.SECONDS);
- }else{
- // 查询结果为空,将请求记录下来,并在布隆过滤器中添加
- BloomFilterUtil.add(id.toString());
- }
- }
- return user;
- }
- 复制代码
缓存击穿指的是在一些高并发访问下,一个热点数据从缓存中不存在,每次请求都要直接查询数据库,从而导致数据库压力过大,并且系统性能下降的现象。
缓存击穿的原因通常有以下几种:
主要思路 : 在遇到缓存击穿问题时,我们可以在查询数据库之前,先判断一下缓存中是否已有数据,如果没有数据则使用Redis的单线程特性,先查询数据库然后将数据写入缓存中。
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- 复制代码
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
-
- @GetMapping("/user/{id}")
- public User getUserById(@PathVariable Long id){
- // 先从缓存中获取值
- String userKey = "user_"+id.toString();
- User user = (User) redisTemplate.opsForValue().get(userKey);
- if(user == null){
- // 查询数据库之前加锁
- String lockKey = "lock_user_"+id.toString();
- String lockValue = UUID.randomUUID().toString();
- try{
- Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 60, TimeUnit.SECONDS);
- if(lockResult != null && lockResult){
- // 查询数据库
- user = userRepository.findById(id).orElse(null);
- if(user != null){
- // 将查询到的数据加入缓存
- redisTemplate.opsForValue().set(userKey, user, 300, TimeUnit.SECONDS);
- }
- }
- }finally{
- // 释放锁
- if(lockValue.equals(redisTemplate.opsForValue().get(lockKey))){
- redisTemplate.delete(lockKey);
- }
- }
- }
- return user;
- }
- 复制代码
指缓存中大量数据的失效时间集中在某一个时间段,导致在这个时间段内缓存失效并额外请求数据库查询数据的请求大量增加,从而对数据库造成极大的压力和负荷。
常见的Redis缓存雪崩场景包括:
在遇到缓存雪崩时,我们可以使用两种方法:一种是将缓存过期时间分散开,即为不同的数据设置不同的过期时间;另一种是使用Redis的多级缓存架构,通过增加一层代理层来解决。具体步骤如下:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache</artifactId>
- <version>2.10.6</version>
- </dependency>
- 复制代码
- spring.cache.type=ehcache
- 复制代码
- @Configuration
- @EnableCaching
- public class CacheConfig {
- @Bean
- public EhCacheCacheManager ehCacheCacheManager(CacheManager cm){
- return new EhCacheCacheManager(cm);
- }
- @Bean
- public CacheManager ehCacheManager(){
- EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
- cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
- cmfb.setShared(true);
- return cmfb.getObject();
- }
- }
- 复制代码
- <?xml version="1.0" encoding="UTF-8"?>
- <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
- updateCheck="true"
- monitoring="autodetect"
- dynamicConfig="true">
-
- <cache name="userCache" maxEntriesLocalHeap="10000" timeToLiveSeconds="60" timeToIdleSeconds="30"/>
-
- </ehcache>
- 复制代码
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
-
- @Autowired
- private CacheManager ehCacheManager;
-
- @GetMapping("/user/{id}")
- @Cacheable(value = "userCache", key = "#id")
- public User getUserById(@PathVariable Long id){
- // 先从Ehcache缓存中获取
- String userKey = "user_"+id.toString();
- User user = (User) ehCacheManager.getCache("userCache").get(userKey).get();
- if(user == null){
- // 再从Redis缓存中获取
- user = (User) redisTemplate.opsForValue().get(userKey);
- if(user != null){
- ehCacheManager.getCache("userCache").put(userKey, user);
- }
- }
- return user;
- }
- 复制代码
以上就是使用SpringBoot时如何解决Redis的缓存穿透、缓存击穿、缓存雪崩的常用方法。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。