磁盘调度算法

  1. # 查看磁盘调度算法
  2. cat /sys/block/sda/queue/scheduler
  3. noop [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

  1. #
  2. # 安装 iostat
  3. #
  4. shell> yum install sysstat
  5. # debian 系: apt-get install sysstat
  6. # 使用
  7. shell> iostat -xm 3 # x表示显示扩展统计信息,m表示以兆为单位显示,3表示每隔3秒显示
  8. # 输出如下:
  9. avg-cpu: %user %nice %system %iowait %steal %idle
  10. 0.58 0.00 0.33 0.00 0.00 99.08
  11. Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
  12. sda 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.07
  13. sdb 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

  1. shell> iotop -u mysql # -u 表示监控哪个user的进程,所以前提是你的mysql服务是用mysql用户启动的

上述命令只能看到 MySQL 的线程 ID(Thread ID)。

performance_schema.threads

  1. mysql> use performance_schema;
  2. Database changed
  3. mysql> desc threads;
  4. +---------------------+---------------------+------+-----+---------+-------+
  5. | Field | Type | Null | Key | Default | Extra |
  6. +---------------------+---------------------+------+-----+---------+-------+
  7. | THREAD_ID | bigint(20) unsigned | NO | | NULL | | -- MySQL内部线程ID
  8. | NAME | varchar(128) | NO | | NULL | |
  9. | TYPE | varchar(10) | NO | | NULL | |
  10. | PROCESSLIST_ID | bigint(20) unsigned | YES | | NULL | |
  11. | PROCESSLIST_USER | varchar(32) | YES | | NULL | |
  12. | PROCESSLIST_HOST | varchar(60) | YES | | NULL | |
  13. | PROCESSLIST_DB | varchar(64) | YES | | NULL | |
  14. | PROCESSLIST_COMMAND | varchar(16) | YES | | NULL | |
  15. | PROCESSLIST_TIME | bigint(20) | YES | | NULL | |
  16. | PROCESSLIST_STATE | varchar(64) | YES | | NULL | |
  17. | PROCESSLIST_INFO | longtext | YES | | NULL | |
  18. | PARENT_THREAD_ID | bigint(20) unsigned | YES | | NULL | |
  19. | ROLE | varchar(64) | YES | | NULL | |
  20. | INSTRUMENTED | enum('YES','NO') | NO | | NULL | |
  21. | HISTORY | enum('YES','NO') | NO | | NULL | |
  22. | CONNECTION_TYPE | varchar(16) | YES | | NULL | |
  23. | THREAD_OS_ID | bigint(20) unsigned | YES | | NULL | | -- 操作系统的线程ID
  24. +---------------------+---------------------+------+-----+---------+-------+
  25. 17 rows in set (0.00 sec)
  26. mysql> select name,type,thread_id,thread_os_id from threads;
  27. +----------------------------------------+------------+-----------+--------------+
  28. | name | type | thread_id | thread_os_id |
  29. +----------------------------------------+------------+-----------+--------------+
  30. | thread/sql/main | BACKGROUND | 1 | 2481 |
  31. | thread/sql/thread_timer_notifier | BACKGROUND | 2 | 2482 |
  32. | thread/innodb/io_read_thread | BACKGROUND | 3 | 2486 |
  33. | thread/innodb/io_read_thread | BACKGROUND | 4 | 2487 |
  34. | thread/innodb/io_read_thread | BACKGROUND | 5 | 2488 |
  35. | thread/innodb/io_write_thread | BACKGROUND | 6 | 2489 |
  36. | thread/innodb/io_write_thread | BACKGROUND | 7 | 2490 |
  37. | thread/innodb/io_write_thread | BACKGROUND | 8 | 2491 |
  38. | thread/innodb/io_write_thread | BACKGROUND | 9 | 2492 |
  39. | thread/innodb/page_cleaner_thread | BACKGROUND | 10 | 2493 |
  40. | thread/innodb/io_read_thread | BACKGROUND | 11 | 2485 |
  41. | thread/innodb/io_log_thread | BACKGROUND | 12 | 2484 |
  42. | thread/innodb/io_ibuf_thread | BACKGROUND | 13 | 2483 |
  43. | thread/innodb/srv_master_thread | BACKGROUND | 15 | 2501 | -- 主线程
  44. | thread/sql/background | BACKGROUND | 16 | 2502 |
  45. | thread/innodb/srv_purge_thread | BACKGROUND | 17 | 2502 |
  46. | thread/sql/background | BACKGROUND | 18 | 2503 |
  47. | thread/innodb/srv_monitor_thread | BACKGROUND | 19 | 2500 |
  48. | thread/innodb/srv_error_monitor_thread | BACKGROUND | 20 | 2499 |
  49. | thread/sql/background | BACKGROUND | 21 | 2504 |
  50. | thread/sql/background | BACKGROUND | 22 | 2505 |
  51. | thread/innodb/srv_lock_timeout_thread | BACKGROUND | 23 | 2498 |
  52. | thread/innodb/dict_stats_thread | BACKGROUND | 24 | 2507 |
  53. | thread/innodb/buf_dump_thread | BACKGROUND | 25 | 2506 |
  54. | thread/sql/signal_handler | BACKGROUND | 26 | 2510 |
  55. | thread/sql/compress_gtid_table | FOREGROUND | 27 | 2511 |
  56. | thread/sql/one_connection | FOREGROUND | 28 | 2514 | -- FOREGROUND前台线程
  57. +----------------------------------------+------------+-----------+--------------+
  58. 27 rows in set (0.00 sec)
  59. -- thread/sql/one_connection 就是我连接的线程
  60. mysql> select name,thread_id,thread_os_id,processlist_id from threads; -- 查看processlist_id
  61. +----------------------------------------+-----------+--------------+----------------+
  62. | name | thread_id | thread_os_id | processlist_id |
  63. +----------------------------------------+-----------+--------------+----------------+
  64. | thread/sql/main | 1 | 2481 | NULL |
  65. | thread/sql/thread_timer_notifier | 2 | 2482 | NULL |
  66. | thread/innodb/io_read_thread | 3 | 2486 | NULL |
  67. | thread/innodb/io_read_thread | 4 | 2487 | NULL |
  68. | thread/innodb/io_read_thread | 5 | 2488 | NULL |
  69. | thread/innodb/io_write_thread | 6 | 2489 | NULL |
  70. | thread/innodb/io_write_thread | 7 | 2490 | NULL |
  71. | thread/innodb/io_write_thread | 8 | 2491 | NULL |
  72. | thread/innodb/io_write_thread | 9 | 2492 | NULL |
  73. | thread/innodb/page_cleaner_thread | 10 | 2493 | NULL |
  74. | thread/innodb/io_read_thread | 11 | 2485 | NULL |
  75. | thread/innodb/io_log_thread | 12 | 2484 | NULL |
  76. | thread/innodb/io_ibuf_thread | 13 | 2483 | NULL |
  77. | thread/innodb/srv_master_thread | 15 | 2501 | NULL |
  78. | thread/sql/background | 16 | 2502 | NULL |
  79. | thread/innodb/srv_purge_thread | 17 | 2502 | NULL |
  80. | thread/sql/background | 18 | 2503 | NULL |
  81. | thread/innodb/srv_monitor_thread | 19 | 2500 | NULL |
  82. | thread/innodb/srv_error_monitor_thread | 20 | 2499 | NULL |
  83. | thread/sql/background | 21 | 2504 | NULL |
  84. | thread/sql/background | 22 | 2505 | NULL |
  85. | thread/innodb/srv_lock_timeout_thread | 23 | 2498 | NULL |
  86. | thread/innodb/dict_stats_thread | 24 | 2507 | NULL |
  87. | thread/innodb/buf_dump_thread | 25 | 2506 | NULL |
  88. | thread/sql/signal_handler | 26 | 2510 | NULL |
  89. | thread/sql/compress_gtid_table | 27 | 2511 | 1 |
  90. | thread/sql/one_connection | 28 | 2514 | 2 |
  91. +----------------------------------------+-----------+--------------+----------------+
  92. 27 rows in set (0.00 sec)
  93. -- processlist_id 对应的就是 show processlist 中的 id
  94. mysql> show processlist;
  95. +----+------+-----------+--------------------+---------+------+----------+------------------+
  96. | Id | User | Host | db | Command | Time | State | Info |
  97. +----+------+-----------+--------------------+---------+------+----------+------------------+
  98. | 2 | root | localhost | performance_schema | Query | 0 | starting | show processlist |
  99. +----+------+-----------+--------------------+---------+------+----------+------------------+
  100. 1 row in set (0.00 sec)
  101. mysql> select connection_id(); -- 查看当前connectionid
  102. +-----------------+
  103. | connection_id() |
  104. +-----------------+
  105. | 2 |
  106. +-----------------+
  107. 1 row in set (0.00 sec)

通过 threads 表中的信息,结合 iotop -u mysql 的输出,就可以知道某个线程的 IO 使用情况。

MySQL 5.6 版本中没有 thread_os_id 这个列。

存储结构对应关系

图片.png

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

O_DIRECT

fwrite & fsync

图片.png

  • fwrite:是把数据写入文件系统层(Filesystem),可能存在 Cache,并不能保证写入 Disk。
  • fsync:可以保证把数据写入到 Disk(数据落盘)。

只通过 fwrite 写入数据特别快(因为有缓存),但随后调用 fsync 就会很慢,这个速度取决于磁盘的 IOPS。

如果不手工执行 fsync,当 FileSystem 的 Cache 小于 10% 时,操作系统才会将数据刷入磁盘,所以可能存在数据丢失的风险,比如掉电。所以对于数据库来说,每次执行 fwrite 都会伴随执行 fsync 来保证数据完全写入磁盘。

O_DIRECT

图片.png
一条数据需要经过 FileSystem Cache 后才能落盘,MySQL 提供了对应的属性来设置数据直接落盘,不用经过 FileSystem Cache 层。

innodb_flush_method = O_DIRECT 的设置参数是告诉系统直接将数据写入磁盘,跳过文件系统的缓存,等同于使用裸设备的效果。

sysbench

在设置 InnoDB 的一些属性之前,需要对当前服务器的 IO 性能作出评估。用的比较多的是 sysbench 这个工具, sysbench 能测试的内容比较多,它能测试文件、CPU、内存、数据库等。

安装

建议安装 sysbenh-0.5 的版本,0.4 的版本不能实时输出某个时间段的结果,它只能输出最终结果。

  1. shell> https://github.com/akopytov/sysbench.git # 通过git clone得到源码
  2. shell> cd sysbench
  3. shell> ./autogen.sh
  4. shell> ./configure --with-mysql-includes=/usr/local/mysql56/include/ --with-mysql-libs=/usr/local/mysql56/lib/ # 关联mysql的头文件和库 ##
  5. ## 注意,如果我这里使用mysql5.7.9 的include和lib ,提示我 /usr/bin/ld: cannot find -lmysqlclient_r ##
  6. shell> make -j 2 # -j 2 表示用几个cpu核心进行编译
  7. shell> make install # 默认安装到 /usr/local/bin , 如果有自定义目录,configure增加参数 --prefix=自定义目录
  8. shell> echo "export LD_LIBRARY_PATH=/usr/local/mysql56/lib/:$LD_LIBRARY_PATH" >> ~/.bashrc # 添加LD_LIBRARY_PATH shell> source ~/.bashrc
  9. shell> sysbench sysbench 0.5

测试

  1. #
  2. # 生成测试文件
  3. #
  4. shell> sysbench --test=fileio \ # File IO测试
  5. --file-num=4 \ # 测试文件数是4个
  6. --file-block-size=8K \ # block size是8K
  7. --file-total-size=1G \ # 4个文件的总大小是1G
  8. --file-test-mode=rndrd \ # 测试方法是随机读
  9. --file-extra-flags=direct \ # direct io,跳过缓存
  10. --max-requests=0 \ # 一共发起多少请求,0表示任意 # 测试3600s
  11. --max-time=3600 \ # 测试3600s
  12. --num-threads=4 \ # 使用4个线程
  13. prepare # run or cleanup # prepare:生成文件
  14. # run:开始测试
  15. # cleanup:删除测试文件
  16. ## 其他说明 sysbench --test=fileio help
  17. # --file-num=N 创建文件数
  18. # --file-block-size=N block size大小
  19. # --file-total-size=SIZE 文件数的大小总和
  20. # --file-test-mode=STRING 测试模式 {seqwr, seqrewr, seqrd, rndrd, rndwr, rndrw} (顺序写,顺序读写,顺序读,随机读,随机写,随机读写)
  21. # --file-io-mode=STRING 文件操作方式 {sync,async,mmap}
  22. # --file-extra-flags=STRING 打开文件的额外标志 {sync,dsync,direct} []
  23. # --file-fsync-freq=N 多少请求后执行fsync。默认是0,不执行
  24. # --file-fsync-all=[on|off] 是否􏰀次操作后都执行fsync
  25. # --file-fsync-end=[on|off] 测完成后执行fsync,默认是on
  26. # --file-fsync-mode=STRING 同步的方法 {fsync, fdatasync}默认是 [fsync]
  27. # --file-merged-requests=N 最多多少IO请求被合并,默认为0,不合并
  28. # --file-rw-ratio=N 读写比例默认是 [1.5],即 3:2
  29. #
  30. # 开始测试
  31. #
  32. shell> sysbench --test=fileio
  33. --file-num=4 \
  34. --file-block-size=8K \
  35. --file-total-size=1G \
  36. --file-test-mode=rndrd \
  37. --file-extra-flags=direct \
  38. --max-requests=0 \
  39. --max-time=30 \ # 简单测试,测试30秒
  40. --num-threads=4 \
  41. --report-interval=3 \ # 􏰀每3秒产生报告
  42. run
  43. sysbench 0.5: multi-threaded system evaluation benchmark
  44. Running the test with following options:
  45. Number of threads: 4
  46. Report intermediate results every 3 second(s)
  47. Random number generator seed is 0 and will be ignored
  48. Extra file open flags: 3
  49. 4 files, 256Mb each
  50. 1Gb total file size
  51. Block size 8Kb
  52. Number of IO requests: 0
  53. Read/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.
  54. Using synchronous I/O mode
  55. Doing random read test
  56. Threads started!
  57. [ 3s] reads: 1.70 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 54.416ms (95%)
  58. [ 6s] reads: 1.78 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 55.469ms (95%)
  59. [ 9s] reads: 1.75 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 55.253ms (95%)
  60. [ 12s] reads: 1.66 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 52.120ms (95%)
  61. [ 15s] reads: 1.76 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 51.840ms (95%)
  62. [ 18s] reads: 1.79 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 50.933ms (95%)
  63. [ 21s] reads: 1.78 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 54.858ms (95%)
  64. [ 24s] reads: 1.88 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 50.857ms (95%)
  65. [ 27s] reads: 1.75 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 56.238ms (95%)
  66. [ 30s] reads: 1.61 MB/s writes: 0.00 MB/s fsyncs: 0.00/s response time: 64.097ms (95%)
  67. Operations performed: 6709 reads, 0 writes, 0 Other = 6709 Total
  68. Read 52.414Mb Written 0b Total transferred 52.414Mb (1.7462Mb/sec)
  69. 223.51 Requests/sec executed # 这个就是IOPS
  70. General statistics:
  71. total time: 30.0160s
  72. total number of events: 6709
  73. total time taken by event execution: 120.0223s
  74. response time:
  75. min: 0.13ms
  76. avg: 17.89ms
  77. max: 254.62ms
  78. approx. 95 percentile: 54.97ms
  79. Threads fairness:
  80. events (avg/stddev): 1677.2500/28.16
  81. execution time (avg/stddev): 30.0056/0.01
  82. ##
  83. ## 上述测试随机读的速度在1.7MB/s左右,
  84. ## (1.7MB/s * 1024 / 8KB =217)换算后得到的值就是IOPS,约等于上面的223。
  85. ##
  • 测试完成后执行 cleanup
  • 如果是真实的测试 max-time 设置成一周的时间
  • run 期间可以使用 iotop 或者 iostat 进行观察

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/hmeu3e 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。