基本原理

应用层I/O操作

是否利用标准库缓存

非缓冲I/O

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
printf("pid=%d\n", getpid());
char *p = "0123456789\n";

int fd, ret;
fd = open("./test.txt", O_RDWR|O_CREAT|O_APPEND);
if(fd == -1) {
perror("open");
return -1;
}
ret = write(fd, p, strlen(p));
if(ret == -1) {
perror("ret");
return -1;
}
fsync(fd);
close(fd);
return 0;
}

# build
gcc -o file file.c

系统调用提供,如:open, read, write, close等

缓冲I/O

c语言标准输入输出库提供,如:fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs等

是否利用内核里的页缓存
open(2) O_DIRECT

  • 直接I/O

跳过操作系统的文件缓存,直接跟文件系统交互来访问文件

  • 接I/O

需要经过操作系统的文件缓存

阻塞自身运行
open(2) O_NONBLOCK

  • 阻塞I/O

  • 非阻塞I/O

** 连续IO和随机IO**

  • 连续IO

read->read->read
write->write->write

  • 随机IO

lseek->read
lseek->write

虚拟文件系统

文件缓存

inode缓存

dentry缓存

I/O内核架构

Storage Stack

通用块设备层

通用块设备层,包括块设备 I/O 队列和 I/O 调度器。它会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发送给下一级的设备层。

I/O队列

对文件系统的 I/O 请求进行排队,再通过【重新排序】和【请求合并】,然后把请求发送到块设备驱动层。

调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. I/O调度指对I/O请求进行排序的过程。Linux 内核支持四种 I/O 调度算法,分别是 NONE、NOOP、CFQ 以及 DeadLine。

2. 查看和修改某个块设备的IO调度器:
$ cat /sys/block/sda/queue/scheduler
noop deadline [cfq]

NONE
完全不使用任何 I/O 调度器,常用在虚拟机中。

NOOP(电梯式调度程序)
它实际上是一个先入先出的队列,只做一些最基本的请求合并(类似电梯算法),常用于闪存、SSD等存储。 NOOP倾向饿死读而利于写!

CFQ(完全公平排队I/O调度器)
CFQ是现在很多发行版的默认 I/O 调度器,它为【每个进程】维护了一个 I/O 调度队列,并按照时间片来均匀分布每个进程的 I/O 请求。 类似于进程 CPU 调度,CFQ 还支持进程 I/O 的优先级调度。 CFQ试图均匀地分布对I/O带宽的访问,避免进程被饿死并实现较低的延迟。

DeadLine(截止时间调度程序)
DeadLine 调度算法,分别为读、写请求创建了不同的 I/O 队列,可以提高机械磁盘的吞吐量。DeadLine 调度算法,多用在 I/O 压力比较重的场景,比如数据库等。

块设备驱动层

负责最终物理设备的I/O操作

HDD

/blog.csdn.net/qyxls/article/details/117322123

寻道时间

旋转时间

数据传输时间

SSD

性能指标

磁盘用量、剩余等

相关工具

df -h

inode节点用量、剩余等

inode节点空间不足,但是磁盘空间充足,可能是过多小文件造成的!

相关工具

df -i

测试: # mount -t tmpfs -o size=1G tmpfs /tftpboot/minio

使用率:磁盘处理I/O的时间占比

相关工具

iostat -d -x -p sda(%util)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
【指标解释】
r/s:设备每秒完成的读请求数(合并后);
w/s:设备每秒完成的写请求数(合并后);
rkB/s:每秒从磁盘读取的数据量;
wkB/s:每秒向磁盘写入的数据量;
rrqm/s:每秒排队到设备上的合并的读请求数;
wrqm/s:每秒排队到设备上的合并的写请求数;
r_await:读请求处理完成等待时间,包括队列中等待时间和设备实际处理所花的时间,单位是毫秒
w_await:写请求处理完成等待时间,包括队列中等待时间和设备实际处理所花的时间,单位是毫秒
aqu-sz:平均请求队列长度
rareq-sz:读请求的平均数据量大小(单位是KB)
wareq-sz:写请求的平均数据量大小(单位是KB)
svctm:处理I/O请求所需的平均时间(单位是毫秒),该指标不准,后续会移除。
%util:磁盘处理I/O的时间百分比

【几个重要的指标】
磁盘 I/O 使用率 --> %util
每秒I/O读请求数 --> r/s
每秒I/O写请求数 --> w/s
每秒I/O读请求大小 --> rkB/s
每秒I/O写请求大小 --> wkB/s
读响应时间 --> r_await
写响应时间 --> w_await
  • 局限

对于RAID和SSD,该指标不能反映其真实性能

每秒的I/O请求数(读、写)

iostat -d -x -p sda (r/s, w/s)

dstat -r

每秒的I/O请求大小(读、写)

iostat -d -x -p sda(rkB/s, wkB/s)

dstat -d

响应时间:I/O 请求从发出到收到响应的间隔时间(读、写)

相关工具

iostat -d -x -p sda(r_await,w_await)

进程I/O大小或者I/O延迟

相关工具

pidstat -d

1
2
3
4
5
6
7
kB_rd/s:该进程每秒从磁盘读取的数据大小

kB_wr/s:该进程每秒写入磁盘的数据大小

kB_ccwr/s:每秒取消的写请求数据大小,当任务截断一些脏页缓存时可能会发生这种情况

iodelay:块 I/O 延迟,包括等待同步块 I/O 完成时间和换入块 I/O 的时间,单位是时钟周期。

iotop
IO:进程在等待I/O上花费的时间占比(%);

biotop
AVGms:进程平均I/O时间,单位是ms

进程每次IO操作的IO延迟

相关工具

biosnoop-bpfcc -Q
LAT(ms):磁盘I/O的延迟,包括请求提交给设备到请求完成的时间

实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1、file.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
pid_t pid = getpid();
char *p = "123456789\n";
char file[128];
sprintf(file, "test-%d.log", pid);
int fd, ret;
fd = open(file, O_RDWR|O_CREAT|O_APPEND);
if(fd == -1) {
perror("open");
return -1;
}
ret = write(fd, p, strlen(p));
if(ret == -1) {
perror("ret");
return -1;
}
fsync(fd);
close(fd);
sleep(1);
return 0;
}

2、test.sh
#!/bin/bash
while :
do
./file
done

文件缓存

相关工具

查看整体:
cat /proc/meminfo | grep “^Cached”

单个文件:
pcstat /tftpboot/test

释放文件缓存:
echo 1 > /proc/sys/vm/drop_caches

dentry和inode缓存

dentry缓存
(dcache)

相关工具

cat /proc/slabinfo | grep -E ‘^#|dentry’

1
2
3
4
5
6
7
8
name:slab object对象的名称
active_objs:被申请走(正在被使用)的对象个数。
num_objs:总对象(slab object)个数。
objsize:每个对象(slab object)大小,以字节为单位。
objperslab:表示一个slab中包含多少个对象(slab object)。
pagesperslab : 一个slab占用几个page内存页。
active_slabs:活动slab个数。
num_slabs:总slab个数。

inode缓存
(icache)

相关工具

cat /proc/slabinfo | grep -E ‘^#|inode’

释放:echo 2 > /proc/sys/vm/drop_caches
前后对比查看:cat /proc/meminfo | grep SReclaimable

工具汇总

iostat

用法

1
2
3
4
5
6
-c:显示cpu指标
-d:显示磁盘使用情况
-k: 以KB为单位显示
-m:以MB为单位显示
-x:显示详细信息
-p:显示某个磁盘或者分区的使用情况

dstat

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
-m: 显示内存使用情况
-c:显示cpu使用情况
-n: 显示网络状况
-l:显示系统负载情况
-r:显示I/O请求读写请求数
-d: 磁盘读写情况
-y:显示中断和上下文切换次数等
--socket:显示套接字(tcp,udp等)的个数
--top-io: 显示消耗I/O最大的进程
--top-cpu: 显示消耗cpu最大的进程
--top-cputime: 显示使用cpu时间最大的进程(ms)
--top-latency: 显示总延迟最大的进程(ms)
--top-mem: 显示使用内存最大的进程

pidstat

用法

1
-d:统计进程的I/O大小及I/O延迟

iotop

1
2
3
4
5
6
7
8
9
- 前两行分别表示,进程的磁盘读写大小总数和磁盘真实的读写大小总数。

- TID/PID:线程ID/进程ID;
- PRIO:I/O 优先级;
- USER:用户名;
- DISK READ:每秒读磁盘的大小;
- DISK WRITE:每秒写磁盘的大小;
- SWAPIN:进程在SWAPIN上花费的时间占比;
- IO:进程在等待I/O上花费的时间占比;

用法

不加任何参数显示所有【线程】的I/O使用情况

1
2
3
4
5
6
7
8
9
- 前两行分别表示,进程的磁盘读写大小总数和磁盘真实的读写大小总数。

- TID/PID:线程ID/进程ID;
- PRIO:I/O 优先级;
- USER:用户名;
- DISK READ:每秒读磁盘的大小;
- DISK WRITE:每秒写磁盘的大小;
- SWAPIN:进程在SWAPIN上花费的时间占比;
- IO:进程在等待I/O上花费的时间占比;

ionice

用法

ionice对进程的IO调度class and priority 的设置只有当调度算法是CFQ时才是有效的。

1
2
3
4
5
-c {class} :指定调度策略
{class}表示调度策略,其中0 for none, 1 for real time, 2 for best-effort, 3 for idle
-n {classdata}:指定IO优先级别
classdata表示IO优先级级别,对于best effort和real time,classdata可以设置为0~7,0的优先级最高
-p {pid}:指定要查看或设置的进程号或者线程号

strace

用法

追踪进程的I/O系统调用

filetop

1
2
3
4
5
6
7
8
9
10
filetop实时追踪文件的读写情况。
TID:线程ID
COMM:线程命令行
READS:读取次数
WRITES:写入次数
R_Kb:读取字节数

W_Kb:写入字节数
T:文件类型
FILE:文件名称

用法

1
2
3
4
5
6
7
-a:包含非常规文件,例如:sockets, FIFOs等
-C:不清屏
-r: 最多打印多少行,默认为20
-s {all,reads,writes,rbytes,wbytes}:按指标排序,默认为rbytes
-p {pid}:只追踪{pid}这个进程的文件读写情况
每隔5秒打印一次,共打印10次
filetop 5 10

biotop

github.com/iovisor/bcc/blob/master/INSTALL.md#ubuntu—binary

用法

github.com/iovisor/bcc/blob/master/tools/biotop_example.txt

1
2
3
4
5
6
7
8
9
10
I/O:读或者写的I/O次数

Kbytes:读或者写的字节数

AVGms:平均I/O时间,单位是ms (统计的是当前时间周期的数据)

追踪进程I/O并按I/O吞吐量大小排序,默认每1秒统计一次
-C:不清屏
设置为每5秒打印一次,共打印10次:
biotop 5 10

biosnoop

用法

github.com/iovisor/bcc/blob/master/tools/biosnoop_example.txt

1
2
3
4
LAT(ms):磁盘I/O的延迟,包括请求提交给设备到请求完成的时间统计。
追踪并打印进程访问I/O时的内核事件
-Q:include OS queued time
-d {DISK}:Trace this disk only

biolatency

用法

github.com/iovisor/bcc/blob/master/tools/biolatency_example.txt

直方图的方式统计系统IO延迟(单位:us)

1
2
3
4
5
6
7
直方图的方式统计系统IO延迟(单位:us)
-T: 在输出里包含时间戳
-Q: include OS queued time in I/O time
-m: 按照ms统计系统IO延迟
-D: 分开打印各个磁盘的直方图
输出带时间戳,每秒输出一次,共输出5次:
biolatency -T 1 5

blktrace

用法

www.cnblogs.com/codelogs/p/16060775.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
第一个字段:主、次设备号;
第二个字段:cpu号;
第三个字段:序列号;
第四个字段:时间戳;
第五个字段:本次I/O对应的进程 ID;
第六个字段:Event,这个字段非常重要,反映了 I/O 进行到了哪一步;
第七个字段:R 表示 Read, W 是 Write,S表示sync;
第八个字段:223490+56,表示的是起始 block number 和 number of blocks,即我们常说的Offset 和 Size;
第九个字段:进程名字;

第六个字段Event:
A:IO被重新映射到不同的设备;
Q:将要被 request 代码处理(即将生成 I/O 请求);
G: I/O 请求(request)生成,为 I/O 分配一个 request 结构体;
P:插入I/O请求
U:准备向磁盘驱动发送该 I/O
D:IO发给driver去处理
C:IO处理完毕

采集:
blktrace -d /dev/vda1

分析:
blkparse -i vda1

例子:
blktrace -d /dev/vda1 -o - | blkparse -i -

fio

IO性能基准测试

调优实践

实践一、linux应用遇到I/O性能问题,如何一步一步进行调试?

分析过程

1
2
3
4
5
6
7
8
9
查看系统总体I/O使用情况:
iostat -d -x -p sda 1
top验证
查看所有进程的I/O使用情况,找到可疑的进程:
pidstat -d 1
追踪可疑进程(子进程)的系统调用情况:
strace -f -p {pid}
show出可疑进程的子进程树:
pstree {pid}

环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1、file.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
pid_t pid = getpid();
char *p = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfd\n";
char file[128];
sprintf(file, "test-%d.log", pid);
int fd, ret;
fd = open(file, O_RDWR|O_CREAT|O_APPEND);
if(fd == -1) {
perror("open");
return -1;
}
ret = write(fd, p, strlen(p));
if(ret == -1) {
perror("ret");
return -1;
}
fsync(fd);
close(fd);
sleep(1);
return 0;
}

gcc -o file file.c

2、exfile
#!/bin/bash
while :
do
./file
done

实践二、如何针对linux应用程序的I/O访问行为,具体分析每一步的时间开销?

环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
1、file.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
pid_t pid = getpid();
char *p = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfd\n";
char file[128];
sprintf(file, "test-%d.log", pid);
int fd, ret;
fd = open(file, O_RDWR|O_CREAT|O_APPEND);
if(fd == -1) {
perror("open");
return -1;
}
ret = write(fd, p, strlen(p));
if(ret == -1) {
perror("ret");
return -1;
}
fsync(fd);
ret = write(fd, p, strlen(p));
if(ret == -1) {
perror("ret");
return -1;
}
fsync(fd);
close(fd);
return 0;
}

2、exfile
#!/bin/bash
while :
do
./file
sleep 1
done

分析过程

查看进程实时读写情况:
biosnoop

blktrace -d /dev/vda1 -o - | blkparse -i -

Q->G:生成 I/O 请求所消耗的时间; G->P:I/O 请求进入 I/O Scheduler 所消耗的时间; P->D: I/O 请求在 I/O Scheduler 中等待的时间; D->C:I/O 请求在 Driver 和硬件上所消耗的时间,可以作为硬件性能的指标; Q->C:整个 I/O 请求所消耗的时间(Q->G + G->P + P->D + D->C = Q2C),相当于 iostat 的 await。

io操作时间戳

img

实践三、rm 删掉的文件还能找回来吗?能,不过得分情况!

实践过程

  • 创建文件:/app/test.c
1
2
3
4
5
6
7
8
9
10
vi /app/test.c
#include <stdio.h>
#include <stdlib.h>
void main(){
printf("pid %d\n", getpid());
while(1){
printf("debug\n");
sleep(1);
}
}
  • 删掉文件
    $ rm /app/test.c

  • 恢复过程1

找到正在打开/app/file.c这个文件的进程id(20904)以及该文件在上述进程里的文件描述符(3)

1
$ lsof | grep /app/test.c filekeep  20904                   root    3r      REG                8,5        449   20731531 /app/file.c (deleted)
  • 恢复过程2

恢复文件: $ cat /proc/20904/fd/3 > /app/test1.c 即可修复被误删除的文件。

  • 前提条件:filekeep
1
2
3
4
5
6
7
8
9
10
11
12
13
filekeep.c

#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE *fp = NULL;

fp = fopen("/app/test.c", "r");
sleep(600);
fclose(fp);
}

实践总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1、struct dentry
{
//..
struct inode *d_inode;//相关联的索引节点
 struct qstr d_name;//目录项名称
 struct dentry *d_parent;//父目录
//..
}

2、struct inode
{
 //...
unsigned long i_ino;//节点号
atomic_t i_count;//引用计数
unsigned int i_nlink;//硬链接数  
//...
}

3、struct file
{
//...
atomic_t f_count;//引用计数
struct path f_path;//包含目录项
struct inode *f_inode;//i节点
//...
}


文件对象(struct file)是已打开的文件在内存中的表示
由于多个进程可以打开和操作同一个文件,所以同一个文件也可能存在多个文件对象
虽然一个文件对应的文件对象不是唯一的,但是对应的索引节点(struct inode)和目录项(struct dentry)对象是唯一的
rm命令底层调用了unlinkat()函数
unlinkat():如果该文件对象是对文件的最后一个引用且没有进程正在打开这个文件,才会真正的删除文件;
否则,文件内容会依然保存在内存里,这样的文件就可以被恢复。

调优方法

应用程序优化

追加写替代随机写

充分利用缓存(包括系统缓存和标准库缓存),降低实际 I/O 的次数

可以在应用程序内部构建自己的缓存,或者用 Redis/memcached 这类外部缓存系统

在需要同步写的场景中,尽量将写请求合并,而不是让每个请求都同步写入磁盘,即可以用 fsync() 取代 O_SYNC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
wfile.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
printf("pid=%d\n", getpid());
char *p = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfdf0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyzdfd\n";
int i = 0;
int fd, len;
fd = open("./test.txt", O_RDWR|O_CREAT|O_APPEND/*|O_SYNC*/);
if(fd == -1) {
perror("open");
return -1;
}
while(i < 10) {
write(fd, p, strlen(p));
write(fd, p, strlen(p));
write(fd, p, strlen(p));
write(fd, p, strlen(p));
write(fd, p, strlen(p));
i++;
sleep(1);
}
fsync(fd);
close(fd);
return 0;
}

【总结】

当文件在open()时指定了O_SYNC选项,则意味着每个write()调用后面都隐含地跟着一个fsync()调用。

在使用 CFQ 调度器时,使用ionice调整进程的I/O调度优先级

在使用 CFQ 调度器时,可以用 ionice 来调整进程的 I/O 调度优先级,特别是提高核心应用的 I/O 优先级。 ionice 支持三个优先级类:Idle、Best-effort 和 Realtime。 其中, Best-effort 和 Realtime 还分别支持 0-7 的级别,数值越小,则表示优先级别越高。

文件系统优化

  • 优化文件系统的缓存

比如,你可以优化 pdflush 脏页的刷新频率(比如设置 dirty_expire_centisecs 和 dirty_writeback_centisecs)以及脏页的限额(比如调整 dirty_background_ratio 和 dirty_ratio 等)。 备注:以上配置都在/proc/sys/vm/目录下。

  • 优化内核回收目录项缓存和索引节点缓存的倾向

可以优化内核回收目录项缓存和索引节点缓存的倾向,即调整 vfs_cache_pressure(/proc/sys/vm/vfs_cache_pressure,默认值 100),数值越大,就表示越倾向于回收目录项缓存和索引节点缓存占用的内存。

  • 使用tmpfs,获得更好的I/O性能

磁盘文件

1
2
3
4
5
6
7
8
9
10
11
12
1、建表
spark-sql>
CREATE table diskfile
USING csv
OPTIONS (
header true,
path "/app/tools/test-tool/data/test.csv"
);

2、查询
spark-sql>
select * from diskfile where name="name23453";

tmpfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、创建tmpfs
mkdir -p /tftpboot/tmp
mount -t tmpfs -o size=1G tmpfs /tftpboot/tmp
2、复制csv文件到tmpfs文件系统目录
3、建表
spark-sql>
CREATE table tmpfile
USING csv
OPTIONS (
header true,
path "/tftpboot/tmp/test.csv"
);
4、查询
spark-sql>
select * from tmpfile where name="name23453";

磁盘优化

  • SSD替代HDD

针对磁盘和应用程序 I/O 模式的特征,我们可以选择最适合的 I/O 调度算法

在顺序读比较多的场景中,我们可以增大磁盘的预读数据

你可以通过下面的proc文件,调整 /dev/sda 设备的预读大小: $ cat /sys/block/sda/queue/read_ahead_kb 128 默认大小是 128,单位为 KB。

可以优化内核块设备 I/O 的选项

比如,可以调整磁盘队列的长度 /sys/block/sda/queue/nr_requests。 适当增大队列长度,可以提升磁盘的吞吐量。