什么是Redis缓存穿透问题?有效预防策略有哪些?

115 阅读4分钟

大家好,我是袁庭新。使用Redis作为缓存数据库时,在高并发场景下有可能出现缓存雪崩、缓存穿透、缓存击穿等问题。

什么是Redis缓存穿透问题.png

1.缓存穿透介绍

Redis的缓存穿透是指客户端请求的数据在缓存中不存在,并且在数据库中也不存在,导致大量请求直接穿透缓存直接打到数据库。如果这个查询频繁发生,那么每次都会直接访问数据库,导致数据库压力增大。更糟糕的是,如果攻击者利用这一点进行恶意请求,可能会对系统造成严重的性能问题甚至服务不可用。

2.解决方案

企业为了避免Redis的缓存穿透,可以采取以下几种策略:

1.缓存空对象:当从数据库中查询不到数据时,仍然将空对象缓存到Redis中,并设置一个较短的过期时间。这样下次再有相同的请求时就可以直接从缓存返回,而不需要再次查询数据库。

以下是使用Spring Boot框架结合Redis实现缓存空对象的示例代码:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class UserService {
    // 空对象缓存过期时间,单位秒
    private static final long NULL_CACHE_EXPIRE_TIME = 60; 
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private UserMapper userMapper;

    public User getUserById(Long id) {
        String key = "user:" + id;
        User user = (User) redisTemplate.opsForValue().get(key);
        if (user == null) {
            user = userMapper.selectById(id);
            if (user == null) {
                // 将空对象缓存到Redis中,并设置过期时间
                redisTemplate.opsForValue().set(key, "", NULL_CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
            } else {
                redisTemplate.opsForValue().set(key, user);
            }
        }
        return user;
    }
}

2.使用布隆过滤器:布隆过滤器是一种基于位图的数据结构,用于快速判断一个元素是否存在于一个集合中。在缓存系统中,可以将数据库中存在的所有key预先加载到布隆过滤器中。当有请求到来时,先通过布隆过滤器进行判断,如果元素不存在于布隆过滤器中,则直接返回,不再查询缓存和数据库。布隆过滤器可能会产生误判(即认为某个元素存在但实际上不存在),但绝对不会漏判。

以下是使用Google Guava库中的布隆过滤器的示例代码:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.Charset;

public class BloomFilterExample {
    private static final int EXPECTED_INSERTIONS = 1000000;
    private static final double FPP = 0.01;

    public static void main(String[] args) {
        // 创建布隆过滤器
        BloomFilter<String> bloomFilter = BloomFilter.create(
                Funnels.stringFunnel(Charset.forName("UTF-8")),
                EXPECTED_INSERTIONS,
                FPP
        );

        // 模拟将数据库中的key加载到布隆过滤器中
        for (int i = 0; i < EXPECTED_INSERTIONS; i++) {
            String key = "key" + i;
            bloomFilter.put(key);
        }

        // 模拟请求判断
        String testKey = "key999999";
        if (bloomFilter.mightContain(testKey)) {
            System.out.println("可能存在");
        } else {
            System.out.println("一定不存在");
        }
    }
}

别忘了还需要在项目的pom.xml文件中添加Google Guava的依赖。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.4.0-jre</version>
</dependency>

3.接口限流与熔断:在客户端请求到达应用服务器之前,通过限流算法对请求进行限制,如限制每秒的请求次数、限制并发请求数等。当请求量超过设定的阈值时,直接拒绝多余的请求,返回友好的提示信息。同时,结合熔断机制,当发现缓存穿透的情况较为严重时,自动熔断对该接口的访问,一段时间内直接返回错误信息,避免大量请求继续穿透到数据库。

以下是使用Spring Cloud Gateway实现接口限流和熔断的示例配置:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@userKeyResolver}"
                redis-rate-limiter.replenishRate: 100
                redis-rate-limiter.burstCapacity: 200
            - name: Hystrix
              args:
                name: userFallback
                fallbackUri: forward:/fallback/user

4.缓存预热:在系统启动或业务低峰期,提前将可能会被频繁访问的数据加载到缓存中,这样可以避免在业务高峰期首次访问数据时出现缓存穿透的情况。

以下是一个简单的缓存预热示例代码:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;

@Component
public class DataPreheat {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private UserMapper userMapper;

    public void preheatUserCache() {
        // 查询数据库中所有用户数据
        List<User> userList = userMapper.selectList(null);
        for (User user : userList) {
            String key = "user:" + user.getId();
            redisTemplate.opsForValue().set(key, user);
        }
    }
}

5.实时监控与告警:通过对缓存系统和数据库的实时监控,及时发现缓存穿透的异常情况。一旦发现异常,立即发出告警通知,以便运维人员和开发人员及时采取措施进行处理。可以使用Prometheus结合Grafana进行监控和告警配置。通过在应用程序中集成Prometheus客户端,暴露缓存命中率、数据库查询次数等指标,然后在Grafana中配置监控面板和告警规则。