Redis 常见经典面试题

举报
微枫 发表于 2022/07/19 12:05:27 2022/07/19
【摘要】 Redis 常见经典面试题

槽位为什么设计为16384个

Redis集群模式下,会有多个节点,节点之间需要进行pingpong心跳通信,通信内容除了消息类型,节点id外,还会在消息主题中携带总结点数十分之一的节点信息进行交换,相互告知。如果槽位分配过大,会导致心跳通信的心跳包过大,浪费带宽且没必要,甚至容易造成网络拥堵。如果哈希槽设计过少,例如几千个的话,我们知道redis建议集群模式不要超过1000节点,那么如果是满载1000个节点,平分几千个哈希槽,在节点迁移时候容易出现数据倾斜,造成单节点访问过高,甚至造成节点连续崩溃,最终导致大规模服务集群雪崩相像。

Redis为什么快

  • Redis完全基于内存
  • Redis整个结构类似于HashMap,查找和操作复杂度为O(1),不需要和MySQL查找数据一样需要产生随机磁盘IO或者全表
  • Redis对于客户端的处理是单线程的,采用单线程处理所有客户端请求,避免了多线程的上下文切换和线程竞争造成的开销
  • 底层采用select/epoll多路复用的高效非阻塞IO模型
  • 客户端通信协议采用RESP,简单易读,避免了复杂请求的解析开销

缓存雪崩(同一时间大量热点key过期)

缓存雪崩造成的原因是因为我们在做缓存时为了保证内存利用率,一般在写入数据时都会给定一个过期时间,而就是因为过期时间的设置有可能导致大量的热点key在同一时间内全部失效,此时来了大量请求访问这些key,而redis中却没有这些数据,从而导致所有请求直接落入DB查询,造成DB出现瓶颈或者直接被打宕导致雪崩情况的发生。

关于解决方案的的话也可以从多个维度来考虑:

  • 设置热点数据永不过期,避免热点数据的失效导致大量的相同请求落入DB
  • 错开过期时间的设置,根据业务以及线上情况合理的设置失效时间
  • 使用分布式锁或者MQ队列使得请求串行化,从而避免同一时间请求大量落入DB(性能会受到很大的影响)

缓存穿透

请求参数不合理,导致Redis一直去数据库中查询。

缓存穿透这个问题是由于请求参数不合理导致的,比如对外暴露了一个接口getUser?userID=xxx,而数据库中的userID是从1开始的,当有黑客通过这个接口携带不存在的ID请求时,比如:getUser?userID=-1,请求会先来到Redis中查询缓存,但是发现没有对应的数据从而转向DB查询,但是DB中也无此值, 所以也无法写入数据到缓存,而黑客就通过这一点利用“肉鸡”等手段疯狂请求这个接口,导致出现大量redis不存在数据的请求落入DB,从而导致DB出现瓶颈或者直接被打宕机,整个系统陷入瘫痪。

解决方案:

  • 做IP限流与黑名单,避免同一IP瞬间发送大量请求
  • 对于请求做非法校验,对于携带非法参数的请求直接过滤
  • 对于DB中查询不存在的数据写入Redis中“Not Data”并设置短暂的过期时间,下次请求能够直接被拦截在redis而不会落入DB
  • 布隆过滤器

缓存击穿

一个热点key突然过期,数据库无法承受大量请求。

缓存击穿和缓存雪崩有点类似,都是由于请求的key过期导致的问题,但是不同点在于失效key的数量,对于雪崩而言指的是大量的key失效导致大量请求落入DB,而对于击穿而言,指的是某一个热点key突然过期,而这个时候又突然又大量的请求来查询它,但是在redis中却并没有查询到结果从而导致所有请求全部打向DB,导致在这个时刻DB直接被打穿。

解决方案的话也是有多种:

  • 设置热点key永不过期
  • 做好redis监控,请求串行化访问(性能较差)
  • 使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

redis淘汰策略

redis在5.0之前为我们提供了六种淘汰策略,而5.0为我们提供了八种,但是大体上来说这些lru、lfu、random、ttl四种类型

  • 在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的。
  • 如果所有数据访问概率大致相等时,可以选择allkeys-random。
  • 如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略。
  • 如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的。
  • 由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru策略,这样就可以不再设置过期时间,高效利用内存了。
  • maxmemory-policy:参数配置淘汰策略。maxmemory:限制内存大小。

三种键删除策略

redis删除Key的策略策略有三种:

  • 定时删除:在设置键的过期时间的同时,设置一个定时器,当键过期了,定时器马上把该键删除。(定时删除对内存来说是友好的,因为它可以及时清理过期键;但对CPU是不友好的,如果过期键太多,删除操作会消耗过多的资源。)
  • 惰性删除:key过期后任然留在内存中不做处理,当有请求操作这个key的时候,会检查这个key是否过期,如果过期则删除,否则返回key对应的数据信息。(惰性删除对CPU是友好的,因为只有在读取的时候检测到过期了才会将其删除。但对内存是不友好,如果过期键后续不被访问,那么这些过期键将积累在缓存中,对内存消耗是比较大的。)
  • 定期删除:redis数据库默认每隔100ms就会进行随机抽取一些设置过期时间的key进行检测,过期则删除。(定期删除是定时删除和惰性删除的一个折中方案。可以根据实际场景自定义这个间隔时间,在CPU资源和内存资源上作出权衡。)

Redis三种持久化机制

Redis的确是将数据存储在内存的,但是也会有相关的持久化机制将内存持久化备份到磁盘,以便于重启时数据能够重新恢复到内存中,避免数据丢失的风险。而Redis持久化机制由三种,在4.X版本之前Redis只支持AOF以及RDB两种形式持久化,但是因为AOF与RDB都存在各自的缺陷,而在4.x版本之后redis还提供一种新的持久化机制:混合型持久化(但是最终生成的文件还是AOF)。

RDB 持久化

RDB 持久化把内存中当前进程的数据生成快照(.rdb)文件保存到硬盘的过程,有手动触发和自动触发:

  • save:阻塞当前 Redis,直到 RDB 持久化过程完成为止,若内存实例比较大 会造成长时间阻塞,线上环境不建议用它

  • bgsave:redis 进程执行 fork 操作创建子进程,由子进程完成持久化,阻塞时间很短(微秒级),是 save 的优化

  • bgsave过程:通过fork创建子进程,RDB 持久化过程由子进程负责,会在后台异步进行快照操作,快照同时还可以响应客户端请求

    而且RDB 是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,重启时加载这个文件达到数据恢复。

  • redis RDB持久化默认开启 save 900 1 – 900s内存在1个写操作 save 300 10 – 300s内存在10个写操作 save 60 10000 – 60s内存在10000个写操作 如上是RDB的自动触发的默认配置,当操作满足如上条件时会被触发。

  • RDB优缺点:

    • 优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能;而且RDB文件存储的是压缩的二进制文件,适用于备份、全量复制,可用于灾难备份,同时RDB文件的加载速度远超于AOF文件。
    • 缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候,因为RDB无法做到实时持久化,而且每次都要创建子进程,频繁创建成本过高;备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(需要的内存是原本的两倍);还有一点,RDB文件保存的二进制文件存在新老版本不兼容的问题。

AOF持久化

而AOF持久化方式能很好的解决RDB持久化方式造成的数据丢失,AOF持久化到硬盘中的并不是内存中的数据快照,而是和MySQL的belog日志一样记录写入命令,AOF的持久化策略也有三种:

  • appendfsync always:同步持久化形式,每次发生数据更改都将命令追加到AOF文件,因为每次写入时都记录会产生大量磁盘IO,从而性能会受到影响,但是数据最安全。
  • appendfsync everysec:redis开启AOF后的缺省配置,异步操作,每秒将写入命令追加到AOF文件,如果在刚持久化之后的一秒内宕机,会造成1S的数据丢失。
  • appendfsync no:redis并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据buffer填充情况/通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因 OS 配置有关。

AOF持久化机制优缺点:

  • 优点:根据不同的fsync策略可以保证数据丢失风险降到最低,数据能够保证是最新的,fsync是后台线程在处理,所以对于处理客户端请求的线程并不影响。
  • 缺点:文件体积由于保存的是所有命令会比RDB大上很多,而且数据恢复时也需要重新执行指令,在重启时恢复数据的时间往往会慢很多。虽然fsync并不是共用处理客户端请求线程的资源来处理的,但是这两个线程还是在共享同一台机器的资源,所以在高并发场景下也会一定受到影响。

AOF rewrite操作

随着redis在线上运行的时间越来越久,客户端执行的命令越来越多,AOF的文件也会越来越大,当AOF达到一定程度大小之后再通过AOF文件恢复数据是异常缓慢的,那么对于这种情况redis在开启AOF持久化机制的时候会存在AOF文件的重写,缺省配置是当AOF文件比上一次重写时的文件大小增长100%并且文件大小不小于64MB时会对整个AOF文件进行重写从而达到“减肥”的目的。

而AOF rewrite 操作就是“压缩”AOF 文件的过程,当然 redis 并没有采用“基于原 aof 文件”来重写的方式,而是采取了类似 snapshot 的方式:基于 copy-on-write,全量遍历内存中数据,然后逐个序列到aof 文件中。因此 AOF rewrite 能够正确反应当前内存数据的状态,这正是我们所需要的;rewrite 过程中,对于新的变更操作将仍然被写入到原 AOF 文件中,同时这些新的变更操作也会被 redis 收集起来。当内存数据被全部写入到新的 aof 文件之后,收集的新的变更操作也将会一并追加到新的 aof 文件中,此后将会重命名新的 aof 文件为 appendonly.aof, 此后所有的操作都将被写入新的 aof 文件。如果在rewrite 过程中,出现故障,将不会影响原 AOF 文件的正常工作,只有当 rewrite 完成之后才会切换文件,因为 rewrite 过程是比较可靠的,触发 rewrite 的时机可以通过配置文件来声明,同时 redis 中可以通过 bgrewriteaof 指令人工干预。

项目中redis采用的是那种持久化方式

在项目中如果redis中不仅仅只是用来做缓存,其中还存储着一些MySQL中不存在的数据,所以数据的安全性要求比较高,而RDB因为并不是实时的持久化,会出现数据丢失,但是采用AOF形式在重启、灾备、迁移的时候过程异常耗时,也并不理想,所以在我们线上是同时采用两种形式的。

线上双开加载顺序:

RDB和AOF同时开启时,会优先加载AOF文件,因为AOF文件的数据完整性是最高的,如果没有AOF文件,则加载RDB,存在RDB文件,则加载RDB文件,加载成功则启动成功。

4.x之后的混合型持久化

在redis4.x之后推出了混合型持久化机制,因为RDB虽然加载快但是存在数据丢失,AOF数据安全但是加载缓慢,redis为了解决这个问题,带来了一个新的持久化选项——混合持久化。

将RDB文件的内容和增量的AOF日志文件存在一起。这里的AOF日志不再是全量 的日志,而是自持久化开始到持久化结束的这段时间发生的增量AOF日志,通常这部分AOF日志很小。redis重启的时候,可以先加载RDB的内容,然后再重放增量AOF日志,就可以完全替代之前的AOF全量文件重放,恢复效率因此大幅得到提升(混合型持久化最终生成的文件后缀是.aof,可以通过redis.conf文件中 aof-use-rdb-preamble yes 配置开启)。

混合型持久化优点:

结合了RDB和AOF的优点,使得数据恢复的效率大幅提升

混合型持久化缺点:

兼容性不好,redis-4.x新增,虽然最终的文件也是.aof格式的文件,但在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,阅读性较差

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。