Redis 缓存穿透

一、Bug 场景

在一个电商商品查询系统中,使用 Redis 作为缓存以减轻数据库压力。用户通过商品 ID 查询商品信息时,系统先从 Redis 缓存中查找,若未找到则查询数据库,并将查询结果存入 Redis 缓存。不过,在遭受恶意攻击时,大量不存在的商品 ID 查询涌入系统,导致所有查询都穿透到数据库,数据库压力剧增,甚至出现服务不可用的情况。

二、代码示例

商品服务(有缺陷)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service public class ProductService {
 #Java
@Autowired private RedisTemplate redisTemplate;

// 假设这是数据库查询方法 private Object queryProductFromDB(String productId) { // 模拟数据库查询逻辑,返回商品信息或 null return null; }

public Object getProduct(String productId) { Object product = redisTemplate.opsForValue().get(productId); if (product == null) { product = queryProductFromDB(productId); if (product != null) { redisTemplate.opsForValue().set(productId, product); } } return product; } }

三、问题描述

  1. 预期行为 :正常情况下,大部分商品查询能从 Redis 缓存中获取数据,只有少部分缓存未命中的查询才会穿透到数据库,数据库压力处于可控范围。
  2. 实际行为 :当大量不存在的商品 ID 查询进入系统时,由于这些 ID 在 Redis 中没有对应的缓存数据,系统会不断查询数据库,导致数据库压力剧增。这是由于系统没有对不存在的数据进行有效处理,每次查询都认为是正常的缓存未命中,从而持续访问数据库。恶意攻击者可以利用这一点,通过大量查询不存在的商品 ID,耗尽数据库资源,导致系统崩溃。

四、解决方案

  1. 布隆过滤器(Bloom Filter) :在查询数据库之前,使用布隆过滤器判断商品 ID 是否存在。布隆过滤器可以快速判断一个元素必定不存在或者可能存在。如果布隆过滤器判断商品 ID 必定不存在,直接返回,不再查询数据库;如果判断可能存在,再查询数据库。
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.RedisTemplate;
import org.springframework.stereotype.Service;

@Service public class ProductService {

@Autowired private RedisTemplate redisTemplate; private static final BloomFilter bloomFilter = BloomFilter.create( Funnels.stringFunnel(java.nio.charset.StandardCharsets.UTF_8), 1000000, 0.01);

// 假设这是数据库查询方法 private Object queryProductFromDB(String productId) { // 模拟数据库查询逻辑,返回商品信息或 null return null; }

public Object getProduct(String productId) { if (!bloomFilter.mightContain(productId)) { return null; } Object product = redisTemplate.opsForValue().get(productId); if (product == null) { product = queryProductFromDB(productId); if (product != null) { redisTemplate.opsForValue().set(productId, product); bloomFilter.put(productId); } } return product; } }
  1. 缓存空值 :当查询数据库发现商品不存在时,将空值存入 Redis 缓存,并设置较短的过期时间。这样,后续一样的查询直接从 Redis 获取空值,避免再次查询数据库。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service public class ProductService {

@Autowired private RedisTemplate redisTemplate;

// 假设这是数据库查询方法 private Object queryProductFromDB(String productId) { // 模拟数据库查询逻辑,返回商品信息或 null return null; }

public Object getProduct(String productId) { Object product = redisTemplate.opsForValue().get(productId); if (product == null) { product = queryProductFromDB(productId); if (product != null) { redisTemplate.opsForValue().set(productId, product); } else { // 缓存空值,设置较短过期时间 redisTemplate.opsForValue().set(productId, null, 10, TimeUnit.MINUTES); } } return product; } }
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...