Redis为什么这么快?
Redis 为什么快?
Redis 的数据在内存中,所有运算都是内存级别的运算。
Redis 是单线程的,避免了线程切换和加锁带来的损耗。
Redis 使用 epoll 作为非阻塞 I/O 多路复用的实现,IO多路复用程序监听多个 socket,并将 socket 放到队列中,每次从队列里取出一个 socket 给事件分派器,事件分派器再把 socket 分派给对应的事件处理器进行处理,这些处理器都是纯内存操作,效率非常高,处理一个事件可能只需要几微秒。
Redis 模型结构:
Redis 内部有一个文件事件处理器,它是单线程的,由四个部分组成,分别是:IO 多路复用程序、socket、事件分派器和事件处理器,其中事件处理器又分为:连接应答处理器、命令请求处理器和命令回复处理器。
IO多路复用
fd_set 数组是存有 0 和 1 的位数组,fd 指文件描述符。
常用的IO多路复用模型有三种:select、poll、epoll。
select:它维护了一个数组结构 fd_set,调用 select 函数时,会从用户空间拷贝 fd_set 到内核空间,并监听是否有事件触发,有就通过无差别轮询的方式遍历找到事件触发的位置,然后执行相关的读或写操作。轮询的时间复杂度为 O(n)。
缺点:内核对被监控的 fd_set 集合做了大小限制,最大为 1024 ;每次调用 select,都需要把 fd_set 集合从用户态拷贝到内核态,都需要在内核遍历 传递进来的所有 fd_set ,效率很低。
poll:与 select 类似,区别是它采用的是 poll_fd 数据结构实现了一个可变长的数组,没有了最大文件描述符数量的限制。
epoll:epoll 与 select 的不同之处在于,epoll 监听事件是否触发时,还设置了回调函数,如果事件触发,就执行回调函数,并将准备就绪的 fd 放到 readyList 中,而不需要轮询遍历所有的 fd_set 。并且 epoll 没有最大文件描述符数量的限制。在高并发情况下 epoll 能支持更多的连接。
理解Select,Poll,Epoll
这三个机制可以理解为:
可以将其比喻为你同时盯着多个聊天窗口。而Select、Poll和Epoll就是不同的方式来管理这些聊天窗口。
- Select:假设你有三个聊天窗口,你需要不停地检查每个窗口是否有新消息。选择机制类似于你反复打开每个窗口,并检查它们是否有新消息。这种方式有一个限制,因为你只能在有限的时间内同时检查有限数量的窗口。
- Poll:现在我们升级到使用Poll。这次,你创建一个列表,将所有的聊天窗口都添加到这个列表中。然后,你只需检查那些有新消息的聊天窗口,而无需关注其他聊天窗口。这种方式通过轮询列表中的聊天窗口来查找有新消息的窗口,避免了Select中的限制。
- Epoll:让我们再进一步,尝试使用Epoll。这次,你注册一个事件回调函数,并告诉操作系统只有在有新消息到达时才通知你。这样,你无需主动去轮询任何窗口,而是等待操作系统的通知。当有新消息到达时,操作系统会触发回调函数来通知你。
这些机制可以帮助你高效地处理多个聊天窗口的消息。Select、Poll和Epoll分别采用了不同的策略来管理和处理这些窗口,Epoll通常被认为是最优的机制,因为它不需要轮询并具有更高的效率。选择哪种机制取决于你的具体需求和系统平台。
Redis IO多路复用技术
redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统。 redis 采用网络IO多路复用技术来保
证在多连接的时候, 系统的高吞吐量。
为什么Redis中要使用I/O多路复用呢?
首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都
是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其
它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个
描述符就绪,能够通知程序进行相应的操作。
redis的io模型主要是基于epoll实现的,不过它也提供了 select和kqueue的实现,默认采用epoll 。
为什么 Redis 使用了单线程 IO 多路复用,为什么那么快?
-
cpu 处理相比于 IO 可以忽略
每个客户端建立连接时,都需要服务端为其 创建 socket 套接字,建立连接。
然后该客户端的每个请求都要经历以下几步:
(1)等待请求数据数据从客户端发送过来
(2)将请求数据从内核复制到用户进程的缓冲区(buffer)
(3)对请求数据进行处理(对于 redis 而言,一般就是简单的 get/set)由于操作简单+只涉及内存,所以第(3)步的处理很简单、很快,主要时间耗在(1)步,所以,如果采用普通 BIO 模式,每个请求都要经历这几步,那么处理十万条数据,就要在(1)步花费大量的时间,这样的话,qps 一定很低。
所以就采用了更高效的 IO 多路复用模式,即,将(1)步统一交给第三方(也就是操作系统,操作系统提供了 select、poll、epoll、kqueue、iocp等系统调用函数),结合 redis 的单线程,现在整个处理流程是这样的:
一下子来了一堆请求,线程将这些请求都交给操作系统去处理,让操作系统帮忙完成第(1)步,等到这些请求里的一个或多个走完了第(1)步,就将一个集合交给这个线程,并说,我这里收集到了几个走完第一步的请求,你去走(2)、(3)步吧。于是线程拿着这个集合去遍历,等遍历结束之后。又去检查操作系统那儿有没有(这个线程自己维护了一个 while 循环)走完第(1)步的请求,发现又有一些了,拿到后继续遍历进行(2)、(3)步,如此循环往复。
注:有些 IO 模式是将(1)(2)步都交给操作系统处理了,线程本身只需处理第(3)步 -
瓶颈在带宽,而不在 cpu
由于 数据存放在内存中+处理逻辑简单,导致即使是单线程,Redis 可支持的 qps 也相当大,而当 qps 相当大的时候,首先限制性能的是带宽,即不需要把 cpu 的性能挖掘出来,因为在这之前,带宽就不够用了。所以没有必要为了提高 cpu 利用率而使用多线程处理业务逻辑。