欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 幼教 > java-redis-穿透

java-redis-穿透

2024/10/24 19:28:41 来源:https://blog.csdn.net/Flying_Fish_roe/article/details/142007380  浏览:    关键词:java-redis-穿透

Redis 缓存穿透是指当请求的数据在缓存和数据库中都不存在时,用户每次请求都会直接查询数据库,导致缓存失效,无法发挥作用。这种情况下,用户发出的每个请求都绕过了缓存,直接打到了数据库,可能导致数据库压力骤增,甚至崩溃。

在实际应用中,缓存穿透通常会由于用户发送恶意请求或非法数据请求导致。例如,用户传递的 id 永远不会在数据库中找到相应的数据(如负数 ID 或过大的 ID)。由于这些 ID 不存在于数据库中,缓存也没有保存这些值,结果是每次请求都会直接访问数据库。

Redis 缓存穿透的常见解决方案

  1. 缓存空值:将不存在的结果也缓存起来,避免重复查询数据库。
  2. 布隆过滤器:通过布隆过滤器提前拦截非法请求,避免查询数据库。
  3. 参数校验:在查询数据库之前,先对请求参数进行校验,直接过滤掉非法请求。

接下来,我们逐一解释这些方案,并给出示例代码。

1. 缓存空值

当请求的键在数据库中不存在时,我们可以将这个“空结果”也缓存起来,并设置一个较短的过期时间,避免缓存永远存储无效值。下次再请求这个键时,直接从缓存中获取到空值,而不会访问数据库。

示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class CacheService {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 模拟查询数据库*/public String getDataFromDB(String key) {// 模拟数据库查询if ("validKey".equals(key)) {return "valueFromDB";}return null; // 模拟数据库中不存在}/*** 查询数据,避免缓存穿透*/public String getData(String key) {// 先从缓存中查询String value = redisTemplate.opsForValue().get(key);if (value != null) {// 缓存命中return "从缓存获取: " + value;}// 如果缓存没有命中,则查询数据库value = getDataFromDB(key);if (value != null) {// 如果数据库存在,则写入缓存redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);return "从数据库获取: " + value;} else {// 如果数据库不存在,则将空值缓存,并设置短期过期时间redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);return "数据不存在,缓存空值";}}
}

在这个例子中,当数据库中找不到数据时,缓存会存储一个空字符串,防止后续的重复查询。可以根据实际场景设置空值的过期时间,例如设置为 5 分钟。这样可以减少数据库的压力。

2. 使用布隆过滤器

布隆过滤器(Bloom Filter)是一种高效的概率数据结构,用于快速判断某个元素是否存在于一个集合中。通过将所有合法的键加入布隆过滤器中,可以在查询数据库前先判断请求是否有意义,如果布隆过滤器认为该键不存在,则可以直接返回,不再查询缓存和数据库。

布隆过滤器的特点:

  • 空间效率高:布隆过滤器使用哈希函数和位数组来表示元素的存在状态,空间占用很小。
  • 存在误判:布隆过滤器可能会误判某个不存在的元素为存在(即假阳性),但不会误判存在的元素为不存在。
示例代码:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;@Service
public class CacheServiceWithBloomFilter {@Autowiredprivate StringRedisTemplate redisTemplate;private BloomFilter<String> bloomFilter;// 初始化布隆过滤器public CacheServiceWithBloomFilter() {// 创建布隆过滤器,设置预计插入元素数量为10000,误判率为0.01this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 10000, 0.01);// 将已有数据添加到布隆过滤器bloomFilter.put("validKey");// 可以继续添加其他已知合法的键}/*** 模拟查询数据库*/public String getDataFromDB(String key) {if ("validKey".equals(key)) {return "valueFromDB";}return null;}/*** 查询数据,使用布隆过滤器避免缓存穿透*/public String getData(String key) {// 先通过布隆过滤器判断该key是否可能存在if (!bloomFilter.mightContain(key)) {return "该数据不存在(布隆过滤器拦截)";}// 如果布隆过滤器认为存在,继续查询缓存String value = redisTemplate.opsForValue().get(key);if (value != null) {return "从缓存获取: " + value;}// 缓存未命中,查询数据库value = getDataFromDB(key);if (value != null) {// 数据库存在,写入缓存redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);return "从数据库获取: " + value;} else {// 数据库不存在,缓存空值redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);return "数据不存在,缓存空值";}}
}

在这个例子中,布隆过滤器用于快速判断请求的键是否可能存在。如果布隆过滤器判断该键不可能存在,则直接返回“数据不存在”,避免不必要的缓存和数据库查询。

3. 参数校验

对于明显不合法的请求参数(例如负数 ID、过大的 ID 等),可以在请求到达 Redis 或数据库之前直接进行参数校验,避免不合法的请求进入后端系统。

示例代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class CacheServiceWithValidation {@Autowiredprivate StringRedisTemplate redisTemplate;/*** 模拟查询数据库*/public String getDataFromDB(String key) {if ("validKey".equals(key)) {return "valueFromDB";}return null;}/*** 查询数据,增加参数校验*/public String getData(String key) {// 进行参数合法性校验,过滤掉明显不合法的请求if (key == null || key.length() == 0 || key.length() > 20) {return "请求参数不合法";}// 查询缓存String value = redisTemplate.opsForValue().get(key);if (value != null) {return "从缓存获取: " + value;}// 缓存未命中,查询数据库value = getDataFromDB(key);if (value != null) {redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);return "从数据库获取: " + value;} else {redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);return "数据不存在,缓存空值";}}
}

在这个例子中,首先对请求的 key 进行参数校验,确保请求是合法的。如果 key 为空、长度过长等情况,直接返回错误信息,避免查询缓存和数据库。

Redis 穿透、击穿和雪崩

除了缓存穿透,还有两个常见的缓存问题:

  • 缓存击穿:缓存中没有但数据库中有的数据,并且该数据在短时间内有大量请求。如果这类请求同时访问数据库,可能导致数据库压力激增。

    解决方案:使用互斥锁(如分布式锁)防止多个线程同时查询数据库,或者为热点数据设置较长的过期时间。

  • 缓存雪崩:由于缓存服务器宕机或大量缓存同时失效,导致大量请求直接打到数据库,给数据库造成很大压力。

    解决方案:为缓存设置不同的过期时间(缓存失效时间的随机化),避免缓存集中失效;增加缓存节点的冗余。

总结

缓存穿透是 Redis 缓存系统中的一个常见问题,它会导致大量请求绕过缓存直接访问数据库,给数据库带来巨大的压力。针对缓存穿透,我们可以使用以下方案:

  1. **缓存空值

**:将数据库中不存在的值缓存起来,防止重复查询。
2. 布隆过滤器:在查询缓存和数据库之前,利用布隆过滤器判断请求是否合法,从而减少不必要的数据库查询。
3. 参数校验:在应用层进行参数合法性校验,直接拦截明显非法的请求。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com