面试题- Redis汇总

Posted by lichao modified on June 22, 2022

请问Redis提供了哪几种持久化方式?

Redis 持久化机制 Redis 4.0 之前有两种,第一种是 RDB 快照,第二种是 AOF 日志。

  • RDB持久化方式能够在指定的时间间隔能对数据进行快照存储。快照是一次全量备份,在停机的时候会导致大量丢失数据。
  • AOF 日志是连续的增量备份。AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾。

快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。AOF 日志在长期的运行过程中会变的无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长。所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身。

Redis 4.0 之后,持久化机制可选择混合持久化方案,混合持久化方案结合了快照和 AOF 的优点。

了解更多…

子问题: 快照(bgsave)原理是什么?

fork 和 cow。fork 是指 Redis 通过创建子进程来进行 bgsave 操作,cow 指的是 copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

请问 Redis 相比 memcached 有哪些优势?

  1. memcached 所有的值均是简单的字符串,Redis 作为其替代者,支持更为丰富的数据类型。
  2. Redis 的速度比 memcached 快很多。
  3. Redis 可以持久化其数据。
  4. Redis 支持数据的备份,即 master-slave 模式的数据备份。

请问 Redis 存储什么情况下会丢失

取决于 aof 日志 sync 属性的配置,如果不要求性能,在每条写指令时都 sync 一下磁盘,就不会丢失数据。但是在高性能的要求下每次都 sync 是不现实的,一般都使用定时 sync,比如 1s 1次,这个时候最多就会丢失 1s 的数据。

Redis 过期策略和内存淘汰策略

过期策略(redis key过期时间):

  1. 定时过期,每个key都创建一个定时器,到期清除,内存友好,cpu不友好
  2. 惰性过期,使用时才判断是否过期,内存不友好,cpu友好
  3. 定期过期,隔一段时间扫描一部分key,并清除已经过期的key

Redis 为了平衡时间和空间,采用了惰性过期和定期过期后两种策略。

了解更多

内存淘汰策略:

  1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键。
  2. allkeys-lru:首先通过 LRU 算法驱逐最久没有使用的键。
  3. volatile-lru:首先从设置了过期时间的键集合中驱逐最久没有使用的键。
  4. allkeys-random:从所有 key 随机删除。
  5. volatile-random:从过期键的集合中随机驱逐。
  6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键。
  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键。
  8. allkeys-lfu:从所有键中驱逐使用频率最少的键。

了解更多

Redis如何实现分布式锁

需要考虑过期时间、value的设置、超时锁被释放导致的数据更新覆盖的相关问题。

了解更多

Redis 支持的数据结构及其实现

  • string:基本的缓存、计数器。采用简单动态字符串实现。

  • list:消息队列、分页。在版本 3.2 之前,Redis 列表数据结构的底层实现是 ziplist 和 linkedlist。在版本 3.2 之后,引入了一个 quicklist 的数据结构,列表的底层都由 quicklist 实现。
  • hash:存储结构化的数据。采用 ziplist + hashtable 实现。
  • set:标签、去重。其实现方法有两种,一种是有序数组,另一种是字典。
  • sortedSet:排行榜。采用 ziplist + skiplist 实现。

了解更多……

子问题:hash 冲突解决

当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时,我们称这些键发生了冲突(collision)。

Redis 的哈希表使用链地址法(separate chaining)来解决键冲突: 每个哈希表节点都有一个 next 指针, 多个哈希表节点可以用 next 指针构成一个单向链表, 被分配到同一个索引上的多个节点可以用这个单向链表连接起来, 这就解决了键冲突的问题。

子问题:rehash过程

渐进式 rehash 的好处在于它采用分而治之的方式,将 rehash 流程所需要的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式 rehash 而带来的庞大计算量。搬迁操作在:

  • 在当前字典的后续指令中(hset/hdel/hget/指令)
  • Redis 还会在定时任务中对字典进行主动搬迁

子问题:跳表和b+树的区别

  • 跳表比 B 树/B+树占用的内存更少

  • 以链表的形式遍历跳跃表,跳跃表的缓存局部性与其他类型的平衡树相当
  • 跳表更容易实现、调试等

了解更多……

Redis 主从复制机制

Redis 主从数据默认是异步复制策略,所以分布式的 Redis 系统并不满足「一致性」要求。 当客户端在 Redis 的主节点修改了数据后,立即返回,即使在主从网络断开的情况下,主节点依旧可以正常对外提供修改服务,所以 Redis 满足「可用性」。

Redis 保证「最终一致性」,从节点会努力追赶主节点,最终从节点的状态会和主节点的状态将保持一致。如果网络断开了,主从节点的数据将会出现大量不一致,一旦网络恢复,从节点会采用多种策略努力追赶上落后的数据,继续尽力保持和主节点一致。

Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存buffer,待完成后将 rdb 文件全量同步到复制节点,复制节点接受完成后将 rdb 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

了解更多……

引申问题:Redis 是强一致还是高可用的?

Redis 主从数据是异步同步的,所以分布式的 Redis 系统并不满足「强一致性」要求。 当客户端在 Redis 的主节点修改了数据后,立即返回,即使在主从网络断开的情况下,主节点依旧可以正常对外提供修改服务,所以 Redis 满足「高可用性」。

关联文章-主从同步

Redis 高效的原因?

  • Redis 基于内存,内存的读写速度非常快;

  • Redis 单线程,省去了很多上下文切换线程的时间;
  • Redis 采用多路复用技术来处理并发的连接。内部实现采用非阻塞 IO(linux, epoll, 采用了 epoll+自己实现的简单的事件框架。将 epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 io 上浪费一点时间)。

Redis 所有的数据都在内存中,所有的运算都是内存级别的运算。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些时间复杂度为 O(n) 级别的指令,一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。

Redis 数据结构并不全是简单的 key-value,还有 list\hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在 hash 当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

关联文档-线程模型

Redis 存在那些问题?

  • Redis 是基于内存的,内存是很昂贵的

  • 不能保证数据持久化,虽然redis有存储方案,但是 aof 持久化也有很多问题。
  • RedisCluster 的分布式方案,是基于 gossip 协议的,当数据量太大的时候,会有 gossip 风暴的问题。

Redis 的常见使用场景?

常见的使用场景是缓存、记数、分布式锁、排序和存储。下面逐一介绍。

缓存

Redis 最常见的用途就是拿来做缓存了,一般和 MySQL 结合使用。当请求到达时,先访问 Redis 尝试取数据,若 Redis 里面没有则从 MySQL 中读取,拿到数据以后写缓存,返回。由于 Redis 访问速度快、支持的数据类型比较丰富,所以 Redis 很适合用来存储热点数据,另外结合 EXPIRE,我们可以设置过期时间然后再进行缓存更新操作,这个功能最为常见,我们几乎所有的项目都有所运用。

Redis 用做缓存时一般只用到 string 类型,具体来讲就是把对象序列化以后保存,而不是用 hash 保存对象的每一个字段

关联文章-Redis 缓存和 MySQL 数据一致性方案

计数

Redis 由于 INCR 命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成、具体业务还体现在比如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。

分布式锁

在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

关联文章-分布式锁

排序

利用 Redis 的列表和有序集合的特点,可以制作排行榜系统,而排行榜系统目前在商城类、新闻类、博客类等等,都是比不可缺的。比如一个博主的所有文章按照发布时间排序等。在抖音中比如商业化挑战里面的视频排序等。

存储

特点是最终一致性、可能会丢数据(业务对丢失少量数据不敏感)

  • 线上存储型的 Redis 开启 aof 落盘
  • 异步落盘

如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,Redis 可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

关联文章-过期清除

Redis 集群的原理是什么?

Redis Cluster 着眼于扩展性,在单个 Redis 内存不足时,使用 Cluster 进行分片存储。

  1. Redis 将所有存储区域划分为 16384 个 slots(槽位),每个节点负责一部分槽位,槽位的信息存储于每个节点中。当客户端请求进来时候会拉去一份槽位信息列表缓存在本地,RedisCluster 的每个节点会将集群的配置信息持久化到自己的配置文件中。
  2. 槽位算法:RedisCluster 默认会根据key使用crc32算法进行hash得到一个整数,然后用这个整数对16384取模定位key所在的槽位。它还运行用户在key字符串里面嵌入tag将key强制写入指定的槽位。
  3. 迁移:
    • 首先使用CLUSTER GETKEYSINSLOT 命令获取该slot中所有的key, 然后每个key依次用MIGRATE命令转移数据。
    • 数据转移完毕之后,正式将slot指派给新的节点
  4. 迁移过程
    • 当有新的节点加入或者断开节点时,就会触发Redis槽位迁移。
    • 当一个槽位正在迁移时候在原节点的状态为migrating,在目标节点的状态为importing。
    • 原节点的单个key执行dump指令得到序列化内容,再向目标节点发送restore携带序列化内容作为参数的指令,目标节点接收到内容后反序列化复制到内存中,响应给原节点成功。原节点收到成功响应后把当前节点的key删掉就完成了节点数据迁移。

古老的 Redis Sentinal 方案着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。 关联文章-哨兵模式

关联文章-集群

追问:数据迁移的过程中,节点还能正常接受请求吗

关联文章-集群

使用过 Redis 做异步队列么,怎么用的?

一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。

如果不想重试,list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。

关联文章-消息队列

pipeline 有什么好处,为什么要用 pipeline?

可以将多次 IO 往返的时间缩减为一次,前提是 pipeline 执行的指令之间没有因果相关性。使用 redis-benchmark 进行压测的时候可以发现影响 redis 的 QPS 峰值的一个重要因素是 pipeline 批次指令的数目。

假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。

Redis 的单线程的。

keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

关联文章-游标查询

Redis 支持事务吗?

Redis 事务使用非常简单,事务模型很不严格,不能像使用关系数据库事务一样来使用 Redis。

关联文章-事务

Redis热点问题

场景:某个热点帖子内容,或者促销时热点商品信息


产生原因:redis 根据key进行分片计算,分配到redis实例中的一个,导致大部分流量集中访问到同一个redis实例上,即所谓的“访问量倾斜”,导致redis实例达到性能瓶颈 解决方案:给hotkey加上后缀,把hotkey数量变成redis实例数N的倍数M,从而由访问一个redis key变成访问N*M个redis key

另外可以顺便考察雪崩的解决方案。