目录
5、Redis没设置key的过期时间,为什么被Redis主动删除了
1、redis是单线程还是多线程
答:分版本
在redis6.0之前是单线程,这里的单线程指的是网络I/O和键值对读写是由一个线程完成的。
在redis6.0之后,引入了多线程这个概念,这个多线程是网络I/O的多线程,而键值对读写还是单线程完成的,所以redis依然是并发安全的。
补充一下:这里的单线程是指网络请求和数据操作,而其他的持久化和集群数据同步等,其实是由额外的线程执行的
2、Redis为什么那么快
答:我们都知道Redis是基于内存的。
一条命令在内存里操作的时间是几十纳秒
命令执行是单线程操作,没有线程切换开销
基于IO多路复用机制提升了Redis的I/O利用率
高效的数据存储结构:全局hash表以及多种高效数据结构,比如:跳表,压缩列表,链表等等。
3、Redis底层数据是如何用跳表来存储的
答:我们都知道Redis有一个有序集合zset里面是用元素:分值 ,它的底层可以用压缩列表O(N)和跳表O(logN)来存储,今天我们就讲讲跳表。
我们这样理解 一开始我们会把这个集合放到一个链表里面来,链表会按照我们每个元素的分值进行从小到大排序。我们也知道一个链表特性就是插入删除快,查询慢。但是这个有序集合在底层给我们做了优化。就是进行横向扩张什么意思呢?
a:3 | e:7 | i:11 | ||||||
a:3 | c:5 | e:7 | g:9 | i:11 | ||||
a:3 | b:4 | c:5 | d:6 | e:7 | f:8 | g:9 | h:10 | i:11 |
如图上最下面一行是我们的链表,在这个基础上,当前元素指向下两个元素。并且那个元素指向自己的下方元素。找元素假设找f:8这个元素 那么会从最上层找,找到e:7后找i:11发现小于i:11这时候e:7就往下一层继续从e:7出发查找,发现小于g:9这时候e:7继续往下一层,再next的时候就找到了f:8这个元素了。
跳表:将有序链表改造为支持近似“折半查找”算法,可以进行快速的插入、删除、查找操作。
4、Redis Key过期了为什么内存没释放(附删除策略)
在我们使用set命令的时候除了可以设置key-value以外 还可以设置key的过期时间,就像这样
set minus 123 ex 60
这时候我们如果执行这个操作
set minus 456
那么这个key的过期时间就会被擦除掉,这时候我们ttl minus 返回的就是-1(永不过期)
如果我们设置了过期时间但是Redis内存持续增长,后面发现过期时间丢失,那么很有可能就是问题造成的了。
当然还有可能就是惰性删除、定期删除和定时删除
1、惰性删除:读/写到一个已经过期的key,就会触发惰性删除,如果过期了就会删除这个key
优点:节约cpu性能,到了必须删除的时候才执行删除。
缺点:内存压力较大,会出现长期占用内存的数据(冷数据)。
空间换时间
2、定时删除:惰性删除无法保证冷数据被及时删除,所以我们可以设置Redis定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。
优点:节省内存,到时间就删除,快速释放不必要的内存占用。
缺点:CPU压力较大,无论CPU此时负载情况如何,均占用CPU来执行删除,会影响Redis服务器的响应时间和指令吞吐量。
时间换空间
3、定期删除:
- Redis服务器初始化 读写配置中的server.hz,默认为10 代表每10秒执行一次sereverCron()操作
- 在serverCron()中会调用databasesCron()来轮询每一个db(即循环expire[*])。
- 在databasesCron()中会调用activeExpireCycle()方法,对每一个expire[*]进行检测,每次执行的时长为250ms/server.hz。
- 对某个expire[*]检测时,随机挑选W个key进行检测。如果key超时,即删除。
- 如果第四步中,删除的key的数量大于W*25%,则继续在此expire[*]上检测执行。
- 如果第四步中删除的key的数量小于等于W*25%,则检测下一个expire[*]。(从0-15循环,Redis默认为16个DB)
其中W值,可以在配置文件中配置,对应的属性为ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 默认20;
在第四步中的activeExpireCycle()方法执行检测的时候,因为时间长度时固定的,下一次activeExpireCycle()方法执行为了接着上一次中断的位置执行,用参数current_db来记录当前检测的expire。
特点:对cpu的使用有峰值,也有一定的自定义空间。删除过程中,内存的压力也不是很大。
Reids采用的是 惰性删除+定期删除
5、Redis没设置key的过期时间,为什么被Redis主动删除了
(淘汰策略)
当redis的内存超过了maxmemory( 不设置为0)限定时,触发主动清理策略
主动清理策略在redis4.0之前一共实现了6种内存淘汰策略,在4.0之后,又增加了2种策略 一共8种:
1)针对过期key的处理:
volatile-ttl: 过期时间的键值对 先后删除,越早过期越先被删。
volatile-random:过期时间的键值对随机删除。
volatile-lru:过期时间的键值对 淘汰最长时间未被使用的。
(新)volatile-lfu:过期时间的键值对 淘汰一定时期内被访问次数最少。
2)针对所有key的处理
allkeys-random: 所有键值对 随机删除。
allkeys-lru: 所有键值对 淘汰最长时间未被使用的。
(新)allkeys-lfu: 所有键值对 淘汰一定时期内被访问次数最少。
3)不处理
noeviction: 不会剔除任何数据,拒绝写入并且返回OOM异常(默认)
6、Redis主从、哨兵、集群架构优缺点比较
主从架构:
单个主 master节点进行操作,多个从 slave进行备份
如果主节点挂了,就需要运维人员到从节点选一台机器当作主节点。
哨兵架构:
有主从的基础上,再增加上一些哨兵sentinel节点(同主 从一样都是redis的一个实例),
每一个哨兵都会对redis主从节点进行监听。客户端是通过哨兵集群来知道主节点信息的。
这时候如果主节点挂掉了,哨兵会到从节点里面挑选一台机器当主节点(这里不赘述选举过程)
并且告诉给客户端新的主节点。
集群架构:
在集群架构,比如我们有100G的数据,我们有三个集群,那么它的存储不是每个节点都100G数据的,而是 30G、30G、40G分别存储,也就是说水平拆分数据。那怎么分片的呢?
7、Redis集群数据分片
我们要知道,Redis Cluster启动的时候,会分配16384个哈希solt(槽位),每个节点负责其中一部分的solt,solt的信息存储于每个节点。
那怎么确定存在哪个solt呢?
HASH_SOLT=CRC16(key) mod 16384
8、Redis主从切换导致缓存雪崩
假设slave机器时钟走的比master快很多。
此时,Redis master里设置了过期时间的key,从slave角度来看,可能会有很多在master里没过期的数据,其实在slave里面已经过期了。
如果这时候主从切换,把slave切换为新的master
它在成为master后,就会开始大量清理过期key(slave不会清理过期的key),这时候就会导致:
1、master大量清理key,导致主线程阻塞,无法及时处理客户端请求。
2、Redis中大量数据过期,引发缓存雪崩。
所有我们一定要保证主从的机器时钟一致性,避免这种问题发生。
9、Redis持久化 RDB、AOF和混合持久化
默认情况下是RDB快照的
Redis在RDB情况下,会把内存数据库快照保存在dump.rdb的二进制文件。
我们可以对Redis进行设置,让它在X秒内数据集至少有Y个改动 满足时,自动保存一次数据集。
比如:Redis在60秒内有1000个key改动,自动保存一次数据集。
# save 60 1000 #关闭RDB只需要将所有的save保存策略注释掉即可
优点:恢复速度快、体积小 缺点:可能丢失数据
还可以手动执行命令生成RDB快照,Redis客户端通过 save 或 bgsave 可以生成 dump.rdb 文件
save:同步IO 会阻塞客户端
bgsave:异步IO 不会阻塞客户端 后台进行写时复制(fork子线程)
AOF
appendonly yes 开启AOF 写入到.aof文件
AOF可以配置几种策略:
appendfsync always 写一条马上持久化一条
appendfsync everysec 可以设置多久执行一次,
appendfsync no 操作系统来处理
优点:丢失极少数据 缺点:恢复速度慢 体积大
AOF重写优化:可能出现set a 1,set a 2 ,set a 3这样的操作,但是最后我们只需要set a 3
我们可以来控制AOF自动重写频率
#auto-aof-rewrite-min-size 64mb //aof文件至少到64mb才会自动重写
#auto-aof-rewrite-percentage 100 //aof自上一次重写后 增长了100% 再次重写
bgrewriteaof 重写aof命令
恢复数据默认用aof恢复
4.0以后的混合模式持久化
#aof-use-rdb-preamble yes #开启混合持久化(必须先开启AOF才能使用)
我们在aof重写时,会把重写这一刻之前的命令变成rdb快照处理(二进制),并且把重写过程中的命令以aof的内容和rdb的内容存一起变为新文件,在重写写入期间新文件名称不叫 appendonly.aof 直到写入完毕就会覆盖掉原来的aof文件,于是Redis重启的时候,可以先加载RDB的内容,然后再aof重写。
10、Redis主从复制原理
通过slaveof命令或者设置slaveof选项,让一个服务器去复制另一个服务器的数据。
全量拷贝:
1、主节点通过bgsave来fork出一个子进程来进行RDB持久化,这个过程是非常消耗CPU、 内存(页表复制)、硬盘IO的
2、主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
3、从节点清空老数据,载入新RDB文件的过程是阻塞的,无法响应客户端的命令,如果从节点执行bgrewriteaof,还会带来额外的消耗
增量拷贝:
1、复制偏移量:执行复制的双方、主从节点、分别会维护一个偏移量offset
2、复制积压缓冲区:主节点内部维护一个固定长度的、先进先出的队列作为复制积压缓冲区,当主从节点offset的差距过大,超过缓冲区长度时,将无法增量拷贝,只能全量拷贝。
3、服务器运行ID(runid):每个Redis节点在运行的时候都会生成一个runid,主节点会把自己的runid发送给其他从节点,从节点会将主节点的runid存起来,从节点重连时,会根据runid判断同步进度
如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用增量拷贝(看offset差距是否超过了缓冲区长度)
如果从节点保存的runid与主节点现在的runid不同(主节点被换成了其他节点),只能进行全量拷贝。