应对面试你要掌握Redis哪些原理

Redis属于多线程还是单线程

其实这道题在考察Redis线程模型。很多初级研发工程师基本都知道 Redis 是单线程的,并且能说出 Redis 单线程的一些优缺点,比如,实现简单,可以在无锁的情况下完成所有操作,不存在死锁和线程切换带来的性能和时间上的开销,但同时单线程也不能发挥多核 CPU 的性能。

很明显,如果你停留在上面的回答思路上,只能勉强及格,因为对于这样一道经典的面试题,你回答得没有亮点,几乎丧失了机会。

Redis只有单线程吗

Redis是单线程的,主要是指Redis网络I/O线程,以及键值对的Set和Get等读写操作都是由一个线程来完成的。但Redis持久化、集群同步等操作,则是由另外的线程来执行的。

Redis采用单线程为什么还这么快

一般来说,单线程的处理能力应该要比多线程差才对,但为什么Redis还能达到每秒数万级的处理能力呢?主要有以下几个原因。

首先,一个重要的原因是,Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构,比如哈希表和跳表。
其次,因为是单线程模型避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。

最后,也是最重要的一点, Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求,这让 Redis 可以高效地进行网络通信,因为基于非阻塞的 I/O 模型,就意味着 I/O 的读写流程不再阻塞

但是因为 Redis 不同版本的特殊性,所以对于 Redis 的线程模型要分版本来看。

Redis 4.0 版本之前,使用单线程速度快的原因就是上述的几个原因;

Redis 4.0 版本之后,Redis 添加了多线程的支持,但这时的多线程主要体现在大数据的异步删除功能上,例如 unlink keyflushdb asyncflushall async 等。

Redis 6.0 版本之后,为了更好地提高 Redis 的性能,新增了多线程 I/O 的读写并发能力,但是在面试中,能把 Redis 6.0 中的多线程模型回答上来的人很少,如果你能在面试中补充Redis 6.0多线程的原理,势必会增加面试官对你的认可。

你可以在面试中这样补充:

虽然Redis一直是单线程模型,但是在Redis 6.0 之后,也采用了多个I/O线程来处理网络请求,这是因为随着网络硬件的性能提升,Redis的性能瓶颈有时会出现在网络I/O的处理上,所以为了提高网络请求处理的并行度,Redis 6.0 对于网络请求采用多线程来处理。但是对于读写命令,Redis任然使用单线程来处理。

当然了, “Redis 的线程模型”只是 Redis 原理中的一个考点,如果你想做足准备,回答好 Redis 相关的问题,还需前提掌握 Redis 的主线知识点。我从高性能、高可用的角度出发,把 Redis 的核心原理考点梳理成一张图

img

从图中,你可以看到,线程模型只是高性能考点中的一环,而高可用考点中,包括了持久化、数据复制(主从复制、哨兵机制)等内容。

所以在了解完线程模型之后,接下来继续了解持久化和数据复制的考点。

关于持久化和数据复制,面试官不会问得很直白,比如“Redis 的持久化是怎么做的?”而是立足在某一个问题点:

  • Redis 是如何实现数据不丢失的(考察持久化)?
  • Redis 是如何实现服务高可用的(考察数据复制)?

Redis是如何实现数据不丢失的(考察持久化)

我们知道,缓存数据库的读写都是在内存中,所以性能才会高。但在内存中的数据会随着服务器的重启而丢失,为了保证数据不丢失,要把内存中的数据存储到磁盘,以便缓存服务器重启之后,还能够从磁盘中恢复所有的数据,这个过程就是**Redis**的持久化。

这也是Redis区别于其他缓存数据库的优点之一(比如Memcached就不具备持久化的功能)。Redis的数据持久化有三种方式。

  • AOF日志(Append Only File,文件追加方式):记录所有的操作命令,并以文本的形式追加到文件夹中。
  • RDB快照(Redis DataBase):将某一个时刻的内存数据,以二进制的方式写入磁盘。
  • 混合持久化方式Redis 4.0 新增了混合持久方式,集成了RDBAOF的优点。

AOF日志是如何实现数据的持久化

通常情况下,关系型数据库(如MySQL)的日志都是"写前日志"(Write Ahead,WAL),也就是说,在实际写数据之前,先把修改的数据记录到日志文件中,以便当出现故障时进行恢复,比如MySQL的redo log重做日志),记录的就是修改后的数据。

AOF里记录的是Redis收到的每一条命令,这些命令是以文本形式保存的,不同的是,RedisAOF日志的记录顺序于传统关系型数据库正好相反,他是写后日志,「写后」是指Redis要先执行命令,把数据写入内存,然后再记录日志到文件。

img

那么面试点来了:**Redis**为什么先执行命令,再把数据写入日志呢?

因为,**Redis**在写入日志之前,不对命令进行语法检查,所以,只记录执行成功的命令,避免了出现记录错误命令的情况。并且,在命令执行完成之后在记录,也不会阻塞当前的写操作。

当然,这样操作也会带来风险(这一点你也要在面试中给出解释):

  • 数据可能会丢失:如果Redis刚执行完命令,此时发生宕机,就会导致这条命令存在丢失的风险。
  • 可能阻塞其他操作:虽然AOF是写后日志,避免阻塞当前命令的执行,但因为AOF日志也是在主线程中执行,所以当Redis把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行。

又因为Redis的持久化离不开AOFRDB,所以我们来了解一下RDB

RDB快照是如何实现数据的持久化

因为**AOF**日志记录的是操作命令,不是实际数据,所以用**AOF**方法做故障恢复时,需要全量把日志都执行一遍,一旦日志非常多,势必会造成**Redis**的恢复操作缓慢。

为了解决这个问题,Redis增加了RDB内存快照所谓内存快照,就是将内存中的某一时刻状态以数据的形式记录在磁盘中)的操作,它既可以保证可靠性,又能在宕机时实现快速恢复。

**AOF**不同的是,**RDB**记录**Redis**某一时刻的数据,而不是操作日志,所以在数据恢复的时候,只需要直接把**RDB**文件读入内存,完成快速恢复。

以上就是RDB的主要原理,这里存在两个考点。

  • RDB快照时会产生阻塞线程吗

因为Redis的单线程模型决定了它所有的操作都要尽量避免阻塞主线程,所以对RDB快照也不例外,这关系到是否会降低Redis的性能。

为了解决这个问题,Redis提供了两个命令来生成RDB快照文件,分别是savebgsave

save命令在主线程中执行,会导致阻塞。而bgsave命令则直接会创建一个子线程,用于写入RDB文件的操作,避免了对主线程的阻塞,这也是Redis RDB的默认配置。

  • RDB做快照的时候数据可以修改吗

这个问题非常重要,考察候选人对 RDB 的技术掌握得够不够深。你可以思考一下,如果在执行快照的过程中,数据如果能被修改或者不能被修改都会带来什么影响?

  1. 如果此时可以执行写操作意味着 **Redis** 还能正常处理写操作,就可能出现正在执行快照的数据是已经被修改了的情况;
  2. 如果此时不可以执行写操作意味着 **Redis** 的所有写操作都得等到快照执行完成之后才能执行,那么就又出现了阻塞主线程的问题。

Redis时如何解决这个问题的呢?它主要利用bgsave的子进程,具体操作如下:

  • 如果主线程执行读操作,则主线程和bgsave子进程互不影响。
  • 如果主线程执行写操作,则被修改的数据会复制一份副本,然后bgsave子进程会把该副本数据写入RDB文件,在这个过程中,主线程任然可以直接修改原来的数据。

img

要注意,RedisRDB的执行频率非常重要,因为这会影响快照数据的完整性以及Redis的稳定性,所以在Redis 4.0 后,增加了AOFRDB的数据持久化机制:「把数据以**RDB**的方式写入文件,再将后续的操作命令以**AOF**的格式存入文件,即保证了**Redis**重启速度,有降低了数据丢失风险」。

我们来总结一下,当面试官问你“问你Redis是如何实现数据不丢失的?”时,你首先要意识到这是在考察你对**Redis**的数据持久化知识的掌握程度,那么你回答的思路是:先说明**Redis**有几种持久化方式,然后分析**AOF****RDB**的原理及存在的问题,最后分析一下**Redis 4.0** 版本之后的持久化机制。

Redis是如何实现服务高可用的(考察数据复制)?

另外,Redis不仅仅可以用来当作缓存使用,很多时候也会直接做为数据存储,那么你就要一个高可用的Redis的服务,来支撑和保证业务的正常运行。

那么你怎么设计一个不宕机的Redis高可用服务器呢?(或者即使发生宕机也能够恢复正常运行)

思考一下,解决数据高可用的手段是什么?是副本。那么要设计一个高可用的Redis的服务,一定要从Redis的多服务节点来考虑,比如Redis主从复制哨兵模式、以及Redis集群。这三点是你一定要在面试中回答出来的。

  • 主从同步(主从复制)

这是Redis高可用服务的最基础的保证,实现方案就是将从前一台**Redis**服务器,同步数据到多台从服务器上,即一主多从的模式,这样就可以对Redis做读写分离了,来承载更多的并发操作,这里和Mysql的主从复制原理上是一样的。

  • Redis Sentinel(哨兵模式)

在使用Redis主从服务的时候,会有一个问题,就是当Redis的主从服务器出现故障宕机时,需要手动进行恢复,为了解决这个问题,Redis增加了哨兵模式(因为哨兵模式做到了可以监控主从服务器,并且提供自动容灾恢复的功能)。

img

  • Redis Cluser(集群)

Redis Cluser 是一种分布式去中心化的运行模式,是在Redis 3.0版本中推出的Redis集群方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高Redis服务的读写性能。

img

Redis Cluser方案采用哈希槽(Hash Slot),来处理数据和实例之间的映射关系。在Redis Cluser方案中,一个切片集群共有16384哈希槽,这些哈希槽类似数据分区,每个键值对都会根据它的key,被映射到一个哈希槽中,具体执行过程分为两大步。

  • 根据键值对的key,按照CRC16 算法计算一个16 bit的值。
  • 再用16 bit值对16384取模,得到0~16383范围内的模数,每个模数代表一个相应的编号的哈希槽。

剩下一个问题就是,这些哈希槽怎么被映射到具体的Redis实例上的呢?有两种方案。

  • 平均分配:在使用cluser create 命令创建Redis集群时,Redis会自动把所有哈希槽平均分布到集群实例上。比如集群中有9个实例,则每个实例上槽的个数为16384/9个。
  • 手动分配:可以使用cluser meet命令手动建立实例间的连接,组成集群,再使用cluser addslots命令,指定每个实例上的哈希槽个数,为了方便你的理解,我通过一张图来解释数据、哈希槽,以及实例三者的映射分布关系。

img

示意图中的分片集群一共有 3 个实例,假设有 4 个哈希槽时,我们就可以通过命令手动分配哈希槽,比如实例 1 保存哈希槽 0 和 1,实例 2 保存哈希槽 2 和 3。

然后在集群运行的过程中,key1key2 计算完 CRC16 值后,对哈希槽总个数 5 进行取模,再根据各自的模数结果,就可以被映射到对应的实例 1 和实例 3 上了。

我们再来总结一下,主从同步是 **Redis** 高可用最基础的服务高可用方案,但它当发生故障时,需要手动恢复故障,因此就有了哨兵模式用于监控和实现主从服务器的自动容灾。最后我讲了目前最常用的高可用方案 Redis 集群,这三种高可用方式都是你需要掌握的。

总结

主要讲了Redis的核心问题:线程模型、数据持久化、以及高可用,我想强调这样几个重点:

  • 对于线程模型的知识点,你要分开三条线进行理解(Redis 4.0之前、Redis4.0之后,以及Redis 6.0)。
  • 对于数据持久化,你要掌握Redis持久化的几种方案,AOFRDB的原理,以及为了弥补他们的缺点,Redis增加了混合持久化方式,以较小的性能开销保证数据的可靠性。
  • 实现高可用的三种手段:主从同步、哨兵模式和Redis集群服务,对于Redis集群,你要掌握哈希槽的数据分布机制,以及自动分配和手动分配的实现原理。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值