一、Redis 有多快?
Redis 是基于内存运行的高性能 K-V 数据库,官方提供的测试报告是单机可以支持约 10w/s 的 QPS
二、Redis 为什么这么快?
(1)完全基于内存,数据存在内存中,绝大部分请求是纯粹的内存操作,非常快速,跟传统的磁盘文件数据存储相比,避免了通过磁盘 IO 读取到内存这部分的开销。
(2)数据结构简单,对数据操作也简单。Redis 中的数据结构是专门进行设计的,每种数据结构都有一种或多种数据结构来支持。Redis 正是依赖这些灵活的数据结构,来提升读取和写入的性能。
(3)采用单线程,省去了很多上下文切换的时间以及 CPU 消耗,不存在竞争条件,不用去考虑各种锁的问题,不存在加锁释放锁操作,也不会出现死锁而导致的性能消耗。
(4)使用基于 IO 多路复用机制的线程模型,可以处理并发的链接。
Redis 基于 Reactor 模式开发了自己的网络事件处理器,这个处理器被称为文件事件处理器 file event handler。由于这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型,但是它采用 IO 多路复用机制同时监听多个 Socket,并根据 Socket 上的事件来选择对应的事件处理器进行处理。文件事件处理器的结构包含 4 个部分,线程模型如下图:
多个 Socket
IO 多路复用程序
文件事件分派器
事件处理器(命令请求处理器、命令回复处理器、连接应答处理器)
多个 Socket 可能会产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 Socket,将 Socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
Redis 客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于 Redis 是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是 Redis 的单线程基本模型。
多路 I/O 复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,然后程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且依次顺序
的 处理就绪的流,这种做法就避免了大量的无用操作。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个客户端的网络 IO 连接请求(尽量减少网络 IO 的时间消耗)
(5)Redis 直接自己构建了 VM 机制 ,避免调用系统函数的时候,浪费时间去移动和请求
三、为什么 Redis 是单线程?
这里我们强调的单线程,指的是网络请求模块使用一个线程来处理,即一个线程处理所有网络请求,其他模块仍用了多个线程。
那为什么使用单线程呢?官方答案是:因为 CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
但是,我们使用单线程的方式是无法发挥多核 CPU 性能,不过我们可以通过在单机开多个 Redis 实例来解决这个问题
四、Redis6.0 的多线程?
1、Redis6.0 之前为什么一直不使用多线程?
Redis 使用单线程的可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。
2、Redis6.0 为什么要引入多线程呢?
因为 Redis 的瓶颈不在内存,而是在网络 I/O 模块带来 CPU 的耗时,所以 Redis6.0 的多线程是用来处理网络 I/O 这部分,充分利用 CPU 资源,减少网络 I/O 阻塞带来的性能损耗。
3、Redis6.0 如何开启多线程?
默认情况下 Redis 是关闭多线程的,可以在 conf 文件进行配置开启:
io-threads-do-reads yes
io-threads 线程数
“##”官方建议的线程数设置:4 核的机器建议设置为 2 或 3 个线程,8 核的建议设置为 6 个线程,线程数一定要小于机器核数,尽量不超过 8 个。
4、多线程模式下,是否
如图,一次 redis 请求,要建立连接,然后获取操作的命令,然后执行命令,最后将响应的结果写到 socket 上。
在 redis 的多线程模式下,获取、解析命令,以及输出结果
Redis 为什么用单线程
Redis 单线程指的什么
Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。 但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执 行的。
多线程提高系统吞吐率
日常写程序时,我们经常会听到一种说法:“使用多线程,可以增加系统吞吐率,或是可 以增加系统扩展性。”的确,对于一个多线程的系统来说,在有合理的资源分配的情况 下,可以增加系统中处理请求操作的资源实体,进而提升系统能够同时处理的请求数,即 吞吐率。下面的左图是我们采用多线程时所期待的结果。
但是,请你注意,通常情况下,在我们采用多线程后,如果没有良好的系统设计,实际得 到的结果,其实是右图所展示的那样。我们刚开始增加线程数时,系统吞吐率会增加,但 是,再进一步增加线程时,系统吞吐率就增长迟缓了,有时甚至还会出现下降的情况。
多线程共享资源
为什么会出现这种情况呢?一个关键的瓶颈在于,系统中通常会存在被多线程同时访问的 共享资源,比如一个共享的数据结构。当有多个线程要修改这个共享资源时,为了保证共 享资源的正确性,就需要有额外的机制进行保证,而这个额外的机制,就会带来额外的开 销。
举个栗子
拿 Redis 来说,Redis 有 List 的数据类型,并提供出队(LPOP) 和入队(LPUSH)操作。假设 Redis 采用多线程设计,如下图所示,现在有两个线程 A 和 B,线程 A 对一个 List 做 LPUSH 操作,并对队列长度加 1。同时,线程 B 对该 List 执行 LPOP 操作,并对队列长度减 1。为了保证队列长度的正确性,Redis 需要让线程 A 和 B 的 LPUSH 和 LPOP 串行执行,这样一来,Redis 可以无误地记录它们对 List 长度的修 改。否则,我们可能就会得到错误的长度结果。这就是多线程编程模式面临的共享资源的 并发访问控制问题。
并发访问控制一直是多线程开发中的一个难点问题,如果没有精细的设计,比如说,只是 简单地采用一个粗粒度互斥锁,就会出现不理想的结果:即使增加了线程,大部分线程也 在等待获取访问共享资源的互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增 加。甚至减少,因为多线程会存在多线程频繁切换开销
采用多线程开发一般会引入同步原语来保护共享资源的并发访问,这也会降低系统 代码的易调试性和可维护性。为了避免这些问题,Redis 直接采用了单线程模式。
Redis 使用单线程小结
- 使用多线程,可以增加系统吞吐率,如果没有良好的系统设计,实际得 到的结果我们刚开始增加线程数时,系统吞吐率会增加,但 是,再进一步增加线程时,系统吞吐率就增长迟缓了,有时甚至还会出现下降的情况
- 出现上述情况的原因是 redis 在使用多线程时同样会存在多线程同时访问的 共享资源的问题,为了保证共 享资源的正确性,就需要有额外的机制进行保证。
- 在没有良好的系统设计,这个额外的机制,就会带来额外的开 销。这种开销有时不但无法增加系统吞吐率,反而会降低系统吞吐率
- 多线程开发一般会引入同步原语来保护共享资源的并发访问,这也会降低系统 代码的易调试性和可维护性