Redis6.0面试题补充

Posted Jonathan的架构日记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis6.0面试题补充相关的知识,希望对你有一定的参考价值。

Redis6.0之前的版本真的是单线程的吗?

Redis在处理客户端请求时,包括获取(socket读)、解析、执行、内容返回(socket写)等都是由一个顺序串行的主线程执行的,这就是所谓的 单线程.单如果严格讲,从Redis4.0之后并不是单线程,除了主线程之外,它也有后台线程在处理一些较为缓慢的操作,例如 清理脏数据, 无用链接的释放, key的删除, 数据持久化bgsave,bgrewriteaof等,都是在主线程之外的子线程单独执行的.

Redis6.0之前为什么一致不用多线程?

官方曾做过类似问题的回复:使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。

使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没有必要使用多线程。单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行。

Redis6.0为什么要引入多线程呢?

Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。

但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个Redis服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。

从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

  • 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式

  • 使用多线程充分利用多核,典型的实现比如 Memcached

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:

  • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核

  • 多线程任务可以分摊 Redis 同步 IO 读写负荷

Redis6.0默认是否开启了多线程?

Redis6.0的多线程默认是禁用的,只使用主线程。如需开启需要修改redis.conf配置文件:io-threads-do-reads yes

Redis6.0多线程开启时,线程数如何设置?

开启多线程后,还需要设置线程数,否则是不生效的。同样修改redis.conf配置文件 io-threads4

关于线程数的设置,官方有一个建议:4核的机器建议设置为2或3个线程,8核的建议设置为6个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好,官方认为超过了8个基本就没什么意义了。

Redis6.0多线程的实现机制?

核心思路是,将主线程的IO读写任务拆分出来给一组独立的线程执行,使得多个 socket 的读写可以并行化

  1. 主线程负责接收建立连接的请求,获取socket放到全局等待处理队列

  2. 主线程处理完读事件之后,通过Round Robin将这些连接分配给IO线程(并不会等待队列满)

  3. 主线程阻塞等待IO线程读取socket完毕

  4. 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行

  5. 主线程阻塞等待IO线程将数据回写socket完毕

  6. 解除绑定,清空等待队列

该线程有如下特点:

  • IO线程要么同时在读socket,要么同时在写,不会同时读或写

  • IO线程只负责读写socket解析命令,不负责命令处理(主线程串行执行命令)

开启多线程后,是否会存在线程并发安全问题?

Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行,因此不存在线程的并发安全问题

Redis线程中经常提到IO多路复用,如何理解?

这是IO模型的一种,即经典的Reactor设计模式,有时也称为异步阻塞IO。

多路指的是多个socket连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。

为什么redis是单线程的以及为什么这么快?

先说为什么redis这么快,理由如下:

  1. redis完全基于内存,绝大部分请求是纯粹的内存操作,非常快速.

  2. 数据结构简单,对数据操作也简单,redis中的数据结构是专门进行设计的

  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多线程或者多线程切换而消耗CPU,不用考虑各种锁的问题,不存在加锁,释放锁的操作,没有因为可能出现死锁而导致性能消耗

  4. 使用了多路IO复用模型,非阻塞IO

  5. 使用底层模型不同,它们之间底层实现方式及与客户端之间的 通信的应用协议不一样,Redis直接构建了自己的VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

为什么redis是单线程的

多线程操作就是使用多个cpu模拟多个线程,对redis进行操作。这样会造成一个巨大的问题,就是cpu的上下文切换问题。cpu的上下文切换的效率比直接在内存中进行读取差的很多.

redis使用单个cpu绑定一个内存,针对内存的处理就是单线程的,这样避免了上下文的切换,所以非常的快。一次cpu的切换时间大约是1500ns。从内存中读取1mb的连续数据,耗时大约是250us。如果1mb的数据被多个线程读取了1000次。那么就是有1000次时间的上下文切换,于是就是1500ns*1000=1500us。结果显而易见。1500us和250us差的还是很多的。

那么redis采取单线程还避免了很多问题。如果redis使用多线程来进行,那么就要考虑多线程带来的数据安全问题,如果我们在操作redis的list,hash等数据结构的时候。多线程就可能存在数据不安全的情况,这是就要加锁。一旦加锁就影响了程序的执行速度


以上是关于Redis6.0面试题补充的主要内容,如果未能解决你的问题,请参考以下文章

史上最强Redis6.0,给分布式源码跪了!

前端面试题之手写promise

面试官:你确定 Redis 是单线程的进程吗?

Java缓存面试题——Redis应用

面试官:你确定 Redis 是单线程的进程吗?

完整的ES6面试题