【引數】Innodb_io_capacity 對於IO穩定性的一些研究

小亮520cl發表於2017-02-24

Innodb_io_capacity 對於IO穩定性的一些研究

 背景:最近在做一臺線上伺服器IO負載情況的時候發現了以下現象:

24小時的IO_UTIL 的曲線看似風平浪靜,毛刺較少

但當圖片放大到半小時級別的時候發現IO_UTIL即磁碟使用率出現了規律性的波動,見下圖:

本文就將從這個現象觸發,探究出現這樣規律性波動的原因。


Step1: 伺服器上進行實時IO負載檢視

透過iostat -x 1 每隔一秒對IO使用情況進行一次負載檢視。可以看到UTIL有規律性的波動(10秒1次)。

且負載的主要來源在於寫請求(負載高時,wsec/s 也同步升高)

又由於伺服器是MySQL獨佔,所以比較容易的就可以將原因歸結為是MySQL的資料刷寫導致(log/data)。

看到這裡大神們應該已經不難猜到是MySQL內部 src_master_thread 中10_seconds 迴圈搗的鬼了。

為了保證診斷思路的連貫性,接下來還是把當時的操作複述一遍。



Step2: 確定MySQL write IO 來源

由於目前只知道是MySQL資料刷寫導致,那麼究竟是redo-log還是data page flush造成的現在還不知道。

innodb內部關於write的statistics還是比較有限的。因此比較粗暴的寫了個類似於top的perl指令碼 ,監控這些引數。

指令碼如下,有興趣的同學可以展開閱讀

  1. use strict;
  2. use warnings;
  3. use utf8;
  4. use DBI;


  5. my $CONFIG_SERVER_IP ='127.0.0.1';
  6. my $CONFIG_SERVER_DB='test';
  7. my $CONFIG_SERVER_PORT='3306';
  8. my $CONFIG_SERVER_USER='user';
  9. my $CONFIG_SERVER_PASS='password';
  10. my $conf_dbh = DBI->connect("DBI:mysql:$CONFIG_SERVER_DB;host=$CONFIG_SERVER_IP;port=$CONFIG_SERVER_PORT", $CONFIG_SERVER_USER, $CONFIG_SERVER_PASS,{RaiseError => 1}) || die "Could not connect to database: $DBI::errstr";
  11. my %last_value_hash;
  12. print "data_write\tdata_written\tdblwr_pages_written\tdblwr_writes\tlog_write_req\tos_log_fsync\tos_log_written\tpages_written\n";
  13. while(1){
  14.     my $conf_sth = $conf_dbh->prepare("show global status like '%innodb%'") || die "Prepare error";
  15.     $conf_sth->execute();
  16.     while(my $row=$conf_sth->fetchrow_hashref){
  17.         my $name=$row->{Variable_name};
  18.         my $value=$row->{Value};

  19.         if( $name eq 'Innodb_data_writes' || $name eq 'Innodb_data_written'
  20.         || $name eq 'Innodb_dblwr_pages_written' || $name eq 'Innodb_dblwr_writes'
  21.         || $name eq 'Innodb_log_write_requests' || $name eq 'Innodb_os_log_fsyncs'
  22.         || $name eq 'Innodb_os_log_written' || $name eq 'Innodb_pages_written'){
  23.             $last_value_hash{$name}=0 if( !defined($last_value_hash{$name}) );
  24.             my $value_step=$value-$last_value_hash{$name};
  25.             $last_value_hash{$name}=$value;
  26.             print "$value_step\t";
  27.         }
  28.     }
  29.     print "\n";
  30.     sleep 1;
  31. }



最後得到引數的波動情況如下:

可以看到 innodb_data_writes / innodb_data_written / innodb_dblwr_pages_written / innodb_pages_written 都有和IO_UTIL一樣有10秒一次的波動。

再結合上iostat中 wsec/s 較大的數值,基本可以確定IO高負載的元兇是data page的flush,而不是redo log的flush(如果是redo log的flush 應該是1秒一重新整理了

  1. data_write data_written dblwr_pages_written dblwr_writes log_write_req os_log_fsync os_log_written pages_written
  2. 1 79360 0 0 169 1 79360 0
  3. 1 72192 0 0 127 1 72192 0
  4. 1 67072 0 0 139 1 67072 0
  5. 1 69120 0 0 149 1 69120 0
  6. 1 74752 0 0 128 1 74752 0
  7. 1 61952 0 0 134 1 61952 0
  8. 1 71168 0 0 131 1 71168 0
  9. 1 62976 0 0 126 1 62976 0
  10. 1 71168 0 0 109 1 71168 0
  11. 1388 44870144 1367 14 125 6 75776 1367
  12. 1 66048 0 0 158 1 66048 0
  13. 1 73728 0 0 144 1 73728 0
  14. 1 69632 0 0 126 1 69632 0
  15. 1 75776 0 0 172 1 75776 0
  16. 1 88576 0 0 151 1 88576 0
  17. 1 67584 0 0 134 1 67584 0
  18. 1 80384 0 0 155 1 80384 0
  19. 1 86528 0 0 191 1 86528 0
  20. 1 72704 0 0 135 1 72704 0
  21. 1525 49385984 1504 13 154 5 102400 1504
  22. 1 74752 0 0 158 1 74752 0


Step3 : Innodb 什麼時候做dirty data page的flush 

 由於之前對於innodb的資料刷寫也只是略知一二,網上對於刷寫策略也是眾說紛紜,診斷到了這裡貌似就無法深入了。

 其實真相就靜靜的躺在那裡:原始碼閱讀。

Innodb寫磁碟的程式碼路徑非常清晰,比較容易閱讀。主要程式碼在 storage/innodb_plugin/Buf/Buf0flu.c 中

程式碼比較冗長這裡就不貼了,主要敘述下大致的呼叫關係。

buf_flush_batch 呼叫 buf_flush_try_neighbors (嘗試刷寫neighbor page)

buf_flush_try_neighbors 呼叫 buf_flush_page (刷寫單個page)

buf_flush_page呼叫buf_flush_write_block_low (實際刷寫單個page)

buf_flush_write_block_low呼叫buf_flush_post_to_doublewrite_buf (將page放到double write buffer中,並準備刷寫)

buf_flush_post_to_doublewrite_buf 呼叫 fil_io ( 檔案IO的封裝)

fil_io 呼叫 os_aio (aio相關操作)

os_aio 呼叫 os_file_write (實際寫檔案操作)

其中buf_flush_batch 只有兩種刷寫方式: BUF_FLUSH_LIST 和 BUF_FLUSH_LRU 兩種方式的方式和觸發時機簡介如下:

BUF_FLUSH_LIST: innodb master執行緒中 1_second / 10 second 迴圈中都會呼叫。觸發條件較多(下文會分析)

BUF_FLUSH_LRU: 當Buffer Pool無空閒page且old list中沒有足夠的clean page時,呼叫。刷寫髒頁後可以空出一定的free page,供BP使用。

從觸發頻率可以看到 10 second 迴圈中對於 buf_flush_batch( BUF_FLUSH_LIST ) 的呼叫是10秒一次IO高負載的元兇所在。

我們再來看10秒迴圈中flush的邏輯:

透過比較過去10秒的IO次數和常量的大小,以及pending的IO次數,來判斷IO是否空閒,如果空閒則buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) );

如果髒頁比例超過70,則 buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) );

否則  buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(10) );

可以看到由於SSD對於隨機寫的請求響應速度非常快,導致IO幾乎沒有堆積。也就讓innodb誤認為IO空閒,並決定全力刷寫。

其中PCT_IO(N)  = innodb_io_capacity *N% ,單位是頁。因此也就意味著每10秒,innodb都至少刷10000個page或者刷完當前所有髒頁。

updated on 2013/10/31: 在5.6中官方的adaptive flush演算法有所改變,但是空閒狀態下innodb_io_capacity對於刷寫page數量的影響仍然不改變。

具體見:文章連結


Step4: 進一步分析找到解決方案

在多次調整 innodb_adaptive_flushing 和 innodb_adaptive_flushing_method 後發現現象沒有任何變化。

這一點也比較容易解釋:因為大批次刷寫的原因是在於10秒迴圈中,innodb認為IO比較空閒,所以根據innodb_io_capacity 全力刷寫;而adaptive只發生在1秒迴圈中。

所以,調整adaptive相關引數實際上不解決問題。

從Step3中的分析可以得出一個比較明顯的結論,減小innodb_io_capacity 後就能很大程度上解決問題。 

當我們把innodb_io_capacity從10000調整到200後,並且新增以下配置後:

innodb_adaptive_flush=OFF;

innodb_adaptive_checkpoint=keep_average

可以看到mysql的data written寫入量大量減少,且保持穩定(見下圖,7:31後是引數調整後的結果)

 同時從flashcard的硬體寫入量來看,也大量的減少(見下圖)


在5.1.X版本中,最多隻會重新整理100個髒頁到磁碟、合併20個插入緩衝,即使磁碟有能力處理更多的請求,只能會處理這麼多,這樣在更新量較大的時候,髒頁重新整理就可能跟不上,導致效能下降。

   但在5.5.X版本里,innodb_io_capacity引數可以動態調整重新整理髒頁的數量,這在一定程度上解決了這一問題。

   innodb_io_capacity預設是200,單位是頁,該引數的設定大小取決於硬碟的IOPS,即每秒每秒的輸入輸出量(或讀寫次數)。

  可以動態調整引數:set global innodb_io_capacity=2000;

  磁碟配置與innodb_io_capacity引數值


innodb_io_capacity 磁碟配置
200 單盤SAS/SATA
2000 SAS*12  RAID  10
5000 SSD
50000 FUSION-IO


13.7.7.11. Controlling the Master Thread I/O Rate

The master thread in InnoDB is a thread that performs various tasks in the background. Most of these tasks are I/O related, such as flushing dirty pages from the buffer cache or writing changes from the insert buffer to the appropriate secondary indexes. The master thread attempts to perform these tasks in a way that does not adversely affect the normal working of the server. It tries to estimate the free I/O bandwidth available and tune its activities to take advantage of this free capacity. Historically, InnoDB has used a hard coded value of 100 IOPs (input/output operations per second) as the total I/O capacity of the server.

Beginning with InnoDB storage engine 1.0.4, a new configuration parameter indicates the overall I/O capacity available to InnoDB. The new parameterinnodb_io_capacity should be set to approximately the number of I/O operations that the system can perform per second. The value depends on your system configuration. When innodb_io_capacity is set, the master threads estimates the I/O bandwidth available for background tasks based on the set value. Setting the value to 100 reverts to the old behavior.

You can set the value of innodb_io_capacity to any number 100 or greater, and the default value is 200. You can set the value of this parameter in the  option file (my.cnf or my.ini) or change it dynamically with the SET GLOBAL command, which requires the SUPER privilege. 

參考:
http://mp.weixin.qq.com/s/fsZ0uwNBbBoXJNmv4Y462A
http://www.cnblogs.com/cenalulu/p/3272606.html --原文


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29096438/viewspace-2134208/,如需轉載,請註明出處,否則將追究法律責任。

相關文章