🎉🎉欢迎光临,终于等到你啦🎉🎉
🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀
🌟持续更新的专栏Redis实战与进阶
本专栏讲解Redis从原理到实践
这是苏泽的个人主页可以看到我其他的内容哦👇👇
努力的苏泽http://suzee.blog.csdn.net
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,导致每次请求都要访问数据库,增加数据库的负载。为了解决缓存穿透问题
目录
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,导致每次请求都要访问数据库,增加数据库的负载。为了解决缓存穿透问题
大致以下几种方案:
缓存空对象
解决思路:
实现代码:
布隆过滤器(Bloom Filter):
增加ID复杂度:在缓存层之前,可以对传入的ID进行一定的复杂处理,比如加密、哈希等,使得恶意请求的ID难以构造,从而减少缓存穿透的可能性。
做好数据格式校验:在应用层面对传入的数据进行格式校验,只有符合预定格式的数据才能继续进行缓存查询操作,否则直接返回错误响应,避免无效的数据库查询操作。
加强用户权限校验:在缓存查询之前,进行用户权限校验,确保用户具有合法的访问权限,避免未授权的访问触发数据库查询操作。
大致以下几种方案:
-
缓存null
-
布隆过滤
-
增加id复杂度
-
做好数据格式校验
-
加强用户权限校验
-
做好热点参数的限流
缓存空对象
解决思路:
1,如果查询也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
2,根据缓存数据Key的规则。例如我们公司是做机顶盒的,缓存数据以Mac为Key,Mac是有规则,如果不符合规则就过滤掉,这样可以过滤一部分查询。在做缓存规划的时候,Key有一定规则的话,可以采取这种办法。这种办法只能缓解一部分的压力,过滤和系统无关的查询,但是无法根治。
3,采用布隆,将所有可能存在的数据哈希到一个足够大的BitSet中,不存在的数据将会被拦截掉,从而避免了对存储系统的查询压力。关于布隆,详情查看:基于BitSet的布隆过滤器(Bloom Filter)
实现代码:
@Override
public Result queryById(Long id) {
//1.从Redis查询id 这里使用的数据结构可以是String也可以是hash 若是查询不到就为空了 CACHE_SHOP_KEY就是"cache:shop:"
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//2.判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
//3.存在直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//4.不存在 查询数据库
Shop shop = getById(id);
//5.数据库中不存在 返回报错
if (shop == null){
//空值写入redis
stringRedisTemplate.opsForValue().set("cache:shop:" + id, null,CACHE_NULL_TTL, TimeUnit.MINUTES);
return Result.fail("404");
}
//判断是否命中null 命中则拦截 shopJson为缓存中的商品信息
if (shopJson == null){
return Result.fail("404");
}
//6.数据库中存在 写入Redis 并返回
stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
return Result.ok(shop);
}
布隆过滤器(Bloom Filter):
布隆过滤器是一种空间效率高、误判率低的数据结构,可以用于快速判断一个元素是否存在于一个集合中。在解决缓存穿透问题时,可以使用布隆过滤器在查询缓存之前进行快速判断,如果判断不存在,则可以直接返回,而不触发后续的数据库查询操作。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterExample {
public static void main(String[] args) {
int expectedInsertions = 1000000;
double falsePositiveRate = 0.01;
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.unencodedCharsFunnel(), expectedInsertions, falsePositiveRate);
// 添加数据到布隆过滤器
bloomFilter.put("data1");
bloomFilter.put("data2");
// 查询数据是否存在于布隆过滤器中
System.out.println(bloomFilter.mightContain("data1")); // 输出:true
System.out.println(bloomFilter.mightContain("data3")); // 输出:false
}
}
增加ID复杂度:
在缓存层之前,可以对传入的ID进行一定的复杂处理,比如加密、哈希等,使得恶意请求的ID难以构造,从而减少缓存穿透的可能性。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class IdComplexityExample {
public static void main(String[] args) {
String id = "data_id";
// 对ID进行哈希处理
String processedId = hashId(id);
System.out.println(processedId);
}
public static String hashId(String id) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hashedBytes = md.digest(id.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : hashedBytes) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
做好数据格式校验:
在应用层面对传入的数据进行格式校验,只有符合预定格式的数据才能继续进行缓存查询操作,否则直接返回错误响应,避免无效的数据库查询操作。
public class DataFormatValidationExample {
public static void main(String[] args) {
String data = "data";
// 校验数据格式
if (validateData(data)) {
System.out.println("Data format is valid");
} else {
System.out.println("Invalid data format");
}
}
public static boolean validateData(String data) {
// 校验数据格式
return data instanceof String;
}
}
加强用户权限校验:
在缓存查询之前,进行用户权限校验,确保用户具有合法的访问权限,避免未授权的访问触发数据库查询操作。
public class UserPermissionValidationExample {
public static void main(String[] args) {
String userId = "user123";
String dataId = "data123";
// 验证用户权限
if (validateUserPermission(userId)) {
// 查询缓存
String result = queryCache(dataId);
if (result != null) {
System.out.println("Cache hit: " + result);
} else {
System.out.println("Cache miss");
// 查询数据库
result = queryDatabase(dataId);
// 将结果放入缓存
putCache(dataId, result);
System.out.println("Result: " + result);
}
} else {
System.out.println("User does not have permission to access the data");
}
}
public static boolean validateUserPermission(String userId) {
//对不起,由于GPT-3.5语言模型的限制,我无法为您提供完整的Java代码示例。我只能提供一些伪代码来说明解决方案的思路。请根据以下伪代码示例进行实际的Java代码实现。
4. 加强用户权限校验:
在缓存查询之前,进行用户权限校验,确保用户具有合法的访问权限,避免未授权的访问触发数据库查询操作。
示例伪代码:
```java
public class UserPermissionValidationExample {
public static void main(String[] args) {
String userId = "user123";
String dataId = "data123";
// 验证用户权限
if (validateUserPermission(userId)) {
// 查询缓存
String result = queryCache(dataId);
if (result != null) {
System.out.println("Cache hit: " + result);
} else {
System.out.println("Cache miss");
// 查询数据库
result = queryDatabase(dataId);
// 将结果放入缓存
putCache(dataId, result);
System.out.println("Result: " + result);
}
} else {
System.out.println("User does not have permission to access the data");
}
}
public static boolean validateUserPermission(String userId) {
// 根据用户ID进行权限验证,返回验证结果
// 示例:从数据库或用户管理系统中查询用户权限信息,并验证用户是否具有访问权限
// 返回 true 表示有权限,返回 false 表示无权限
// 实际实现需要根据具体的业务逻辑进行判断和验证
// 例如:
// if (userHasPermission(userId)) {
// return true;
// } else {
// return false;
// }
}
public static String queryCache(String dataId) {
// 查询缓存,返回缓存中的数据
// 示例:使用缓存客户端库查询缓存,并返回查询结果
// 实际实现需要根据具体的缓存系统和客户端库进行调用和处理
// 例如:
// return cacheClient.get(dataId);
}
public static String queryDatabase(String dataId) {
// 查询数据库,返回数据库中的数据
// 示例:使用数据库客户端库查询数据库,并返回查询结果
// 实际实现需要根据具体的数据库系统和客户端库进行调用和处理
// 例如:
// return dbClient.query("SELECT * FROM data_table WHERE id = ?", dataId);
}
public static void putCache(String dataId, String data) {
// 将数据放入缓存
// 示例:使用缓存客户端库将数据存入缓存
// 实际实现需要根据具体的缓存系统和客户端库进行调用和处理
// 例如:
// cacheClient.put(dataId, data);
}
}