Redis是单线程的,为什么支持高并发?

要搞清楚这个问题,首先来看看以下几个问题:

  1. 并行,并发以及它们的区别
  2. BIO,NIO, IO多路复用,AIO
  3. Redis如何实现高并发
  • 并行与并发的定义:

并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

并行:在操作系统中,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。

理解:

1)并发是两个任务可以在重叠的时间段内启动,运行和完成;并行是任务在同一时间运行,例如,在多核处理器上。
2)并发是独立执行过程的组合,而并行是同时执行(可能相关的)计算。
3)并发是一次处理很多事情,并行是同时做很多事情。

如下图所示,箭头表示空闲时间:

Redis是单线程的,为什么支持高并发?

总结:并发就是指代码逻辑上可以并行,有并行的潜力,但是不一定当前是真的以物理并行的方式运行。并发指的是代码的性质,并行指的是物理运行状态。

  顾名思义,并发强调的是一起出发,并行强调的是一起执行。并发的反义是顺序,并行的反义是串行。并发并行不是互斥概念,只不过并发强调任务的抽象调度,并行强调任务的实际执行。

  • BIO,NIO, IO多路复用,AIO简单介绍(前序题库有详细介绍,这里进行回顾)

BIO(同步阻塞):

Redis是单线程的,为什么支持高并发?

步骤:1、用户发出请求。

2、一致等待数据是否准备好,如果数据没有准备好则进行阻塞。

3、数据准备好后,将数据从内核复制到用户空间。

4、数据返回给程序。

NIO(同步非阻塞):

Redis是单线程的,为什么支持高并发?

非阻塞忙轮询:数据没来,进程就不停的去检测数据(轮询的方式),直到数据来。

IO多路复用:

Redis是单线程的,为什么支持高并发?

如上图所示,和非阻塞一样,在后面也是阻塞的,但是在获取数据时不再由自己去询问操作系统,而是统一交给一个内核线程去处理。

虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

IO多路复用模型使用了Reactor设计模式实现了这一机制。

Redis是单线程的,为什么支持高并发?

Io多路复用有三种发方式select,poll,epoll

select: 注册事件由数组管理, 数组是有长度的, 32位机上限1024, 64位机上限2048. 轮询查找时需要遍历数组。

poll: 把select的数组采用链表实现, 因此没了最大数量的限制。

epoll方式: 基于事件回调机制, 回调时直接通知进程, 无须使用某种方式来查看状态。

拓展一:什么是c10k

最初的服务器是基于进程/线程模型。新到来一个TCP连接,就需要分配一个进程。假如有C10K,就需要创建1W个进程,可想而知单机是无法承受的。那么如何突破单机性能是高性能网络编程必须要面对的问题,进而这些局限和问题就统称为C10K问题。

拓展二:同步、异步、阻塞、非阻塞概念

  1. 同步:执行一个操作之后,进程触发IO操作并等待(也就是我们说的阻塞)或者轮询的去查看IO操作(也就是我们说的非阻塞)是否完成,等待结果,然后才继续执行后续的操作。
  2. 异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
  3. 阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
  4. 非阻塞:进程给CPU传达任我后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。

解决方案:每个进程/线程同时处理多个连接(IO多路复用)

  • Redis如何实现高并发

单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),这样避免了不必要的上下文切换和竞争条件(锁)。

redis采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求。

Redis的I/O多路复用:

     Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)

文件事件处理器使用 I/O 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器

虽然整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以保证整个 Redis 服务实现的简单

redis的多路复用, 提供了select, epoll, evport, kqueue几种选择,在编译的时候来选择一种。

select是POSIX提供的, 一般的操作系统都有支撑;

epoll 是LINUX系统内核提供支持的;

evport是Solaris系统内核提供支持的;

kqueue是Mac 系统提供支持的。

  • redis速度快总结:
  1. Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快。
  2. Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
  3. Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
  4. 数据结构也帮了不少忙,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
  5. Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。