1. 什么是主从复制?是怎么实现的?
为什么要搭建主从架构?
这是因为Redis
在实际应用上通常是读多写少,因此为了提升Redis
的读性能,就需要将Redis
的读写的能力进行均衡,也就是可以搭建一个主从集群,让主节点来承担写操作,然后主节点发送对应的指令到从节点中进行同步,其他读操作的请求都发送从节点上。
如何搭建主从架构?
(1)创建目录,名字分别为这些端口的名称
mkdir 7001 7002 7003
(2)拷贝目录
cp redis-6.2.4/redis.conf 7001
cp redis-6.2.4/redis.conf 7002
cp redis-6.2.4/redis.conf 7003
(3)修改每个实例的端口、工作目录
sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/tmp\/7001\//g' 7001/redis.conf
(4)开启主从
# 修改配置文件,在redis.conf中添加一行配置
slaveof masterip masterport
# 使用redis-cli客户端连接到redis服务(重启后失效)
slaveof masterip masterport
然后输入指令的节点就会成为master
的从节点
(5)查看信息
info replication
自测:假设A、B两个redis实例,如何让B作为A的从节点?
关键slaveof masterip masterport
2. Redis数据同步原理
首次全量同步原理
流程概述:首先如果是第一次执行同步的话,从节点会向master节点发送一个请求数据同步的指令,然后1它会判断当前是否是第一次同步,如果是第一次同步,那么就会返回master的数据版本信息
,然后从节点就会保存这个版本信息
接着由于从节点需要全量数据,因此主节点就会开启basave
,生成当前内存数据快照,也就是RDB
文件,由于这个过程过于耗时,因此在这个过程中还会记录RDB
期间所执行的所有指令到repl_log
内存缓冲区中,当RDB
文件准备好了之后就会发生到从节点上,然后从节点就会清空本地数据(这是因为从节点可能以前是单机或者别的集群中使用的)
,然后从节点就会加载这个RDB文件,同时服务端还会发送一个增量日志
这个增量日志考虑的是耗时较长的RDB过程中的执行命令的情况,然后从节点
接收到这个增量日志就会将命令进行执行,这就是首次全量同步的原理
- 问题:主节点如何知道从节点是第一次来的?原理:
Replication ID
:简称为replid
,是数据集的标记,id如果一致就是同一个数据集,每一个master
都有唯一的replid
,slave
则会继承master
节点的replid
offset
:偏移量,随着记录在repl_baklog
中的数据增多而逐渐增大,从节点完成同步的时候也会记录当前同步的offset
,如果slave
的offset
小于等于master
的offset
,那么说明从节点的数据要落后于主节点的数据
,需要完成更新
<- psync replid offset
-> replid offset
因此在首次的master和slave握手的时候,slave必须要向master声明自己的replicationId
和offset
,然后master
才能够判断到底需同步哪些数据,具体来说,就是slave给master发送rID
,然后判断它的rID
和master的rID
是否相同,如果不一样就是第一次来,然后更正对方的rID
增量同步原理
假设这样的场景,如果从节点事先已经完成首次全量同步的握手,但如果slave
重启后同步,那么就会执行增量的同步,增量同步的原理如下:
首先slave
发起数据同步请求,psync replid offset
然后主节点判断这个replid
是否和主机的数据版本一致,如果一致的话那么就是进行一次增量同步,发回一个continue,继续数据同步
然后主节点会根据从节点给的offset
去到repl_baklog
的offset
之后去取数据,然后打包这些数据发送到slave
接着slave
接收到这些数据就执行,并且记录当前offset是多少,便于后续的同步
增量同步失败的原因
repl_baklog
是有大小上限的,一旦写满之后就会覆盖最早的数据,当slave
断开的时间过长,导致还没有备份的数据被覆盖,那么具体体现就是offset
在备份文件中找不到了,那么就无法基于log
做一个增量同步,那么就只能够完成全量同步了
如何来优化主从集群?
通过上面可以看出,主从集群之间的数据同步极有可能会导致RDB
的发生,因此为了优化这种现象可以:
- 在master中配置
repl-diskless-sync yes
来启用无磁盘复制,避免全量同步的时的磁盘IO,因为这个RDB是要通过网络IO发送出去的,因此可以直接将缓冲区中的数据直接发送到网络设备上
repl-diskless-sync yes
当磁盘比较慢,网络比较快的时候,可以使用这个,但是如果网络比较慢,就可能导致网络阻塞,具体的表现就是主进程处理请求后的数据无法发送出去,因为存在竞争IO通道的现象
Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
提供
repl_baklog
的大小,当发现slave
宕机时尽快完成故障恢复,尽可能避免全量同步限制一个master的slave的数量,如果实在太多slave,那么就可以采用主-从-从的链式结构,减轻master的压力,比如说让同步完成的
slave
充当master
来发送数据同步数据
全量同步和增量同步的区别
全量同步
是基于RDB
来实现的,它在实现的时候需要主节点将当前内存的数据形成快照并且通过网络IO发送到从节点上,然后在这期间其他读写指令还要通过repl_baklog
来补充,执行较慢,重量级
增量同步
是基于repl_baklog
来实现的,它通过从节点发送到主节点的offset
来确定还有哪些数据需要同步,不需要master
整个内存的状态,执行较快,轻量级,但是在repl_baklog
溢出的时候还是会触发全量同步
3. 哨兵机制详解
哨兵其实本质上是一个运行在特殊模式下的Redis
进程,因此它也是一个节点,从哨兵这个名字就能看出来,它实际上是基于观察者模式
实现的一个观察者节点
它在观察到有异常的情况下,就会做出一些动作来修复异常的状态
哨兵节点主要负责三件事情:监控
、选主
和通知
问题引入:slave
节点宕机恢复后可以找到master
节点同步数据,那么master
节点宕机怎么办?
Redis
提供了哨兵机制来实现主从集群的自动故障恢复,其结构和作用:
- 监控:
Sentinel
会不断检查master
和slave
是否预期工作 - 自动故障的恢复:如果有master节点故障了,那么Sentinel会将一个slave提升为master,当故障实例恢复后会以新的master为主
- 故障通知:
Sentinel
充当Redis
客户端的服务发现来源,当集群发生故障转移的时候,会将最新信息推送到Redis
的客户端
哨兵机制是如何工作的?哨兵如何判断一个redis实例是否健康?
哨兵节点是如何监控节点的?又是如何判断主节点是否真的故障了?
Sentinel
是基于心跳机制检测服务状态,每隔1s就向集群中的每个实例发送ping
命令,当主从节点收到这个Ping命令之后,就会发送一个响应的命令给哨兵,这样就可以判断它们是否在正常运行
如果主节点或者从节点没有在规定时间内响应哨兵的Ping命令,那么哨兵就会将它们标记为主观下线
,这个规定的时间是可以配置的
- 主观下线,如果某节点发现某个实例在固定的时间内没有响应,那么就会认为该实例主观下线了
- 客观下线,如果超过指定数量
quorum
的sentinel
都认为这个实例主观下线,那么这个实例就是客观下线了,这个数值最好要超过实例数量的一半
为什么要设计主观下线和客观下线?
这是因为有可能主节点
其实并没有发生故障,可能只是因为主节点的系统压力比较大
或者网络发生了阻塞
,导致主节点没有在规定时间内响应哨兵的请求,从而导致主节点没有在规定的时间内响应哨兵的PING
命令。
所以为了减少误判的情况,哨兵在部署的时候不会只部署一个节点,而是用多个节点部署成哨兵集群(最少需要三台机器来部署哨兵集群)
,通过多个哨兵节点一起判断,就可以避免单个哨兵的误判而导致主节点直接下线的情况,同时,多个哨兵的网络同时不稳定的概率小,误判的概念也能降低
客观下线的具体流程:
- 当一个哨兵判断主节点为主观下线之后,就会向其他哨兵发起命令,其他哨兵收到这个命令之后,就会根据自身和主节点的网络情况,做出赞成投票或者拒绝投票的响应
一般来说,当有超过一般的哨兵认为这个实例下线之后,才说这个实例确实是下线了
这个一半和拜占庭将军问题有关
,至于为什么要集体决策,这是因为每个哨兵节点检查服务监控的时机不一致,因此就可能导致前一秒你这个服务是好的,在后面就不好了
如何选举新的master?
一旦发生master故障,哨兵需要在slaves
中选取一个作为新的master
节点,具体的逻辑是这样的
首先拿到
slave
与master节点断开的时间长短,如果超过了指定的值(down-after-milliseconds*10)
那么就会排除 这个slave
节点,如果断开时间越长,那么就证明数据越不同步接着会判断
slave
节点的slave-priority值
,越小的话就代表这个优先级越高,如果是0的话就代表是永远不参与选举如果
slave-prority
一样,那么就判断salve
节点的offset
值,如果越大,那么就说明数据越新,优先级越高,最后判断slave
节点的运行ID大小,越小的优先级越高(大家都一样)
如何实现故障转移?
当选举出来新的master
之后,首先哨兵会给备选的slave
节点发送slaveof no one
命令,然后让这个节点成为master
,哨兵给其他所有的slave
发送salveof newMasterIp newMasterPort
的命令,然后开始新的master
上同步数据
最后哨兵将故障节点标记为slave
,当故障节点恢复后就会自动成为新的master
的slave节点
由哪个哨兵进行主从故障转移?
为了更加客观的判断主节点故障了,一般不会只由单个哨兵的检测结果来判断,而是多个哨兵一起判断,这样可以减少误判的概率,因此通常来说,哨兵是以哨兵集群的模式存在的
那么是由集群中的哪个哨兵来执行故障转移的呢?
这时候还需要在哨兵集群中选出一个leader
,让这个leader
来执行主从的切换
选举leader
的过程其实是一个投票的过程,在投票开始前,肯定要有一个候选者
如何选举候选者?
哪个哨兵判断主节点为客观下线的,这个哨兵节点就是候选者,所谓的候选者就是想当Leader
的哨兵
如下图所示,假设有三个哨兵,当哨兵B先判断到主节点主观下线后,就会给其他实例发送is-master-down-by-addr
的指令,然后其他的哨兵就会根据自己和主节点的网络连接情况,做出赞成投票或者是拒绝投票的响应
当哨兵B收到赞成票数达到哨兵配置文件中的quorum
配置项设定的值之后,就会将主节点标记为客观下线,此时这个哨兵B就是一个Leader
的候选者
候选者如何选举成为Leader?
候选者会向其他哨兵发送命令,表明希望称为Leader
来执行主从切换,其他哨兵节点可以不用干预,如何让所有其他哨兵对它进行投票,每个哨兵只有一次的投票机会,如果用完之后就不能投票了,可以投给自己或者投票给其他人,但是候选者只能将票投给自己
那么在投票过程中,任何一个候选者都需要满足两个条件
- 拿到半数以上的赞成票
- 拿到的票数同时还需要大于等于哨兵配置文件中的
quorum
值
分布式环境下,如果同时出现多个候选人怎么办?
首先每一位的候选者都会先给自己投一票,然后向其他哨兵发起投票请求,然后向其他哨兵发起投票请求,如果投票者先收到候选者A的投票请求,那么就会先投票给它,如果投票者将机会用完之后还收到了候选者B的请求,那么就会拒绝投票
为什么哨兵节点至少要有3个?
这是因为哨兵集群中的只有两个哨兵节点,如果一个哨兵想要成功称为Leader
,那么就必须获取2票,因此如果哨兵集群中有一个哨兵挂掉了,那么就只剩下一个哨兵了,此时的票数就无法到达2票,那么此时就无法称为Leader,此时无法进行主从节点的切换
通常至少会配置3个哨兵节点,这时候如果哨兵中有一个节点挂掉了,那么还剩下两个哨兵,如果这个哨兵想要成为Leader,这时候还是有机会达到2票的,因此还是可以选举成功的,不会导致无法进行主从节点的切换
如果3个哨兵节点挂了两个,那么这时候存活节点太少了,处于一种不稳定的状态,需要人为干预了
Redis
1主4从,5个哨兵,quorum
设置为3,如果两个哨兵故障了,当主节点挂掉的时候,哨兵可以判断主节点客观下线吗?主从能否自动切换?
对这个实战问题展开分析的关键是:
- 客观下线的条件:哨兵集群达到了
quorum
的数量判断这个节点挂掉了 - 主从自动切换的条件:执行Leader哨兵要能够收到半数以上的票
哨兵集群可以判断主节点客观下线,因为两个哨兵故障了,这时候还剩下三个哨兵,当一个哨兵判断主节点主观下线后,询问另外两个哨兵后,有可能拿到两张下线的票,这时候就达到了quorum
的值,因此这时候哨兵集群就可以判定主节点客观下线了
可以完成主从切换,当有个哨兵标记主节点为客观下线后,此时第一步,要完成哨兵的Leader的选举,因为此时哨兵集群还剩下3个哨兵,那么就还是可以拿到5/2+1=3
的票,此时还是可以达到quorum
的值的,因此满足了选举Leader
的两个条件,所以就可以选举成功,因此哨兵集群可以完成主从切换
但是如果quorum
的值设置为2,并且有3个哨兵故障的话,此时哨兵集群还是可以判断主节点为客观下线,但是哨兵不能完成主从切换。
这是因为至少要哨兵拿到一半的票数,同时拿到的这个票数还需要大于哨兵配置文件中的quorum
的值,那么它首先就无法完成一半的票数的这个条件了,因此最终就无法实现主从切换了
如果quorum
设置为3,这就意味着无论如何都无法达到quorum
了。
详细说说主从故障转移的过程?
在哨兵集群中通过投票的方式,选举出了哨兵的Leader之后,就可以进行主从故障转移的过程了
- 从原来的所有从节点下所有的从节点中挑选出一个从节点,然后标记为主节点
- 让已下线的主节点下的所有从节点修改复制目标,修改为复制新主节点
- 让新主节点的IP地址和信息,通过发布订阅模式通知到客户端,这样的话客户端就可以向这个新主节点发送指令了
- 添加哨兵监视旧主节点的任务,当旧主节点上线的时候,将它设置为新主节点的从节点
如何选举出新主节点?
故障转移操作的第一步要做的就是在已下线主节点属下的所有从节点中,挑选出一个状态良好、数据完整的从节点,然后向这个从节点发送SLAVEOF no one(站起来,不准跪)
,这就将这个从节点转换为主节点了
但是这个从节点到底是如何选出来的呢?
一般来说,数据越完整的从节点就越适合做新的主节点,Redis
集群是通过down-after-milliseconds*10
的这个配置项来实现的,这个参数的含义是说从节点和主节点失去连接的秒数
,那么*10是什么呢?
如果在down-after-milliseconds毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断联了,如果发生断联的次数超过了10次,那么就说明这个从节点的网络不是太好,不适合作为主节点
这就先把网络状态不同的从节点过滤掉了,接下就要对从节点执行三轮考察,优先级、复制进度、ID号
第一轮考察:哨兵首先会根据节点的优先级来进行排序,优先级越小的排名就越靠前
salve-priority
这个配置项,可以给从节点设置优先级,每一台从节点的服务器配置不一定是相同的,我们可以根据服务器性能配置来设置从节点的优先级,比如说如果A从节点的物理内存是所有从节点中最大的,那么就可以从部署A上的Redis实例调整为最大的,这样的话在第一轮考察就会优先胜出
第二轮考虑:如果优先级相同,那么就查看复制的下标,这个也就是offset,当offset越新,那么就证明数据越同步,哪个就越靠前
复制进度最靠前的从节点胜出,说的是在主从架构中,主节点会将写操作同步给从节点,在这个过程中,主节点会用master_repl_offset
记录当前的最新写操作在repl_baklog_buffer
中的位置。
从节点会用slave_repl_offset
来记录当前的复制进度,也就是从服务器要读的位置
如果某个从节点的slave_repl_offset
最接近master_repl_offset
,那么就说明它的复制进度是最靠前的,于是就可以将它选作是新的主节点
第三轮考虑:如果优先级和下标都相同,那么基本上性能无差异,随机选就行了,这里Redis做的一个操作是选择节点ID比较小的那个
总结
在选举出从节点之后,负责主从切换的哨兵Leader会向被选中的节点说SLAVEOF no one
,让这个节点解除从节点的身份,标记为新的主节点
在发送SLAVEOF no one
之后,哨兵leader
就会以每秒1次的频率向被升级的节点发送INFO
命令(在没进行故障转移之前,INFO命令的频率都是10s一次)
,并且观察命令中回复的角色信息,简单来说就是当新的主节点升级为master
的时候,哨兵leader就知道它顺利升级成主节点了
那么新的主节点出现了,那么此时就需要告诉原来的从节点,主节点发送变更了,具体的逻辑就是哨兵leader向所有的从节点发送SLAVEOF masterIp masterPort
,让他们去成为新的从节点
接着,集群内部环境已经完成部署,那么就要通知外部客户端具体事件的变更了,Redis是通过发布订阅模式实现的,每个哨兵节点提供发布/订阅的机制,客户端可以从哨兵中订阅消息,客户端和哨兵建立连接之后,客户端就会订阅哨兵的频道,那么在完成主从切换之后,此时哨兵就会向频道中发布新的主节点IP和端口信息,此时客户端就可以收到这条信息,然后用新主节点的IP地址和端口与客户端进行通信了
为什么要使用发布订阅模式?
通过发布者订阅的模式,有了这些事件的通知,客户端不仅可以在主从切换后得到新主节点的连接信息,还可以监控到主从节点切换中发生的事件,这样给了一个信息源,可以及时处理各种异常事件
最终,哨兵节点添加了一个监听事件,这个事件监听旧主节点的信息,当旧主节点重新上线的时候,哨兵集群就会发送一个SLAVEOF
,让它称为新主节点的从节点
哨兵集群是如何组成的?
在搭建基集群的时候,在配置集群信息的时候,设置主节点的名字,主节点的IP地址和端口号以及quorum值
不需要填充其他哨兵节点的信息,这时候没有相关指令说配置说这个节点加入到哪个集群中,哨兵节点之间是通过redis
的发布订阅机制来实现的
在主从集群中,主节点上有一个名为__sentinel__:hello
的频道,不同哨兵就是通过它来互相发现,实现互相通信的。当哨兵A监控集群的时候,此时哨兵A会把自己的IP地址和端口的信息发布到公共频道上,因为在频道上的哨兵订阅了该频道,哨兵B和哨兵C就可以从这个频道直接获取哨兵A的IP地址和端口号,然后哨兵BC和哨兵A就可以建立网络连接
哨兵集群会对
从节点
的运行状态进行监控,那么哨兵集群如何知道从节点
的信息?
主节点知道所有从节点的信息,所以哨兵会每10s一次的频率向主节点发送INFO
信息来获取所有从节点的信息
总结一下,之所以哨兵只需要监听主节点即可完成,首先第一个问题,就是哨兵如何加入集群,它是基于发布/订阅来实现的,原理是在主节点上创建了一个公共频道,这个公共频道中,在集群中的哨兵会订阅消息,还没有加入到哨兵的集群会订阅信息,当一个集群监听主节点的时候,就会向主节点上创建的这个公共频道发布一个消息,然后其他已经在集群中的哨兵,就会订阅到这个消息,这样的话其他哨兵实例就能够和这个新加入进来的哨兵建立网络连接关系。这样看起来就好像哨兵自动加入了集群一样
其次,哨兵是如何知道从节点的信息呢?在哨兵加入集群的时候,哨兵是能够知道主节点的信息不假,但是哨兵如何知道从节点的信息呢?这是因为哨兵基于INFO
指令来知道主节点上的所有从节点的信息,从而哨兵就可以监控到从节点了
4. 什么是切片集群模式?
解决的是海量数据存储、高并发写的问题
当Redis
中数据多到一定程度的导致无法缓存的时候,就会将数据分配到不同的Redis
实例上
当使用分片集群的时候,此时集群中有多个master
,每个master
中保存的是不同的数据,因此这样的话就可以支持海量数据的存储,但是它并不支持高并发写,这是因为集群中有多个master
,每个master
保存了有不同的数据,每个master
都可以有多个slave
节点,这样的话,高并发写就会压到每个matser
的主节点上,而读操作通过自动路由寻址,就可以将这些数据的访问压力压到每个master
的slave
的节点上
如何监控健康状态?
以前还需要哨兵来监控master
的健康状态,但是现在由于存在多个master节点,这些节点互相独立,因此就可以实现的是,让这些master之间互相检查自己的健康状态,通过ping
来进行实现。客户端请求可以访问集群任意节点,最终都会被转发到正确的节点
什么叫散列插槽?
核心概念:1.哈希槽:Redis Cluster
模式采用哈希槽,来处理数据和节点之间的关系,在Redis Cluster
中,一共有16384
个哈希槽,这些槽类似于数据分区,每个键值对都会根据它的key
,被映射到一个哈希槽中,具体的执行流程分为两大步:
- 根据键值对的key,计算出一个
16
bit的数字 - 然后将这个
16
bit的数字对16384
取余,得到0~16383
中的任意一个数字
然后根据这个数字,来决定到底应该将数据放在哪个Redis
实例中
比如说,有2台Redis
的实例,然后就会那么第一台主机的哈希槽范围就是0~16383/2
,第二台主机的哈希槽范围就是16383/2 +1 ~16383
当然也可以手动配置权重,也就是
redis-cli -h 192.168.132.10.1 -p 6379 cluster addslots 0,1
在手动分配槽的时候,需要将这16384
个槽都分配完毕,否则的话集群无法工作
插槽所对应的key到底是如何计算出来的?
数据key不是和节点绑定,而是和插槽绑定,redis
会根据key的有效部分计算插槽,分为两种情况:
- key中包含有
{}
,并且这个{}
中至少包含了有一个字符,那么{}
中的部分就是有效的部分 - key中不包含有
{}
,那么整个key就是有效的部分
比如说key => num
,那么就会根据num来计算,如果是形如{itcast}num
,那么就会以{itcast}
作为有效部分,具体的方式是利用CRC16
算法来得到一个hash()
,然后对16384
进行取余,得到的结果就是slot
值
使用分片集群,如何做集群新节点的添加?并且指定元素存储到指定的位置
- 启动一个新的
redis
实例,端口为7004
- 添加
7004
到之前的集群,并且作为一个master
节点 - 给
7004
分配哈希插槽,使得num
这个key
可以存储到7004
的实例
5. 什么是脑裂现象?会带来什么问题?
集群中的脑裂现象就是集群中的主节点出现了两个,此时将会产生一个脑裂导致数据丢失的问题
一个例子:比如说当Redis
中的集群主节点与内部的从节点的网络出现了问题,但是和客户端的网络是好的,这时候客户端并不知道Redis
内部产生了问题,于是就客户端还是不断地向主节点中写入数据,然后哨兵在检查健康状况的时候发生主节点失联了,它认定它是宕机了,于是重新选举一个leader
当leader
被选举出来了,网络又突然好了,这时候时候主节点又恢复运行,但是是以一个slave
节点存在于网络中的,于是就会申请同步,由于第一次同步是全量同步,逻辑是将从节点中的数据全部删除,然后将数据全部同步过去
那么也就意味着之前客户端向主节点写入的数据被清除掉了,而且数据已经完全找不到了。
这就是脑裂现象带来的一个数据丢失的问题
6. 怎么解决脑裂现象?
脑裂现象在Redis
中,可以基于一个主机连接从节点的最小值
、从节点接收到指令到完成数据复制的最短延迟
来实现,当主机连接从节点的个数小于了N个,那么就不允许主机写入指令了
、当节点接收到指令后完成数据复制的延迟超过了这个时限了
,就不允许主机写入指令了
通过这个机制,当主机失联的时候,它失联的期间无法响应哨兵的心跳检测,也不能和从库进行同步,自然也就无法进行ACK了,原主库就会限制被接收请求,客户端也就无法写入数据了
当哨兵检测到原主库宕机了之后,这个就会执行主从切换,等到新主库上线的时候,就只有新主库能够写数据,原主库因为被下线了,并且限制了写入,这样的话就不会有两个主节点,并且也最大程度的避免了数据的丢失
7. Redis使用的过期删除策略是什么?
Redis
的过期删除策略通常有惰性删除策略
和定期删除策略
- 惰性删除策略:只有在客户端访问到这个
key
的时候,才会判断这个key
是否过期,具体的流程是这样的,Redis会将设置了过期时间的key放到一个过期字典
中,里面存储了这个key和相应的过期时间,当访问一个key的时候,就会先看这个key有没有在这个过期字典中,如果在这个过期字典中,那么就会先判断这个对应的过期时间和目前的服务器的时间,如果大于了,那么就过期了,删除并且返回空,否则的话就会返回实际的值 - 定期删除策略:在
Redis
中,会每隔一段时间,就会随机抽取过期字典
中的20个key,如果有百分之25的key过期了,那么就会继续下一轮的检查,否则的话就会结束本次删除
问题以及解决策略
惰性删除策略对CPU是友好的,因为它不会在繁忙的时候占用CPU的时间片,但是它对内存是不友好的,因为它如果过期了但是无人访问,就会一直占用内存
定期删除策略则是:算法具有一定的缺陷,当不断随机到有百分之25的键过期,就会不断占用CPU
解决策略:Redis中两种策略混合使用,这样的话规避了惰性删除对内存不友好的缺点,同时对定期删除策略设定了一个最大执行时间,当超过了这个最大执行时间就直接返回,从而就能够避免长时间的过期键回收,避免过度占用CPU
8. Redis持久化的时候,如何处理过期的键?
Redis
的持久化策略有AOF
和RDB
,这两种持久策略在不同的阶段对过期的键都有特殊的处理
RDB
RDB
的关键是将内存中的数据全部持久化到文件中,因此当将内存数据持久化到文件的时候,会对key做一个过期的检查,过期的键不会写入到RDB中
当RDB
数据被加载到内存中的时候,要看加载的是主服务器还是从服务器,
- 如果此时的Redis是主服务器,在载入RDB的时候,会对键是否过期做一个判断,当键过期的时候,就不会将对应的键值对数据载入到Redis中
- 如果此时的Redis是从服务器,无论键值对数据是否过期,都会被载入到内存中,但是由于在进行数据同步的时候,从服务器的数据都会被清空,不会造成影响
AOF
AOF文件写入阶段
:由于AOF是追加写的模式,因此对于之前的数据是不会进行改动的,而是追加一条DEL
的日志,来针对这个键值对进行删除
AOF文件重写阶段
:会检查过期的键,当检测到过期的键的时候,就不会写入到Redis
数据库中
9. Redis主从模式中,如何处理过期的键?
主节点
对于过期的键,是主动的,也就是会用上惰性删除策略+定期删除策略
来进行删除,因此在主节点的话,过期的键不会造成任何影响,但是从节点对于过期的键的管理是被动的,当外面有请求打到从节点的时候,如果键过期了但是没有被删除掉,这时候会直接返回
那么是如何来保证数据一致的呢?是通过主节点发送一个DEL
的指令到从节点上,然后从节点执行指令就删除掉了
10. Redis内存被打满,会发生什么?
当Redis
的运行内存到了maxmemory
的时候,就会启动内存淘汰策略
11. Redis内存淘汰策略有哪些?
- noevication:当内存被打满了之后,此时不再存储数据,而是直接返回错误到客户端
进行数据淘汰的策略,可以分为是在设置了过期时间的数据中进行淘汰和在所有数据范围内进行淘汰
简单来说,可以分成是ttl
、random
、lfu
、lru
在设置了过期时间的数据中:
- 随机淘汰
- 淘汰最早过期的
- 根据访问的频率,淘汰那些访问频率最低的
- 根据访问的次数,淘汰那些访问次数最少的
在没有设置过期时间的数据中
- 随机淘汰
- LFU
- LRU
12. LRU算法是什么?Redis是如何实现的?
LRU
算法叫做是最近最少使用优先淘汰算法,它是根据键值对的访问时间来决定要淘汰谁的
一般来说,我们实现是通过一个哈希链表
来实现的,当需要查询的时候,只需要输入一个key
,就可以得到相应的value
,并且由于插入都是在尾部,而删除在中间和头部可能发生,因此要使用一个链表来维护插入的次序
但是Redis并没有采用这样的实现
因为这样的话就要维护一个大链表,这样的话可能会导致内存更快被占满,那么它使用的是一种近似的思路,也就是在RedisObject
的结构体中添加一个lru
这个字段(存储的是访问时间)
,它会随机抽样,在抽中的N个元素中,选择一个最近最久没有访问过
的元素进行淘汰
有什么问题?
缓存污染
的问题,就是当Redis
中缓存了一大批的只使用一次的数据,那么这时候就会造成一个问题,就是说这些一大批数据会长久地占在内存中,而且很长一段时间内都不会被访问
13. LFU算法是什么?Redis是如何实现?
LFU
算法就是根据数据被访问的频率来决定谁要被淘汰,它会记录每一个数据的访问次数,当一个数据被访问的时候,就会增加这个数据的访问次数,这样就解决了偶尔访问了一次之后,数据留存在缓存中很长一段时间的问题
那么在Redis
中的实现是这样的:
它复用了LRU算法
中的lru
字段,将这24bit
转换成了高16bit存储时间戳
,低8bit存储访问次数logc
,然后在被访问的时候,也就是需要更新lru
字段的时候,更新流程如下:
logc
初始化为5,先按照上次访问时间戳,对它进行衰减
然后按照logc
的值对这个缓存的有效期进行增加
如果logc
的值越小,那么就越难再增加
14. 什么是BigKey?
首先BigKey
它指的是Key
所对应的value
占用空间很大
- String类型的值大于10kb
- Hash,List,Set,ZSet中的存储元素个数大于5000个
BigKey有什么问题?
可以从单机的角度上分析
- 当单机上存在BigKey的时候,由于是单线程处理,那么就会可能所操作这个bigKey的时候耗时比较长,从而阻塞后面的请求,响应的时间会很长
- 引发网络阻塞的问题,如果一个key的大小是1MB,每秒的访问量是1000,那么每秒就会产生1000MB的流量,会造成非常严重的网络阻塞
- 阻塞工作线程,如果使用del删除大Key的时候,就会阻塞工作线程,没法处理后续的命令
可以从集群的角度上分析
内存分布不均,当一个bigKey落在某一个slot的时候,就会导致这些节点占用内存大,QPS也会比较高
如何查找BigKey?
redis-cli -h 127.0.0.1 -p6379 -a 'password' --bigkeys
- 最好选择在从节点上执行该命令。因为主节点上执行时,会阻塞主节点;
- 如果没有从节点,那么可以选择在 Redis 实例业务压力的低峰阶段进行扫描查询,以免影响到实例的正常运行;或者可以使用 -i 参数控制扫描间隔,避免长时间扫描降低 Redis 实例的性能。
15. 如何来删除BigKey?
为什么删除BigKey要格外小心?
删除操作本质上是释放掉内存,而释放掉内存的过程中,操作系统需要将内存的回收,将可用的内存快写入到链表中,如果内存过大,那么就会导致空闲的内存块可能会很多,最终导致删除BigKey
十分耗时
- 分批次删除
比如说对hash
每次获取100个字段,然后分批次删除,对于list
,每次使用ltrim
,每次删除少量元素,对于大set
,每次使用sscan
,再用sream
每次删除一个建
- 异步删除
用unlink
来删除,这样的话Redis
会将这个key
放入到后台线程中,不会造成主线程的阻塞
16. 什么是Redis的管道?
pipeline
是客户端提供的一种批处理的技术,用于一次性处理多个Redis
命令,从而提高交互的性能
使用管道技术可以解决多个命令执行时的网络等待,它是将多个命令整合到一起后发送给服务端批处理后同一返回客户端,这样就免去了每条指令都要等待的情况
17. Redis事务支持回滚吗?
不支持回滚,discard
指令只能用来主动放弃事务的执行,将待执行的命令全部放弃
大概的意思是,作者不支持事务回滚的原因有以下两个:
- 他认为 Redis 事务的执行时,错误通常都是编程错误造成的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,所以他认为没有必要为 Redis 开发事务回滚功能;
- 不支持事务回滚是因为这种复杂的功能和 Redis 追求的简单高效的设计主旨不符合。
这里不支持事务回滚,指的是不支持事务运行时错误的事务回滚。