redis 缓存穿透
缓存穿透是指查询一个既不存在于缓存中,也不存在于数据库中的数据。这种情况下,缓存无法命中,请求会直接打到数据库,如果大量请求都是查询不存在的数据,将导致数据库压力过大甚至崩溃。解决缓存穿透的方案主要是通过布隆过滤器、缓存空对象以及设置异常值等方式来避免。
1. 使用布隆过滤器
布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否存在于集合中。它的优点是不会占用过多内存,且查询速度非常快。
实现步骤:
- 在查询缓存之前,先通过布隆过滤器判断数据是否存在。
- 如果布隆过滤器返回不存在,则直接返回,不再查询缓存和数据库。
- 如果布隆过滤器返回可能存在,则继续查询缓存和数据库。
java
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;</p>
<p>import java.nio.charset.Charset;</p>
<p>public class CachePenetrationSolution {
private static final BloomFilter bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 100000, 0.01);</p>
<pre><code>public static void main(String[] args) {
// 假设数据库中存在的 key
bloomFilter.put("existingKey");
String queryKey = "nonExistingKey";
if (!bloomFilter.mightContain(queryKey)) {
System.out.println("Key does not exist, no need to query cache or database.");
return;
}
// 查询缓存或数据库逻辑
System.out.println("Querying cache or database for key: " + queryKey);
}
}
2. 缓存空对象
当查询到数据库中不存在的数据时,可以将这个“不存在”的结果也存入缓存中,并设置较短的过期时间。这样下次再查询相同的数据时,可以直接从缓存中获取,而不需要再次查询数据库。
实现代码:
java
import redis.clients.jedis.Jedis;</p>
<p>public class CachePenetrationSolutionWithEmptyObject {
private static final Jedis jedis = new Jedis("localhost", 6379);
private static final int EMPTY<em>OBJECT</em>TTL = 60; // 空对象缓存过期时间(秒)</p>
<pre><code>public static String getFromCacheOrDatabase(String key) {
String cacheValue = jedis.get(key);
if (cacheValue == null) {
// 查询数据库
String dbValue = queryDatabase(key);
if (dbValue == null) {
// 数据库中也不存在,缓存空对象
jedis.setex(key, EMPTY_OBJECT_TTL, "null");
return null;
} else {
// 数据库存在,缓存有效值
jedis.setex(key, 3600, dbValue);
return dbValue;
}
} else {
return "null".equals(cacheValue) ? null : cacheValue;
}
}
private static String queryDatabase(String key) {
// 模拟数据库查询
if ("existingKey".equals(key)) {
return "value";
}
return null;
}
public static void main(String[] args) {
String result = getFromCacheOrDatabase("nonExistingKey");
System.out.println("Result: " + result);
}
}
3. 设置异常值
对于一些恶意攻击或误操作导致的缓存穿透问题,可以通过设置异常值来处理。例如,定义一个特殊的标识符表示数据不存在。
实现思路:
- 当数据库查询不到数据时,返回一个特殊值(如
-1
或"NOT_FOUND"
)。 - 缓存这个特殊值,并在后续查询时直接返回。
java
public class CachePenetrationSolutionWithSpecialValue {
private static final String NOT<em>FOUND = "NOT</em>FOUND";</p>
<pre><code>public static String getFromCacheOrDatabase(String key) {
String cacheValue = jedis.get(key);
if (cacheValue == null) {
String dbValue = queryDatabase(key);
if (dbValue == null) {
// 缓存特殊值
jedis.setex(key, 3600, NOT_FOUND);
return NOT_FOUND;
} else {
jedis.setex(key, 3600, dbValue);
return dbValue;
}
} else {
return NOT_FOUND.equals(cacheValue) ? null : cacheValue;
}
}
private static String queryDatabase(String key) {
if ("existingKey".equals(key)) {
return "value";
}
return null;
}
}
缓存穿透是分布式系统中常见的性能问题,但通过合理的解决方案可以有效避免。布隆过滤器适用于大规模数据场景,能够快速判断数据是否存在;缓存空对象和设置异常值则更加简单直接,适合中小规模应用。根据实际需求选择合适的方案,才能更好地提升系统的稳定性和性能。