菜鸟笔记
提升您的技术认知

Day7、Redis常见面试题

阅读 : 591

1、对 Redis 的理解

Redis,全称为 Remote Dictionary Server,本质上是一个 Key-Value 类型的内存数据库,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据写入磁盘或把修改操作写入追加的记录文件,并且在此基础上实现 Master-Slave(主从)同步。它支持存储的 Value 类型多样,包括 String(字符串)、List(链表)、Set(集合)、zset(sorted set —— 有序集合)和 Hash(哈希类型),这些数据类型都支持 push/popadd/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。

Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。

2、Redis 和 MySQL 的区别

  • 首先,类型上的不同。MySQL 是关系型数据库,采用表格的存储方式,数据以行列组织:一个表中,每行是一个条目,都有且仅有此表的列所规定的属性。MySQL 作为关系型数据库遵守了传统的数据库的 ACID 原则 [①],这些要求在记录重要数据(如用户账户密码,银行户头等)时是必须的。Redis 是 NoSQL非关系型数据库),不严格遵守 ACID 原则,其采用 key-value 存储组织数据,大量使用缓存来读写数据,周期性地把更新数据写入磁盘或在追加文件中写入修改操作,以及同步分布数据库等。这些特点为大大提高了 Redis 在极大并发量下的读写性能。由于不以表格方式组织数据,Redis 的数据扩展十分灵活方便。
  • 功能不同。 MySQL 主要用于持久化的存储数据到硬盘,功能强大,但是读取速度较慢。而 Redis 将使用频繁的数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是一般在使用中,Redis 缓存的数据保存时间是有限的。
  • 运行机制不同。MySQL 作为典型的关系型数据库,为了严格遵守 ACID 规则必须采取保守的缓存策略,特别是为了保证数据的一致性,在保证数据可靠性的同时,牺牲了许多(特别是多并发情形下的)读写性能。而现在互联网许多应用场景往往面临了海量用户的访问请求,对数据库并不需要非常强的一致性 [②]。如 Redis 等非关系型数据库(NoSQL),往往以不再满足 ACID 为代价求得更高的吞吐容量。NoSQL 通常采用极大的缓存、分布式服务来提高读写性能。

3、Redis 有哪些数据类型

Redis 的五大常用数据类型:

  • String(字符串),是 Redis 最基本的数据类型,二进制安全的,可以包含任何数据,比如 JPG 图片或者序列化的对象,最大能存储 512 MB。
  • Hash(哈希),是一个键值对(key => value)集合,特别适合用于存储对象。
  • List(列表),Redis 列表是简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头部(左边)或者尾部(右边)。
  • Set(集合),是 String 类型的无序集合,通过哈希表实现,添删查找操作的复杂度都是 O(1)。
  • Sorted set(有序集合),和 Set 一样也是 String 类型元素的集合,且不允许元素重复, 不同的是每个元素都会关联一个 Double 类型的分数(可重复), 通过此分数来为集合中的成员进行从小到大的排序。

4、Redis的持久化机制?

为了能够重用Redis数据,或者防止系统故障,我们需要将Redis中的数据写入到磁盘空间中,即持久化。Redis提供了两种不同的持久化方法可以将数据存储在磁盘中,一种叫快照RDB,另一种叫只追加文件AOF)。

RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘( Snapshot),它恢复时是将快照文件直接读到内存里。

优势:适合大规模的数据恢复;对数据完整性和一致性要求不高

劣势:在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

AOF
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。

优势

  • 每修改同步: appendfsync always同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好。
  • 每秒同步: appendfsync everysec异步操作,每秒记录,如果一秒内宕机,有数据丢失。
  • 不同步:appendfsync no从不同步。

劣势

  • 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
  • aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同。

5、如何选择合适的持久化方式

  • 如果是数据不那么敏感,且可以从其他地方重新生成补回的,那么可以关闭持久化。
  • 如果是数据比较重要,不想再从其他地方获取,且可以承受数分钟的数据丢失,比如缓存等,那么可以只使用RDB。
  • 如果是用做内存数据库,要使用Redis的持久化,建议是RDB和AOF都开启,或者定期执行bgsave做快照备份,RDB方式更适合做数据的备份,AOF可以保证数据的不丢失。

6、什么是缓存击穿?

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

解决方案:

1、加互斥锁。在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。

关于互斥锁的选择,网上看到的大部分文章都是选择 Redis 分布式锁(可以参考我之前的文章:面试必问的分布式锁,你懂了吗?),因为这个可以保证只有一个请求会走到数据库,这是一种思路。

但是其实仔细想想的话,这边其实没有必要保证只有一个请求走到数据库,只要保证走到数据库的请求能大大降低即可,所以还有另一个思路是 jvm 锁。

JVM 锁保证了在单台服务器上只有一个请求走到数据库,通常来说已经足够保证数据库的压力大大降低,同时在性能上比分布式锁更好。

需要注意的是,无论是使用“分布式锁”,还是“JVM 锁”,加锁时要按 key 维度去加锁。

2、热点数据不过期。直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。这种方式适用于比较极端的场景,例如流量特别特别大的场景,使用时需要考虑业务能接受数据不一致的时间,还有就是异常情况的处理,不要到时候缓存刷新不上,一直是脏数据,那就凉了。

7、什么是缓存穿透?

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案:

1、接口校验。 在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。

2、缓存空值。当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。

3、布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。

8、什么是缓存雪崩?

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案:

1、过期时间打散。既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

2、热点数据不过期。该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。

3、加互斥锁。该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。

9、什么是缓存降级?

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
  • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
  • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
  • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

10、什么是缓存预热?

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中,对数据库造成流量的压力。

缓存预热解决方案:

  • 数据量不大的时候,工程启动的时候进行加载缓存动作;
  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。