解决Redis缓存穿透问题的方法有多种,具体的解决方案可以根据实际情况选择。以下是几种常见的解决方法及详细的代码示例,包括缓存空结果、使用布隆过滤器以及参数校验。
1. 缓存空结果
当查询数据库返回空结果时,也将其缓存起来,并设置一个较短的过期时间,比如5分钟。这样即使请求数据不存在,也不需要每次都访问数据库。
示例代码:
java
import redis.clients.jedis.Jedis;
import com.fasterxml.jackson.databind.ObjectMapper;
public class CachePenetrationExample {
private Jedis jedis;
private static final ObjectMapper objectMapper = new ObjectMapper();
public CachePenetrationExample(Jedis jedis) {
this.jedis = jedis;
}
public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) {
try {
String cacheValue = jedis.get(key);
if (cacheValue != null) {
if (cacheValue.equals("null")) {
return null;
}
return objectMapper.readValue(cacheValue, clazz);
}
T data = provider.getData();
if (data == null) {
jedis.setex(key, 300, "null"); // 缓存空结果5分钟
} else {
jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data));
}
return data;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public interface DataProvider<T> {
T getData();
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
CachePenetrationExample cache = new CachePenetrationExample(jedis);
String userId = "nonexistent";
String cacheKey = "user:" + userId;
int cacheTime = 3600; // 缓存 1 小时
User user = cache.getCachedData(cacheKey, User.class, () -> {
// 模拟数据库查询,返回null表示数据不存在
return null;
}, cacheTime);
if (user == null) {
System.out.println("User not found.");
} else {
System.out.println("User: " + user);
}
jedis.close();
}
static class User {
private String id;
private String name;
private String email;
// Getters and Setters
@Override
public String toString() {
return "User{id='" + id + "', name='" + name + "', email='" + email + "'}";
}
}
}
2. 布隆过滤器
使用布隆过滤器快速判断数据是否存在。布隆过滤器是一种概率型数据结构,它能以较低的内存消耗判断一个元素是否存在某个集合中。虽然存在误判的可能,但不会漏判。
示例代码:
java
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;
import java.nio.charset.Charset;
public class BloomFilterExample {
private Jedis jedis;
private BloomFilter<String> bloomFilter;
public BloomFilterExample(Jedis jedis, int expectedInsertions, double falsePositiveProbability) {
this.jedis = jedis;
this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, falsePositiveProbability);
// 初始化布隆过滤器,通常从数据库加载已有数据
initializeBloomFilter();
}
private void initializeBloomFilter() {
// 模拟加载数据
bloomFilter.put("existingUser1");
bloomFilter.put("existingUser2");
// 真实环境中应从数据库加载
}
public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) {
try {
if (!bloomFilter.mightContain(key)) {
return null;
}
String cacheValue = jedis.get(key);
if (cacheValue != null) {
if (cacheValue.equals("null")) {
return null;
}
return objectMapper.readValue(cacheValue, clazz);
}
T data = provider.getData();
if (data == null) {
jedis.setex(key, 300, "null"); // 缓存空结果5分钟
} else {
jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data));
}
return data;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public interface DataProvider<T> {
T getData();
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
BloomFilterExample cache = new BloomFilterExample(jedis, 10000, 0.01);
String userId = "nonexistent"; // 修改这个值来测试存在和不存在的情况
String cacheKey = "user:" + userId;
int cacheTime = 3600; // 缓存 1 小时
User user = cache.getCachedData(cacheKey, User.class, () -> {
// 模拟数据库查询,返回null表示数据不存在
return null;
}, cacheTime);
if (user == null) {
System.out.println("User not found.");
} else {
System.out.println("User: " + user);
}
jedis.close();
}
static class User {
private String id;
private String name;
private String email;
// Getters and Setters
@Override
public String toString() {
return "User{id='" + id + "', name='" + name + "', email='" + email + "'}";
}
}
}
3. 参数校验
对请求参数进行严格校验,过滤掉明显无效的请求。例如,用户ID应为正整数,可以在程序中对用户ID进行校验,过滤掉不符合规则的请求。
示例代码:
java
import redis.clients.jedis.Jedis;
public class ParameterValidationExample {
private Jedis jedis;
public ParameterValidationExample(Jedis jedis) {
this.jedis = jedis;
}
public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) {
try {
if (!isValidKey(key)) {
return null;
}
String cacheValue = jedis.get(key);
if (cacheValue != null) {
if (cacheValue.equals("null")) {
return null;
}
return objectMapper.readValue(cacheValue, clazz);
}
T data = provider.getData();
if (data == null) {
jedis.setex(key, 300, "null"); // 缓存空结果5分钟
} else {
jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data));
}
return data;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private boolean isValidKey(String key) {
// 仅示例性校验,实际中应视具体情况进行校验
return key != null && key.matches("^user:\\d+$");
}
public interface DataProvider<T> {
T getData();
}
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
ParameterValidationExample cache = new ParameterValidationExample(jedis);
String userId = "123"; // 正确的用户ID
String cacheKey = "user:" + userId;
int cacheTime = 3600; // 缓存 1 小时
User user = cache.getCachedData(cacheKey, User.class, () -> {
// 模拟数据库查询
return getUserFromDatabase(userId);
}, cacheTime);
if (user == null) {
System.out.println("User not found.");
} else {
System.out.println("User: " + user);
}
jedis.close();
}
private static User getUserFromDatabase(String userId) {
// 模拟数据库查询
User user = new User();
user.setId(userId);
user.setName("John Doe");
user.setEmail("john.doe@example.com");
return user;
}
static class User {
private String id;
private String name;
private String email;
// Getters and Setters
@Override
public String toString() {
return "User{id='" + id + "', name='" + name + "', email='" + email + "'}";
}
}
}
总结
通过以上示例代码,您可以分别使用缓存空结果、布隆过滤器和参数校验的方法来解决Redis的缓存穿透问题。选择合适的方法可以有效地减少对数据库的无效访问,提高系统的性能和稳定性。