目 录CONTENT

文章目录

Spring Boot 中如何解决 Redis 的缓存穿透、缓存击穿、缓存雪崩?

在等晚風吹
2024-12-10 / 0 评论 / 0 点赞 / 12 阅读 / 0 字 / 正在检测是否收录...

Spring Boot 中如何解决 Redis 的缓存穿透、缓存击穿、缓存雪崩?

缓存是提升系统性能的重要手段,但在高并发场景下,可能会遇到缓存穿透、缓存击穿和缓存雪崩等问题。本文将介绍这三类问题的定义、常见场景及解决方法。


缓存穿透

什么是缓存穿透?

缓存穿透指缓存系统无法命中某些查询的数据,导致每次查询都直接访问数据库,从而造成数据库压力增大。

常见场景包括:

  1. 查询不存在的数据:攻击者可能发送大量无效请求。
  2. 频繁查询热门数据:数据频繁访问导致缓存未命中。
  3. 异常查询:数据服务故障导致缓存无法提供数据。

如何解决?

可以使用 布隆过滤器(Bloom Filter) 来拦截非法查询。

添加依赖

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

    // 添加元素
    public static void add(String key) {
        bloomFilter.put(key);
    }

    // 判断元素是否存在
    public static boolean mightContain(String key) {
        return bloomFilter.mightContain(key);
    }
}

在 Controller 中使用布隆过滤器

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@GetMapping("/user/{id}")
public User getUserById(@PathVariable Long id) {
    if (!BloomFilterUtil.mightContain(id.toString())) {
        return null;
    }

    String userKey = "user_" + id;
    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;
}

缓存击穿

什么是缓存击穿?

缓存击穿指高并发访问下某个热点数据在缓存中不存在或过期,导致大量请求直接访问数据库。


如何解决?

通过加锁机制确保同一时间只有一个请求更新缓存。

添加依赖

<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;
    User user = (User) redisTemplate.opsForValue().get(userKey);
    if (user == null) {
        String lockKey = "lock_user_" + id;
        String lockValue = UUID.randomUUID().toString();
        try {
            Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 60, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(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;
}

缓存雪崩

什么是缓存雪崩?

缓存雪崩指大量缓存数据在同一时间失效,导致数据库压力骤增。

常见场景包括:

  1. 缓存服务器宕机。
  2. 大量数据同时过期。
  3. 数据访问波动大,如促销活动。

如何解决?

方法 1:分散过期时间

为不同数据设置随机化的过期时间。

方法 2:多级缓存架构

结合 Redis 和本地缓存(如 Ehcache)。

添加依赖

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

配置 Ehcache

application.properties

spring.cache.type=ehcache

CacheConfig 配置类

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

ehcache.xml

<ehcache>
    <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) {
    String userKey = "user_" + id;
    User user = (User) ehCacheManager.getCache("userCache").get(userKey, User.class);
    if (user == null) {
        user = (User) redisTemplate.opsForValue().get(userKey);
        if (user != null) {
            ehCacheManager.getCache("userCache").put(userKey, user);
        }
    }
    return user;
}

通过以上方法,可以有效解决缓存穿透、缓存击穿和缓存雪崩问题,提升系统的稳定性与性能。

0

评论区