Redis面试题整理(附答案,持续更新)

202 阅读14分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

Redis是一款常用的NoSQL,在日常开发中的地位很重要,在面试中也已经屡见不鲜,我对这部分的面试问题做了些整理。

Redis

Redis有哪些数据类型

  • string:用来实现简单的kv键值对存储,比如计数器
  • list:实现双向链表,比如用户的关注,粉丝列表
  • hash:用来存储彼此相关信息的键值对
  • set:存储不重复元素,比如用户的关注等
  • sorted set:实时信息排行榜

Redis 优势

  • 性能极高 – Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s 。
  • 丰富的数据类型 – Redis 支持二进制案例的 Strings, Lists, Hashes, Sets 及Ordered Sets 数据类型操作。
  • 原子 – Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC指令包起来。
  • 丰富的特性 – Redis 还支持 publish/subscribe, 通知, key 过期等等特性。

为什么Redis能这么快

10W QPS

  • 完全基于内存,绝大部分请求时纯粹的内存操作,执行效率高
  • 数据结构简单,对数据操作也简单
  • 采用单线程,单线程也能处理高并发请求,想多核也可启动多实例(不会有并发问题,避免了上下文切换和锁竞争)
  • 使用多路IO复用模型,非阻塞IO

Redis 单线程模型详解

Redis 基于 Reactor 模式来设计开发了⾃⼰的⼀套⾼效的事件处理模型 (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是⾼性能 IO 的基⽯),这套事件处理模型对应的是 Redis中的⽂件事件处理器(file event handler)。由于⽂件事件处理器(file event handler)是单线程⽅式运⾏的,所以我们⼀般都说 Redis 是单线程模型。

既然是单线程,那怎么监听⼤量的客户端连接呢?

Redis 通过IO 多路复⽤程序 来监听来⾃客户端的⼤量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发⽣。

这样的好处⾮常明显: I/O 多路复⽤技术的使⽤让 Redis 不需要额外创建多余的线程来监听客户端的⼤量连接,降低了资源的消耗(和 NIO 中的 Selector 组件很像)。

Redis适合的场景

1、会话缓存(Session Cache)

最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台Magento 也提供 Redis 的插件。

2、全页缓存(FPC)

除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。 再次以 Magento 为例,Magento提供一个插件来使用 Redis 作为全页缓存后端。 此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

3、队列

Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis能作为一个很好的消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如 Python)对 list 的 push/pop 操作。 如果你快速的在 Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用 Redis 作为 broker,你可以从这里去查看。

4,排行榜/计数器

Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: ZRANGE user_scores 0 10 WITHSCORES Agora Games 就是一个很好的例子,用 Ruby 实现的,它的排行榜就是使用 Redis 来存储数据的,你可以在这里看到。

5、发布/订阅

最后(但肯定不是最不重要的)是 Redis 的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用 Redis 的发布/订阅功能来建立聊天系统!

缓存穿透,缓存击穿,缓存雪崩

缓存穿透

大量查询不到的数据的请求落到后端数据库,数据库压力增大

  • 由于大量缓存查不到就去数据库取,数据库也没有要查的数据
  • 解决:对于没查到返回为None的数据也缓存
  • 插入数据的时候删除相应缓存,或者设置较短的超时时间

缓存击穿

某些非常热点的数据key过期,大量请求打倒后端数据库

  • 热点数据key失效导致大量请求打到数据库增加数据库压力
  • 分布式锁:获取锁的线程从数据库拉数据更新缓存,其他线程等待
  • 异步后台更新:后台任务针对过期的key自动刷新

缓存雪崩

缓存不可用或者大量缓存key同时失效,大量请求直接打到数据库

  • 多级缓存:不同级别的key设置不同的超时时间
  • 随机超时:key的超时时间随机设置,防止同时超时
  • 架构层:提升系统可用性。监控、报警完善

如何通过Redis实现分布式锁

分布式锁需要解决的问题:

  • 互斥性
  • 安全性
  • 死锁
  • 容错

setnx key value:如果key不存在,则创建并赋值

  • 时间复杂度O(1)
  • 返回值:设置成功,返回1;设置失败,返回0

如何解决SETNX长期有效的问题

expire key seconds

  • 设置key的生存时间,当key过期时(生存时间为0),会被自动删除
  • 缺点:原子性得不到满足

但是上面的操作没有原子性

set key value [EX seconds] [PX millisecond] [NX|XX]

  • EX second:设置键的过期时间为second秒
  • PX millisecond: 设置键的过期时间为millisecond毫秒
  • NX:只在键不存在时,才对键进行设置操作
  • XX: 只在键已经存在时,才对键进行设置操作
  • SET操作成功完成时,返回OK,否则返回nil

其他要注意的地方

  • 使用setnx实现加锁,可以同时通过expire添加超时时间
  • 锁的value值可以使用一个随机的uuid或者特定的命名
  • 释放锁的时候,通过uuid判断是否是该锁,是则执行释放锁

Redis 持久化机制

很多时候我们需要持久化数据也就是将内存中的数据写⼊到硬盘⾥⾯,⼤部分原因是为了之后重⽤数据(⽐如重启机器、机器故障之后恢复数据),或者是为了防⽌系统故障⽽将数据备份到⼀个远程位置。

Redis 不同于 Memcached 的很重要⼀点就是,Redis ⽀持持久化,⽽且⽀持两种不同的持久化操作。Redis 的⼀种持久化⽅式叫快照(snapshotting,RDB),另⼀种⽅式是只追加⽂件(append-only file, AOF)。这两种⽅法各有千秋,下⾯我会详细这两种持久化⽅法是什么,怎么⽤,如何选择适合⾃⼰的持久化⽅法。

快照(snapshotting)持久化(RDB)

Redis 可以通过创建快照来获得存储在内存⾥⾯的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进⾏备份,可以将快照复制到其他服务器从⽽创建具有相同数据的服务器副本(Redis 主从结构,主要⽤来提⾼ Redis 性能),还可以将快照留在原地以便重启服务器的时候使⽤。

快照持久化是 Redis 默认采⽤的持久化⽅式,在 Redis.conf 配置⽂件中默认有此下配置:

save 900 1 #在900秒(15分钟)之后,如果⾄少有1个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。 save 300 10 #在300秒(5分钟)之后,如果⾄少有10个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。 save 60 10000 #在60秒(1分钟)之后,如果⾄少有10000个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。

AOF(append-only file)持久化

与快照持久化相⽐,AOF 持久化 的实时性更好,因此已成为主流的持久化⽅案。默认情况下Redis 没有开启 AOF(append only file)⽅式的持久化,可以通过 appendonly 参数开启:

appendonly yes

开启 AOF 持久化后每执⾏⼀条会更改 Redis 中的数据的命令,Redis 就会将该命令写⼊硬盘中的 AOF ⽂件。AOF ⽂件的保存位置和 RDB ⽂件的位置相同,都是通过 dir 参数设置的,默认的⽂件名是 appendonly.aof。

在 Redis 的配置⽂件中存在三种不同的 AOF 持久化⽅式,它们分别是:

appendfsync always #每次有数据修改发⽣时都会写⼊AOF⽂件,这样会严重降低Redis的速度 appendfsync everysec #每秒钟同步⼀次,显示地将多个写命令同步到硬盘 appendfsync no #让操作系统决定何时进⾏同步

为了兼顾数据和写⼊性能,⽤户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步⼀次AOF ⽂件,Redis 性能⼏乎没受到任何影响。⽽且这样即使出现系统崩溃,⽤户最多只会丢失⼀秒之内产⽣的数据。当硬盘忙于执⾏写⼊操作的时候,Redis 还会优雅的放慢⾃⼰的速度以便适应硬盘的最⼤写⼊速度。

拓展:Redis 4.0 对于持久化机制的优化

Redis 4.0 开始⽀持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdbpreamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF ⽂件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的,AOF ⾥⾯的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

缓存使用模式

  • cache aside:同时更新缓存和数据库
  • read/write through:先更新缓存,缓存负责同步更新数据库
  • write behind caching:先更新缓存,缓存定期异步更新数据库

一般先更新数据库然后删除缓存

过期的数据的删除策略了解么

如果假设你设置了⼀批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进⾏删除的呢?

常⽤的过期数据的删除策略就两个(重要!⾃⼰造缓存轮⼦的时候需要格外考虑的东⻄):

  1. 惰性删除 :只会在取出key的时候才对数据进⾏过期检查。这样对CPU最友好,但是可能会 造成太多过期 key 没有被删除。
  2. 定期删除 : 每隔⼀段时间抽取⼀批 key 执⾏删除过期key操作。并且,Redis 底层会通过限 制删除操作执⾏的时⻓和频率来减少删除操作对CPU时间的影响。

定期删除对内存更加友好,惰性删除对CPU更加友好。两者各有千秋,所以Redis 采⽤的是 定期删除+惰性/懒汉式删除 。

但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致⼤量过期 key 堆积在内存⾥,然后就Out of memory了。

怎么解决这个问题呢?答案就是: Redis 内存淘汰机制。

缓存淘汰策略

Redis 提供 6 种数据淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使⽤的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru(least recently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的 key(这个是最常⽤的)
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-eviction:禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错。这个应该没⼈使⽤吧!

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使⽤的数据淘汰
  2. allkeys-lfu(least frequently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移除最不经常使⽤的 key

Redis事务

Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能。

使⽤ MULTI命令后可以输⼊多个命令。Redis不会⽴即执⾏这些命令,⽽是将它们放到队列,当调⽤了EXEC命令将执⾏所有命令。

Redis 是不⽀持 roll back 的,因⽽不满⾜原⼦性的(⽽且不满⾜持久性)。

你可以将Redis中的事务就理解为 :Redis事务提供了⼀种将多个命令请求打包的功能。然后,再按顺序执⾏打包的所有命令,并且不会被中途打断。

zhuanlan.zhihu.com/p/43897838

Redis的同步机制

主从同步原理

全同步过程

  • Slave发送sync命令到Master
  • Master启动一个后台进程,将Redis中的数据快照保存到文件中
  • Master将保存数据快照期间接收到的写命令缓存起来
  • Master完成写文件操作后,将该文件发送给Slave
  • 使用新的AOF文件替换掉旧的AOF文件
  • Master将这期间收集的增量写命令发送给Slave端

增量同步流程

  • Master接收到用户的操作指令,判断是否需要传播到Slave
  • 将操作记录追加到AOF文件
  • 将操作传播到其他Slave:
    • 对齐主从库
    • 往响应缓存写入指令
  • 将缓存中的数据发送给Slave

主从模式的弊端就是不具备高可用性 master挂掉之后,将不能提供写入操作,因此sentinel应运而生

Redis Sentinel

解决主从同步Master宕机后的主从切换问题:

  • 监控:检查主从服务器是否运行正常
  • 提醒:通过API向管理员或者其他应用程序发送故障通知
  • 自动故障迁移:主从切换