解决Redis缓存穿透问题的方法有多种,具体的解决方案可以根据实际情况选择。以下是几种常见的解决方法及详细的代码示例,包括缓存空结果、使用布隆过滤器以及参数校验。
1. 缓存空结果
当查询数据库返回空结果时,也将其缓存起来,并设置一个较短的过期时间,比如5分钟。这样即使请求数据不存在,也不需要每次都访问数据库。
示例代码:
1import redis.clients.jedis.Jedis; 2import com.fasterxml.jackson.databind.ObjectMapper; 3 4public class CachePenetrationExample { 5 private Jedis jedis; 6 private static final ObjectMapper objectMapper = new ObjectMapper(); 7 8 public CachePenetrationExample(Jedis jedis) { 9 this.jedis = jedis; 10 } 11 12 public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) { 13 try { 14 String cacheValue = jedis.get(key); 15 if (cacheValue != null) { 16 if (cacheValue.equals("null")) { 17 return null; 18 } 19 return objectMapper.readValue(cacheValue, clazz); 20 } 21 22 T data = provider.getData(); 23 if (data == null) { 24 jedis.setex(key, 300, "null"); // 缓存空结果5分钟 25 } else { 26 jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data)); 27 } 28 return data; 29 } catch (Exception e) { 30 e.printStackTrace(); 31 return null; 32 } 33 } 34 35 public interface DataProvider<T> { 36 T getData(); 37 } 38 39 public static void main(String[] args) { 40 Jedis jedis = new Jedis("localhost", 6379); 41 CachePenetrationExample cache = new CachePenetrationExample(jedis); 42 43 String userId = "nonexistent"; 44 String cacheKey = "user:" + userId; 45 int cacheTime = 3600; // 缓存 1 小时 46 47 User user = cache.getCachedData(cacheKey, User.class, () -> { 48 // 模拟数据库查询,返回null表示数据不存在 49 return null; 50 }, cacheTime); 51 52 if (user == null) { 53 System.out.println("User not found."); 54 } else { 55 System.out.println("User: " + user); 56 } 57 58 jedis.close(); 59 } 60 61 static class User { 62 private String id; 63 private String name; 64 private String email; 65 66 // Getters and Setters 67 68 @Override 69 public String toString() { 70 return "User{id='" + id + "', name='" + name + "', email='" + email + "'}"; 71 } 72 } 73} 74
2. 布隆过滤器
使用布隆过滤器快速判断数据是否存在。布隆过滤器是一种概率型数据结构,它能以较低的内存消耗判断一个元素是否存在某个集合中。虽然存在误判的可能,但不会漏判。
示例代码:
1import com.google.common.hash.BloomFilter; 2import com.google.common.hash.Funnels; 3import redis.clients.jedis.Jedis; 4import java.nio.charset.Charset; 5 6public class BloomFilterExample { 7 private Jedis jedis; 8 private BloomFilter<String> bloomFilter; 9 10 public BloomFilterExample(Jedis jedis, int expectedInsertions, double falsePositiveProbability) { 11 this.jedis = jedis; 12 this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, falsePositiveProbability); 13 // 初始化布隆过滤器,通常从数据库加载已有数据 14 initializeBloomFilter(); 15 } 16 17 private void initializeBloomFilter() { 18 // 模拟加载数据 19 bloomFilter.put("existingUser1"); 20 bloomFilter.put("existingUser2"); 21 // 真实环境中应从数据库加载 22 } 23 24 public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) { 25 try { 26 if (!bloomFilter.mightContain(key)) { 27 return null; 28 } 29 30 String cacheValue = jedis.get(key); 31 if (cacheValue != null) { 32 if (cacheValue.equals("null")) { 33 return null; 34 } 35 return objectMapper.readValue(cacheValue, clazz); 36 } 37 38 T data = provider.getData(); 39 if (data == null) { 40 jedis.setex(key, 300, "null"); // 缓存空结果5分钟 41 } else { 42 jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data)); 43 } 44 return data; 45 } catch (Exception e) { 46 e.printStackTrace(); 47 return null; 48 } 49 } 50 51 public interface DataProvider<T> { 52 T getData(); 53 } 54 55 public static void main(String[] args) { 56 Jedis jedis = new Jedis("localhost", 6379); 57 BloomFilterExample cache = new BloomFilterExample(jedis, 10000, 0.01); 58 59 String userId = "nonexistent"; // 修改这个值来测试存在和不存在的情况 60 String cacheKey = "user:" + userId; 61 int cacheTime = 3600; // 缓存 1 小时 62 63 User user = cache.getCachedData(cacheKey, User.class, () -> { 64 // 模拟数据库查询,返回null表示数据不存在 65 return null; 66 }, cacheTime); 67 68 if (user == null) { 69 System.out.println("User not found."); 70 } else { 71 System.out.println("User: " + user); 72 } 73 74 jedis.close(); 75 } 76 77 static class User { 78 private String id; 79 private String name; 80 private String email; 81 82 // Getters and Setters 83 84 @Override 85 public String toString() { 86 return "User{id='" + id + "', name='" + name + "', email='" + email + "'}"; 87 } 88 } 89} 90
3. 参数校验
对请求参数进行严格校验,过滤掉明显无效的请求。例如,用户ID应为正整数,可以在程序中对用户ID进行校验,过滤掉不符合规则的请求。
示例代码:
1import redis.clients.jedis.Jedis; 2 3public class ParameterValidationExample { 4 private Jedis jedis; 5 6 public ParameterValidationExample(Jedis jedis) { 7 this.jedis = jedis; 8 } 9 10 public <T> T getCachedData(String key, Class<T> clazz, DataProvider<T> provider, int cacheTime) { 11 try { 12 if (!isValidKey(key)) { 13 return null; 14 } 15 16 String cacheValue = jedis.get(key); 17 if (cacheValue != null) { 18 if (cacheValue.equals("null")) { 19 return null; 20 } 21 return objectMapper.readValue(cacheValue, clazz); 22 } 23 24 T data = provider.getData(); 25 if (data == null) { 26 jedis.setex(key, 300, "null"); // 缓存空结果5分钟 27 } else { 28 jedis.setex(key, cacheTime, objectMapper.writeValueAsString(data)); 29 } 30 return data; 31 } catch (Exception e) { 32 e.printStackTrace(); 33 return null; 34 } 35 } 36 37 private boolean isValidKey(String key) { 38 // 仅示例性校验,实际中应视具体情况进行校验 39 return key != null && key.matches("^user:\\d+$"); 40 } 41 42 public interface DataProvider<T> { 43 T getData(); 44 } 45 46 public static void main(String[] args) { 47 Jedis jedis = new Jedis("localhost", 6379); 48 ParameterValidationExample cache = new ParameterValidationExample(jedis); 49 50 String userId = "123"; // 正确的用户ID 51 String cacheKey = "user:" + userId; 52 int cacheTime = 3600; // 缓存 1 小时 53 54 User user = cache.getCachedData(cacheKey, User.class, () -> { 55 // 模拟数据库查询 56 return getUserFromDatabase(userId); 57 }, cacheTime); 58 59 if (user == null) { 60 System.out.println("User not found."); 61 } else { 62 System.out.println("User: " + user); 63 } 64 65 jedis.close(); 66 } 67 68 private static User getUserFromDatabase(String userId) { 69 // 模拟数据库查询 70 User user = new User(); 71 user.setId(userId); 72 user.setName("John Doe"); 73 user.setEmail("[email protected]"); 74 return user; 75 } 76 77 static class User { 78 private String id; 79 private String name; 80 private String email; 81 82 // Getters and Setters 83 84 @Override 85 public String toString() { 86 return "User{id='" + id + "', name='" + name + "', email='" + email + "'}"; 87 } 88 } 89} 90
总结
通过以上示例代码,您可以分别使用缓存空结果、布隆过滤器和参数校验的方法来解决Redis的缓存穿透问题。选择合适的方法可以有效地减少对数据库的无效访问,提高系统的性能和稳定性。
《Redis(80)如何解决Redis的缓存穿透问题?》 是转载文章,点击查看原文。
