Redis为什么能那么快?

2022-03-01 19:54:57 浏览数 (1)

我们先给出结论,为什么Redis单机QPS能够达到10W

  1. Redis大部分请求是基于内存的;
  2. Redis拥有简单高效的数据结构;
  3. Redis是基于单线程的IO多路复用的事件机制;

对上述三大原因进行逐条分析

Redis大部分请求都是基于内存操作:

  • 我知道内存磁盘读写速度完全不是一个量级的。Redis启动时便会将持久化文件中的数据加载到内存中,而传统关系型数据库对于新的查询需要经历磁盘IO时间消耗

Redis拥有简单高效的数据结构:

  • Redis拥有SDS,链表,字典,跳跃表,整数集合,压缩列表简单的数据结构,这使得CPU处理这些数据结构向上封装的数据类型会十分的快速

Redis是基于单线程的IO多路复用的事件机制: (重点)

  • 单线程 、IO多路复用的事件机制

单线程:

Redis单线程指的是网络IO处理Key-Value数据信息处理共用一个线程;

为什么Redis使用单线程:

我们都知道多线程可以提升程序的吞吐率,提高程序并发程度

通常情况下,在我们采用多线程后,如果没有良好的系统设计,实际得到的结果,其实是右图所展示的那样。我们刚开始增加线程数时,系统吞吐率会增加,但是,再进一步增加线程时,系统吞吐率就增长迟缓了,有时甚至还会出现下降的情况。

图1图1

为什么会出现这种情况呢?一个关键的瓶颈在于,程序中通常存在被多线程同时访问的共享资源,比如一个共享的数据变量。当有多个线程要修改这个共享变量时,为了保证共享变量的正确性,就需要有额外的机制进行保证(比如:加锁),而这个额外的机制,就会带来额外的系统开销

总体来说Redis基于内存读取数据基于简单数据结构处理数据,这些都是十分快速的操作。而引入多进程可能导致出现额外开销时间 多线程处理简单数据时间 > 单线程处理简单数据的时间,显然是得不偿失的。因而Redis的设计理念就是需将单核CPU的性能发挥到极致,这也便是单机Redis实例QPS性能只能在10W左右的根因,因其无法像多线程程序那般通过堆加CPU核实现高并发。然而,对于普通的场景来说,这个单机性能也是足够用的。

IO多路复用的事件机制:

上面过说,Redis单线程需要用来处理网络IOKey-value数据信息; (Redis6.0版本网络IO改为多线程)

但是传统网络编程模型阻塞式的。如果按照这种阻塞模型的设计,那么Redis主线程接受到连接请求并等待数据输入时主线程被阻塞的,不能够处理KV数据信息。解决这个问题,可以引入多线程,专门启动一个新线程负责处理网络IO主线程只进行KV数据处理(Redis6.0设计方案),6.0之前则是IO多路复用的机制,一个线程负责处理网络IO和KV数据

代码语言:javascript复制
accept(listenfd) #接收到请求,等待数据输入

IO多路复用解释:

为每个客户端创建一个线程,服务器端的线程资源很容易被耗光

图2图2

当然还有个聪明的办法,我们可以当每接收一个客户端连接后 (只接收连接请求,还没接收请求的数据信息),将这个文件描述符(connfd)放到一个数组里。

代码语言:javascript复制
fdlist.add(connfd);

然后弄一个新的线程去不断遍历这个数组,调用每一个元素的非阻塞 read 方法 (read不会阻塞线程,accept会阻塞线程)

代码语言:javascript复制
while(1) {
  for(fd <-- fdlist) {
    if(read(fd) != -1) {
      doSomeThing();
    }
  }
}

这样,我们就成功用一个线程处理了多个客户端连接。

图3图3

你是不是觉得这有些多路复用的意思

并且我们将遍历的委托给操作系统内核,它提供给我们一个有这样效果的函数 。我们将一批文件描述符通过一次系统调用传给内核,由内核层去遍历,真正高效解决这个问题。

Redis中IO多路复用的事件机制解释:

Redis中将 IO多路复用 事件机制进行整合使用。在Linux操作系统中是epoll (IO多路复用的一种) 事件机制

下图是基于epoll机制Redis IO模型。图中的多个FD是连接套接字。通过epoll机制,让内核监听这些套接字。此时,Redis线程不会阻塞在某一个特定的监听已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis可以同时和多个客户端连接并处理请求,从而提升并发性。

图4图4

为了在请求到达时能通知到Redis线程,epoll提供了基于事件的回调机制,即针对不同事件的发生,会调用不同的处理函数

那么,回调机制是怎么工作的呢epoll一旦监测到FD上有数据到达时,就会触发相应的事件。这些触发的事件会被放进一个事件队列并且通知Redis线程对该事件队列不断进行处理。这样一来,Redis无需一直轮询是否有请求实际发生,这就可以避免造成CPU资源浪费。 同时,Redis在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调

解释:Redis接收到连接请求,把连接请求交给操作系统内核内核接收到该请求数据返回事件到队列中并通知主线程主线程遍历事件队列,将对应的数据从内核缓冲区拷贝到用户缓冲区,然后在用户空间请求数据(get/set)进行处理。

参考资料:

  • 你管这破玩意叫 IO 多路复用? (qq.com)
  • 高性能IO模型:为什么单线程Redis能那么快? - 掘金 (juejin.cn)

0 人点赞