Redis面试题

目录

一、Redis单线程为什么这么快

原理

处理请求流程

单线程块的原因

二、Redis分布式锁底层是如何实现的

三、Redis的持久化机制

RDB:

手动触发:

自动触发:

RDB的优缺点

AOF:

执行顺序

RDB的优缺点

RDB和AOF比较

四、Redis的过期键删除策略

1、惰性过期

2、定期过期

五、缓存雪崩、缓存穿透、缓存击穿

1、缓存雪崩

2、缓存穿透

3、缓存击穿

六、Redis主从赋值的核心原理 

全量复制:

部分复制:

七、简述Redis数据结构

八、如何保证数据库与缓存的一致性

九、Redis集群方案

哨兵模式:

Redis Cluster

Redis Sharding


一、Redis单线程为什么这么快

原理

Redis基于Reactor(响应式模式)开发了网络事件处理器(单线程)又叫作文件事件处理器file event handler。它是单线程的,所以Redis才叫做单线程的模型(具有后台线程),它采用lO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis内部的线程模型的简单性。
文件事件处理器的结构包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。
多个Socket可能并发的产生不同的事件,IO多路复用程序会监听多个Socket,会将Socket放入一个队列中排队,每次从队列中有序、同步取出一个Socket给事件分派器,事件分派器把Socket给对应的事件处理器。然后一个Socket的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket给事件分派器。文件事件分派器会根据每个Socket当前产生的事件,来选择对应的事件处理器来处理。

处理请求流程

  1. Redis启动初始化时,将连接应答处理器跟AE_READABLE事件关联
  2. 若一个客户端发起连接,会产生一个AE_READABLE事件,然后由连接应答处理器负责和客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件和命令请求处理器关联,使得客户端可以向主服务器发送命令请求。
  3. 当客户端向Redis发请求时(不管读还是写请求),客户端socket都会产生一个AE_READABLE事件触发命令请求处理器。处理器读取客户端的命令内容,然后传给相关程序执行。
  4. 当Redis服务器准备好给客户端的响应数据后,会将socket的AE_WRITABLE事件和命令回复处理器关联,当客户端准备好读取响应数据时,会在socket产生一个AE_WRITABLE事件,由对应命令回复处理器处理,即将准备好的响应数据写入socket,供客户端读取。
  5. 命令回复处理器全部写完到socket后,就会删除该socket的AE_WRITABLE事件和命令回复处理器的映射

单线程块的原因

  1. 内存操作 
  2. 核心是基于非阻塞的IO多路复用机制
  3. 单线程反而避免了多线程的频繁切换上下文带来的性能问题

二、Redis分布式锁底层是如何实现的

  1. 首先利用setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
  2. 然后还要利用lua脚本来保证多个redis操作的原了性
  3. 同时还要考虑到锁过期,所以需要额外的一个看门狗定时任务来监听锁是否需要续约
  4. 同时还要考虑到redis节点挂掉后的情况,所以需要采用红锁的方式来同时向N/2+1个节点申请锁,都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到
     

三、Redis的持久化机制

因为Redis的读写是基于内存操作,如果一旦发生事故比如断电,宕机,那么数据就会没了,因此需要持久化来保障数据安全。远程调试

RDB:

Redis DataBase 将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘。

手动触发:

  • save命令使Redis处于阻塞状态,直到RDB持久化完成,才会响应其他客户端发来的命令,所以在生产环境—定要慎用。
  • bgsave命令fork出一个子进程执行持久化CopyOnWrite机制。主进程只在fork过程中有短暂的阻塞,子进程创建之后,主进程就可以响应客户端请求了

自动触发:

  • save m n :m秒内,如果有n个键发生改变,则自动触发持久化,通过bgsave执行,如果设置多个、只要满足其—就会触发,配置文件有默认配置(可以注释掉)
  • flushall:用于清空redis所有的数据库flushdb清空当前redis所在库数据(默认是O号数据库),会清空RDB文件,同时也会生成dump.rdb、内容为空
  • 主从同步:全量同步时会自动触发bgsave命令,生成rdb发送给从节点 

RDB的优缺点

优点:

  • 整个Redis数据库将只包含一个文件dump.rdb,方便持久化。
  • 容灾性好,方便备份。
  • 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是I0最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
  • 相对于数据集大时,比AOF的启动效率更高

缺点:

  • 数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
  • 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。会占用cpu

AOF:

Append only File 以日志的形式记录服务器所处理的每一个写、删除操作(如果宕机则直接执行一遍日志文件即可),查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录,调操作系统命令进程刷盘

执行顺序

  1. 所有的写命令会追加到AOF缓冲中
  2. AOF缓冲区根据对应的策略向硬盘进行同步操作
  3. 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
  4. 当Redis重启时,可以加载AOF文件进行数据恢复。

同步策略:

  • 每秒同步:异步完成,效率非常高,一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。
  • 每修改同步:同步持久化,每次发生的数据变化都会被立即记录到磁盘中,最多丢一条
  • 不同步:由操作系统控制,可能丢失较多数据
     

RDB的优缺点

优点

  • 数据安全
  • 通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof 工具解决数据一致性问题。
  • AOF机制的rewrite模式。定期对AOF文件进行重写,以达到压缩的目的·

缺点

  • AOF文件比RDB文件大,且恢复速度慢。
  • 数据集大的时候,比rdb 启动效率低。
  • 运行效率没有RDB高
     

RDB和AOF比较

AOF文件比RDB更新频率高,优先使用AOF还原数据。                                                            AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF

四、Redis的过期键删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

(0、定时过期,redis没用)

每个key添加定时器,时间一到就会删除,定时器实时监控是否到期,对cup不友好。

1、惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存

2、定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

Redis中同时使用了惰性过期和定期过期两种过期策略。

五、缓存雪崩、缓存穿透、缓存击穿

1、缓存雪崩

是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
  • 缓存预热
  • 互斥锁

2、缓存穿透

是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  • 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

3、缓存击穿

是指缓存中没有数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。 


解决方案

  • 设置热点数据永远不过期。
  • 加互斥锁

六、Redis主从赋值的核心原理 

通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

全量复制:

  1. 主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘lO的
  2. 主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
  3. 从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗

部分复制:

  1. 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset(例复制0-10条数据,下一次复制第11-20条数据)
  2. 复制积压缓冲区︰主节点内部维护了一个固定长度的、先进先出(FIFO)队列作为复制积压缓冲区。当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
  3. 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。从节点Redis断开重连的时候,就是根据运行ID来判断同步的进度
  • 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
  • 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

七、简述Redis数据结构

  • String:字符串(*)

可以用来做最简单的数据,可以缓存某个简单的字符串,也可以缓存某个json格式的字符串,Redis分布式锁的实现就利用了这种数据结构,还包括可以实现计数器、Session共享、分布式ID

  • List:列表(*)

Redis的列表通过命令的组合,既可以当做栈,也可以当做队列来使用,可以用来缓存类似微信公众号、微博等消息流数据

  • Hash:哈希表(*)

可以用来存储一些key-value对,更适合用来存储对象

  • set:无序集合(*)

和列表类似,也可以存储多个元素,但是不能重复,集合可以进行交集、并集、差集操作,从而可以实现类似,我和某人共同关注的人、朋友圈点赞等功能

  • sorted Set:有序集合(*)

集合是无序的,有序集合可以设置顺序,可以用来实现排行榜功能

  • bitmap:布隆过滤器
  • GeoHash:坐标,借助Sorted Set实现,通过zset的score进行排序就可以得到坐标附近的其它元素,通过将score还原成坐标值就可以得到元素的原始坐标
  • HyperLogLog: 统计不重复数据,用于大数据基数统计
  • Streams:内存版的kafka

八、如何保证数据库与缓存的一致性

由于缓存和数据库是分开的,无法做到原子性的同时进行数据修改,可能出现缓存更新失败,或者数据库更新失败的情况,这时候会出现数据不一致,影响前端业务

  • 先更新数据库,再更新缓存。缓存可能更新失败,读到老数据
  • 先删缓存,再更新数据库。并发时,读操作可能还是会将旧数据读回缓存
  • 先更新数据库,再删缓存。也存在缓存删除失败的可能

最经典的缓存+数据库读写的模式,cache Aside Pattern。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。更新的时候,先更新数据库,然后再删除缓存。

为什么是删除而不是更新?
删除更加轻量,延迟加载的一种实现,更新可能涉及多个表、比较耗时


延时双删:

先删除缓存,再更新数据库,休眠1s、再次删除缓存。写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据,并发还是可能读到旧值覆盖缓存

终极方案:
将访问操作串行化

  1. 先删缓存,将更新数据库的操作放进有序队列中
  2. 从缓存查不到的查询操作,都进入有序队列

会面临的问题:

  • 读请求积压,大量超时,导致数据库的压力:限流、熔断
  • 如何避免大量请求积压:将队列水平拆分,提高并行度。
  • 保证相同请求路由正确。

九、Redis集群方案

主从

哨兵模式:

sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控redis master和slave进程是否正常工作。
  • 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果master node挂掉了,会自动转移到slave node 上。
  • 配置中心:如果故障转移发生了,通知client客户端新的 master地址。

哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作

  • 故障转移时,判断一个master node是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
  • 哨兵通常需要3个实例,来保证自己的健壮性。
  • 哨兵+ redis 主从的部署架构,是不保证数据零丢失的,只能保证redis集群的高可用性。
  • 对于哨兵+ redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
     

Redis Cluster

是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行
方案说明:

  • 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384个槽位。
  • 每份数据分片会存储在多个互为主从的多节点上
  • 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)·
  • 同一分片多个节点间的数据不保持强一致性
  • 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
  • 扩容时需要需要把旧节点的数据迁移一部分到新节点

在redis cluster架构下,每个redis要放开两个端口号,比如一个是6379,另外一个就是加1w的端口号,比如16379。
16379端口号是用来进行节点间通信的,也就是cluster bus的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

优点

  • 无中心架构,支持动态扩容,对业务透明
  • 具备Sentinel的监控和自动Failover(故障转移)能力
  • 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可高性能,客户端直连redis服务,免去了proxy代理的损耗
     

缺点

  • 运维也很复杂,数据迁移需要人工千预
  • 只能使用O号数据库
  • 不支持批量操作(pipeline管道操作)
  • 分布式逻辑和存储模块耦合等

Redis Sharding

Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。Java redis客户端驱动jedis,支持Redis Sharding功能,即ShardedJedis以及结合缓存池的ShardedJedisPool
优点

  • 优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强

缺点

  • 由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。
  • 客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值