【腾讯阿里最全面试题】Redis 持久化 RDB 和 AOF 的区别
跳槽必备:
嵌入式开发转互联网开发面经分享:嵌入式开发成功转战互联网行业
大厂 offer 的前置条件 学校学历,技术水准分析:大厂必备前置条件,项目经验
腾讯 T9(原 T3.1)offer,“8+1”的技术维度总结:腾讯T9技术栈知识点解析
本科校招成功拿下 B 站 offer 面经:普通二本校招,脱颖而出,成功拿下B站offer
持久化方式有哪些?有什么区别?
redis 持久化方案分为 RDB 和 AOF 两种。
RDB
RDB 持久化可以手动执行也可以根据配置定期执行,它的作用是将某个时间点上的数据库状态保存到 RDB 文件中,RDB 文件是一个压缩的二进制文件,通过它可以还原某个时刻数据库的状态。由于 RDB 文件是保存在硬盘上的,所以即使 redis 崩溃或者退出,只要 RDB 文件存在,就可以用它来恢复还原数据库的状态。
可以通过 SAVE 或者 BGSAVE 来生成 RDB 文件。
SAVE 命令会阻塞 redis 进程,直到 RDB 文件生成完毕,在进程阻塞期间,redis 不能处理任何命令请求,这显然是不合适的。
BGSAVE 则是会 fork 出一个子进程,然后由子进程去负责生成 RDB 文件,父进程还可以继续处理命令请求,不会阻塞进程。
AOF
AOF 和 RDB 不同,AOF 是通过保存 redis 服务器所执行的写命令来记录数据库状态的。
AOF 通过追加、写入、同步三个步骤来实现持久化机制。
当 AOF 持久化处于激活状态,服务器执行完写命令之后,写命令将会被追加 append 到 aof_buf 缓冲区的末尾
在服务器每结束一个事件循环之前,将会调用 flushAppendOnlyFile 函数决定是否要将 aof_buf 的内容保存到 AOF 文件中,可以通过配置 appendfsync 来决定。
如果不设置,默认选项将会是 everysec,因为 always 来说虽然最安全(只会丢失一次事件循环的写命令),但是性能较差,而 everysec 模式只不过会可能丢失 1 秒钟的数据,而 no 模式的效率和 everysec 相仿,但是会丢失上次同步 AOF 文件之后的所有写命令数据。
说说 Redis 基本数据类型有哪些吧
字符串:redis 没有直接使用 C 语言传统的字符串表示,而是自己实现的叫做简单动态字符串 SDS 的抽象类型。C 语言的字符串不记录自身的长度信息,而 SDS 则保存了长度信息,这样将获取字符串长度的时间由 O(N)降低到了 O(1),同时可以避免缓冲区溢出和减少修改字符串长度时所需的内存重分配次数。
链表 linkedlist:redis 链表是一个双向无环链表结构,很多发布订阅、慢查询、监视器功能都是使用到了链表来实现,每个链表的节点由一个 listNode 结构来表示,每个节点都有指向前置节点和后置节点的指针,同时表头节点的前置和后置节点都指向 NULL。
字典 hashtable:用于保存键值对的抽象数据结构。redis 使用 hash 表作为底层实现,每个字典带有两个 hash 表,供平时使用和 rehash 时使用,hash 表使用链地址法来解决键冲突,被分配到同一个索引位置的多个键值对会形成一个单向链表,在对 hash 表进行扩容或者缩容的时候,为了服务的可用性,rehash 的过程不是一次性完成的,而是渐进式的。
跳跃表 skiplist:跳跃表是有序集合的底层实现之一,redis 中在实现有序集合键和集群节点的内部结构中都是用到了跳跃表。redis 跳跃表由 zskiplist 和 zskiplistNode 组成,zskiplist 用于保存跳跃表信息(表头、表尾节点、长度等),zskiplistNode 用于表示表跳跃节点,每个跳跃表的层高都是 1-32 的随机数,在同一个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯一的,节点按照分值大小排序,如果分值相同,则按照成员对象的大小排序。
整数集合 intset:用于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。
压缩列表 ziplist:压缩列表是为节约内存而开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
基于这些基础的数据结构,redis 封装了自己的对象系统,包含字符串对象 string、列表对象 list、哈希对象 hash、集合对象 set、有序集合对象 zset,每种对象都用到了至少一种基础的数据结构。
redis 通过 encoding 属性设置对象的编码形式来提升灵活性和效率,基于不同的场景 redis 会自动做出优化。不同对象的编码如下:
字符串对象 string:int 整数、embstr 编码的简单动态字符串、raw 简单动态字符串
列表对象 list:ziplist、linkedlist
哈希对象 hash:ziplist、hashtable
集合对象 set:intset、hashtable
有序集合对象 zset:ziplist、skiplist
文章福利更多一线大厂面试题集锦,需要的朋友点击 面试题资料 获取
"金三银四"程序员 26 套求职简历模板
Redis 为什么快呢?
redis 的速度非常的快,单机的 redis 就可以支撑每秒 10 几万的并发,相对于 mysql 来说,性能是 mysql 的几十倍。速度快的原因主要有几点:
完全基于内存操作
C 语言实现,优化过的数据结构,基于几种基础的数据结构,redis 做了大量的优化,性能极高
使用单线程,无上下文的切换成本
基于非阻塞的 IO 多路复用机制
那为什么 Redis6.0 之后又改用多线程呢?
redis 使用多线程并非是完全摒弃单线程,redis 还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程。
这样做的目的是因为 redis 的性能瓶颈在于网络 IO 而非 CPU,使用多线程能提升 IO 读写的效率,从而整体提高 redis 的性能。
知道什么是热 key 吗?热 key 问题怎么解决?
所谓热 key 问题就是,突然有几十万的请求去访问 redis 上的某个特定 key,那么这样会造成流量过于集中,达到物理网卡上限,从而导致这台 redis 的服务器宕机引发雪崩。
针对热 key 的解决方案:
提前把热 key 打散到不同的服务器,降低压力
加入二级缓存,提前加载热 key 数据到内存中,如果 redis 宕机,走内存查询
什么是缓存击穿、缓存穿透、缓存雪崩?
缓存击穿
缓存击穿的概念就是单个 key 并发访问过高,过期时导致所有请求直接打到 db 上,这个和热 key 的问题比较类似,只是说的点在于过期导致请求全部打到 DB 上而已。
解决方案:
加锁更新,比如请求查询 A,发现缓存中没有,对 A 这个 key 加锁,同时去数据库查询数据,写入缓存,再返回给用户,这样后面的请求就可以从缓存中拿到数据了。
将过期时间组合写在 value 中,通过异步的方式不断的刷新过期时间,防止此类现象。
缓存穿透
缓存穿透是指查询不存在缓存中的数据,每次请求都会打到 DB,就像缓存不存在一样。
针对这个问题,加一层布隆过滤器。布隆过滤器的原理是在你存入数据的时候,会通过散列函数将它映射为一个位数组中的 K 个点,同时把他们置为 1。
这样当用户再次来查询 A,而 A 在布隆过滤器值为 0,直接返回,就不会产生击穿请求打到 DB 了。
显然,使用布隆过滤器之后会有一个问题就是误判,因为它本身是一个数组,可能会有多个值落到同一个位置,那么理论上来说只要我们的数组长度够长,误判的概率就会越低,这种问题就根据实际情况来就好了。
缓存雪崩
当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到 DB 上,这样可能导致整个系统的崩溃,称为雪崩。雪崩和击穿、热 key 的问题不太一样的是,他是指大规模的缓存都过期失效了。
针对雪崩几个解决方案:
针对不同 key 设置不同的过期时间,避免同时过期
限流,如果 redis 宕机,可以限流,避免同时刻大量请求打崩 DB
二级缓存,同热 key 的方案。
Redis 的过期策略有哪些?
redis 主要有 2 种过期删除策略
惰性删除
惰性删除指的是当我们查询 key 的时候才对 key 进行检测,如果已经达到过期时间,则删除。显然,他有一个缺点就是如果这些过期的 key 没有被访问,那么他就一直无法被删除,而且一直占用内存。
定期删除
定期删除指的是 redis 每隔一段时间对数据库做一次检查,删除里面的过期 key。由于不可能对所有 key 去做轮询来删除,所以 redis 会每次随机取一些 key 去做检查和删除。
那么定期+惰性都没有删除过期的 key 怎么办?
假设 redis 每次定期随机查询 key 的时候没有删掉,这些 key 也没有做查询的话,就会导致这些 key 一直保存在 redis 里面无法被删除,这时候就会走到 redis 的内存淘汰机制。
volatile-lru:从已设置过期时间的 key 中,移出最近最少使用的 key 进行淘汰
volatile-ttl:从已设置过期时间的 key 中,移出将要过期的 key
volatile-random:从已设置过期时间的 key 中随机选择 key 淘汰
allkeys-lru:从 key 中选择最近最少使用的进行淘汰
allkeys-random:从 key 中随机选择 key 进行淘汰
noeviction:当内存达到阈值的时候,新写入操作报错
对标腾讯,阿里后端开发技术栈学习路线总结图:【清晰版点击 思维导图 获取】
怎么实现 Redis 的高可用?
要想实现高可用,一台机器肯定是不够的,而 redis 要保证高可用,有 2 个可选方案。
主从架构
主从模式是最简单的实现高可用的方案,核心就是主从同步。主从同步的原理如下:
slave 发送 sync 命令到 master
master 收到 sync 之后,执行 bgsave,生成 RDB 全量文件
master 把 slave 的写命令记录到缓存
bgsave 执行完毕之后,发送 RDB 文件到 slave,slave 执行
master 发送缓存中的写命令到 slave,slave 执行
这里我写的这个命令是 sync,但是在 redis2.8 版本之后已经使用 psync 来替代 sync 了,原因是 sync 命令非常消耗系统资源,而 psync 的效率更高。
哨兵
基于主从方案的缺点还是很明显的,假设 master 宕机,那么就不能写入数据,那么 slave 也就失去了作用,整个架构就不可用了,除非你手动切换,主要原因就是因为没有自动故障转移机制。而哨兵(sentinel)的功能比单纯的主从架构全面的多了,它具备自动故障转移、集群监控、消息通知等功能。
哨兵可以同时监视多个主从服务器,并且在被监视的 master 下线时,自动将某个 slave 提升为 master,然后由新的 master 继续接收命令。整个过程如下:
初始化 sentinel,将普通的 redis 代码替换成 sentinel 专用代码
初始化 masters 字典和服务器信息,服务器信息主要保存 ip:port,并记录实例的地址和 ID
创建和 master 的两个连接,命令连接和订阅连接,并且订阅 sentinel:hello 频道
每隔 10 秒向 master 发送 info 命令,获取 master 和它下面所有 slave 的当前信息
当发现 master 有新的 slave 之后,sentinel 和新的 slave 同样建立两个连接,同时每个 10 秒发送 info 命令,更新 master 信息
sentinel 每隔 1 秒向所有服务器发送 ping 命令,如果某台服务器在配置的响应时间内连续返回无效回复,将会被标记为下线状态
选举出领头 sentinel,领头 sentinel 需要半数以上的 sentinel 同意
领头 sentinel 从已下线的的 master 所有 slave 中挑选一个,将其转换为 master
让所有的 slave 改为从新的 master 复制数据
将原来的 master 设置为新的 master 的从服务器,当原来 master 重新回复连接时,就变成了新 master 的从服务器
sentinel 会每隔 1 秒向所有实例(包括主从服务器和其他 sentinel)发送 ping 命令,并且根据回复判断是否已经下线,这种方式叫做主观下线。当判断为主观下线时,就会向其他监视的 sentinel 询问,如果超过半数的投票认为已经是下线状态,则会标记为客观下线状态,同时触发故障转移。
能说说 redis 集群的原理吗?
如果说依靠哨兵可以实现 redis 的高可用,如果还想在支持高并发同时容纳海量的数据,那就需要 redis 集群。redis 集群是 redis 提供的分布式数据存储方案,集群通过数据分片 sharding 来进行数据的共享,同时提供复制和故障转移的功能。
节点
一个 redis 集群由多个节点 node 组成,而多个 node 之间通过 cluster meet 命令来进行连接,节点的握手过程:
节点 A 收到客户端的 cluster meet 命令
A 根据收到的 IP 地址和端口号,向 B 发送一条 meet 消息
节点 B 收到 meet 消息返回 pong
A 知道 B 收到了 meet 消息,返回一条 ping 消息,握手成功
最后,节点 A 将会通过 gossip 协议把节点 B 的信息传播给集群中的其他节点,其他节点也将和 B 进行握手
槽 slot
redis 通过集群分片的形式来保存数据,整个集群数据库被分为 16384 个 slot,集群中的每个节点可以处理 0-16384 个 slot,当数据库 16384 个 slot 都有节点在处理时,集群处于上线状态,反之只要有一个 slot 没有得到处理都会处理下线状态。通过 cluster addslots 命令可以将 slot 指派给对应节点处理。
slot 是一个位数组,数组的长度是 16384/8=2048,而数组的每一位用 1 表示被节点处理,0 表示不处理,如图所示的话表示 A 节点处理 0-7 的 slot。
当客户端向节点发送命令,如果刚好找到 slot 属于当前节点,那么节点就执行命令,反之,则会返回一个 MOVED 命令到客户端指引客户端转向正确的节点。(MOVED 过程是自动的)
如果增加或者移出节点,对于 slot 的重新分配也是非常方便的,redis 提供了工具帮助实现 slot 的迁移,整个过程是完全在线的,不需要停止服务。
故障转移
如果节点 A 向节点 B 发送 ping 消息,节点 B 没有在规定的时间内响应 pong,那么节点 A 会标记节点 B 为 pfail 疑似下线状态,同时把 B 的状态通过消息的形式发送给其他节点,如果超过半数以上的节点都标记 B 为 pfail 状态,B 就会被标记为 fail 下线状态,此时将会发生故障转移,优先从复制数据较多的从节点选择一个成为主节点,并且接管下线节点的 slot,整个过程和哨兵非常类似,都是基于 Raft 协议做选举。
了解 Redis 事务机制吗?
redis 通过 MULTI、EXEC、WATCH 等命令来实现事务机制,事务执行过程将一系列多个命令按照顺序一次性执行,并且在执行期间,事务不会被中断,也不会去执行客户端的其他请求,直到所有命令执行完毕。事务的执行过程如下:
服务端收到客户端请求,事务以 MULTI 开始
如果客户端正处于事务状态,则会把事务放入队列同时返回给客户端 QUEUED,反之则直接执行这个命令
当收到客户端 EXEC 命令时,WATCH 命令监视整个事务中的 key 是否有被修改,如果有则返回空回复到客户端表示失败,否则 redis 会遍历整个事务队列,执行队列中保存的所有命令,最后返回结果给客户端
WATCH 的机制本身是一个 CAS 的机制,被监视的 key 会被保存到一个链表中,如果某个 key 被修改,那么 REDIS_DIRTY_CAS 标志将会被打开,这时服务器会拒绝执行事务。
评论