tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 各种MQ

  • Nginx

  • IdentityServer

  • Redis

    • 初始Redis与安装
    • Redis常用的一些场景
    • Redis中的缓存失效
      • 缓存击穿
        • 如何解决
      • 缓存穿透
        • 如何解决
        • 布隆过滤器
        • 定义
        • 添加与查询过程
        • 使用与注意事项
        • 代码实现
      • 缓存雪崩
        • 如何解决
    • 关于Redis的一些问题
    • Redis的持久化策略
    • Redis从单机到集群等模式
    • Redis和MySql实现数据一致性
    • 通过Redis实现分布式锁
    • Redis的客户端介绍
  • Linux

  • 中间件
  • Redis
EffectTang
2023-10-30
目录

Redis中的缓存失效

# Redis中的缓存失效

# 缓存击穿

Redis中的缓存击穿是指,在高并发的情况下,如果缓存中不存在某个请求的Key,并发请求会直接打到数据库,造成数据库压力过大甚至崩溃的现象。

这种情况通常发生在某个热点Key的缓存在失效的瞬间,大量的并发请求由于没有在缓存中找到对应的Value而直接打到了数据库上。

简单总结:Redis缓存中没有对应数据,但数据库还有。

# 如何解决

为了避免这种现象的发生,可以采用双重检测锁、互斥锁等技术手段,让每次只有一个请求能够直接打到数据库,其他的请求则在获取到锁之前处于等待状态,这样就可以有效缓解数据库的压力。

  • 双重检测锁

“双重检测锁”机制:从数据库查询数据方法外加锁,锁内再次判断缓存是否有数据。

//使用"双重检测锁"机制查询redis
//第一次查询redis
if(查询结果 是否为空){
  // code .....
	// 如果存在,直接返回,无需查询数据库
}else{
  synchronized(){
    // 再次查询redis 如果没有才继续往数据库查询
  }
}
1
2
3
4
5
6
7
8
9
10

因为加了锁,所以请求会一个一个的进行,在第一个查询为空后,接着往mysql等数据库后,会将对应的redis-key给重新设置,便不会发生大量请求同时查询为空的情况,不会发生缓存击穿。

  • 对数据提前预热

或者提前对热点数据进行设置,预先存在Redis中,或者适当延长Redis中对应key的过期时间。

# 缓存穿透

缓存穿透:客户端请求的数据在缓存中和数据库中都不存在,这样缓存就永远不会生效,同一时间大量请求请求都会打到数据库上,造成数据库压力过大甚至崩溃的现象。

这种情况通常发生在缓存失效之后,客户端仍然不断发起请求,而且请求的数据在数据库中也不存在。

Redis缓存中跟数据库中都没有对应的数据。

# 如何解决

为了避免这种现象的发生,可以先判断一下客户端请求的数据是否存在,如果不存在,则直接返回空结果,避免打到数据库上;或者进行类似用户权限的拦截,将一些请求进行拦截,不允许访问。

或者使用布隆过滤器

# 布隆过滤器

# 定义

布隆过滤器是1970年由一个叫布隆的小伙子提出来的。它其实就是一个很长的二进制向量,可能还要加上一个(或多个)哈希函数。

主要由两个部分组成:一个位数组(Bit Array)和一组哈希函数。

布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于判断一个元素是否在一个集合中。它通过牺牲一定的准确性来换取空间和时间的高效性。

# 添加与查询过程

以下是一个简要的示例图:

初始状态:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

插入 "apple":
- 哈希函数1: 索引2 -> [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
- 哈希函数2: 索引5 -> [0, 0, 1, 0, 0, 1, 0, 0, 0, 0]
- 哈希函数3: 索引8 -> [0, 0, 1, 0, 0, 1, 0, 0, 1, 0]

插入 "banana":
- 哈希函数1: 索引3 -> [0, 0, 1, 1, 0, 1, 0, 0, 1, 0]
- 哈希函数2: 索引6 -> [0, 0, 1, 1, 0, 1, 1, 0, 1, 0]
- 哈希函数3: 索引9 -> [0, 0, 1, 1, 0, 1, 1, 0, 1, 1]

最终状态:[0, 0, 1, 1, 0, 1, 1, 0, 1, 1]
1
2
3
4
5
6
7
8
9
10
11
12
13

上述就是它的一个简要添加元素过程,

  1. 将要插入的元素通过每个哈希函数计算出一个索引值。
  2. 将这些索引值对应的位数组中的位设置为1。

而查询操作也类似:

  1. 将要查询的元素通过每个哈希函数计算出一个索引值。
  2. 检查这些索引值对应的位数组中的位是否都为1。
  3. 如果所有位都是1,则认为该元素可能在集合中;如果有任何一个位为0,则可以确定该元素不在集合中。

# 使用与注意事项

布隆过滤器的查询效率是很高的,因为它是通过哈希函数进行运算,再在一个有序的位数组上查找。

但是,请注意,布隆过滤器可能会出现误判,即判断一个不在集合中的元素为可能存在。因为存在哈希碰撞,不同的值它们的哈希值可能相同。所以,布隆过滤器采用了多个hash函数来计算,这样一定程度上可以避免hash碰撞的问题,当然因为要存储多个hash值,所以要想准确率高,位数组的长度也要足够长才行。

不过,布隆过滤器不会出现漏判,即不会判断一个在集合中的元素为不存在。

关于删除:

说了添加与查找,为什么没说删除呢?是布隆过滤器不支持吗?是的,布隆过滤器不支持删除元素。

原因:因为存在哈希碰撞,所以,可能出现同一个位置,代表多个值的哈希函数的值。如果删除了其中一个,那么其余的值将也会被删掉。

# 代码实现

在代码中,如何实现呢?可以借用Google公司出的Guava,里面有具体的实现。同时它还可以自定义出错率。

  • 使用Guava中的布隆过滤器
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>29.0-jre</version>
</dependency>

1
2
3
4
5
6

使用布隆过滤器

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
 
public class BloomFilterTest {
 
    /** 预计插入的数据 */
    private static Integer expectedInsertions = 10000000;
    /** 误判率 */
    private static Double fpp = 0.01;
    /** 布隆过滤器 */
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), expectedInsertions, fpp);
 
    public static void main(String[] args) {
        // 插入 1千万数据
        for (int i = 0; i < expectedInsertions; i++) {
            bloomFilter.put(i);
        }
 
        // 从10000000开始,用1千万数据测试误判率
        int count = 0;
        for (int i = expectedInsertions; i < expectedInsertions *2; i++) {
            if (bloomFilter.mightContain(i)) {
                count++;
            }
        }
        System.out.println("一共误判了:" + count);
 
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  • 使用Java集成Redis使用布隆过滤器
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.16.0</version>
</dependency>

1
2
3
4
5
6
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
 
public class RedisBloomFilterTest {
 
    /** 预计插入的数据 */
    private static Integer expectedInsertions = 10000;
    /** 误判率 */
    private static Double fpp = 0.01;
 
    public static void main(String[] args) {
        // Redis连接配置,无密码
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.211.108:6379");
        // config.useSingleServer().setPassword("123456");
 
        // 初始化布隆过滤器
        RedissonClient client = Redisson.create(config);
        RBloomFilter<Object> bloomFilter = client.getBloomFilter("user");
        bloomFilter.tryInit(expectedInsertions, fpp);
 
        // 布隆过滤器增加元素
        for (Integer i = 0; i < expectedInsertions; i++) {
            bloomFilter.add(i);
        }
 
        // 统计元素
        int count = 0;
        for (int i = expectedInsertions; i < expectedInsertions*2; i++) {
            if (bloomFilter.contains(i)) {
                count++;
            }
        }
        System.out.println("误判次数" + count);
 
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 缓存雪崩

Redis中的缓存雪崩是指在某一时刻,大量的缓存同时过期失效,导致大量的请求直接打到数据库上,造成数据库压力剧增,甚至可能引发整个系统的崩溃。这种情况通常发生在某些热点Key的缓存在同一时刻失效,或者是大量Key在同一时刻过期失效。

缓存雪崩跟缓存击穿有些类似,不过雪崩是大量缓存同时失效,而击穿则是少数几个失效,而大量请求同时请求它们。

导致雪崩的场景通常有两个:

  • 大量热点key同时过期
  • 缓存服务出现故障或者宕机

# 如何解决

为了避免这种现象的发生,可以采取以下几种措施:

  1. 设置合理的缓存失效时间:将缓存中的数据设置成不同的过期时间,或者设置一个较长的缓存失效时间,从而减少缓存失效的数量,从而减少缓存雪崩的概率。
  2. 数据预热:可以在缓存失效之前,提前将热点数据加载到缓存中,从而减少缓存失效的数量,减少缓存雪崩的概率。
  3. 分布式锁:在缓存失效的瞬间,先使用分布式锁锁定该Key,然后只允许一个请求能够打到数据库上,其他的请求则等待锁释放后才能继续执行。
  4. 容错降级:当发生缓存雪崩时,可以考虑开启容错降级模式,限制请求量,降低数据库压力。
上次更新: 2025/04/23, 16:23:16
Redis常用的一些场景
关于Redis的一些问题

← Redis常用的一些场景 关于Redis的一些问题→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
时间跟其他数据的序列化
05-19
03
数据加密与安全
05-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式