Loading...
墨滴

万喜

2021/11/28  阅读:33  主题:科技蓝

Redis面试常见内容

本文为整理出来的面试题,大部分是本人面试遇到的,文章内参考的文章都标明原网址了(有些课外知识也标记了网址),有兴趣的可以去帮各位点个赞!

1 Redis常用数据结构

String:是以一种纯字符串作为value的形式存在的,使用场景一般是存储简单的键值类型。比如用户信息,登录信息,配置信息等。还有一种用得比较多的是string的incr/decr操作,即自减/自增操作。调用它是原子性的,无论调用多少次,都一一计算成功,例如需要增减库存的操作。

Hash:在redis中,hash因为是一个集合,分为大key、小key、小key的value,使用场景类似存储商品信息,大key为商家id,小key是商品id即goodsId,value为该goodsId的详细信息。

List:是一个集合,在redis中,插入list中的值,只需要找到list的key即可,而不需要像hash一样插入两层的key,list是一种有序的、可重复的集合,使用场景类似存储中奖号码,取出的时候可以使用左推、左拉、右推、右拉的方式。

Set:是一种无序的,不能重复的集合。并且在redis中,只有一个key,使用场景比如要统计一件商品在一分钟内被多少人浏览过,同一用户不做重复统计,就可以使用该结构来统计。

Zset:是有序的,并且不能重复。既有list的有序,又有set的不可重复性,不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序,有序集合的成员是唯一的,但分数(score)却可以重复,使用场景如统计直播间消费前三的用户,key为直播间id,score为消费金额,value为消费商品详情。

Hyperloglog:是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的(有得有失,如统计一件商品在一分钟内被多少人浏览过,同一用户不做重复统计,我们用Set集合存储的话,value为用户详细信息,假设单个用户详细信息占用10个字节,那么100万个用户详细信息就是10M,如果1千万,就是150M,获取的时候是能获取到用户详细信息的,无误差,而HyperLogLog只用12kb空间就可以理论存储近似接近2的64次方个值,只是获取的时候是不能获取到用户详细信息的,有误差,最大不超过1%),因为Hyperloglog并不存储元素本身。原理信息可以看此文章![1]

Bitmap:是通过最小的单位bit来进行0或者1的设置,表示某个元素对应的值或者状态,8bit = 1b = 0.001kb,一个bit的值,或者是0,或者是1,使用场景如统计用户登陆信息,key为日期+userId,若登陆了则value为1。布隆过滤器(Bloom Filter)底层就是Bitmap,详情信息可以看此文章![2]

Geo:主要用于存储地理位置信息,并对存储的信息进行操作,使用场景即为存储指定的地理空间位置(经纬度),内置方法可以计算位置距离等,适用详情可以看此文章![3]

2 Redis持久化机制

Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制,Redis 的持久化机制有两种:

  • RDB快照

RDB快照是某个时间点的一次全量数据备份,是二进制文件,在存储上非常紧凑,一般是靠bgsave命令来生成rdb快照。

save命令:会阻塞当前服务器,直到RDB完成为止,如果数据量大的话会造成长时间的阻塞,线上环境一般禁止使用。

bgsave命令:就是background save,执行bgsave命令时Redis主进程会fork一个子进程来完成RDB的过程,完成后自动结束(操作系统的多进程Copy On Write机制,简称COW)。所以Redis主进程阻塞时间只有fork阶段的那一下。相对于save,阻塞时间很短。

配置redis.conf,触发规则,自动执行:

# 当在规定的时间内,Redis发生了写操作的个数满足条件,会触发发生BGSAVE命令。
# save <seconds> <changes>
# 当用户设置了多个save的选项配置,只要其中任一条满足,Redis都会触发一次BGSAVE操作
save 900 1 
save 300 10 
save 60 10000
# 以上配置的含义:
# 900秒之内至少一次写操作
# 300秒之内至少发生10次写操作
# 60秒之内发生至少10000次写操作
# 只要满足任一条件,均会触发bgsave

RDB执行流程:

1、执行bgsave命令的时候,Redis主进程会检查是否有子进程在执行RDB/AOF持久化任务,如果有的话,直接返回

2、Redis主进程会fork一个子进程来执行执行RDB操作,fork操作会对主进程造成阻塞(影响Redis的读写),fork操作完成后会发消息给主进程,从而不再阻塞主进程。(阻塞仅指主进程fork子进程的过程,后续子进程执行操作时不会阻塞)

3、RDB子进程会根据Redis主进程的内存生成临时的快照文件,持久化完成后会使用临时快照文件替换掉原来的RDB文件。(该过程中主进程的读写不受影响,但Redis的写操作不会同步到主进程的主内存中,而是会写到一个临时的内存区域作为一个副本)

4、子进程完成RDB持久化后会发消息给主进程,通知RDB持久化完成(将上阶段内存副本中的增量写数据同步到主内存)

RDB的优缺点:

优点:

1、RDB文件小,非常适合定时备份,用于灾难恢复

2、Redis加载RDB文件的速度比AOF快很多,因为RDB文件中直接存储的时内存数据,而AOF文件中存储的是一条条命令,需要重演命令。

缺点:

1、RDB无法做到实时持久化,若在两次bgsave间宕机,则会丢失区间(分钟级)的增量数据,不适用于实时性要求较高的场景

2、RDB的cow机制中,fork子进程属于重量级操作,并且会阻塞redis主进程

3、存在老版本的Redis不兼容新版本RDB格式文件的问题

  • AOF日志

AOF日志是持续增量的备份,是基于写命令存储的可读的文本文件。AOF日志会在持续运行中持续增大,由于Redis重启过程需要优先加载AOF日志进行指令重放以恢复数据,恢复时间会无比漫长。所以需要定期进行AOF重写,对AOF日志进行瘦身。目前AOF是Redis持久化的主流方式。

AOF默认是关闭的,通过redis.conf配置文件进行开启

## 此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能  
## 只有在“yes”下,aof重写/文件同步等特性才会生效  
appendonly yes  

## 指定aof文件名称  
appendfilename appendonly.aof  

## 指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec  
appendfsync everysec  
## 在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”  
no-appendfsync-on-rewrite no  

## aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”  
auto-aof-rewrite-min-size 64mb  

## 相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比  
## 每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A)
## aof文件增长到A*(1 + p)之后,触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。  
auto-aof-rewrite-percentage 100

always:每一条AOF记录都立即同步到文件,性能很低,但较为安全。
everysec:每秒同步一次,性能和安全都比较中庸的方式,也是redis推荐的方式。如果遇到物理服务器故障,可能导致最多1秒的AOF记录丢失。
no:Redis永不直接调用文件同步,而是让操作系统来决定何时同步磁盘。性能较好,但很不安全。

AOF的优缺点

优点:

1、AOF只是追加写日志文件,对服务器性能影响较小,速度比RDB要快,消耗的内存较少

缺点:

1、AOF方式生成的日志文件太大,需要不断AOF重写,进行瘦身。
2、即使经过AOF重写瘦身,由于文件是文本文件,文件体积较大(相比于RDB的二进制文件)。
3、AOF重演命令式的恢复数据,速度显然比RDB要慢。

  • Redis 4.0 混合持久化

仅使用RDB快照方式恢复数据,由于快照时间粒度较大,时回丢失大量数据。
仅使用AOF重放方式恢复数据,日志性能相对 rdb 来说要慢。在 Redis 实例很大的情况下,启动需要花费很长的时间。

Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。相当于: 大量数据使用粗粒度(时间上)的rdb快照方式,性能高,恢复时间快。 增量数据使用细粒度(时间上)的AOF日志方式,尽量保证数据的不丢失。 在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

更多信息请点击此处查看![4]

3 Redis过期键删除策略

常见的删除策略有以下3种:

  • 定时删除

在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。

优点:对内存非常友好
缺点:对CPU时间非常不友好

  • 惰性删除

放任过期键不管,每次从键空间中获取键时,检查该键是否过期,如果过期,就删除该键,如果没有过期,就返回该键。

优点:对CPU时间非常友好
缺点:对内存非常不友好

  • 定期删除

每隔一段时间,程序对数据库进行一次检查,删除里面的过期键,至于要删除哪些数据库的哪些过期键,则由算法决定。

定期删除策略是定时删除策略和惰性删除策略的一种整合折中方案,每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响,同时,通过定期删除过期键,也有效地减少了因为过期键而带来的内存浪费。

具体实现请看此文章![5]

4 Redis内存淘汰策略

  1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键(redis默认

  2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键

  3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键

  4. allkeys-random:加入键的时候如果过限,从所有key随机删除

  5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐

  6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键

  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键

  8. allkeys-lfu:从所有键中驱逐使用频率最少的键

LRU算法和Redis实现请看此文章![6]

5 Redis集群模式

  • 主从复制 主从复制原理

主从复制原理:

  • 从数据库连接主数据库,发送SYNC命令。
  • 主数据库接收到SYNC命令后,可以执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令。
  • 主数据库BGSAVE执行完后,向所有从数据库发送快照文件,并在发送期间继续记录被执行的写命令。
  • 从数据库收到快照文件后丢弃所有旧数据,载入收到的快照。
  • 主数据库快照发送完毕后开始向从数据库发送缓冲区中的写命令。
  • 从数据库完成对快照的载入,开始接受命令请求,并执行来自主数据库缓冲区的写命令;(从数据库初始化完成)。
  • 主数据库每执行一个写命令就会向从数据库发送相同的写命令,从数据库接收并执行收到的写命令(从数据库初始化完成后的操作)。
  • 出现断开重连后,2.8之后的版本会将断线期间的命令传给从数据库,增量复制。
  • 主从刚刚连接的时候,进行全量同步。全同步结束后,进行增量同步。当然,如果有需要,slave在任何时候都可以发起全量同步。Redis的策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

优缺点:

优点:

  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
  • 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务依然必须由Master来完成。
  • Slave同样可以接受其他Slaves的连接和同步请求,这样可以有效地分载Master的同步压力。
  • Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。
  • Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据。

缺点:

  • Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。

  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。

  • 如果多个Slave断线了,需要重启的时候,尽量不要在同一时间段进行重启。因为只要Slave启动,就会发送sync请求和主机全量同步,当多个Slave重启的时候,可能会导致Master IO剧增从而宕机。

  • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

  • 哨兵模式

哨兵模式的作用:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换到master,然后通过发布订阅模式通过其他的从服务器,修改配置文件,让它们切换主机。
  • 然而一个哨兵进程对Redis服务器进行监控,也可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

故障切换的过程:

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

哨兵模式的工作方式:

  • 每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
  • 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。
  • 如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。
  • 当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)。
  • 在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
  • 当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
  • 若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

优缺点:

优点:

  • 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
  • 主从可以自动切换,系统更健壮,可用性更高。

缺点:

  • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

  • Cluster集群模式

Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容。

集群的特点:

  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
  • 节点的fail是通过集群中超过半数的节点检测失效时才生效。
  • 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
  • redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value。
  • Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。

集群的工作方式:

在 Redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383。还有一个就是cluster,可以理解为是一个集群管理的插件。当我们的存取的 Key到达的时候,Redis 会根据 crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。

为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。

不得不提的Hash一致性算法,建议多看!

redis集群参考文章(详情配置)查看请点击这里!

6 Redis缓存击穿、穿透、雪崩

  • 缓存击穿

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

解决方案:

1、热点数据不设置过期时间。
2、在凌晨或者用户量少的时候开定时任务去保证热点数据完整性。
3、做类似双重检查锁的操作,一般现在架构都是微服务了,外层开个分布式锁,锁内再尝试去拿缓存,拿不到就去查数据库,查到之后放回redis内。

  • 缓存穿透

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

解决方案:

1、从缓存取不到的数据,在数据库中也没有取到,可以写一个默认值进去,比如-1,设置较短的过期时间,查询到缓存值为-1就直接返回,等待过期之后再去数据库查。
2、使用布隆过滤器(Bloom Filter),判断key是否存在,存在的话去数据库查,查完再放回redis,Bloom Filter里面不存在的话,则直接返回或者报错。

  • 缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,后面的请求都会落到数据库上(一般程序是先查redis,redis没有的话就会查数据库,然后将值再放回redis),造成数据库短时间内承受大量请求而崩掉。

解决方案:

1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2、一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
3、给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

参考资料

[1]

原理信息可以看此文章!: https://blog.csdn.net/mango_love/article/details/106394667

[2]

详情信息可以看此文章!: https://www.cnblogs.com/liyulong1982/p/6013002.html

[3]

适用详情可以看此文章!: https://www.runoob.com/redis/redis-geo.html

[4]

更多信息请点击此处查看!: https://zhuanlan.zhihu.com/p/77646963

[5]

具体实现请看此文章!: https://www.cnblogs.com/zwwhnly/p/12689792.html

[6]

LRU算法和Redis实现请看此文章!: https://blog.csdn.net/hopeztm/article/details/79547052

万喜

2021/11/28  阅读:33  主题:科技蓝

作者介绍

万喜