You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

135 lines
9.7 KiB
Markdown

2 years ago
# 32 | 答疑(四):阻塞、非阻塞 I/O 与同步、异步 I/O 的区别和联系
你好,我是倪朋飞。
专栏更新至今,四大基础模块的第三个模块——文件系统和磁盘 I/O 篇,我们就已经学完了。很开心你还没有掉队,仍然在积极学习思考和实践操作,并且热情地留言与讨论。
今天是性能优化的第四期。照例,我从 I/O 模块的留言中摘出了一些典型问题,作为今天的答疑内容,集中回复。同样的,为了便于你学习理解,它们并不是严格按照文章顺序排列的。
每个问题,我都附上了留言区提问的截屏。如果你需要回顾内容原文,可以扫描每个问题右下方的二维码查看。
## 问题1阻塞、非阻塞 I/O 与同步、异步 I/O 的区别和联系
![](https://static001.geekbang.org/resource/image/1c/b0/1c3237118d1c55792ac0d9cc23f14bb0.png)
在[文件系统的工作原理](https://time.geekbang.org/column/article/76876)篇中,我曾经介绍了阻塞、非阻塞 I/O 以及同步、异步 I/O 的含义,这里我们再简单回顾一下。
首先我们来看阻塞和非阻塞 I/O。根据应用程序是否阻塞自身运行可以把 I/O 分为阻塞 I/O 和非阻塞 I/O。
* 所谓阻塞I/O是指应用程序在执行I/O操作后如果没有获得响应就会阻塞当前线程不能执行其他任务。
* 所谓非阻塞I/O是指应用程序在执行I/O操作后不会阻塞当前的线程可以继续执行其他的任务。
再来看同步 I/O 和异步 I/O。根据 I/O 响应的通知方式的不同,可以把文件 I/O 分为同步 I/O 和异步 I/O。
* 所谓同步 I/O是指收到 I/O 请求后,系统不会立刻响应应用程序;等到处理完成,系统才会通过系统调用的方式,告诉应用程序 I/O 结果。
* 所谓异步 I/O是指收到 I/O 请求后,系统会先告诉应用程序 I/O 请求已经收到,随后再去异步处理;等处理完成后,系统再通过事件通知的方式,告诉应用程序结果。
你可以看出,阻塞/非阻塞和同步/异步,其实就是两个不同角度的 I/O 划分方式。它们描述的对象也不同,阻塞/非阻塞针对的是 I/O 调用者(即应用程序),而同步/异步针对的是 I/O 执行者(即系统)。
我举个例子来进一步解释下。比如在 Linux I/O 调用中,
* 系统调用 read 是同步读所以在没有得到磁盘数据前read 不会响应应用程序。
* 而 aio\_read 是异步读,系统收到 AIO 读请求后不等处理就返回了,而具体的 read 结果,再通过回调异步通知应用程序。
再如,在网络套接字的接口中,
* 使用 send() 直接向套接字发送数据时,如果套接字没有设置 O\_NONBLOCK 标识,那么 send() 操作就会一直阻塞,当前线程也没法去做其他事情。
* 当然,如果你用了 epoll系统会告诉你这个套接字的状态那就可以用非阻塞的方式使用。当这个套接字不可写的时候你可以去做其他事情比如读写其他套接字。
## 问题2“文件系统”课后思考
![](https://static001.geekbang.org/resource/image/40/a6/40c924ea4b11e12d6d34181a00f292a6.jpg)
在[文件系统原理](https://time.geekbang.org/column/article/76876)[文章](https://time.geekbang.org/column/article/76876)的最后,我给你留了一道思考题,那就是执行 find 命令时,会不会导致系统的缓存升高呢?如果会导致,升高的又是哪种类型的缓存呢?
关于这个问题,白华和 coyang 的答案已经很准确了。通过学习Linux 文件系统的原理我们知道文件名以及文件之间的目录关系都放在目录项缓存中。而这是一个基于内存的数据结构会根据需要动态构建。所以查找文件时Linux 就会动态构建不在缓存中的目录项结构,导致 dentry 缓存升高。
![](https://static001.geekbang.org/resource/image/48/c5/488110263a9c7ff801a3e04c010f0bc5.png)![](https://static001.geekbang.org/resource/image/57/58/57e4cf5a42a91392ebebf106f992a858.png)
事实上除了目录项缓存增加Buffer 的使用也会增加。如果你用 vmstat 观察一下,会发现 Buffer 和 Cache 都在增长:
```
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 1 0 7563744 6024 225944 0 0 3736 0 574 3249 3 5 89 3 0
1 0 0 7542792 14736 236856 0 0 8708 0 13494 32335 8 19 66 7 0
0 1 0 7494452 27280 272284 0 0 12544 0 4550 17084 5 15 68 13 0
0 1 0 7475084 42380 276320 0 0 15096 0 2541 14253 2 6 78 13 0
0 1 0 7455728 57600 280436 0 0 15220 0 2025 14518 2 6 70 22 0
```
这里Buffer 的增长是因为,构建目录项缓存所需的元数据(比如文件名称、索引节点等),需要从文件系统中读取。
## 问题3“磁盘 I/O 延迟”课后思考
在[磁盘 I/O 延迟案例](https://time.geekbang.org/column/article/78409)的最后,我给你留了一道思考题。
我们通过 iostat ,确认磁盘 I/O 已经出现了性能瓶颈,还用 pidstat 找出了大量磁盘 I/O 的进程。但是,随后使用 strace 跟踪这个进程,却找不到任何 write 系统调用。这是为什么呢?
![](https://static001.geekbang.org/resource/image/64/09/6408b3aa2aa9a98a930d1a5b2e2fef09.jpg)
很多同学的留言都准确回答了这个问题。比如,划时代和 jeff 的留言都指出,在这个场景中,我们需要加 -f 选项,以便跟踪多进程和多线程的系统调用情况。
![](https://static001.geekbang.org/resource/image/e4/55/e4e9a070022f7b49cb8d5554b9a60055.png)![](https://static001.geekbang.org/resource/image/71/05/71a6df4144ce59d9e1a01c26453acf05.png)
你看,仅仅是不恰当的选项,都可能会导致性能工具“犯错”,呈现这种看起来不合逻辑的结果。非常高兴看到,这么多同学已经掌握了性能工具使用的核心思路——弄清楚工具本身的原理和问题。
## 问题4“MySQL 案例”课后思考
在 [MySQL 案例](https://time.geekbang.org/column/article/78633)的最后,我给你留了一个思考题。
为什么 DataService 应用停止后即使仍没有索引MySQL 的查询速度还是快了很多,并且磁盘 I/O 瓶颈也消失了呢?
![](https://static001.geekbang.org/resource/image/92/78/924fbc974313b1e0fe6b8d14e7a44178.png)
ninuxer 的留言基本解释了这个问题,不过还不够完善。
事实上,当你看到 DataService 在修改 _/proc/sys/vm/drop\_caches_ 时,就应该想到前面学过的 Cache 的作用。
我们知道,案例应用访问的数据表,基于 MyISAM 引擎,而 MyISAM 的一个特点,就是只在内存中缓存索引,并不缓存数据。所以,在查询语句无法使用索引时,就需要数据表从数据库文件读入内存,然后再进行处理。
所以,如果你用 vmstat 工具,观察缓存和 I/O 的变化趋势,就会发现下面这样的结果:
```
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
# 备注: DataService正在运行
0 1 0 7293416 132 366704 0 0 32516 12 36 546 1 3 49 48 0
0 1 0 7260772 132 399256 0 0 32640 0 37 463 1 1 49 48 0
0 1 0 7228088 132 432088 0 0 32640 0 30 477 0 1 49 49 0
0 0 0 7306560 132 353084 0 0 20572 4 90 574 1 4 69 27 0
0 2 0 7282300 132 368536 0 0 15468 0 32 304 0 0 79 20 0
# 备注DataService从这里开始停止
0 0 0 7241852 1360 424164 0 0 864 320 133 1266 1 1 94 5 0
0 1 0 7228956 1368 437400 0 0 13328 0 45 366 0 0 83 17 0
0 1 0 7196320 1368 470148 0 0 32640 0 33 413 1 1 50 49 0
...
0 0 0 6747540 1368 918576 0 0 29056 0 42 568 0 0 56 44 0
0 0 0 6747540 1368 918576 0 0 0 0 40 141 1 0 100 0 0
```
在 DataService 停止前cache 会连续增长三次后再降回去,这正是因为 DataService 每隔3秒清理一次页缓存。而 DataService 停止后cache 就会不停地增长,直到增长为 918576 后,就不再变了。
这时磁盘的读bi降低到 0同时iowaitwa也降低到 0这说明此时的所有数据都已经在系统的缓存中了。我们知道缓存是内存的一部分它的访问速度比磁盘快得多这也就能解释为什么 MySQL 的查询速度变快了很多。
从这个案例你会发现MySQL 的 MyISAM 引擎,本身并不缓存数据,而要依赖系统缓存来加速磁盘 I/O 的访问。一旦系统中还有其他应用同时运行MyISAM 引擎就很难充分利用系统缓存。因为系统缓存可能被其他应用程序占用,甚至直接被清理掉。
所以,一般来说,我并不建议,把应用程序的性能优化完全建立在系统缓存上。还是那句话,最好能在应用程序的内部分配内存,构建完全自主控制的缓存,比如 MySQL 的 InnoDB 引擎,就同时缓存了索引和数据;或者,可以使用第三方的缓存应用,比如 Memcached、Redis 等。
今天主要回答这些问题,同时也欢迎你继续在留言区写下疑问和感想,我会持续不断地解答。希望借助每一次的答疑,可以和你一起,把文章知识内化为你的能力,我们不仅在实战中演练,也要在交流中进步。