为什么Redis快呢,其实在学习完了应用场景和集群等模式后可以回归本源了,用Redis的本质原因到底是什么?Redis的设计方式有什么好处,这是向更深处探索的第一步。
Redis为什么快
因为Redis是基于内存的一个数据库,所以第一点可想而知,内存当然比IO快啊,其次就是两个不常见的点:
- redis是基于内存的,内存的读写速度非常快, 数据存放在内存中,内存的响应时间大约是 100纳秒 ,这是Redis每秒万亿级别访问的重要基础。
- redis是单线程的,省去了很多上下文切换线程的时间,单线 避免了线程切换和竞态产生的消耗,当然要注意:每条命令执行如果占用大量时间, 会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库
- redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。
- 数据结构简单,操作节省时间,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。当然解决的场景也有限,还是以性能为第一位的。
以上四点就是Redis快的原因。
Redis多路复用技术
IO多路复用技术以及Redis的实现模式,redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量。
- 多路-指的是多个socket连接,也就是多个客户端连接
- 复用-指的是复用一个线程,也就是复用Redis的单个存取线程
- 多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术
采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)。数据输入操作一般包含两个步骤:
- 等待数据准备好(waiting for data to be ready)。对于一个套接口上的操作,这一步骤关系到数据从网络到达,并将其复制到内核的某个缓冲区。
- 将数据从内核缓冲区复制到进程缓冲区(copying the data from the kernel to the process)。
以下内容来自于Redis 和 IO 多路复用:
阻塞模型
当使用 read 或者 write 对某一个文件描述符(File Descriptor 以下简称 FD)进行读写时,如果当前 FD 不可读或不可写,整个 Redis 服务就不会对其它的操作作出响应,导致整个服务不可用。这也就是传统意义上的,也就是我们在编程中使用最多的阻塞模型
阻塞模型虽然开发中非常常见也非常易于理解,但是由于它会影响其他 FD 对应的服务,所以在需要处理多个客户端任务的时候,往往都不会使用阻塞模型
I/O 多路复用
阻塞式的 I/O 模型并不能满足这里的需求,我们需要一种效率更高的 I/O 模型来支撑 Redis 的多个客户(redis-cli),这里涉及的就是 I/O 多路复用模型了:
在 I/O 多路复用模型中,最重要的函数调用就是 select,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select 方法就会返回可读以及可写的文件描述符个数
一个例子
从网上找了一个讲的特别好的例子,生动形象的表明了I/O多路复用模式的好处:IO模式一般分为同步IO和异步IO.
- 同步IO会阻塞进程, 同步IO又分为阻塞IO, 非阻塞IO, IO多路复用,非阻塞IO虽然在请求数据时不阻塞, 但真正数据来临时,也就是内核数据拷贝到用户数据时, 此时进程是阻塞的
- 异步IO不会阻塞进程. 目前linux上大部分用的是同步IO, 异步IO在linux上目前还不成熟, 不过windows的iocp算是
那么这些IO模式的区别分别是什么? 接下来举个小例子来说明. 假设你现在去女生宿舍楼找自己的女神, 但是你只知道女神的手机号,并不知道女神的具体房间 先说同步IO的情况,
- 阻塞IO,给女神发一条短信, 说我来找你了, 然后就默默的一直等着女神下楼, 这个期间除了等待你不会做其他事情, 属于备胎做法.
- 非阻塞IO, 给女神发短信, 如果不回, 接着再发, 一直发到女神下楼, 这个期间你除了发短信等待不会做其他事情, 属于专一做法.
- IO多路复用, 是找一个宿管大妈来帮你监视下楼的女生, 这个期间你可以些其他的事情. 例如可以顺便看看其他妹子,玩玩王者荣耀, 上个厕所等等. IO复用又包括 select, poll, epoll 模式. 那么它们的区别是什么?
- select大妈, 每一个女生下楼, select大妈都不知道这个是不是你的女神, 她需要一个一个询问, 并且select大妈能力还有限, 最多一次帮你监视1024个妹子
- poll大妈,不限制盯着女生的数量, 只要是经过宿舍楼门口的女生, 都会帮你去问是不是你女神
- epoll大妈,不限制盯着女生的数量, 并且也不需要一个一个去问,那么如何做呢? epoll大妈会为每个进宿舍楼的女生脸上贴上一个大字条,上面写上女生自己的名字, 只要女生下楼了, epoll大妈就知道这个是不是你女神了, 然后大妈再通知你。以空间换时间,时间复杂度O(1)
I/O多路复用模型使用了Reactor设计模式实现了这一机制。通过Reactor的方式,可以将用户线程轮询I/O操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作,而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路I/O复用模型也被称为异步阻塞I/O模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket
上面这些同步IO有一个共同点就是, 当女神走出宿舍门口的时候【数据来临时】, 你已经站在宿舍门口等着女神的, 此时你属于阻塞状态
Reactor 设计模式
Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符):
文件事件处理器使用 I/O 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器。
虽然整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以保证整个 Redis 服务实现的简单。
Redis单线程模型
什么是Redis单线程模型?Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型,优势有:
- 不需要各种锁的性能消耗,Redis的数据结构并不全是简单的Key-Value,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
- CPU消耗低,采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU
- 代码更清晰,处理逻辑更简单
缺点也很明显,无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善
Redis的性能瓶颈是什么
Redis在内存里,省下来CPU从硬盘里取数据拿到内存的时间,也就是更依赖内存,CPU不可能对它一点影响没有,但是相比别的数据库,Redis受CPU的限制更少一点。而Redis还需要经常和网络进行连接,也就是网络IO高。相比于内存和网络,Redis对CPU的依赖稍低,所以Redis的性能瓶颈不是CPU而是内存和网络,但是如果有多线程比较好的解决方案,可以把CPU利用起来。
Redis为什么是单线程
当性能出现问题时,我们一般考虑的是使用多线程进行调度,而多线程调度需要CPU、内存、网络带宽。但是CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。当然单线程的Redis在瓶颈是CPU的IO时(这不是大多数应用的实际应用场景),确实速度会比多线程慢。
如何不让CPU闲置
如果万一CPU成为你的Redis瓶颈了,或者你就是不想让服务器其他核闲置,那怎么办?那也很简单,你多起几个Redis进程就好了。Redis是keyvalue数据库,又不是关系数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis进程上就可以了【通过配置文件启动可以很轻松的区分】,redis-cluster当然是更好的解决方案。