Redis实战:缓存穿透及其解决思路 实战演示

🎉🎉欢迎光临,终于等到你啦🎉🎉

🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀

🌟持续更新的专栏Redis实战与进阶

本专栏讲解Redis从原理到实践

这是苏泽的个人主页可以看到我其他的内容哦👇👇

努力的苏泽icon-default.png?t=N7T8http://suzee.blog.csdn.net


缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,导致每次请求都要访问数据库,增加数据库的负载。为了解决缓存穿透问题

目录

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,导致每次请求都要访问数据库,增加数据库的负载。为了解决缓存穿透问题

大致以下几种方案:

缓存空对象

解决思路:

实现代码: 

布隆过滤器(Bloom Filter):

增加ID复杂度:在缓存层之前,可以对传入的ID进行一定的复杂处理,比如加密、哈希等,使得恶意请求的ID难以构造,从而减少缓存穿透的可能性。

做好数据格式校验:在应用层面对传入的数据进行格式校验,只有符合预定格式的数据才能继续进行缓存查询操作,否则直接返回错误响应,避免无效的数据库查询操作。

加强用户权限校验:在缓存查询之前,进行用户权限校验,确保用户具有合法的访问权限,避免未授权的访问触发数据库查询操作。


大致以下几种方案:

  1. 缓存null

  2. 布隆过滤

  3. 增加id复杂度

  4. 做好数据格式校验

  5. 加强用户权限校验

  6. 做好热点参数的限流

缓存空对象

解决思路:

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);
    }
}

最近更新

  1. TCP协议是安全的吗?

    2024-03-25 08:40:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-25 08:40:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-25 08:40:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-25 08:40:02       20 阅读

热门阅读

  1. 【DevOps云实践】Azure Function中使用发布/订阅模式

    2024-03-25 08:40:02       18 阅读
  2. spring boot常见的面试题

    2024-03-25 08:40:02       17 阅读
  3. 解决 Jupyter Notebook 中没有显示想要的内核的问题

    2024-03-25 08:40:02       18 阅读
  4. C语言题目:字符提取(自定义函数)

    2024-03-25 08:40:02       19 阅读
  5. ipv4、ipv6、tcp、udp包结构以及字段解释

    2024-03-25 08:40:02       21 阅读
  6. 快速入门Kotlin③类与对象

    2024-03-25 08:40:02       20 阅读
  7. 如何理解React

    2024-03-25 08:40:02       21 阅读