磁盘调度算法
# 查看磁盘调度算法cat /sys/block/sda/queue/schedulernoop [deadline] cfq
CFQ
CFQ 把 I/O 请求按照进程分别放入进程对应的队列中,所以 A 进程和 B 进程发出的 I/O 请求会在两个队列中。而各个队列内部仍然采用合并和排序的方法,区别仅在于,每一个提交 I/O 请求的进程都有自己的 I/O 队列。
CFQ 的“公平”是针对进程而言的,它以时间片算法为前提,轮转调度队列,默认从当前队列中取4个请求处理,然后处理下一个队列的4个请求。这样就可以确保每个进程享有的 I/O 资源是均衡的。
CFQ 的缺点是先来的 IO 请求不一定能被及时满足,可能出现饥饿的情况。
Deadline
同 CFQ 一样,除了维护一个拥有合并和排序功能的请求队列以外,还额外维护了两个队列,分别是读请求队列和写请求队列,它们都是带有超时的 FIFO 队列。当新来一个 I/O 请求时,会被同时插入普通队列和读/写队列,然后处理普通队列中的请求。当调度器发现读/写请求队列中的请求超时的时候,会优先处理这些请求,保证尽可能不产生请求饥饿。
在 DeadLine 算法中,每个 I/O 请求都有一个超时时间,默认读请求是 500ms,写请求是 5s。
Noop
Noop 做的事情非常简单,它不会对 I/O 请求排序也不会进行任何其它优化(除了合并)。Noop 除了对请求合并以外,不再进行任何处理,直接以类似 FIFO 的顺序提交 I/O 请求。
Noop 面向的不是普通的块设备,而是随机访问设备(例如 SSD),对于这种设备,不存在传统的寻道时间,那么就没有必要去做那些多余的为了减少寻道时间而采取的事情了。
iostat
## 安装 iostat#shell> yum install sysstat# debian 系: apt-get install sysstat# 使用shell> iostat -xm 3 # x表示显示扩展统计信息,m表示以兆为单位显示,3表示每隔3秒显示# 输出如下:avg-cpu: %user %nice %system %iowait %steal %idle0.58 0.00 0.33 0.00 0.00 99.08Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %utilsda 0.00 0.00 0.00 0.67 0.00 0.00 8.00 0.00 2.00 0.00 2.00 1.00 0.07sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
| CPU 属性 | 说明 |
|---|---|
| %user | CPU 处在用户模式下的时间百分比 |
| %nice | CPU 处在带 NICE 值的用户模式下的时间百分比 |
| %sys | CPU 处在系统模式下的时间百分比 |
| %iowait | CPU 等待 IO 完成时间的百分比 |
| %steal | 管理程序维护另一个虚拟处理器时,虚拟 CPU 的无意的等待时间的百分比 |
| %idle | 闲置 CPU 的百分比 |
提示:
- 如果 %iowait 的值过高,表示硬盘存在 I/O 瓶颈。
- 如果 %idle 值高,表示 CPU 较空闲,如果 %idle 值高但系统响应慢时,有可能是 CPU 等待分配内存,此时应加大内存容量。
- 如果 %idle 值如果持续很低,那么系统的 CPU 处理能力相对较低,表明系统中最需要解决的资源是 CPU。
| Device 属性 | 说明 |
|---|---|
| rrqm/s | 每秒进行 merge 的读操作数目 - merge 将若干个连续地址的 IO 请求进行合并,来提高 IO 的效率 |
| wrqm/s | 每秒进行 merge 的写操作数目 - merge 将若干个连续地址的 IO 请求进行合并,来提高 IO 的效率 |
| r/s | 每秒完成的读 I/O 设备次数(合并之后) |
| w/s | 每秒完成的写 I/O 设备次数(合并之后) |
| rsec/s | 每秒读扇区数,一个扇区是 512Byte |
| wsec/s | 每秒写扇区数,一个扇区是 512Byte |
| rkB/s | 每秒读 K 字节数 |
| wkB/s | 每秒写 K 字节数 |
| rMB/s | 每秒读 M 字节数 |
| wMB/s | 每秒写 M 字节数 |
| avgrq-sz | 平均每次设备 I/O 操作的数据大小(扇区) - 一块磁盘可能存储数据的同时还存储日志,所以请求的 IO 大小是不一样的 |
| avgqu-sz | 平均 I/O 队列长度 - HDD 可能在4左右,SSD 可以达到30左右 |
| await | 平均每次设备 I/O 操作的等待时间(毫秒) |
| r_await | 读 IO 请求的等待 |
| w_await | 写 IO 请求的等待 |
| svctm | 平均每次设备 I/O 操作的服务时间(毫秒) - man 文档中提示不要相信该值,以后会被移除 |
| %util | 一秒中有百分之多少的时间用于 I/O 操作,即被 IO 消耗的 CPU 百分比 |
提示:
**
- r/s + w/s = IOPS
- %util 表示磁盘是否空闲,不能简单的等同于 IO 的使用率,该值可以解释为磁盘是否繁忙
- 如果该值达到 100% 不能简单的等同于磁盘的负载满了,达到了瓶颈
- 需要综合 avgqu-sz、await 等其他指标进行综合判断磁盘是否达到瓶颈
- 如果 svctm 比较接近 await,说明 I/O 几乎没有等待时间。
- 如果 await 远大于 svctm,说明 I/O 队列太长,IO 响应太慢,则需要进行必要优化。
- 如果 avgqu-sz 比较大,也表示有当量 IO 在等待。
MySQL 的 IO 使用情况
iotop
shell> iotop -u mysql # -u 表示监控哪个user的进程,所以前提是你的mysql服务是用mysql用户启动的
上述命令只能看到 MySQL 的线程 ID(Thread ID)。
performance_schema.threads
mysql> use performance_schema;Database changedmysql> desc threads;+---------------------+---------------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra |+---------------------+---------------------+------+-----+---------+-------+| THREAD_ID | bigint(20) unsigned | NO | | NULL | | -- MySQL内部线程ID| NAME | varchar(128) | NO | | NULL | || TYPE | varchar(10) | NO | | NULL | || PROCESSLIST_ID | bigint(20) unsigned | YES | | NULL | || PROCESSLIST_USER | varchar(32) | YES | | NULL | || PROCESSLIST_HOST | varchar(60) | YES | | NULL | || PROCESSLIST_DB | varchar(64) | YES | | NULL | || PROCESSLIST_COMMAND | varchar(16) | YES | | NULL | || PROCESSLIST_TIME | bigint(20) | YES | | NULL | || PROCESSLIST_STATE | varchar(64) | YES | | NULL | || PROCESSLIST_INFO | longtext | YES | | NULL | || PARENT_THREAD_ID | bigint(20) unsigned | YES | | NULL | || ROLE | varchar(64) | YES | | NULL | || INSTRUMENTED | enum('YES','NO') | NO | | NULL | || HISTORY | enum('YES','NO') | NO | | NULL | || CONNECTION_TYPE | varchar(16) | YES | | NULL | || THREAD_OS_ID | bigint(20) unsigned | YES | | NULL | | -- 操作系统的线程ID+---------------------+---------------------+------+-----+---------+-------+17 rows in set (0.00 sec)mysql> select name,type,thread_id,thread_os_id from threads;+----------------------------------------+------------+-----------+--------------+| name | type | thread_id | thread_os_id |+----------------------------------------+------------+-----------+--------------+| thread/sql/main | BACKGROUND | 1 | 2481 || thread/sql/thread_timer_notifier | BACKGROUND | 2 | 2482 || thread/innodb/io_read_thread | BACKGROUND | 3 | 2486 || thread/innodb/io_read_thread | BACKGROUND | 4 | 2487 || thread/innodb/io_read_thread | BACKGROUND | 5 | 2488 || thread/innodb/io_write_thread | BACKGROUND | 6 | 2489 || thread/innodb/io_write_thread | BACKGROUND | 7 | 2490 || thread/innodb/io_write_thread | BACKGROUND | 8 | 2491 || thread/innodb/io_write_thread | BACKGROUND | 9 | 2492 || thread/innodb/page_cleaner_thread | BACKGROUND | 10 | 2493 || thread/innodb/io_read_thread | BACKGROUND | 11 | 2485 || thread/innodb/io_log_thread | BACKGROUND | 12 | 2484 || thread/innodb/io_ibuf_thread | BACKGROUND | 13 | 2483 || thread/innodb/srv_master_thread | BACKGROUND | 15 | 2501 | -- 主线程| thread/sql/background | BACKGROUND | 16 | 2502 || thread/innodb/srv_purge_thread | BACKGROUND | 17 | 2502 || thread/sql/background | BACKGROUND | 18 | 2503 || thread/innodb/srv_monitor_thread | BACKGROUND | 19 | 2500 || thread/innodb/srv_error_monitor_thread | BACKGROUND | 20 | 2499 || thread/sql/background | BACKGROUND | 21 | 2504 || thread/sql/background | BACKGROUND | 22 | 2505 || thread/innodb/srv_lock_timeout_thread | BACKGROUND | 23 | 2498 || thread/innodb/dict_stats_thread | BACKGROUND | 24 | 2507 || thread/innodb/buf_dump_thread | BACKGROUND | 25 | 2506 || thread/sql/signal_handler | BACKGROUND | 26 | 2510 || thread/sql/compress_gtid_table | FOREGROUND | 27 | 2511 || thread/sql/one_connection | FOREGROUND | 28 | 2514 | -- FOREGROUND前台线程+----------------------------------------+------------+-----------+--------------+27 rows in set (0.00 sec)-- thread/sql/one_connection 就是我连接的线程mysql> select name,thread_id,thread_os_id,processlist_id from threads; -- 查看processlist_id+----------------------------------------+-----------+--------------+----------------+| name | thread_id | thread_os_id | processlist_id |+----------------------------------------+-----------+--------------+----------------+| thread/sql/main | 1 | 2481 | NULL || thread/sql/thread_timer_notifier | 2 | 2482 | NULL || thread/innodb/io_read_thread | 3 | 2486 | NULL || thread/innodb/io_read_thread | 4 | 2487 | NULL || thread/innodb/io_read_thread | 5 | 2488 | NULL || thread/innodb/io_write_thread | 6 | 2489 | NULL || thread/innodb/io_write_thread | 7 | 2490 | NULL || thread/innodb/io_write_thread | 8 | 2491 | NULL || thread/innodb/io_write_thread | 9 | 2492 | NULL || thread/innodb/page_cleaner_thread | 10 | 2493 | NULL || thread/innodb/io_read_thread | 11 | 2485 | NULL || thread/innodb/io_log_thread | 12 | 2484 | NULL || thread/innodb/io_ibuf_thread | 13 | 2483 | NULL || thread/innodb/srv_master_thread | 15 | 2501 | NULL || thread/sql/background | 16 | 2502 | NULL || thread/innodb/srv_purge_thread | 17 | 2502 | NULL || thread/sql/background | 18 | 2503 | NULL || thread/innodb/srv_monitor_thread | 19 | 2500 | NULL || thread/innodb/srv_error_monitor_thread | 20 | 2499 | NULL || thread/sql/background | 21 | 2504 | NULL || thread/sql/background | 22 | 2505 | NULL || thread/innodb/srv_lock_timeout_thread | 23 | 2498 | NULL || thread/innodb/dict_stats_thread | 24 | 2507 | NULL || thread/innodb/buf_dump_thread | 25 | 2506 | NULL || thread/sql/signal_handler | 26 | 2510 | NULL || thread/sql/compress_gtid_table | 27 | 2511 | 1 || thread/sql/one_connection | 28 | 2514 | 2 |+----------------------------------------+-----------+--------------+----------------+27 rows in set (0.00 sec)-- processlist_id 对应的就是 show processlist 中的 idmysql> show processlist;+----+------+-----------+--------------------+---------+------+----------+------------------+| Id | User | Host | db | Command | Time | State | Info |+----+------+-----------+--------------------+---------+------+----------+------------------+| 2 | root | localhost | performance_schema | Query | 0 | starting | show processlist |+----+------+-----------+--------------------+---------+------+----------+------------------+1 row in set (0.00 sec)mysql> select connection_id(); -- 查看当前connection的id+-----------------+| connection_id() |+-----------------+| 2 |+-----------------+1 row in set (0.00 sec)
通过 threads 表中的信息,结合 iotop -u mysql 的输出,就可以知道某个线程的 IO 使用情况。
MySQL 5.6 版本中没有 thread_os_id 这个列。
存储结构对应关系

- 最终在存储设备上,文件会根据 512B 存储,这个 512B 就是磁盘的扇区。
- SSD 扇区的大小一般为 4K 或者 8K,但是为了兼容 HDD,SSD 通过 Flash Translation Layer(FTL)的方式转换成 512B。
O_DIRECT
fwrite & fsync

- fwrite:是把数据写入文件系统层(Filesystem),可能存在 Cache,并不能保证写入 Disk。
- fsync:可以保证把数据写入到 Disk(数据落盘)。
只通过 fwrite 写入数据特别快(因为有缓存),但随后调用 fsync 就会很慢,这个速度取决于磁盘的 IOPS。
如果不手工执行 fsync,当 FileSystem 的 Cache 小于 10% 时,操作系统才会将数据刷入磁盘,所以可能存在数据丢失的风险,比如掉电。所以对于数据库来说,每次执行 fwrite 都会伴随执行 fsync 来保证数据完全写入磁盘。
O_DIRECT

一条数据需要经过 FileSystem Cache 后才能落盘,MySQL 提供了对应的属性来设置数据直接落盘,不用经过 FileSystem Cache 层。
innodb_flush_method = O_DIRECT 的设置参数是告诉系统直接将数据写入磁盘,跳过文件系统的缓存,等同于使用裸设备的效果。
sysbench
在设置 InnoDB 的一些属性之前,需要对当前服务器的 IO 性能作出评估。用的比较多的是 sysbench 这个工具, sysbench 能测试的内容比较多,它能测试文件、CPU、内存、数据库等。
安装
建议安装 sysbenh-0.5 的版本,0.4 的版本不能实时输出某个时间段的结果,它只能输出最终结果。
shell> https://github.com/akopytov/sysbench.git # 通过git clone得到源码shell> cd sysbenchshell> ./autogen.shshell> ./configure --with-mysql-includes=/usr/local/mysql56/include/ --with-mysql-libs=/usr/local/mysql56/lib/ # 关联mysql的头文件和库 #### 注意,如果我这里使用mysql5.7.9 的include和lib ,提示我 /usr/bin/ld: cannot find -lmysqlclient_r ##shell> make -j 2 # -j 2 表示用几个cpu核心进行编译shell> make install # 默认安装到 /usr/local/bin , 如果有自定义目录,configure增加参数 --prefix=自定义目录shell> echo "export LD_LIBRARY_PATH=/usr/local/mysql56/lib/:$LD_LIBRARY_PATH" >> ~/.bashrc # 添加LD_LIBRARY_PATH shell> source ~/.bashrcshell> sysbench sysbench 0.5
测试
## 生成测试文件#shell> sysbench --test=fileio \ # File IO测试--file-num=4 \ # 测试文件数是4个--file-block-size=8K \ # block size是8K--file-total-size=1G \ # 4个文件的总大小是1G--file-test-mode=rndrd \ # 测试方法是随机读--file-extra-flags=direct \ # direct io,跳过缓存--max-requests=0 \ # 一共发起多少请求,0表示任意 # 测试3600s--max-time=3600 \ # 测试3600s--num-threads=4 \ # 使用4个线程prepare # run or cleanup # prepare:生成文件# run:开始测试# cleanup:删除测试文件## 其他说明 sysbench --test=fileio help# --file-num=N 创建文件数# --file-block-size=N block size大小# --file-total-size=SIZE 文件数的大小总和# --file-test-mode=STRING 测试模式 {seqwr, seqrewr, seqrd, rndrd, rndwr, rndrw} (顺序写,顺序读写,顺序读,随机读,随机写,随机读写)# --file-io-mode=STRING 文件操作方式 {sync,async,mmap}# --file-extra-flags=STRING 打开文件的额外标志 {sync,dsync,direct} []# --file-fsync-freq=N 多少请求后执行fsync。默认是0,不执行# --file-fsync-all=[on|off] 是否次操作后都执行fsync# --file-fsync-end=[on|off] 测完成后执行fsync,默认是on# --file-fsync-mode=STRING 同步的方法 {fsync, fdatasync}默认是 [fsync]# --file-merged-requests=N 最多多少IO请求被合并,默认为0,不合并# --file-rw-ratio=N 读写比例默认是 [1.5],即 3:2## 开始测试#shell> sysbench --test=fileio--file-num=4 \--file-block-size=8K \--file-total-size=1G \--file-test-mode=rndrd \--file-extra-flags=direct \--max-requests=0 \--max-time=30 \ # 简单测试,测试30秒--num-threads=4 \--report-interval=3 \ # 每3秒产生报告runsysbench 0.5: multi-threaded system evaluation benchmarkRunning the test with following options:Number of threads: 4Report intermediate results every 3 second(s)Random number generator seed is 0 and will be ignoredExtra file open flags: 34 files, 256Mb each1Gb total file sizeBlock size 8KbNumber of IO requests: 0Read/Write ratio for combined random IO test: 1.50 Periodic FSYNC enabled, calling fsync() each 100 requests. Calling fsync() at the end of test, Enabled.Using synchronous I/O modeDoing random read testThreads started![ 3s] reads: 1.70 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 54.416ms (95%)[ 6s] reads: 1.78 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 55.469ms (95%)[ 9s] reads: 1.75 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 55.253ms (95%)[ 12s] reads: 1.66 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 52.120ms (95%)[ 15s] reads: 1.76 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 51.840ms (95%)[ 18s] reads: 1.79 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 50.933ms (95%)[ 21s] reads: 1.78 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 54.858ms (95%)[ 24s] reads: 1.88 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 50.857ms (95%)[ 27s] reads: 1.75 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 56.238ms (95%)[ 30s] reads: 1.61 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 64.097ms (95%)Operations performed: 6709 reads, 0 writes, 0 Other = 6709 TotalRead 52.414Mb Written 0b Total transferred 52.414Mb (1.7462Mb/sec)223.51 Requests/sec executed # 这个就是IOPSGeneral statistics:total time: 30.0160stotal number of events: 6709total time taken by event execution: 120.0223sresponse time:min: 0.13msavg: 17.89msmax: 254.62msapprox. 95 percentile: 54.97msThreads fairness:events (avg/stddev): 1677.2500/28.16execution time (avg/stddev): 30.0056/0.01#### 上述测试随机读的速度在1.7MB/s左右,## (1.7MB/s * 1024 / 8KB =217)换算后得到的值就是IOPS,约等于上面的223。##
- 测试完成后执行 cleanup
- 如果是真实的测试 max-time 设置成一周的时间
- run 期间可以使用 iotop 或者 iostat 进行观察
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/hmeu3e 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
