Redis 缓存穿透介绍
缓存穿透(Cache Penetration)是指客户端请求的数据既不在缓存中,也不在数据库中,导致每次请求都直接访问数据库。这种情况不仅无法发挥缓存的性能优势,还会加重数据库的负担,导致系统性能下降。
缓存穿透的原因
缓存穿透通常有以下几种原因:
- 查询的数据不存在:客户端请求的数据在缓存中和数据库中都没有,例如,用户请求的商品或用户数据在系统中根本不存在。
- 恶意请求:攻击者故意发送不存在的数据请求,绕过缓存,直接向数据库发起请求。
- 缓存未命中数据:由于缓存没有存储数据(可能是由于缓存过期或删除),每次都需要访问数据库。
缓存穿透的影响
- 增加数据库负载:每次请求都需要访问数据库,即使数据库中没有数据,也会浪费数据库资源,增加数据库的负载。
- 浪费带宽:客户端无效的请求造成了系统资源的浪费。
- 性能下降:频繁的缓存穿透会使得缓存的优势无法发挥,系统性能和响应时间会受到影响。
如何解决缓存穿透
- 缓存空对象:当查询的结果为空时,可以将空对象缓存,避免相同的无效请求反复查询数据库。
- 使用布隆过滤器:布隆过滤器是一种概率型数据结构,适合用来判断某个元素是否在一个集合中。通过使用布隆过滤器,可以在访问缓存之前先进行快速判断,如果请求的数据不在布隆过滤器中,就可以直接拒绝请求,避免不必要的数据库查询。
- 请求校验和防火墙:通过验证请求是否合法,限制无效或恶意请求的访问,从而减少无效请求导致的缓存穿透。
Redis 缓存穿透的 Java 实现
1. 使用布隆过滤器防止缓存穿透
布隆过滤器是一种空间效率极高的概率型数据结构,适合用来判断某个元素是否在一个集合中。布隆过滤器能够快速判断某个元素是否存在于数据库中,从而减少不必要的缓存查询。
我们可以通过 Redis 实现布隆过滤器,防止缓存穿透。
步骤:
- 安装依赖:使用 Redisson,它是 Redis 的 Java 客户端,支持布隆过滤器功能。
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.1</version>
</dependency>
2.代码实现:
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.Redisson;
public class RedisBloomFilterExample {
// 初始化 Redisson 客户端
private static RedissonClient redisson;
public static void main(String[] args) {
// 配置 Redisson 连接 Redis
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
redisson = Redisson.create(config);
// 创建布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("userExistsFilter");
// 初始化布隆过滤器,预计存储100万个元素,误判率设置为0.03%
bloomFilter.tryInit(1000000, 0.03);
// 添加数据到布隆过滤器
bloomFilter.add("user:1001");
bloomFilter.add("user:1002");
bloomFilter.add("user:1003");
// 模拟检查数据是否存在
String userId = "user:1001";
if (bloomFilter.contains(userId)) {
// 如果布隆过滤器包含该数据,则从缓存获取数据
System.out.println(userId + " exists in cache.");
} else {
// 如果布隆过滤器没有该数据,则查询数据库
System.out.println(userId + " does not exist in cache, querying database...");
}
// 关闭连接
redisson.shutdown();
}
}
- Redisson 客户端初始化:通过
Redisson.create()方法初始化客户端连接 Redis。 - 创建布隆过滤器:使用
redisson.getBloomFilter()方法创建布隆过滤器。tryInit方法用于初始化布隆过滤器的预期容量和误判率。 - 添加数据:使用
add()方法将数据添加到布隆过滤器。 - 查询数据:使用
contains()方法检查数据是否在布隆过滤器中存在。如果存在则从缓存中获取数据,否则查询数据库。
布隆过滤器的优势在于,它能够在大多数情况下快速判断数据是否存在,避免了无效查询数据库的情况。虽然布隆过滤器会存在一定的误判率(可能返回“存在”的错误结果),但它绝对不会漏掉真实存在的数据。
2. 缓存空对象
如果查询到的数据为空,可以将空对象缓存一段时间,避免后续相同的数据查询再次访问数据库。
代码实现:
import redis.clients.jedis.Jedis;
public class CacheEmptyObjectExample {
private static Jedis jedis = new Jedis("localhost", 6379);
public static void main(String[] args) {
String key = "user:9999"; // 假设这个用户不存在
// 尝试从缓存获取数据
String value = getFromCache(key);
if (value == null) {
// 如果缓存中没有,查询数据库
value = getFromDatabase(key);
// 如果数据库中没有数据,缓存一个空值(避免下次重复查询)
if (value == null) {
setToCache(key, "null");
} else {
setToCache(key, value);
}
}
System.out.println("Fetched value: " + value);
}
// 从缓存获取数据
public static String getFromCache(String key) {
return jedis.get(key);
}
// 设置数据到缓存
public static void setToCache(String key, String value) {
jedis.setex(key, 3600, value); // 设置 1 小时过期时间
}
// 模拟数据库查询
public static String getFromDatabase(String key) {
// 假设数据库中没有这个数据
return null;
}
}
代码解析:
- 查询缓存:首先从 Redis 获取缓存,如果返回为
null,则说明缓存中没有数据。 - 查询数据库:如果 Redis 缓存没有数据,则从数据库查询。
- 缓存空值:如果数据库查询结果为空(如用户不存在),则将
"null"存入 Redis 中,表示该数据不存在。这样可以避免频繁访问数据库,提高性能。 - 缓存设置过期时间:使用
setex设置缓存的过期时间(如 1 小时)。
Redis的碎碎念 文章被收录于专栏
Redis面试中的碎碎念
