redis 从0到1完整学习 (十八):阻塞/非阻塞 IO

发布时间:2024年01月15日


1. 引言

前情提要:
《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》
《redis 从0到1完整学习 (六):Hash 表数据结构》
《redis 从0到1完整学习 (七):ZipList 数据结构》
《redis 从0到1完整学习 (八):QuickList 数据结构》
《redis 从0到1完整学习 (九):SkipList 数据结构》
《redis 从0到1完整学习 (十):RedisObject 数据结构》
《redis 从0到1完整学习 (十一):RedisObject 之 String 类型》
《redis 从0到1完整学习 (十二):RedisObject 之 List 类型》
《redis 从0到1完整学习 (十三):RedisObject 之 Set 类型》
《redis 从0到1完整学习 (十四):RedisObject 之 ZSet 类型》
《redis 从0到1完整学习 (十五):RedisObject 之 Hash 类型》
《redis 从0到1完整学习 (十六):内存回收之 key 过期处理策略》
《redis 从0到1完整学习 (十七):内存回收之内存淘汰策略》

之前我们介绍了 redis 的内存回收相关处理策略。Redis 巧妙地结合了非阻塞 I/O 技术和多路复用机制来实现高并发、低延迟的服务能力,这也是它作为高性能缓存数据库的重要原因之一。

为了连续性,本文主要介绍下 Linux 的阻塞和非阻塞 IO 概念,方便后续理解。

2. redis 源码下载

Redis 源码可以点击这里下载,方便查看其中定义的一些数据结构。
在这里插入图片描述

3. I/O 模型

在操作系统和计算机网络中,通常提到的五种I/O(Input/Output)模型是:

I/O 模型描述
阻塞I/O (Blocking I/O)进程发起I/O操作后,会一直等待(阻塞)直到该操作完成。期间不能执行其他任务。
非阻塞I/O (Non-Blocking I/O)进程发起请求后立即返回,不关心数据是否准备好。进程需不断轮询检查I/O状态。
I/O复用 (I/O Multiplexing)单个线程可以同时监听多个文件描述符,当任意一个准备就绪时进行实际I/O操作,如selectpollepoll
信号驱动I/O (Signal-Driven I/O)应用程序注册信号处理器,内核在数据准备就绪时发送信号通知进程,进程在信号处理函数中完成I/O操作。
异步I/O (Asynchronous I/O, AIO)进程发起I/O请求后立刻返回,内核在后台完成操作并在所有操作完成后通过回调函数通知应用程序。进程中无需关心I/O何时完成也不需要轮询。

3.1 阻塞 I/O

在这里插入图片描述
阻塞I/O(Blocking I/O)的详细过程描述如下:

  1. 进程发起调用:

    • 当用户进程需要从网络套接字或磁盘等设备读取数据时,它会调用一个系统级别的阻塞I/O函数,调用 revcfrom函数来系统调用。
  2. 内核等待数据准备:

    • 当所请求的数据尚未准备好(如网络数据包还未到达,或磁盘上的数据还未加载到内存缓冲区),那么该进程会被操作系统挂起(阻塞),即暂时停止执行。
  3. 数据准备阶段:

    • 内核在此期间负责等待数据准备完成。对于网络 I/O 来说,这可能涉及到接收完整的数据包、处理网络协议栈、填充内核缓冲区等步骤。
  4. 数据复制:

    • 当所需的数据完全准备好并存在于内核缓冲区中后,内核开始将数据从内核空间复制到用户空间,即将数据从内核的缓冲区复制到应用程序提供的缓冲区。
  5. 唤醒进程:

    • 数据复制完成后,内核解除对该进程的阻塞状态,并返回到用户态。此时,调用 I/O 操作的函数会返回,通常表示成功读取了数据以及读取了多少字节。
  6. 进程继续执行:

    • 进程现在可以访问已读取的数据,并进行后续处理。与此同时,如果还有更多的 I/O 操作需要执行,则该进程将继续发出新的 I/O 请求,整个过程可能会再次进入阻塞状态直到数据准备好。

总结来说,在阻塞 I/O 模型下,进程在等待 I/O 操作完成期间不能执行其他任务,这种模式简单直观,但当 I/O 操作耗时较长时,会导致进程效率低下。在高并发和实时性要求较高的场景下,不建议使用阻塞 I/O 模型。

3.2 非阻塞 I/O

在这里插入图片描述
非阻塞I/O(Non-Blocking I/O)的详细过程描述如下:

  1. 进程发起调用:

    • 当用户进程尝试从一个非阻塞套接字读取数据时,它会调用相应的非阻塞 I/O 函数,调用 revcfrom 函数来系统调用。
  2. 内核处理请求:

    • 如果所请求的数据还未准备好(即没有数据可读),而非阻塞模式下,内核不会等待数据准备完成,而是立即返回一个错误代码,例如在Linux中通常会是EAGAINEWOULDBLOCK,表示当前无数据可读。
  3. 轮询与事件循环:

    • 进程收到错误码后,知道此时无法读取到数据,但它并不会被挂起或阻塞。相反,它会通过循环不断地重新尝试读取操作,或者切换执行其他任务,然后在一段时间后再次检查该文件描述符是否可以进行读取。
  4. 数据准备阶段:

    • 同时,内核在后台继续处理网络数据包接收、磁盘读取等操作,一旦数据准备好,内核将更新其内部状态。
  5. 读取数据:

    • 当进程再次尝试读取时,如果数据已经就绪,那么这次调用将会成功,并且数据会被复制到用户空间缓冲区中。

总结来说,在非阻塞 I/O 模型下,进程不会因 I/O 操作而阻塞,而是不断轮询以检测 I/O 事件的状态,当事件发生时才执行实际的 I/O 操作。这种模式能够提高并发处理能力,但也增加了编程复杂度,因为它要求应用开发者自行处理事件循环和轮询逻辑。

4. 参考

《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》
《redis 从0到1完整学习 (六):Hash 表数据结构》
《redis 从0到1完整学习 (七):ZipList 数据结构》
《redis 从0到1完整学习 (八):QuickList 数据结构》
《redis 从0到1完整学习 (九):SkipList 数据结构》
《redis 从0到1完整学习 (十):RedisObject 数据结构》
《redis 从0到1完整学习 (十一):RedisObject 之 String 类型》
《redis 从0到1完整学习 (十二):RedisObject 之 List 类型》
《redis 从0到1完整学习 (十三):RedisObject 之 Set 类型》
《redis 从0到1完整学习 (十四):RedisObject 之 ZSet 类型》
《redis 从0到1完整学习 (十五):RedisObject 之 Hash 类型》
《redis 从0到1完整学习 (十六):内存回收之 key 过期处理策略》
《redis 从0到1完整学习 (十七):内存回收之内存淘汰策略》

欢迎关注本人,我是喜欢搞事的程序猿; 一起进步,一起学习;

也欢迎关注我的wx公众号:一个比特定乾坤

文章来源:https://blog.csdn.net/qq_36803941/article/details/135604564
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。