【Redis系列笔记】缓存三剑客

简介: 缓存穿透是指请求一个不存在的数据,缓存层和数据库层都没有这个数据,这种请求会穿透缓存直接到数据库进行查询。它通常发生在一些恶意用户可能故意发起不存在的请求,试图让系统陷入这种情况,以耗尽数据库连接资源或者造成性能问题。缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。

在使用缓存时特别是在高并发场景下会遇到很多问题,常用的问题有缓存穿透、缓存击穿、缓存雪崩以及缓存一致性问题。

1. 缓存穿透

1.1. 概述

缓存穿透是指请求一个不存在的数据,缓存层和数据库层都没有这个数据,这种请求会穿透缓存直接到数据库进行查询。它通常发生在一些恶意用户可能故意发起不存在的请求,试图让系统陷入这种情况,以耗尽数据库连接资源或者造成性能问题。

比如:在快速入门程序中,查询一个缓存中不存在的数据将会执行方法查询数据库,数据库也不存在此数据,查询完数据库也没有缓存数据,缓存没有起到作用。

1.2. 解决方案

1.2.1. 对请求增加校验机制

比如:查询的Id是长整型并且是19位,如果发来的不是长整型或不符合位数则直接返回不再查询数据库。

1.2.2. 缓存空值或特殊值

当查询数据库得到的数据不存在,此时我们仍然去缓存数据,缓存一个空值或一个特殊值的数据,避免每次都会查询数据库,避免缓存穿透。

流程如下:

下边通过测试查询区域服务,查询一下不存在的区域服务:

//区域服务查询
@Test
public void test_queryServeByIdCache(){
    //指定一个不存在serve表的id
    Serve serve = serveService.queryServeByIdCache(123L);
    Assert.notNull(serve,"服务为空");
}

当查询一个数据库不存在的数据时向redis缓存了NullValue对象。

第一次会查询数据库,得到一个空值,缓存一个空值。第二次不再查询数据库。

1.2.3. 使用布隆过滤器

详见下面

1.3. 布隆过滤器

1.3.1. 什么是布隆过滤器?

布隆过滤器(Bloom Filter)是一种数据结构,用于快速判断一个元素是否属于一个集合中。

它使用多个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点,将Bit array理解为一个二进制数组,数组元素是0或1。

当一个元素加入集合时,通过N个散列函数将这个元素映射到一个Bit array中的N个点,把它们设置为1。

检索某个元素时再通过这N个散列函数对这个元素进行映射,根据映射找到具体位置的元素,如果这些位置有任何一个0,则该元素一定不存在,如果都是1很可能存在误判(哈希冲突)

哈希函数的基本特性

同一个数使用同一个哈希函数计算哈希值,其哈希值总是一样的。

对不同的数用相同的哈希函数计算哈希值,其哈希值可能一样,这称为哈希冲突

哈希函数通常是单向的不可逆的,即从哈希值不能逆向推导出原始输入。

这使得哈希函数适用于加密和安全应用。

1.3.2. 为什么会存在误判?

主要原因是哈希冲突。布隆过滤器使用多个哈希函数将输入的元素映射到位数组中的多个位置,当多个不同的元素通过不同的哈希函数映射到相同的位数组位置时就发生了哈希冲突。

由于哈希函数的有限性,不同的元素可能会映射到相同的位置上,这种情况下即使元素不在布隆过滤器中可能产生误判,即布隆过滤器判断元素在集合中。

如何降低误判率?

增加Bit array空间,减少哈希冲突,优化散列函数,使用更多的散列函数。

1.3.3. 如何使用布隆过滤器?

将要查询的元素通过N个散列函数提前全部映射到Bit array中,比如:查询服务信息,需要将全部服务的id提前映射到Bit array中,当去查询元素是否在数据库存在时从布隆过滤器查询即可,如果哈希函数返回0则表示肯定不存在。

1.3.4. 特点

优点:二进制数组占用空间少,插入和查询效率高效。

缺点:存在误判率,并且删除困难,因为同一个位置由于哈希冲突可能存在多个元素,删除某个元素可能删除了其它元素。

1.3.5. 应用场景

1、海量数据去重:比如URL去重,搜索引擎爬虫抓取网页,使用布隆过滤器可以快速判定一个URL是否已经被爬取过,避免重复爬取。

2、垃圾邮件过滤:使用布隆过滤器可以用于快速判断一个邮件地址是否是垃圾邮件发送者,对于海量的邮件地址,布隆过滤器可以提供高效的判定。

3、安全领域:在网络安全中,布隆过滤器可以用于检查一个输入值是否在黑名单中,用于快速拦截一些潜在的恶意请求。

4、避免缓存穿透:通过布隆过滤器判断是否不存在,如果不存在则直接返回。

1.3.6. 代码实现

  • 使用redit的bitmap位图结构实现。
  • 使用redisson实现。
  • 使用google的Guava库实现。

下边举例说明:

引入依赖

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>28.2-jre</version>
</dependency>

测试代码:

package com.jzo2o.foundations.service;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.Charset;
/**
 * @author Mr.M
 * @version 1.0
 * @description TODO
 * @date 2023/10/25 17:10
 */
public class BloomFilterExample {
    public static void main(String[] args) {
        // 创建一个布隆过滤器,预期元素数量为1000,误判率为0.01
        BloomFilter<String> bloomFilter = BloomFilter
        .create(Funnels.stringFunnel(Charset.defaultCharset()), 1000, 0.01);
        // 添加元素到布隆过滤器
        bloomFilter.put("example1");
        bloomFilter.put("example2");
        bloomFilter.put("example3");
        // 测试元素是否在布隆过滤器中
        System.out.println(bloomFilter.mightContain("example1")); // true
        System.out.println(bloomFilter.mightContain("example4")); // false
    }
}

在上述代码中,我们创建了一个预期包含1000个元素、误判率为0.01的布隆过滤器。然后,我们向布隆过滤器中添加了三个元素("example1"、"example2" 和 "example3"),并测试了几个元素是否在布隆过滤器中。

请注意,误判率是你可以调整的一个参数。较低的误判率通常需要更多的空间和计算资源

2. 缓存击穿

2.1. 概念

缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用

比如某手机新品发布,当缓存失效时有大量并发到来导致同时去访问数据库。

2.2. 解决方案

2.2.1. 使用锁

单体架构且单实例部署下(单进程内)可以使用同步锁控制查询数据库的代码,只允许有一个线程去查询数据库,查询得到数据库存入缓存。

synchronized(obj){
    //查询数据库
    //存入缓存
}

分布式架构下(多个进程之间)可以使用分布式锁进行控制。

// 获取分布式锁对象
RLock lock = redisson.getLock("myLock");
try {
    // 尝试加锁,最多等待100秒,加锁后自动解锁时间为30秒
    boolean isLocked = lock.tryLock(100, 30, java.util.concurrent.TimeUnit.SECONDS);
    if (isLocked) {
        //查询数据库
        //存入缓存
    } else {
        System.out.println("获取锁失败,可能有其他线程持有锁");
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    // 释放锁
    lock.unlock();
    System.out.println("释放锁...");
}

2.2.2. 热点数据不过期

可以由后台程序提前将热点数据加入缓存,缓存过期时间不过期,由后台程序做好缓存同步。

例如:当服务上架后将服务信息缓存到redis且永不过期,此时需要使用put注解。

2.2.3. 缓存预热

分为提前预热、定时预热。

提前预热就是提前写入缓存。

定时预热是使用定时程序去更新缓存。

2.2.4. 热点数据查询降级处理

对热点数据查询定义单独的接口,当缓存中不存在时走降级方法避免查询数据库。

3. 缓存雪崩

3.1. 概念

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。

比如对某信息设置缓存过期时间为30分钟,在大量请求同时查询该类信息时,此时就会有大量的同类信息存在相同的过期时间,一旦失效将同时失效,造成雪崩问题。

3.2. 解决方案

3.2.1. 使用锁进行控制

思路同缓存击穿。

3.2.2. 对同一类型信息的key设置不同的过期时间

通常对一类信息的key设置的过期时间是相同的,这里可以在原有固定时间的基础上加上一个随机时间使它们的过期时间都不相同。

具体实现:在framework工程中定义缓存管理器指定过期时间加上随机数。

3.2.3. 缓存定时预热

不用等到请求到来再去查询数据库存入缓存,可以提前将数据存入缓存。使用缓存预热机制通常有专门的后台程序去将数据库的数据同步到缓存。

目录
相关文章
|
10月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
5月前
|
缓存 负载均衡 监控
135_负载均衡:Redis缓存 - 提高缓存命中率的配置与最佳实践
在现代大型语言模型(LLM)部署架构中,缓存系统扮演着至关重要的角色。随着LLM应用规模的不断扩大和用户需求的持续增长,如何构建高效、可靠的缓存架构成为系统性能优化的核心挑战。Redis作为业界领先的内存数据库,因其高性能、丰富的数据结构和灵活的配置选项,已成为LLM部署中首选的缓存解决方案。
|
6月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
302 1
Redis专题-实战篇二-商户查询缓存
|
10月前
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
1378 0
|
5月前
|
缓存 运维 监控
Redis 7.0 高性能缓存架构设计与优化
🌟蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕Redis 7.0高性能缓存架构,探索函数化编程、多层缓存、集群优化与分片消息系统,用代码在二进制星河中谱写极客诗篇。
|
6月前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
10月前
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
329 32
|
10月前
|
缓存 NoSQL Java
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
256 5
Redis:现代服务端开发的缓存基石与电商实践-优雅草卓伊凡
|
存储 缓存 NoSQL
解决Redis缓存数据类型丢失问题
解决Redis缓存数据类型丢失问题
556 85
|
缓存 NoSQL Java
Redis应用—8.相关的缓存框架
本文介绍了Ehcache和Guava Cache两个缓存框架及其使用方法,以及如何自定义缓存。主要内容包括:Ehcache缓存框架、Guava Cache缓存框架、自定义缓存。总结:Ehcache适合用作本地缓存或与Redis结合使用,Guava Cache则提供了更灵活的缓存管理和更高的并发性能。自定义缓存可以根据具体需求选择不同的数据结构和引用类型来实现特定的缓存策略。
828 16
Redis应用—8.相关的缓存框架

相关产品

  • 云数据库 Tair(兼容 Redis)