首页 > 数据库 >Redis 缓存穿透是什么?如何缓解缓存穿透?

Redis 缓存穿透是什么?如何缓解缓存穿透?

时间:2024-03-25 16:30:33浏览次数:20  
标签:缓存 布谷鸟 Redis 布隆 穿透 哈希 过滤器

缓存穿透是指在使用缓存技术时,恶意或无效的请求无法从缓存中获取到数据,从而直接落到底层存储系统(如数据库)上,导致频繁地查询底层存储系统,增加系统负载并降低性能。

缓存通常用于存储经常被请求的数据,以提高系统的访问速度。但是,当恶意用户故意发送无效或不存在的请求时,缓存无法命中,导致每次请求都直接访问底层存储系统。如果这种情况持续发生,会对底层存储系统造成严重的压力,并使系统无法承受高负载。

缓解方式

布隆过滤器(Bloom Filter)

布隆过滤器(Bloom Filter)是一种概率型数据结构,用于快速判断某个元素是否属于一个集合,并具有高效的存储和查询性能。它通过使用位数组和多个哈希函数来实现。

布隆过滤器的核心是一个长度为 m 的位数组(bitmap)和 k 个独立的哈希函数。初始时,位数组中的所有位都被置为0。

当要将一个元素插入到布隆过滤器中时,该元素会依次经过 k 个哈希函数的运算,每个哈希函数都会计算出一个位数组的索引位置,将对应位数组位置置为1(即标记)。例如,哈希函数1计算得到索引位置为7,哈希函数2计算得到索引位置为12,那么位数组的第7和第12个位置都会被置为1。

当需要判断一个元素是否属于布隆过滤器时,同样会经过 k 个哈希函数的运算,检查对应索引位置的位数组值是否都为1。如果发现有任意一个位数组位置为0,则可以判定元素不属于布隆过滤器;如果所有位数组位置都为1,则只能说元素可能存在于布隆过滤器中,但并非绝对准确,存在一定的误判率。

布隆过滤器的误判率(false positive)取决于位数组的长度 m 和哈希函数的个数 k,同时也与插入的元素数量和布隆过滤器的设计有关。通过调整位数组长度和哈希函数个数,可以控制误判率的发生概率。

布隆过滤器的主要优点是占用空间小且查询效率高,在处理大规模数据集时,可以有效地降低底层存储系统的访问频率。然而,布隆过滤器也有一些限制,如不支持删除操作、存在一定的误判率和无法获取具体的误判元素等。

布隆过滤器在实际应用中常用于缓存系统、网络爬虫、数据库查询优化以及信息安全等领域,用于过滤掉明显不存在的数据,减少不必要的查询或处理开销,提高系统性能。

安装使用

在 Redis 中,可以使用 RedisBloom 模块来实现布隆过滤器的功能。RedisBloom 是一个开源的 Redis 模块,提供了多种布隆过滤器相关的功能。

要在 Redis 上使用布隆过滤器,需要先安装 RedisBloom 模块。可以通过以下步骤进行安装:

  1. 下载 RedisBloom 源代码:

git clone https://github.com/RedisBloom/RedisBloom.git
  1. 编译 RedisBloom 模块:

cd RedisBloom
make
  1. 将编译生成的 RedisBloom.so 文件复制到 Redis 的模块目录:

cp redisbloom.so /path/to/redis/modules/
  1. 打开 Redis 配置文件 redis.conf,并在 "Modules" 部分添加以下配置:

loadmodule /path/to/redis/modules/redisbloom.so
  1. 重启 Redis 使配置生效。

安装完毕后,就可以使用 RedisBloom 提供的布隆过滤器功能了。以下是一些常用的 RedisBloom 命令:

  • BF.ADD:将元素插入到布隆过滤器中。

    BF.ADD <key> <item>
  • BF.EXISTS:判断元素是否存在于布隆过滤器中。

    BF.EXISTS <key> <item>
  • BF.MADD:批量插入多个元素到布隆过滤器中。

    BF.MADD <key> <item1> [<item2> ... <itemN>]
  • BF.MEXISTS:批量判断多个元素是否存在于布隆过滤器中。

    BF.MEXISTS <key> <item1> [<item2> ... <itemN>]
  • BF.INFO:获取布隆过滤器的基本信息,如误判率、元素数量等。

    BF.INFO <key>

在上述命令中,<key> 代表布隆过滤器的键名,<item> 代表要插入或查询的元素。可以使用不同的键名创建多个独立的布隆过滤器。

需要注意的是,RedisBloom 的布隆过滤器在创建时需要指定预期的元素数量和误判率参数。通过调整这些参数,可以在满足需求的情况下控制内存占用和误判率。

布谷鸟过滤器(Cuckoo Filter)

布谷鸟过滤器(Cuckoo Filter)是一种基于哈希的概率数据结构,用于快速判断一个元素是否在集合中。它是布隆过滤器的一种变体,相比于传统的布隆过滤器,布谷鸟过滤器有较低的空间消耗并提供了更高的查询性能。

布谷鸟过滤器的原理如下:

  1. 布谷鸟过滤器由一个哈希表和一组哈希函数构成。

  2. 对于每个插入的元素,使用哈希函数对其进行多次哈希操作,然后将哈希结果存储在哈希表中。

  3. 当检查一个元素是否存在时,同样使用相同的哈希函数对其进行哈希操作,并查询哈希表中的对应位置。

  4. 如果所有哈希函数得到的位置都包含了此元素,那么插入时这个元素可能在集合中;如果有一个或多个位置为空,那么插入时这个元素一定不在集合中。

布谷鸟过滤器的优点是它不仅可以判断元素是否存在,还可以删除存在的元素。

安装使用

在 Redis 上使用布谷鸟过滤器,可以使用 RedisBloom 模块中的 CF(Cuckoo Filter)命令。以下是一些常用的 RedisBloom CF 命令:

  • CF.ADD:将元素插入到布谷鸟过滤器中。

    CF.ADD <key> <item>
  • CF.EXISTS:判断元素是否存在于布谷鸟过滤器中。

    CF.EXISTS <key> <item>
  • CF.DEL:从布谷鸟过滤器中删除指定元素。

    CF.DEL <key> <item>
  • CF.INSERT:批量插入多个元素到布谷鸟过滤器中。

    CF.INSERT <key> <item1> [<item2> ... <itemN>]
  • CF.EXISTS:批量判断多个元素是否存在于布谷鸟过滤器中。

    CF.EXISTS <key> <item1> [<item2> ... <itemN>]
  • CF.INFO:获取布谷鸟过滤器的基本信息,如过滤器容量、元素数量等。

    CF.INFO <key>

在上述命令中,<key> 代表布谷鸟过滤器的键名,<item> 代表要插入、查询或删除的元素。可以使用不同的键名创建多个独立的布谷鸟过滤器。

需要注意的是,布谷鸟过滤器在创建时需要指定容量参数。通过调整容量参数,可以在满足需求的情况下控制内存占用和查询性能。

与布隆对比

布隆过滤器(Bloom Filter)和布谷鸟过滤器(Cuckoo Filter)都是概率型数据结构,用于判断一个元素是否属于一个集合。它们之间存在一些区别:

  1. 存储结构:

    • 布隆过滤器: 布隆过滤器通常由一个位数组和若干个哈希函数组成,位数组中的每个位表示一个元素的存在与否。

    • 布谷鸟过滤器: 布谷鸟过滤器由一个哈希表和一组哈希函数构成,哈希表中存储了元素的哈希值。

  2. 内存消耗:

    • 布隆过滤器: 布隆过滤器通过位数组来表示元素的存在与否,因此在存储空间上相对紧凑。

    • 布谷鸟过滤器: 布谷鸟过滤器相比于布隆过滤器有较低的空间消耗,但一般会稍微多占用一些内存。

  3. 查找性能:

    • 布隆过滤器: 布隆过滤器具有高效的查询性能,可以在常数时间内判断一个元素是否存在于集合中。但布隆过滤器在查询时存在一定的误判率(可能会误判某个元素存在)。

    • 布谷鸟过滤器: 布谷鸟过滤器在查询性能上通常优于布隆过滤器,可以在常数时间内完成查询操作。而且布谷鸟过滤器对于存在于集合中的元素一定不会产生误判。

  4. 删除操作:

    • 布隆过滤器: 布隆过滤器不支持直接删除元素,因为删除一个元素会影响其他元素的判断结果。如果需要删除元素,通常需要使用其他的技巧,或者重新构建一个新的布隆过滤器。

    • 布谷鸟过滤器: 布谷鸟过滤器支持元素的删除操作,可以直接从过滤器中删除一个存在的元素。

总体而言,布隆过滤器适用于对存储空间有限制且对查询性能要求较高的场景,而布谷鸟过滤器则在一些空间相对宽裕的场景下提供了更好的性能和功能。选择使用哪种过滤器应根据具体的应用需求来决定。

缓存空对象(Cache Null Objects)

当缓存无法命中时,可以在缓存中存储一个特殊的空对象或占位符,表示该请求所对应的数据为空。这样,当再次发生相同的无效请求时,可以直接从缓存中获取到空对象,而不必访问底层存储系统。

实现
  1. 设定一个特殊的值,例如"NULL"或"EMPTY",用于表示空对象。

  2. 在获取对象时,先从Redis中查询对应的键值。

  3. 如果键存在且对应的值不等于特殊值,说明对象存在,直接返回该值。

  4. 如果键不存在或对应的值等于特殊值,说明对象为空,返回空结果。

  5. 当要缓存一个空对象时,将特殊值作为值存储到Redis中对应的键。

Example
require 'predis/autoload.php';
​
// 连接 Redis
$client = new Predis\Client();
​
// 获取对象
function get_object($key) {
    global $client;
    $value = $client->get($key);
    if ($value === null || $value === 'NULL') {
        return null; // 返回空对象
    } else {
        return $value; // 返回对象值
    }
}
​
// 缓存对象
function cache_object($key, $value) {
    global $client;
    if ($value === null) {
        $client->set($key, 'NULL');
    } else {
        $client->set($key, $value);
    }
}
​
// 示例使用
$object_key = 'my_object';
​
// 获取对象
$cached_object = get_object($object_key);
if ($cached_object === null) {
    echo "对象不存在或为空\n";
} else {
    echo "对象值: $cached_object\n";
}
​
// 缓存空对象
cache_object($object_key, null);
​

请求参数校验(Request Parameter Validation)

在应用层面对请求参数进行严格的校验,过滤掉无效或非法的请求。通过验证请求参数的有效性,可以在早期阶段抛弃掉无效请求,从而减轻底层存储系统的负载.

在 Redis 中,可以使用 Lua 脚本来实现请求参数的校验。使用 Lua 脚本可以在 Redis 服务器端执行一系列的操作,并返回结果给客户端,这样可以在单个原子操作中完成参数校验。

Example
lua-- 脚本参数:1-主键,2-请求参数
local key = KEYS[1]
local requestParam = ARGV[1]
​
-- 获取存储在 Redis 中的参数值
local storedParam = redis.call('GET', key)
​
-- 进行参数校验
if requestParam == storedParam then
    -- 参数校验通过,返回成功结果
    return "OK"
else
    -- 参数校验失败,返回错误结果
    return "ERROR"
end

在 PHP 中,可以使用 Predis 库来执行 Lua 脚本。以下是一个示例代码:

phprequire 'predis/autoload.php';
​
// 连接 Redis
$client = new Predis\Client();
​
// 执行 Lua 脚本进行参数校验
function execute_validation_script($key, $requestParam) {
    global $client;
​
    // 定义 Lua 脚本
    $script = "
        local key = KEYS[1]
        local requestParam = ARGV[1]
​
        local storedParam = redis.call('GET', key)
​
        if requestParam == storedParam then
            return 'OK'
        else
            return 'ERROR'
        end";
​
    // 执行脚本
    $result = $client->eval($script, 1, $key, $requestParam);
​
    return $result;
}
​
// 示例使用
$param_key = 'param_key';
$request_param = 'example_value';
​
// 执行参数校验脚本
$result = execute_validation_script($param_key, $request_param);
​
// 处理结果
if ($result === 'OK') {
    echo "参数校验通过\n";
} else {
    echo "参数校验失败\n";
}

缓存预热(Cache Pre-warming)

在系统启动或低负载期间,预先加载常用数据到缓存中,提前填充缓存。这样可以避免在高负载时期发生大量的缓存穿透,因为常用数据已经预先缓存,可以直接从缓存中获取,而不必访问底层存储系统。

实现
  1. 确定需要预热的数据:首先确定需要预热的数据集,可以是一些常用的热点数据、经常查询的数据,或者是应用程序启动时必须的数据。

  2. 编写预热脚本:编写一个脚本来加载数据到Redis缓存中。根据数据量的大小和加载过程的复杂程度,可以选择使用编程语言的Redis客户端(如PHP的Predis库)或直接使用Redis的命令行工具(redis-cli)。

  3. 预热数据到Redis缓存:在应用程序启动之前,调用预热脚本来将数据加载到Redis缓存中。可以通过循环遍历数据集并逐个添加到缓存中,或者使用Redis的批量操作命令(如MSET)来更高效地加载数据。

  4. 启动应用程序:在数据加载完成后,启动应用程序。此时应用程序可以直接从Redis缓存中获取数据,而无需从其他数据源(如数据库)中查询数据,提高了响应速度和性能。

require 'predis/autoload.php';
​
// 连接 Redis
$client = new Predis\Client();
​
// 预热数据到Redis缓存
function preload_data() {
    global $client;
​
    // 遍历数据集,以键值对的形式添加到Redis缓存中
    $data = [
        'key1' => 'value1',
        'key2' => 'value2',
        // 添加更多的键值对...
    ];
​
    foreach ($data as $key => $value) {
        $client->set($key, $value);
    }
​
    echo "数据预热完成\n";
}
​
// 执行预热操作
preload_data();
​
// 在应用程序中使用缓存数据
$result = $client->get('key1');
if ($result === null) {
    echo "缓存中不存在该数据\n";
} else {
    echo "获取到缓存数据: $result\n";
}

标签:缓存,布谷鸟,Redis,布隆,穿透,哈希,过滤器
From: https://blog.csdn.net/weixin_42576071/article/details/136940292

相关文章

  • redis自学(24)RESP协议
    Redis是一个CS架构的软件,通信一般分两步(不包括pipeline和PubSub):①客户端(client)向服务端(server)发送一条命令②服务端解析并执行命令,返回响应结果给客户端因此客户端发送命令的格式、服务端相应结果的格式必须有一个规范,这个规范就是通讯协议。而在Redis中采用的是RESP(Redi......
  • 清理系统centos下缓存并释放内存
    问题描述在启动容器的时候报错Exceptioninthread"main"java.lang.RuntimeException:startingjavafailedwith[1]output:##ThereisinsufficientmemoryfortheJavaRuntimeEnvironmenttocontinue.#Nativememoryallocation(mmap)failedtomap832988774......
  • redis哨兵 ,redis集群 缓存 以及某些问题: 最左前缀原则,,celery架构
    Redis哨兵#主从复制存在的问题:#1主从复制,主节点发生故障,需要做故障转移,可以手动转移:让其中一个slave变成master-哨兵解决#2主从复制,只能主写数据,所以写能力和存储能力有限-集群来解决#搭建哨兵的目的一旦一主多从的架构,主库发生故障,能够自动转移一......
  • 鸿鹄电子招投标系统源码实现与立项流程:基于Spring Boot、Mybatis、Redis和Layui的企业
    随着企业的快速发展,招采管理逐渐成为企业运营中的重要环节。为了满足公司对内部招采管理提升的要求,建立一个公平、公开、公正的采购环境至关重要。在这个背景下,我们开发了一款电子招标采购软件,以最大限度地控制采购成本,提高招投标工作的公开性和透明性,并确保符合国家电子招投标......
  • 一文彻底搞懂Redis底层数据结构
    文章目录1.数据结构与数据类型的关系2.底层数据结构详解2.1SDS:简单动态字符串2.2双端链表2.3压缩列表2.4哈希表2.5整数集合2.6跳表2.7quicklist2.8listpack1.数据结构与数据类型的关系Redis是一个基于内存的数据存储系统,它支持多种数据结构和数据类型,......
  • 容器镜像加速指南:探索 Kubernetes 缓存最佳实践
    介绍将容器化应用程序部署到Kubernetes集群时,由于从registry中提取必要的容器镜像需要时间,因此可能会出现延迟。在应用程序需要横向扩展或处理高速实时数据的情况下,这种延迟尤其容易造成问题。幸运的是,有几种工具和策略可以改善Kubernetes中容器镜像的可用性和缓存。在本篇......
  • redis面试题
    redis面试题一、基础知识面试1.说说你对Redis的理解Redis是一个基于Key-Value存储结构的开源内存数据库,也是一种NoSQL数据库。它支持多种数据类型,包括String、Map、Set、ZSet和List,以满足不同应用场景的需求。Redis以内存存储和优化的数据结构为基础,提供了快速的读写性能和高......
  • 微软的Redis替代项目——Garnet
    微软研究院最近开源了一个新的C#项目,叫Garnet,它实现了Redis协议,基本可以看做redis的替代品了。文档地址:https://microsoft.github.io/garnet/简单的看了下,它居然是以nuget包发布的,通过它我们可以直接快速实现一个自己的redisserver。usingGarnet;try{usingvarserve......
  • Redis
    RedisRedis常规八股(Rediscommoninterviewquestionsandconcepts):Redis是什么?(WhatisRedis?):Redis是一个开源的内存数据存储系统,可以用作数据库、缓存和消息中间件。Redis的特点是什么?(WhatarethefeaturesofRedis?):Redis具有高性能、支持多种数据结构、提供持久化选项......
  • redis实际应用场景及并发问题的解决
    业务场景接下来要模拟的业务场景:每当被普通攻击的时候,有千分之三的概率掉落金币,每回合最多爆出两个金币。1.每个回合只有15秒。2.每次普通攻击的时间间隔是0.5s3.这个服务是一个集群(这个要求暂时不实现)编写接口,实现上述需求。核心问题可以想到要解决的主要问题是,1.如何......