@@ -788,6 +788,9 @@ dd读取文件,首次慢,但是第二次快,可以通过以上命令查看
788
788
789
789
790
790
# # 文件系统
791
+
792
+ ! [[Pasted image 20250630092025.png]]
793
+
791
794
磁盘为系统提供了最基本的持久化存储。
792
795
文件系统则在磁盘的基础上,提供了一个用来管理文件的树状结构
793
796
# ## 文件系统原理
@@ -843,6 +846,18 @@ VFS 定义了一组所有文件系统都支持的数据结构和标准接口。
843
846
# ### 文件系统缓存
844
847
# ### 文件系统种类
845
848
# ## 性能指标
849
+
850
+ # ### 从文件系统和磁盘 I/O 的性能指标出发
851
+
852
+ ! [[Pasted image 20250630092707.png]]
853
+
854
+ - lsblk
855
+
856
+
857
+ # ### 从工具出发
858
+
859
+ ! [[Pasted image 20250630092729.png]]
860
+
846
861
# ### 容量
847
862
` ` ` bash
848
863
# -h 人类可读形式展示
@@ -852,20 +867,84 @@ df -i /dev/sfa1
852
867
` ` `
853
868
索引节点的容量,(也就是 Inode 个数)是在格式化磁盘时设定好的,一般由格式化工具自动生成。当你发现索引节点空间不足,但磁盘空间充足时,很可能就是过多小文件导致的。
854
869
所以,一般来说,删除这些小文件,或者把它们移动到索引节点充足的其他磁盘中,就可以解决这个问题。
855
- # ### IOPS
856
- # ### 缓存命中率
870
+
871
+ # ### 阻塞、非阻塞I/O与同步、异步I/O的区别和联系
872
+ 首先我们来看阻塞和非阻塞 I/O。根据应用程序是否阻塞自身运行,可以把 I/O 分为阻塞I/O 和非阻塞 I/O。
873
+ - 所谓阻塞 I/O,是指应用程序在执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程,不能执行其他任务
874
+ - 所谓非阻塞 I/O,是指应用程序在执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务。
875
+
876
+ 再来看同步 I/O 和异步 I/O。根据 I/O 响应的通知方式的不同,可以把文件 I/O 分为同步I/O 和异步 I/O。
877
+ - 所谓同步 I/O,是指收到 I/O 请求后,系统不会立刻响应应用程序;等到处理完成,系统才会通过系统调用的方式,告诉应用程序 I/O 结果。
878
+ - 所谓异步 I/O,是指收到 I/O 请求后,系统会先告诉应用程序 I/O 请求已经收到,随后再去异步处理;等处理完成后,系统再通过事件通知的方式,告诉应用程序结果
879
+
857
880
# ## 性能剖析
858
- # ### df
859
- # ### strace
860
- # ### vmstat
861
- # ### sar
862
- # ### perf
863
- # ### proc文件系统
881
+ 文件系统中的文件最终是要进行落盘的,最容易想到的是存储空间的使用情况,包括容量、使用量以及剩余空间等。还有一个容易忽略的是索引节点的使用情况,它也包括容量、使用量以及剩余量等三个指标。如果文件系统中存储过多的小文件,就可能碰到
882
+ 索引节点容量已满的问题。
883
+
884
+ 其次,你应该想到的是前面多次提到过的缓存使用情况,包括页缓存、目录项缓存、索引节点缓存以及各个具体文件系统(如 ext4、XFS 等)的缓存。这些缓存会使用速度更快的内存,用来临时存储文件数据或者文件系统的元数据,从而可以减少访问慢速磁盘的次数。
885
+
886
+ 除了以上这两点,文件 I/O 也是很重要的性能指标,包括 IOPS(包括 r/s 和 w/s)、响应时间(延迟)以及吞吐量(B/s)等。在考察这类指标时,通常还要考虑实际文件的读写情况。比如,结合文件大小、文件数量、I/O 类型等,综合分析文件 I/O 的性能。
887
+
888
+ ` ` ` bash
889
+ # 随机读
890
+ fio -name=randread -direct=1 -iodepth=64 -rw=randread -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
891
+ # 随机写
892
+ fio -name=randwrite -direct=1 -iodepth=64 -rw=randwrite -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
893
+ # 顺序读
894
+ fio -name=read -direct=1 -iodepth=64 -rw=read -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
895
+ # 顺序写
896
+ fio -name=write -direct=1 -iodepth=64 -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1 -runtime=1000 -group_reporting -filename=/dev/sdb
897
+ ` ` `
898
+ direct,表示是否跳过系统缓存。上面示例中,我设置的 1 ,就表示跳过系统缓存。
899
+
900
+ iodepth,表示使用异步 I/O(asynchronous I/O,简称 AIO)时,同时发出的 I/O 请求上限。在上面的示例中,我设置的是 64。
901
+
902
+ rw,表示 I/O 模式。我的示例中, read/write 分别表示顺序读 / 写,而 randread/randwrite 则分别表示随机读 / 写。
903
+
904
+ ioengine,表示 I/O 引擎,它支持同步(sync)、异步(libaio)、内存映射(mmap)、网络(net)等各种 I/O 引擎。上面示例中,我设置的 libaio 表示使用异步 I/O。
905
+
906
+ bs,表示 I/O 的大小。示例中,我设置成了 4K(这也是默认值)。
907
+
908
+ filename,表示文件路径,当然,它可以是磁盘路径(测试磁盘性能),也可以是文件路径(测试文件系统性能)。示例中,我把它设置成了磁盘 /dev/sdb。不过注意,用磁盘路径测试写,会破坏这个磁盘中的文件系统,所以在使用前,你一定要事先做好数据备份。
909
+
910
+
864
911
# ## 调优方法
865
- # ### 文件系统选型
866
- # ### 利用文件系统缓存
867
- # ### I/O隔离
868
912
913
+ ! [[Pasted image 20250630093928.png]]
914
+
915
+ 第一,在文件系统的原理中,我介绍了查看文件系统容量的工具 df。它既可以查看文件系统数据的空间容量,也可以查看索引节点的容量。至于文件系统缓存,我们通过/proc/meminfo、/proc/slabinfo 以及 slabtop 等各种来源,观察页缓存、目录项缓存、索引节点缓存以及具体文件系统的缓存情况
916
+
917
+ 第二,在磁盘 I/O 的原理中,我们分别用 iostat 和 pidstat 观察了磁盘和进程的 I/O 情况。它们都是最常用的 I/O 性能分析工具。通过 iostat ,我们可以得到磁盘的 I/O 使用率、吞吐量、响应时间以及 IOPS 等性能指标;而通过 pidstat ,则可以观察到进程的 I/O吞吐量以及块设备 I/O 的延迟等。
918
+
919
+ 第三,在狂打日志的案例中,我们先用 top 查看系统的 CPU 使用情况,发现 iowait 比较高;然后,又用 iostat 发现了磁盘的 I/O 使用率瓶颈,并用 pidstat 找出了大量 I/O 的进程;最后,通过 strace 和 lsof,我们找出了问题进程正在读写的文件,并最终锁定性能问题的来源——原来是进程在狂打日志。
920
+
921
+ 第四,在磁盘 I/O 延迟的单词热度案例中,我们同样先用 top、iostat ,发现磁盘有 I/O瓶颈,并用 pidstat 找出了大量 I/O 的进程。可接下来,想要照搬上次操作的我们失败了。在随后的 strace 命令中,我们居然没看到 write 系统调用。于是,我们换了一个思路,用新工具 filetop 和 opensnoop ,从内核中跟踪系统调用,最终找出瓶颈的来源。
922
+
923
+ 最后,同样的思路,我们先用 top、iostat 以及 pidstat ,确定并找出 I/O 性能问题的瓶颈来源,它们正是 mysqld 和 redis-server。随后,我们又用 strace+lsof 找出了它们正在读写的文件。
924
+
925
+ # ### 应用程序优化
926
+ 应用程序处于整个 I/O 栈的最上端,它可以通过系统调用,来调整 I/O 模式(如顺序还是随机、同步还是异步), 同时,它也是 I/O 数据的最终来源。在我看来,可以有这么几种方式来优化应用程序的 I/O 性能。
927
+
928
+ 第一,可以用追加写代替随机写,减少寻址开销,加快 I/O 写的速度。
929
+ 第二,可以借助缓存 I/O ,充分利用系统缓存,降低实际 I/O 的次数。
930
+ 第三,可以在应用程序内部构建自己的缓存,或者用 Redis 这类外部缓存系统。这样,一方面,能在应用程序内部,控制缓存的数据和生命周期;另一方面,也能降低其他应用程序使用缓存对自身的影
931
+
932
+ C 标准库提供的 fopen、fread 等库函数,都会利用标准库的缓存,减少磁盘的操作。而你直接使用 open、read 等系统调用时,就只能利用操作系统提供的页缓存和缓冲区等,而没有库函数的缓存可用。
933
+
934
+ 第四,在需要频繁读写同一块磁盘空间时,可以用 mmap 代替 read/write,减少内存的拷贝次数。
935
+ 第五,在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync () 取代 O_SYNC。
936
+ 第六,在多个应用程序共享相同磁盘时,为了保证 I/O 不被某个应用完全占用,推荐你使用 cgroups 的 I/O 子系统,来限制进程 / 进程组的 IOPS 以及吞吐量。
937
+ 最后,在使用 CFQ 调度器时,可以用 ionice 来调整进程的 I/O 调度优先级,特别是提高核心应用的 I/O 优先级。ionice 支持三个优先级类:Idle、Best-effort 和 Realtime。其中, Best-effort 和 Realtime 还分别支持 0-7 的级别,数值越小,则表示优先级别越高。
938
+
939
+ # ### 文件系统优化
940
+
941
+ 应用程序访问普通文件时,实际是由文件系统间接负责,文件在磁盘中的读写。所以,跟文件系统中相关的也有很多优化 I/O 性能的方式。
942
+
943
+ 第一,你可以根据实际负载场景的不同,选择最适合的文件系统。比如 Ubuntu 默认使用ext4 文件系统,而 CentOS 7 默认使用 xfs 文件系统。
944
+ 第二,在选好文件系统后,还可以进一步优化文件系统的配置选项,包括文件系统的特性(如 ext_attr、dir_index)、日志模式(如 journal、ordered、writeback)、挂载选项(如 noatime)等等。
945
+ 第三,可以优化文件系统的缓存
946
+ 比如,你可以优化 pdflush 脏页的刷新频率(比如设置 dirty_expire_centisecs 和dirty_writeback_centisecs)以及脏页的限额(比如调整 dirty_background_ratio 和dirty_ratio 等)。
947
+ 最后,在不需要持久化时,你还可以用内存文件系统 tmpfs,以获得更好的 I/O 性能 。tmpfs 把数据直接保存在内存中,而不是磁盘中。比如 /dev/shm/ ,就是大多数 Linux 默认配置的一个内存文件系统,它的大小默认为总内存的一半。
869
948
870
949
# # 磁盘I/O
871
950
磁盘是可以持久化存储的设备,根据存储介质的不同,常见磁盘可以分为两类:机械磁盘(Hard Disk Driver)和固态磁盘(Solid State Disk)。
@@ -902,9 +981,6 @@ df -i /dev/sfa1
902
981
903
982
通用块层是 Linux 磁盘 I/O 的核心。向上,它为文件系统和应用程序,提供访问了块设备的标准接口;向下,把各种异构的磁盘设备,抽象为统一的块设备,并会对文件系统和应用程序发来的 I/O 请求,进行重新排序、请求合并等,提高了磁盘访问的效率。
904
983
905
- # ### 磁盘管理
906
- # ### 磁盘类型
907
- # ### 磁盘接口
908
984
# ### 磁盘I/O栈
909
985
我们可以把 Linux 存储系统的 I/O 栈,由上到下分为三个层次,分别是文件系统层、通用块层和设备层。这三个 I/O 层的关系如下图所示,这其实也是 Linux 存储系统的 I/O 栈全景图。
910
986
! [[Pasted image 20250627182100.png]]
@@ -986,28 +1062,22 @@ TID PRIO USER DISK READ DISK WRITE SWAPIN IO>
986
1062
` ` `
987
1063
988
1064
989
- # ### 使用率
990
- # ### IOPS
991
- # ### 吞吐量
992
- # ### IOWAIT
993
1065
# ## 性能剖析
994
- # ### dstat
995
- # ### sar
996
- # ### iostat
997
- # ### pidstat
998
- # ### iotop
999
- # ### iolatency
1000
- # ### blktrace
1001
- # ### fio
1002
- # ### perf
1003
1066
1004
1067
# ## 调优方法
1005
- # ### 系统调用
1006
- # ### I/O资源控制
1007
- # ### 充分利用缓存
1008
- # ### RAID
1009
- # ### I/O隔离
1010
1068
1069
+ # ### 磁盘优化
1070
+ 数据的持久化存储,最终还是要落到具体的物理磁盘中,同时,磁盘也是整个 I/O 栈的最底层。
1071
+ 第一,最简单有效的优化方法,就是换用性能更好的磁盘,比如用 SSD 替代 HDD。
1072
+ 第二,我们可以使用 RAID ,把多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列。这样做既可以提高数据的可靠性,又可以提升数据的访问性能。
1073
+ 第三,针对磁盘和应用程序 I/O 模式的特征,我们可以选择最适合的 I/O 调度算法。比方说,SSD 和虚拟机中的磁盘,通常用的是 noop 调度算法。而数据库应用,我更推荐使用deadline 算法。
1074
+ 第四,我们可以对应用程序的数据,进行磁盘级别的隔离。比如,我们可以为日志、数据库等 I/O 压力比较重的应用,配置单独的磁盘。
1075
+
1076
+ 第五,在顺序读比较多的场景中,我们可以增大磁盘的预读数据,比如,你可以通过下面两种方法,调整 /dev/sdb 的预读大小。
1077
+ - 调整内核选项 /sys/block/sdb/queue/read_ahead_kb,默认大小是 128 KB,单位为KB。
1078
+ - 使用 blockdev 工具设置,比如 blockdev --setra 8192 /dev/sdb,注意这里的单位是512B(0.5KB),所以它的数值总是 read_ahead_kb 的两倍。
1079
+ 第六,我们可以优化内核块设备 I/O 的选项。比如,可以调整磁盘队列的长度/sys/block/sdb/queue/nr_requests,适当增大队列长度,可以提升磁盘的吞吐量(当然也会导致 I/O 延迟增大)
1080
+ 最后,要注意,磁盘本身出现硬件错误,也会导致 I/O 性能急剧下降,所以发现磁盘性能急剧下降时,你还需要确认,磁盘本身是不是出现了硬件错误。
1011
1081
1012
1082
# # 网络
1013
1083
# ## 网络原理
0 commit comments