Redis的消息订阅、pipeline、事务、modules、布隆过滤器、缓存LRU

Redis的消息订阅、pipeline、事务、modules、布隆过滤器、缓存LRU以及缓存常见问题

Posted by 石福鹏 on 2021-03-21
Estimated Reading Time 22 Minutes
Words 5k In Total
Viewed Times

首先要注意拿Redis作为数据库或者缓存的区别,比如如果拿redis做缓存,那么常见的问题有哪些?击穿、雪崩、穿透、一致性等。

一、管道(Pipelining)

通常情况下,Redis一个请求会遵循:

1)、客户端发送一个查询,并监听socket返回,通常是阻塞模式,等到服务端响应

2)、服务端处理命令,,返回结果到客户端

这个时间被称为RTT(Round Trip Time 往返时间)

较多的系统调用,即伴随着网络I/O增加,为了减少系统调用、减少网络I/O,就有了管道。

这就是在计算机编程中,我们会更多的使用buffer机制的原因,减少没必要的调来调去

一次请求,未收到响应 ,但是服务器依然可以处理新的请求,所以就可以将多个命令发送到服务器。不用等待回复,最后在一个步骤中一次性读取所有回复

比如使用管道操作Redis:

通过NC建立一个socket通信(如果没有NC,需要安装一下 yum install nc)

1
[root@hadoop01 ~]# nc localhost 6379

然后就按照客户端输入,比如keys *等命令就可以操作

如果想发更多,不打印出来,直接通过nc创建一个连接之后发出去

1
echo -e "set k2 99\nincr k2\n get k2" | nc localhost 6379

echo -e:-e可以识别换行符

1
2
3
4
5
6
[root@hadoop01 ~]# echo -e "set k2 99\nincr k2\n get k2" | nc localhost 6379
+OK # set k2 99
:100 # nincr k2
$3
100 # get k2
[root@hadoop01 ~]#

a、知识延伸

Redis有个冷启动观念,redis冷启动的时候。我们期望在启动后预加载一部分数据,即涉及到批量插入,这里面就会用到管道的知识

如果我们需要生成一个10亿的`keyN -> ValueN’的大数据集,我们会创建一个如下的redis命令集的文件:

1
2
3
4
SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN
1
cat data.txt | redis-cli --pipe

二、发布订阅(hub/sub)

先看看帮助命令help @pubsub

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
127.0.0.1:6379> help @pubsub

PSUBSCRIBE pattern [pattern ...]
summary: Listen for messages published to channels matching the given patterns
since: 2.0.0

PUBLISH channel message
summary: Post a message to a channel
since: 2.0.0

PUBSUB subcommand [argument [argument ...]]
summary: Inspect the state of the Pub/Sub subsystem
since: 2.8.0

PUNSUBSCRIBE [pattern [pattern ...]]
summary: Stop listening for messages posted to channels matching the given patterns
since: 2.0.0

SUBSCRIBE channel [channel ...]
summary: Listen for messages published to the given channels
since: 2.0.0

UNSUBSCRIBE [channel [channel ...]]
summary: Stop listening for messages posted to the given channels
since: 2.0.0

127.0.0.1:6379>

PUBLISHSUBSCRIBE

1
2
3
127.0.0.1:6379> publish ooxx hello
0
127.0.0.1:6379>

另外起一个客户端,也是6379,发现并没有收到"hello"

1
2
3
4
127.0.0.1:6379> SUBSCRIBE ooxx
subscribe
ooxx
1

需要注意的是,只有消费端监听之后,别人推送消息才能收到,此时再次推送一条消息publish ooxx helloworld,就会发现收到了

1
2
3
4
5
6
7
127.0.0.1:6379> SUBSCRIBE ooxx
subscribe
ooxx
1
message
ooxx
helloworld

思考一个问题

我们在使用微信或者QQ的时候,除了聊天新的消息外,还有聊天记录,那所有的这些数据应该放在哪里呢?关系型数据库,还是Redis,如果是数据库,数据全量可以保证,那查的人多的时候,微信要支持很多群,人的查询,成本会非常高

Redis缓存通常解决的是数据的读请求。有时候也会解决写的请求,但是一般写的请求,就会考虑双写的复杂性;

所以一般把数据完整性不是特别强的,直接就搬到redis作为数据库,存那些实时写的数据,比如购买数,浏览数,收藏数等

所以,实时性的,就可以使用redis的发布订阅,三天以前的历史数据,直接从数据库中读取。三天内的数据,可以使用sorted_set(这里面有个zremrangebyrank、zremrangebyscore),根据排名,给定一个外围,删除三天以前的数据。存放的时候,将时间作为分值

image-20210322145152718

这里其实是可以使用两个redis进程,一台redis服务器只负责发布订阅,同时做sorted_set排序,另外一个redis进程订阅第一台,收到订阅消息,后转给kafka来做消息写入

image-20210322153102071


三、Redis事务

使用Redis,永远记住一句话,就是redis速度快,才会使用redis,如果你把这个特征抹杀了,还不如不用。

1)、 mutliexec

如图,两个客户端,分别执行两个事务,顺序是1先开启事务,然后2也开启一个事务,2事务中执行del k1,1事务中执行get k1,然后,client2先执行exec,稍后client1d的exec,因为redis是单线程的,所以不可能同时执行 ,一定是挨个执行的。

其实就是关注的是exec的执行谁先执行

image-20210322174705336

image-20210322173459881

如上图,同一个客户端发来的命令是放在一个缓冲区的,所以对着这个例子,就是client2先执行,1排在后面。所以肯定是2先把k1先删除了,等到1执行的时候,就会说k1不存在了

对于两个事务同时来,谁先执行exec,先执行谁的事务

2)、 discardwatch

watch:监听,通过乐观锁实现CAS(check-and-set)操作

依然是上面的例子,如果在client1前先加一个watch,因为client2先执行exec,那么当执行到1的exec时,就不会发生任何变化,

也就是说,如果在2中执行了set k1 "xxooo",那么在2中执行命令get k1时,发现k1发生变化了,那么2的这些指令是不会执行的

至于这种情况怎么解决,需要客户端自己的去捕获这种情况,客户端自己修复造成的后果

为什么Redis不支持回滚? (以下为官方的解释)

1
2
3
4
5
6
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。

以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。

四、redis扩展模块-布隆过滤器

redis除了自己本身的功能外,其实还提供了一些模块,实现其他的功能,这个在中文的官网是没有的,在英文的官网导航栏是又的https://redis.io/->[Modules](https://redis.io/modules)

其中有一个redis的布隆过滤器RedisBloom

具体的安装方式参考官方文档下面是我集成RedisBloom的过程

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
[root@hadoop01 local]# wget https://github.com/RedisBloom/RedisBloom/archive/refs/heads/master.zip
--2021-03-23 12:05:08-- https://github.com/RedisBloom/RedisBloom/archive/refs/heads/master.zip
正在解析主机 github.com (github.com)... 13.250.177.223
正在连接 github.com (github.com)|13.250.177.223|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 302 Found
位置:https://codeload.github.com/RedisBloom/RedisBloom/zip/refs/heads/master [跟随至新的 URL]
--2021-03-23 12:05:09-- https://codeload.github.com/RedisBloom/RedisBloom/zip/refs/heads/master
正在解析主机 codeload.github.com (codeload.github.com)... 13.229.189.0
正在连接 codeload.github.com (codeload.github.com)|13.229.189.0|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:未指定 [application/zip]
正在保存至: “master.zip”

[ <=> ] 114,470 201KB/s 用时 0.6s

2021-03-23 12:05:12 (201 KB/s) - “master.zip” 已保存 [114470]

[root@hadoop01 local]# unzip master.zip
Archive: master.zip
2ebe83623b32a96a1074f31c6e99da3c33f685bb
creating: RedisBloom-master/
creating: RedisBloom-master/.circleci/
inflating: RedisBloom-master/.circleci/config.yml
inflating: RedisBloom-master/.clang-format
creating: RedisBloom-master/.github/
creating: RedisBloom-master/.github/workflows/
.......
inflating: RedisBloom-master/tests/test_topk.c
inflating: RedisBloom-master/tests/topk.py
[root@hadoop01 local]# cd RedisBloom-master/
[root@hadoop01 RedisBloom-master]# make
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/src/rebloom.o /usr/local/RedisBloom-master/src/rebloom.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/contrib/MurmurHash2.o /usr/local/RedisBloom-master/contrib/MurmurHash2.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/rmutil/util.o /usr/local/RedisBloom-master/rmutil/util.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/src/sb.o /usr/local/RedisBloom-master/src/sb.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/src/cf.o /usr/local/RedisBloom-master/src/cf.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/src/rm_topk.o /usr/local/RedisBloom-master/src/rm_topk.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/src/topk.o /usr/local/RedisBloom-master/src/topk.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/src/rm_cms.o /usr/local/RedisBloom-master/src/rm_cms.c
cc -Wall -Wno-unused-function -g -ggdb -O2 -fPIC -std=gnu99 -D_GNU_SOURCE -I/usr/local/RedisBloom-master -I/usr/local/RedisBloom-master/contrib -c -o /usr/local/RedisBloom-master/src/cms.o /usr/local/RedisBloom-master/src/cms.c
cc /usr/local/RedisBloom-master/src/rebloom.o /usr/local/RedisBloom-master/contrib/MurmurHash2.o /usr/local/RedisBloom-master/rmutil/util.o /usr/local/RedisBloom-master/src/sb.o /usr/local/RedisBloom-master/src/cf.o /usr/local/RedisBloom-master/src/rm_topk.o /usr/local/RedisBloom-master/src/topk.o /usr/local/RedisBloom-master/src/rm_cms.o /usr/local/RedisBloom-master/src/cms.o -o /usr/local/RedisBloom-master/redisbloom.so -shared -Wl,-Bsymbolic,-Bsymbolic-functions -lm -lc
[root@hadoop01 RedisBloom-master]# ll
总用量 388
-rw-r--r-- 1 root root 211 2月 25 14:48 changelog
-rw-r--r-- 1 root root 520 2月 25 14:48 codecov.yml
drwxr-xr-x 2 root root 4096 3月 23 12:05 contrib
-rw-r--r-- 1 root root 431 2月 25 14:48 Dockerfile
drwxr-xr-x 3 root root 4096 2月 25 14:48 docs
-rw-r--r-- 1 root root 5794 2月 25 14:48 LICENSE
-rw-r--r-- 1 root root 3639 2月 25 14:48 Makefile
-rw-r--r-- 1 root root 1437 2月 25 14:48 mkdocs.yml
drwxr-xr-x 4 root root 4096 2月 25 14:48 opt
-rw-r--r-- 1 root root 543 2月 25 14:48 ramp.yml
-rw-r--r-- 1 root root 4693 2月 25 14:48 README.md
-rwxr-xr-x 1 root root 331656 3月 23 12:05 redisbloom.so
drwxr-xr-x 2 root root 4096 3月 23 12:05 rmutil
drwxr-xr-x 2 root root 4096 3月 23 12:05 src
drwxr-xr-x 3 root root 4096 2月 25 14:48 tests
[root@hadoop01 RedisBloom-master]# ps -fe | grep redis
root 18725 1 0 3月21 ? 00:04:37 /usr/local/bin/redis-server 127.0.0.1:6379
root 22528 1 0 3月21 ? 00:05:08 /usr/local/bin/redis-server 127.0.0.1:6380
root 68499 68090 0 13:37 pts/4 00:00:00 grep --color=auto redis
[root@hadoop01 RedisBloom-master]# redis-cli shutdown
[root@hadoop01 RedisBloom-master]# ps -fe | grep redis
root 22528 1 0 3月21 ? 00:05:08 /usr/local/bin/redis-server 127.0.0.1:6380
root 68502 68090 0 13:38 pts/4 00:00:00 grep --color=auto redis
[root@hadoop01 RedisBloom-master]# redis-server --loadmodule /usr/local/redis-6.0.0/redisbloom.so
68518:C 23 Mar 2021 13:40:43.296 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
68518:C 23 Mar 2021 13:40:43.296 # Redis version=6.0.0, bits=64, commit=00000000, modified=0, pid=68518, just started
68518:C 23 Mar 2021 13:40:43.296 # Configuration loaded
68518:M 23 Mar 2021 13:40:43.297 * Increased maximum number of open files to 10032 (it was originally set to 1024).
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.0 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 68518
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

68518:M 23 Mar 2021 13:40:43.298 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
68518:M 23 Mar 2021 13:40:43.298 # Server initialized
68518:M 23 Mar 2021 13:40:43.298 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
68518:M 23 Mar 2021 13:40:43.298 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
68518:M 23 Mar 2021 13:40:43.298 * Module 'bf' loaded from /usr/local/redis-6.0.0/redisbloom.so
68518:M 23 Mar 2021 13:40:43.299 * Ready to accept connections

此时,输入bf命令,按TAB键,就会发现相关的命令可以执行了,当然上面的第67行加载扩展也可以直接在配置文件中include

1
2
[root@hadoop01 ~]# redis-cli -p 6379  --raw
127.0.0.1:6379> BF.DEBUG key ...options...

另外,还多出来了一些CF的命令,即布谷鸟过滤器,可以了解一下

那布隆过滤器可以解决什么问题呢?

缓存穿透 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

布隆过滤器如何解决这个问题呢? ,就是用小的空间去解决大的数据匹配的一个过程,也就是说,在redis中要将全量数据体现出来,客户端搜索的时候,在redis中做对比,如果没有找到,就不用去数据库了

但是既然在数据库中都有大量的数据,放在内存中,肯定也大,这时候我们想到redis的bitmap,如果用几个二进制位来代表每件商品,或者每条记录,那么整体的体积会很小。

采用二进制位来标记,比如每个元素,,然后经历n或者k个哈希函数或者映射函数,将二进制的某一位标记成了1,每个元素都会经历这个过程。但是有一个元素,在经过运算,这个时候可能会发生碰撞,也就是说,元素1和元素2 ,虽然他们的元素不同,但是某个函数重叠了,但是依然还有一些函数的位置是不重叠的

但是即使有些搜索的东西数据库没有,也有可能放行过去。即元素3出现的位置即覆盖了元素1的某一个函数标,也覆盖了函数2的某个函数标,,虽然内存是数据库都没有,但是的确映射到了,也就是说:

有一定概率是 依然出现缓存穿透的情况的,但是这个概率是很低的,小于1%(应该是0.000x),但是成本很低,速度很快

image-20210323113956128

以下三种方式解决缓存击穿问题:

image-20210323114238695

需要自己学习了解一下counting bloom布谷鸟过滤器cukcoo,面试的时候也可以多说一点

关于布隆过滤器的原理理解还可以看看这篇文章https://blog.csdn.net/qq_33709582/article/details/108407706

对于部分穿透的,如何解决?

1)、如果经过数据库发现不存在,可以在redis中增加key,value标记为某个值,下次客户端请求时,直接返回

2)、如果数据库有了新增加的数据,那么要完成元素对bloom过滤器的添加


五、Redis作为数据库、缓存的区别

1、缓存数据“不重要”,不是全量数据,缓存应该随着访问变化(热数据)

那么redis中的数据如何随着业务的变化,保留热数据,因为内存的大小是有限制的,也就是瓶颈

1)key的有效期 :期望到期之后就清除掉,因为会有新的数据,这是随着业务逻辑推导的

注意:

A:如果k1设置了有效期,但是在有效期之内,重新设置了k1的值,但是没有设置过期时间,那么之前设置的过期时间会被踢除

B:有效期除了设置一个时间,也可以设置一个时间点,比如到晚上12点过期(EXPIREAT key timestamp

2)淘汰冷数据 :是有业务运转来的,由于内存是有限的,所以要淘汰掉冷数据

另外还有一些常用的配置要了解:vi /etc/redis/6379.conf

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
################################## INCLUDES ###################################

# 一个主机中有多个实例,有些配置是通用的,有些是私有的,可以写一个配置文件是基础的,可以再写一个是个性化配置
# include /path/to/local.conf
# include /path/to/other.conf

################################## MODULES #####################################

# 前面load扩展是通过命令,也可以在这里配置(绝对路径)
# loadmodule /path/to/my_module.so
# loadmodule /path/to/other_module.so

################################## NETWORK #####################################

# 绑定IP地址,只有来源是下面配置的这些目标地址的,才允许访问
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1 ::1

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bind 127.0.0.1

# 是否可以远程访问
protected-mode yes

# 端口号配置
port 6379


################################# GENERAL #####################################
# 全局配置
# 是否是后台服务模式
daemonize yes

# 启动时候的进程ID文件
pidfile /var/run/redis_6379.pid

# 日志级别
loglevel notice

# 日志目录
logfile /var/log/redis_6379.log

# 默认数据库的数量
databases 16



################################## SECURITY ###################################

# 其他客户端连接redis需要提供密码
# requirepass foobared

# 为了安全起见,可以把一些命令重命名,比如flushall、flushdb
# rename-command CONFIG ""


################################### CLIENTS ####################################
# 配置连接数
# maxclients 10000

############################## MEMORY MANAGEMENT ################################

# redis最大内存
# maxmemory <bytes>

#
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
# 回收策略
#LRU表示最近最少使用
#LFU表示使用频率最低

# LRU:强调多久没碰(时间)
# LFU:碰了多少次(次数)

#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#


# 内存满了之后怎么处理,👆的就是枚举
# maxmemory-policy noeviction

六、过期

Redis如何淘汰过期的keys(过期判定)

Redis keys过期有两种方式:被动和主动方式。

当一些客户端尝试访问它时,key会被发现并主动的过期。

当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。

具体就是Redis每秒10次做的事情:

  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于25%的keys过期,重复步奏1.

这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。

七、缓存常见问题

击穿、雪崩、穿透,一致性(双写)


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !