重大事故!IO问题引发线上20台机器同时崩溃( 二 )


如下图:

重大事故!IO问题引发线上20台机器同时崩溃

文章插图
 
在 DMA 模式下执行 IO 操作是不占用 CPU 的 , 所以 CPU IO 等待(上图的wa)实际上属于 CPU 空闲率的一部分 。
所以我们执行 top 命令时 , 除了要关注 CPU 空闲率 , CPU 使用率(us , sy) , 还要关注 IO Wait(wa) 。注意 , wa 只代表磁盘 IO Wait , 不包括网络 IO Wait 。
JAVA 中线程状态和 IO 的关系
当我们用 jstack 查看 Java 线程状态时 , 会看到各种线程状态 。当发生 IO 等待时(比如远程调用时) , 线程是什么状态呢 , Blocked 还是 Waiting?
答案是 Runnable 状态 , 是不是有些出乎意料!实际上 , 在操作系统层面 Java 的 Runnable 状态除了包括 Running 状态 , 还包括 Ready(就绪状态 , 等待 CPU 调度)和 IO Wait 等状态 。
重大事故!IO问题引发线上20台机器同时崩溃

文章插图
 
 
如上图 , Runnable 状态的注解明确说明了 , 在 JVM 层面执行的线程 , 在操作系统层面可能在等待其他资源 。
如果等待的资源是 CPU , 在操作系统层面线程就是等待被 CPU 调度的 Ready 状态;如果等待的资源是磁盘网卡等 IO 资源 , 在操作系统层面线程就是等待 IO 操作完成的 IO Wait 状态 。
有人可能会问 , 为什么 Java 线程没有专门的 Running 状态呢?
【重大事故!IO问题引发线上20台机器同时崩溃】目前绝大部分主流操作系统都是以时间分片的方式对任务进行轮询调度 , 时间片通常很短 , 大概几十毫秒 。
也就是说一个线程每次在 CPU 上只能执行几十毫秒 , 然后就会被 CPU 调度出来变成 Ready 状态 , 等待再一次被 CPU 执行 , 线程在 Ready 和 Running 两个状态间快速切换 。
通常情况 , JVM 线程状态主要为了监控使用 , 是给人看的 。当你看到线程状态是 Running 的一瞬间 , 线程状态早已经切换 N 次了 。所以 , 再给线程专门加一个 Running 状态也就没什么意义了 。
深入理解网络 IO 模型
5 种 linux 网络 IO 模型包括:
  • 同步阻塞 IO
  • 同步非阻塞 IO
  • 多路复用 IO
  • 信号驱动 IO
  • 异步 IO
为了更好地理解网络 IO 模型 , 我们先了解几个基本概念:
①Socket(套接字):Socket 可以理解成 , 在两个应用程序进行网络通信时 , 分别在两个应用程序中的通信端点 。
通信时 , 一个应用程序将数据写入 Socket , 然后通过网卡把数据发送到另外一个应用程序的 Socket 中 。
我们平常所说的 HTTP 和 TCP 协议的远程通信 , 底层都是基于 Socket 实现的 。5 种网络 IO 模型也都要基于 Socket 实现网络通信 。
②阻塞与非阻塞:所谓阻塞 , 就是发出一个请求不能立刻返回响应 , 要等所有的逻辑全处理完才能返回响应 。
非阻塞反之 , 发出一个请求立刻返回应答 , 不用等处理完所有逻辑 。
③内核空间与用户空间:在 Linux 中 , 应用程序稳定性远远比不上操作系统程序 , 为了保证操作系统的稳定性 , Linux 区分了内核空间和用户空间 。
可以这样理解 , 内核空间运行操作系统程序和驱动程序 , 用户空间运行应用程序 。
Linux 以这种方式隔离了操作系统程序和应用程序 , 避免了应用程序影响到操作系统自身的稳定性 。
这也是 Linux 系统超级稳定的主要原因 。所有的系统资源操作都在内核空间进行 , 比如读写磁盘文件 , 内存分配和回收 , 网络接口调用等 。
所以在一次网络 IO 读取过程中 , 数据并不是直接从网卡读取到用户空间中的应用程序缓冲区 , 而是先从网卡拷贝到内核空间缓冲区 , 然后再从内核拷贝到用户空间中的应用程序缓冲区 。
对于网络 IO 写入过程 , 过程则相反 , 先将数据从用户空间中的应用程序缓冲区拷贝到内核缓冲区 , 再从内核缓冲区把数据通过网卡发送出去 。
同步阻塞 IO
我们先看一下传统阻塞 IO 。在 Linux 中 , 默认情况下所有 Socket 都是阻塞模式的 。


推荐阅读