Redis漫谈之IO(从BIO到Epoll)

Redis 6.x 将出现 IO Threads(IO 多线程)。IO 多线程不是 Redis 的业务逻辑线程(业务逻辑线程是单线程的),而是去和 IO 打交道,即只会触发 read 调用读取数据。IO 多线程,即可以增加 IO 处理的效率和性能。

下面将介绍一下 IO 的发展技术,从 BIO 到 NIO 到多路复用,最后到 Epoll。

前提知识

Kernel 内核

上层有许多程序(进程),当有客户端(client)。

一个 Redis Server 启动时会占用一个进程号。在 /proc/PID/fd 中存了描述符。

TCP 三次握手

文件描述符

/proc/PID/fd 中存了某一个进程的所有文件描述符,以数值文件表示(Linux 中一切以文件来表示)。

fd 中 0 为标准输入,1 为标准输出,2 为错误输出。另外还有 socket 类型的 fd 和 pipe 类型的 fd。

socket 类型的 fd,建立 socket 连接时,会返回一个 fd(内核 socket 函数)。比如当 Redis 启动起来,就会启动一个 socket 的 fd 一直在监听客户端的连接。当有一个客户端连接上时,则会再启动一个 fd 专门用于与此客户端的连接。

当服务启动时

man 帮助文档

安装:

yum install man man-pages

查看系统文档:

man 2 read # 查看 2 类的系统调用函数 read 函数

Strace 工具

见文章 Redis 漫谈,其可以抓取服务进程的系统调用情况,再结合 man 帮助文档,可以了解服务的执行过程。

用户态和内核态

BIO

BIO (1).png

BIO 的问题是:每一个客户端开启了一个线程。java 中每个线程要占 1 M的内存,因此有了 NIO。

(BIO, NIO, Epoll 都是内核的发展)

NIO

内核不再抛出多个线程,而是一直在循环读取 fd,若没有数据会读下一个。

NIO.png

NIO 的问题:频繁的用户空间和内核空间的切换。可能空转了 1000 次造成了浪费。由此,内核中出现了 select 多路复用。

一些服务程序是运行在用户空间的,叫用户态;而内核中运行的程序叫内核态,运行在内核空间的。所有程序调用内核时,就需要从用户态切换到内核态。

多路复用(内核的 select 方法)

解决了用户态和内核态的频繁切换。

内核支持了 select 方法,支持传入一个 fd 数组,内核去循环遍历,当有输入时返回对应 fd。

当内核返回 fd 结果集,服务进程收到返回的结果集后自行再调用 read 方法读取数据。(这叫同步非阻塞。异步是内核收到数据直接存入 buffer 中就抛弃了此连接的占用,将 buffer 返回给对应进程)

多路复用.png

多路复用的问题:给内核传入多个 fd(传入量大),内核还进行了多个 fd 的循环。由此出来了 Epoll(Poll跳过)。

Epoll

解决的问题:1. 传入的参数少了;2. 内核不再遍历,更智能了。

可用 man epoll 查看文档(epoll 是 7 类的杂项描述),可看到三个系统调用 epoll_create, epoll_ctlepoll_wait(它们是 2 类的系统调用)。

  • epoll_create 返回一个 fd
  • epoll_ctl 传入 epoll_create 创建的文件描述符,还有另一个文件描述符
  • epoll_wait 会等待事件
  1. 当服务启动(启动在 fd6)时, redis server 先会调用一次 epoll_create,epoll_create 会创建一个空间(event poll),并返回空间的指向 fd4
  2. 而后会调用 epoll_ctl 传入 fd4、fd6 以及所监听的事件 accept。在 fd4 的空间位置中创建了对 fd6 的 accept 事件。
  3. 之后会调用 epoll_wait,等待系统中断的发生。

只要有事件发生,操作系统都会发生一个中断。当中断发生,就会去找中断事件(在)

  1. 当 client_1 第一次连接过来时,中断发生,查到是 fd6(图中 4.1),epoll_wait 收到这个事件(图中 4.2)
  2. 当 redis server 收到 fd7 输入的请求后,调一次 epoll_ctl(fd4, fd7, read)(图中 2.1,2.2)

自 Redis 服务启动开始
epoll_create 只调了一次,创建一个 event poll
epoll_ctl 有多少事件发生就调多少次
epol_wait 会一直在轮巡

Epoll.png

测试

最后,可以用 strace -ff -o path/to/file ./redis-server 工具来追踪进程的系统调用。可进入 /proc/PID/fd 中查看所接入的文件描述符(不知道这是 什么工具,竟然可以看来 fd 的类型指向)。

image.png

其他

无论是多路复用还是 Epoll,这都是 同步非阻塞

什么是同步,同步就是我知道这个事件后,我自己去读,等我读回数据后,再去处理。

而异步是我读了这个事件后就不管了,而且读这个行为也不再是我调了,全是内核自己研发的一套东西去做,这就叫 AIO。

但 Linux 中的 AIO 是伪的,只有 Windows 中的 LCP 这样的内核技术才是真正的 AIO。

Natty (发音是它,也不知对不对) 使用 Epoll 而不用 AIO 是因为 AIO 的性能还不如 Epoll。


  • 参考其他文章以做补充理解: 聊聊BIO,NIO和AIO (1)

你可能感兴趣的:(Redis漫谈之IO(从BIO到Epoll))