ホームページ  >  記事  >  データベース  >  MySQL · エンジンの機能 · InnoDB IO サブシステムの詳細な紹介

MySQL · エンジンの機能 · InnoDB IO サブシステムの詳細な紹介

黄舟
黄舟オリジナル
2017-03-04 14:29:381153ブラウズ

前書き

InnoDB は、成熟したクロスプラットフォーム データベース エンジンとして、同期、非同期 IO、IO マージなどを含む、効率的で使いやすい IO インターフェイスのセットを実装しています。この記事では、その内部実装について簡単に紹介します。主なコードは os0file.cc ファイルに集中しています。この記事の分析はデフォルトで MySQL 5.6、CentOS 6、および gcc 4.8 に基づいています。他のバージョンに関する情報は別途指摘されます。

基礎知識

WALテクノロジー: ログファーストテクノロジー、基本的にすべてのデータベースがこのテクノロジーを使用します。簡単に言うと、データ ブロックを書き込む必要がある場合、データベース フロントエンド スレッドはまず対応するログをディスクに書き込み (バッチ シーケンシャル書き込み)、その後クライアントに操作が成功したことを通知します。データ ブロックのデータ ブロック (離散ランダム書き込み) 次に、それをバックグラウンド IO スレッドに置きます。このテクノロジーを使用すると、ディスク書き込み操作が 1 回増えますが、ログがバッチで順次書き込まれるため、効率が非常に高く、クライアントは迅速に応答を得ることができます。さらに、実際のデータ ブロックがディスクに書き込まれる前にデータベースがクラッシュした場合、データベースは再起動時にデータを失うことなくクラッシュ リカバリにログを使用できます。
データの事前読み取り: データブロックAに「隣接」するデータブロックBとCが読み込まれると、BとCも読み込まれる可能性が高くなりますので、読み出すことができます Bの場合、読み出しますこれがデータ先読み技術です。ここでいう隣接とは、物理的な隣接性と論理的な隣接性の2つの意味があります。基礎となるデータ ファイル内の隣接関係は、物理的隣接と呼ばれます。データファイルが隣接していないが、論理的に隣接している場合(id=1のデータとid=2のデータは論理的に隣接しているが、物理的に隣接しているとは限らず、同じファイル内の異なる場所に存在する場合もあります)、論理的隣接と呼ばれます。
ファイルを開くモード: Open システム コールには、O_DIRECT、O_SYNC、およびデフォルト モードの 3 つの共通モードがあります。 O_DIRECT モードは、ファイルに対する後続の操作がファイル システム キャッシュを使用しないことを意味します。別の観点から見ると、ユーザー モードは、ファイルの書き込みに O_DIRECT モードを使用します。成功した場合、データは実際にディスクに書き込まれます (ディスクに付属のキャッシュに関係なく)。ファイルの読み取りには O_DIRECT モードが使用されます。各読み取り操作は実際にはディスクから読み取られます。ファイルシステムのキャッシュ。 O_SYNC はオペレーティング システムのキャッシュを使用することを意味し、ファイルの読み取りと書き込みはカーネルを経由しますが、このモードでは、データが書き込まれるたびにデータがディスクに書き込まれることも保証されます。デフォルト モードは O_SYNC モードと似ていますが、データを書き込んだ後、ホストがダウンしたときにデータがまだファイル システムに存在する可能性があるという点が異なります。失われます。さらに、書き込み操作では、変更または追加されたデータをディスクに書き込むだけでなく、ファイルのメタ情報もディスクに書き込む必要があります。両方の部分がディスクに書き込まれる場合にのみ、データが失われることはありません。 O_DIRECT モードでは、ファイルのメタ情報がディスクに書き込まれることが保証されないため (ただし、ほとんどのファイル システムでは保証されます、バグ #45892)、他の操作が実行されない場合、O_DIRECT でファイルを書き込んだ後に損失が発生するリスクがあります。 O_SYNC は、データとメタ情報の両方がディスク上に配置されることを保証します。どちらのデータもデフォルト モードでは保証されません。
関数 fsync を呼び出した後は、データとログがすべてディスクに書き込まれることが保証されます。そのため、O_DIRECT およびデフォルト モードを使用してファイルを開く場合は、データの書き込み後に fsync 関数を呼び出す必要があります。

同期 IO: (Linux 上で) 一般的に使用される読み取り/書き込み関数は、このタイプの IO です。その特徴は、関数の実行時に呼び出し元が関数の実行が完了するまで待機することです。メッセージ通知機構は関数が を返すため、後で戻り値を直接確認することで操作が成功したかどうかを知ることができます。このタイプの IO 操作はプログラミングが比較的簡単で、すべての操作を同じスレッドで完了できますが、データベース システムでは、たとえばログインするために特定のデータが緊急に必要な場合に呼び出すのが適しています。 WAL はクライアントに返される必要があります。ダウンロードする前に、同期 IO 操作が実行されます。
非同期 IO: データベースでは、バックグラウンドでデータ ブロックをブラッシングする IO スレッドは、基本的に非同期 IO を使用します。データベース フロントエンド スレッドは、他の作業に戻る前にブラシ ブロック リクエストを非同期 IO キューに送信するだけで済みます。一方、バックグラウンド スレッドの IO スレッドは、これらの送信されたリクエストが完了したかどうかを定期的にチェックし、完了している場合は、いくつかの処理を実行します。後続の処理作業。同時に、非同期 IO はリクエストのバッチで送信されることがよくあります。異なるリクエストが同じファイルにアクセスし、連続したオフセットを持つ場合、それらのリクエストは 1 つの IO リクエストにマージされる可能性があります。たとえば、最初のリクエストはファイル 1、オフセット 100 から始まる 200 バイトのデータを読み取り、2 番目のリクエストはファイル 1、オフセット 300 から始まる 100 バイトのデータを読み取ります。その後、2 つのリクエストを「ファイル 1 の読み取り、300 バイト」にマージできます。オフセット 100 から始まるデータ。データの事前読み取りにおける論理的な事前読み取りでも、多くの場合、非同期 IO テクノロジが使用されます。 Linux 上の現在の非同期 IO ライブラリでは、ファイルを O_DIRECT モードで開く必要があり、データ ブロックが保存されるメモリ アドレス、ファイルの読み取りと書き込みのオフセット、読み取りと書き込みのデータ量は整数倍である必要があります。ファイル システムの論理ブロック サイズ。ファイル システムの論理ブロック サイズは、
と同様のステートメントを使用してクエリできます。上記の 3 つがファイル システムの論理ブロック サイズの整数倍ではない場合、読み取り関数と書き込み関数を呼び出すときに EINVAL エラーが報告されます。ただし、ファイルが O_DIRECT を使用して開かれていない場合、プログラムは実行できますが、同期 IO に低下し、io_submit 関数呼び出しをブロックします。 sudo blockdev --getss /dev/sda5

InnoDB の通常の IO 操作と同期 IO

InnoDB では、システムに pred/pwrite 関数 (os_file_read_func および os_file_write_func) がある場合は、それらを読み取りと書き込みに使用します。それ以外の場合は、 lseek+読み取り/書き込みソリューション。これは InnoDB 同期 IO です。 pread/pwrite のドキュメントを見ると、これら 2 つの関数はファイル ハンドルのオフセットを変更せず、スレッド セーフであるため、マルチスレッド環境で推奨されることがわかります。lseek+read/write ソリューションには独自の関数が必要です。ミューテックス保護。同時実行条件下では、カーネル状態の頻繁な障害がパフォーマンスに一定の影響を与えます。 os_file_read_funcos_file_write_func),则使用它们进行读写,否则使用lseek+read/write方案。这个就是InnoDB同步IO。查看pread/pwrite文档可知,这两个函数不会改变文件句柄的偏移量且线程安全,所以多线程环境下推荐使用,而lseek+read/write方案则需要自己使用互斥锁保护,在高并发情况下,频繁的陷入内核态,对性能有一定影响。

在InnoDB中,使用open系统调用打开文件(os_file_create_func),模式方面除了O_RDONLY(只读),O_RDWR(读写),O_CREAT(创建文件)外,还使用了O_EXCL(保证是这个线程创建此文件)和O_TRUNC(清空文件)。默认情况下(数据库不设置为只读模式),所有文件都以O_RDWR模式打开。innodb_flush_method这个参数比较重要,重点介绍一下:

  • 如果innodb_flush_method设置了O_DSYNC,日志文件(ib_logfileXXX)使用O_SYNC打开,因此写完数据不需要调用函数fsync刷盘,数据文件(ibd)使用default模式打开,因此写完数据需要调用fsync刷盘。

  • 如果innodb_flush_method设置了O_DIRECT,日志文件(ib_logfileXXX)使用default模式打开,写完数据需要调用fsync函数刷盘,数据文件(ibd)使用O_DIRECT模式打开,写完数据需要调用fsync函数刷盘。

  • 如果innodb_flush_method设置了fsync或者不设置,数据文件和日志文件都使用default模式打开,写完数据都需要使用fsync来刷盘。

  • 如果innodb_flush_method设置为O_DIRECT_NO_FSYNC,文件打开方式与O_DIRECT模式类似,区别是,数据文件写完后,不调用fsync函数来刷盘,主要针对O_DIRECT能保证文件的元数据也落盘的文件系统。
    InnoDB目前还不支持使用O_DIRECT模式打开日志文件,也不支持使用O_SYNC模式打开数据文件。
    注意,如果使用linux native aio(详见下一节),innodb_flush_method一定要配置成O_DIRECT,否则会退化成同步IO(错误日志中不会有任务提示)。

InnoDB使用了文件系统的文件锁来保证只有一个进程对某个文件进行读写操作(os_file_lock),使用了建议锁(Advisory locking),而不是强制锁(Mandatory locking),因为强制锁在不少系统上有bug,包括linux。在非只读模式下,所有文件打开后,都用文件锁锁住。

InnoDB中目录的创建使用递归的方式(os_file_create_subdirs_if_neededos_file_create_directory)。例如,需要创建/a/b/c/这个目录,先创建c,然后b,然后a,创建目录调用mkdir函数。此外,创建目录上层需要调用os_file_create_simple_func函数,而不是os_file_create_func,需要注意一下。

InnoDB也需要临时文件,临时文件的创建逻辑比较简单(os_file_create_tmpfile),就是在tmp目录下成功创建一个文件后直接使用unlink函数释放掉句柄,这样当进程结束后(不管是正常结束还是异常结束),这个文件都会自动释放。InnoDB创建临时文件,首先复用了server层函数mysql_tmpfile的逻辑,后续由于需要调用server层的函数来释放资源,其又调用dup函数拷贝了一份句柄。

如果需要获取某个文件的大小,InnoDB并不是去查文件的元数据(stat函数),而是使用lseek(file, 0, SEEK_END)的方式获取文件大小,这样做的原因是防止元信息更新延迟导致获取的文件大小有误。

InnoDB会预分配一个大小给所有新建的文件(包括数据和日志文件),预分配的文件内容全部置为零(os_file_set_size

InnoDB では、O_RDONLY (読み取り専用)、O_RDWR (読み取り/書き込み)、O_CREAT (作成) に加えて、open システム コールを使用してファイルを開きます (os_file_create_func)。ファイル)、O_EXCL (このスレッドがこのファイルを作成したことを確認します)、および O_TRUNC (ファイルをクリアします)。デフォルトでは (データベースは読み取り専用モードに設定されていません)、すべてのファイルは O_RDWR モードで開かれます。 innodb_flush_method のパラメータの方が重要です。

  • innodb_flush_method が O_DSYNC を設定する場合、ログ ファイル (ib_logfileXXX) は O_SYNC を使用して開かれるため、データの書き込み後に関数を呼び出す必要があります。 fsync はディスクをフラッシュし、データ ファイル (ibd) はデフォルト モードで開かれるため、データの書き込み後に fsync を呼び出してディスクをフラッシュする必要があります。 🎜
  • 🎜 innodb_flush_method が O_DIRECT に設定されている場合、データを書き込んだ後、ログ ファイル (ib_logfileXXX) を呼び出してディスクをフラッシュする必要があります。 ) は O_DIRECT モードで開かれます。データを書き込んだ後、fsync 関数を呼び出してディスクをフラッシュする必要があります。 🎜
  • 🎜 innodb_flush_method が fsync に設定されているか、設定されていない場合、データ ファイルとログ ファイルはデフォルト モードで開かれ、データの書き込み後にディスクをフラッシュするには fsync を使用する必要があります。 🎜
  • 🎜 innodb_flush_method が O_DIRECT_NO_FSYNC に設定されている場合、ファイルを開く方法は O_DIRECT モードと似ていますが、データ ファイルの書き込み後にディスクをフラッシュするために fsync 関数が呼び出されない点が異なります。これは主に、O_DIRECT によってファイル システムのメタデータもディスクに確実に保持されるためです。
    InnoDB は現在、O_DIRECT モードを使用してログ ファイルを開くことをサポートしておらず、O_SYNC モードを使用してデータ ファイルを開くこともサポートしていません。
    Linux ネイティブ AIO を使用する場合 (詳細については次のセクションを参照)、innodb_flush_method を O_DIRECT として構成する必要があることに注意してください。そうしないと、同期 IO に低下します (エラー ログにタスク プロンプトが表示されなくなります)。 🎜
🎜 InnoDB は、ファイル システムのファイル ロックを使用して、1 つのプロセスのみがファイルの読み取りと書き込みを行うようにし (os_file_lock)、代わりに勧告ロック (勧告ロック) を使用します。強制ロック (強制ロック)。強制ロックには Linux を含む多くのシステムにバグがあるためです。非読み取り専用モードでは、すべてのファイルは、開かれた後にファイル ロックでロックされます。 🎜🎜 InnoDB でのディレクトリの作成には再帰 (os_file_create_subdirs_if_needed および os_file_create_directory) が使用されます。たとえば、ディレクトリ /a/b/c/ を作成する必要がある場合、最初に c、次に b、次に a を作成し、ディレクトリを作成して mkdir 関数を呼び出します。また、ディレクトリの上位層を作成するには、os_file_create_func の代わりに os_file_create_simple_func 関数を呼び出す必要があることに注意してください。 🎜🎜 InnoDB には一時ファイルも必要です。一時ファイルの作成ロジックは比較的単純です (os_file_create_tmpfile)。tmp ディレクトリにファイルを作成した後、unlink 関数を直接使用してハンドルを解放します。プロセスが終了すると(正常終了、異常終了に関わらず)、このファイルは自動的に解放されます。 InnoDB は一時ファイルを作成するときに、最初にサーバー層関数 mysql_tmpfile のロジックを再利用します。その後、サーバー層関数を呼び出してリソースを解放する必要があるため、dup 関数を呼び出してハンドルをコピーします。 🎜🎜特定のファイルのサイズを取得する必要がある場合、InnoDB はファイルのメタデータをチェック (stat 関数) せず、lseek(file, 0, SEEK_END) を使用します。 code> ファイルサイズを取得する理由は、メタ情報の更新遅延により不正なファイルサイズが取得されることを防ぐためです。 🎜🎜 InnoDB は、新しく作成されたすべてのファイル (データ ファイルとログ ファイルを含む) にサイズを事前に割り当て、現在のファイルがいっぱいになると、事前に割り当てられたすべてのファイルの内容をゼロに設定します (<code>os_file_set_size)。もう一度延長してください。さらに、ログ ファイルが作成されるとき、つまり install_db フェーズ中に、割り当ての進行状況が 100MB 間隔でエラー ログに出力されます。 🎜🎜一般的に従来のIO操作や同期IOは比較的シンプルですが、InnoDBでは基本的にデータファイルの書き込みには非同期IOが使用されます。 🎜

InnoDB 非同期 IO

MySQL は Linux ネイティブ AIO よりも前に誕生したため、MySQL 非同期 IO コードで非同期 IO を実装するには 2 つのソリューションがあります。
1 つ目は、オリジナルのシミュレートされた aio です。InnoDB は、Linux ネイティブの air がインポートされる前、および air をサポートしていない一部のシステムで aio メカニズムをシミュレートしました。非同期の読み取りおよび書き込みリクエストが送信されると、そのリクエストはキューに入れられて返されるだけで、プログラムは他のことを行うことができます。バックグラウンドにはいくつかの非同期 IO 処理スレッドがあり (innobase_read_io_threads および innobase_write_io_threads の 2 つのパラメータによって制御されます)、これらのスレッドは継続的にこのキューからリクエストを取り出し、同期 IO を使用して読み取りおよび書き込みリクエストと読み取りおよび書き込み後の作業を完了します。完成しました。
もう1つはネイティブアイオです。現在、Linux 上の io_submit、io_getevents などの関数を使用して完了します (glibc aio は使用されません。これもシミュレートされています)。 io_submit を使用してリクエストを送信し、io_getevents を使用してリクエストを待ちます。さらに、ウィンドウ プラットフォームには、対応する独自の aio もありますが、ここでは紹介しません。ウィンドウ テクノロジ スタックを使用する場合、データベースは sqlserver を使用する必要があります。現在、他のプラットフォーム (Linux と Windows を除く) は Simulate aio のみを使用できます。

まず、いくつかの一般的な関数と構造を紹介し、次に Linux 上の Simulate alo と Native aio を詳しく紹介します。
グローバル配列は os0file.cc で定義され、タイプは os_aio_array_t です。これらの配列は、読み取りおよび書き込み要求をキャッシュするために Simulate aio によって使用されるキューです。配列の各要素は os_aio_slot_t code> Type。各 IO リクエストのタイプ、ファイルの fd、オフセット、読み取られるデータの量、IO リクエストが開始された時刻、IO リクエストが完了したかどうかなどが記録されます。さらに、Linux ネイティブ io の struct iocb も <code>os_aio_slot_t にあります。配列構造 os_aio_slot_t は、使用されたデータ要素 (os_aio_slot_t) の数、空かどうか、いっぱいかどうかなどの統計情報を記録します。このようなグローバル配列は合計 5 つあり、非同期データ ファイル読み取りリクエスト (os_aio_read_array)、データ ファイル書き込み非同期リクエスト (os_aio_write_array)、およびログ ファイルを保存するために使用されます。非同期書き込みリクエスト (os_aio_log_array)、バッファ書き込み非同期リクエスト (os_aio_ibuf_array)、データ ファイルの同期読み取りおよび書き込みリクエスト (os_aio_sync_array)。ログ ファイルのデータ ブロックの書き込みは同期 IO ですが、ここでログの書き込みに非同期リクエスト キュー (os_aio_log_array) を割り当てる必要があるのはなぜですか?その理由は、チェックポイント情報を InnoDB ログ ファイルのログ ヘッダーに記録する必要があるためです。現在、チェックポイント情報の読み取りと書き込みは、それほど緊急ではないため、依然として非同期 IO を使用して実装されています。ウィンドウ プラットフォームでは、特定のファイルに非同期 IO が使用されている場合、そのファイルは同期 IO を使用できないため、データ ファイルの同期読み取りおよび書き込み要求キュー (os_aio_sync_array) が導入されます。ログ ファイルは非同期リクエスト キューから読み取る必要はありません。ログはクラッシュ リカバリ中にのみ読み取る必要があり、クラッシュ リカバリを実行するときはデータベースがまだ使用できないため、非同期読み取りモードに入る必要はありません。 。ここで注意すべき点は、変数 innobase_read_io_threads と innobase_write_io_threads の 2 つのパラメーターが何であっても、os_aio_read_arrayos_aio_write_array は 1 つだけであるということです。ただし、os_aio_slot_tLinux では、それに応じて要素が増加し、変数が 1 増加し、要素の数が 256 増加します。たとえば、innobase_read_io_threads=4 の場合、os_aio_read_array 配列は 4 つの部分に分割され、各部分には 256 個の要素があり、各部分には 4 つのスレッドをシミュレートするために使用される独自の独立したロック、セマフォ、および統計変数があります。innobase_write_io_threads も同様です。ここから、各非同期読み取り/書き込みスレッドがキャッシュできる読み取りおよび書き込みリクエストの上限が 256 であることもわかります。この数を超えると、後続の非同期リクエストは待機する必要があります。 256 は、非同期 IO 同時実行数に対する InnoDB レイヤーの制御として理解できます。また、ファイル システム レイヤーとディスク レベルでの長さ制限もあります。<code>cat /sys/block/sda/queue/nr_requestsを使用します。 > と はそれぞれ cat /sys/block/sdb/queue/nr_requests クエリです。 os_aio_array_t,这些数组就是Simulate aio用来缓存读写请求的队列,数组的每一个元素是os_aio_slot_t类型,里面记录了每个IO请求的类型,文件的fd,偏移量,需要读取的数据量,IO请求发起的时间,IO请求是否已经完成等。另外,Linux native io中的struct iocb也在os_aio_slot_t中。数组结构os_aio_slot_t中,记录了一些统计信息,例如有多少数据元素(os_aio_slot_t)已经被使用了,是否为空,是否为满等。这样的全局数组一共有5个,分别用来保存数据文件读异步请求(os_aio_read_array),数据文件写异步请求(os_aio_write_array),日志文件写异步请求(os_aio_log_array),insert buffer写异步请求(os_aio_ibuf_array),数据文件同步读写请求(os_aio_sync_array)。日志文件的数据块写入是同步IO,但是这里为什么还要给日志写分配一个异步请求队列(os_aio_log_array)呢?原因是,InnoDB日志文件的日志头中,需要记录checkpoint的信息,目前checkpoint信息的读写还是用异步IO来实现的,因为不是很紧急。在window平台中,如果对特定文件使用了异步IO,就这个文件就不能使用同步IO了,所以引入了数据文件同步读写请求队列(os_aio_sync_array)。日志文件不需要读异步请求队列,因为只有在做奔溃恢复的时候日志才需要被读取,而做崩溃恢复的时候,数据库还不可用,因此完全没必要搞成异步读取模式。这里有一点需要注意,不管变量innobase_read_io_threads和innobase_write_io_threads两个参数是多少,os_aio_read_arrayos_aio_write_array都只有一个,只不过数据中的os_aio_slot_t元素会相应增加,在linux中,变量加1,元素数量增加256。例如,innobase_read_io_threads=4,则os_aio_read_array数组被分成了四部分,每一个部分256个元素,每个部分都有自己独立的锁、信号量以及统计变量,用来模拟4个线程,innobase_write_io_threads类似。从这里我们也可以看出,每个异步read/write线程能缓存的读写请求是有上限的,即为256,如果超过这个数,后续的异步请求需要等待。256可以理解为InnoDB层对异步IO并发数的控制,而在文件系统层和磁盘层面也有长度限制,分别使用cat /sys/block/sda/queue/nr_requestscat /sys/block/sdb/queue/nr_requests查询。
os_aio_init在InnoDB启动的时候调用,用来初始化各种结构,包括上述的全局数组,还有Simulate aio中用的锁和互斥量。os_aio_free则释放相应的结构。os_aio_print_XXX系列的函数用来输出aio子系统的状态,主要用在show engine innodb status语句中。

Simulate aio

Simulate aio相对Native aio来说,由于InnoDB自己实现了一套模拟机制,相对比较复杂。

  • 入口函数为os_aio_funcos_aio_init は、InnoDB が開始され、前述のグローバル配列や Simulate aio で使用されるロックやミューテックスなどのさまざまな構造を初期化するときに呼び出されます。 os_aio_free は、対応する構造体を解放します。 os_aio_print_XXX 一連の関数は、aio サブシステムのステータスを出力するために使用され、主に show Engine innodb status ステートメントで使用されます。

    Simulate aio

    🎜 InnoDB が独自のシミュレーション メカニズムのセットを実装しているため、ネイティブ aio と比較すると、Simulate aio は比較的複雑です。 🎜
    • 🎜 エントリ関数は os_aio_func です。デバッグ モードでは、データ ブロックが保存されているメモリ アドレスなどのパラメータがチェックされます。 、およびファイル読み取り 書き込まれたオフセットと読み書きされたデータ量が OS_FILE_LOG_BLOCK_SIZE の整数倍であるかどうか。ただし、Simulate aio は最終的に同期 IO を使用し、ファイルを開くモードで O_DIRECT が使用されているかどうかはチェックされません。ファイルを開くには O_DIRECT を使用する必要があります。 🎜
    • 検証に合格した後、os_aio_array_reserve_slot が呼び出され、この IO リクエストが特定のバックグラウンド IO 処理スレッド (innobase_xxxx_io_threads によって割り当てられますが、実際には同じグローバル配列内にあります) に割り当てられます。バックグラウンドの IO スレッド処理を容易にするための IO リクエストの関連情報。 IO リクエスト タイプが同じで、同じファイルがリクエストされ、オフセットが比較的近い場合 (デフォルトでは、オフセットの差は 1M 以内)、InnoDB は後続の手順を容易にするために 2 つのリクエストを同じ IO スレッドに割り当てます。マージ。 os_aio_array_reserve_slot,作用是把这个IO请求分配到某一个后台io处理线程(innobase_xxxx_io_threads分配的,但其实是在同一个全局数组中)中,并把io请求的相关信息记录下来,方便后台io线程处理。如果IO请求类型相同,请求同一个文件且偏移量比较接近(默认情况下,偏移量差别在1M内),则InnoDB会把这两个请求分配到同一个io线程中,方便在后续步骤中IO合并。

    • 提交IO请求后,需要唤醒后台io处理线程,因为如果后台线程检测到没有IO请求,会进入等待状态(os_event_wait)。

    • 至此,函数返回,程序可以去干其他事情了,后续的IO处理交给后台线程了。
      介绍一下后台IO线程怎么处理的。

    • InnoDB启动时,后台IO线程会被启动(io_handler_thread)。其会调用os_aio_simulated_handle从全局数组中取出IO请求,然后用同步IO处理,结束后,需要做收尾工作,例如,如果是写请求的话,则需要在buffer pool中把对应的数据页从脏页列表中移除。

    • os_aio_simulated_handle首先需要从数组中挑选出某个IO请求来执行,挑选算法并不是简单的先进先出,其挑选所有请求中offset最小的请求先处理,这样做是为了后续的IO合并比较方便计算。但是这也容易导致某些offset特别大的孤立请求长时间没有被执行到,也就是饿死,为了解决这个问题,在挑选IO请求之前,InnoDB会先做一次遍历,如果发现有请求是2s前推送过来的(也就是等待了2s),但是还没有被执行,就优先执行最老的请求,防止这些请求被饿死,如果有两个请求等待时间相同,则选择offset小的请求。

    • os_aio_simulated_handle接下来要做的工作就是进行IO合并,例如,读请求1请求的是file1,offset100开始的200字节,读请求2请求的是file1,offset300开始的100字节,则这两个请求可以合并为一个请求:file1,offset100开始的300字节,IO返回后,再把数据拷贝到原始请求的buffer中就可以了。写请求也类似,在写操作之前先把需要写的数据拷贝到一个临时空间,然后一次写完。注意,只有在offset连续的情况下IO才会合并,有间断或者重叠都不会合并,一模一样的IO请求也不会合并,所以这里可以算是一个可优化的点。

    • os_aio_simulated_handle如果发现现在没有IO请求,就会进入等待状态,等待被唤醒

    综上所述,可以看出IO请求是一个一个的push的对立面,每push进一个后台线程就拿去处理,如果后台线程优先级比较高的话,IO合并效果可能比较差,为了解决这个问题,Simulate aio提供类似组提交的功能,即一组IO请求提交后,才唤醒后台线程,让其统一进行处理,这样IO合并的效果会比较好。但这个依然有点小问题,如果后台线程比较繁忙的话,其就不会进入等待状态,也就是说只要请求进入了队列,就会被处理。这个问题在下面的Native aio中可以解决。
    总体来说,InnoDB实现的这一套模拟机制还是比较安全可靠的,如果平台不支持Native aio则使用这套机制来读写数据文件。

    Linux native aio

    如果系统安装了libaio库且在配置文件里面设置了innodb_use_native_aio=on则启动时候会使用Native aio。

    • 入口函数依然为os_aio_func,在debug模式下,依然会检查传入的参数,同样不会检查文件是否以O_DIRECT模式打开,这算是一个有点风险的点,如果用户不知道linux native aio需要使用O_DIRECT模式打开文件才能发挥出aio的优势,那么性能就不会达到预期。建议在此处做一下检查,有问题输出到错误日志。

    • 检查通过之后,与Simulated aio一样,调用os_aio_array_reserve_slot,把IO请求分配给后台线程,分配算法也考虑了后续的IO合并,与Simulated aio一样。不同之处,主要是需要用IO请求的参数初始化iocb这个结构。IO请求的相关信息除了需要初始化iocb外,也需要在全局数组的slot中记录一份,主要是为了在os_aio_print_XXX

    • IO リクエストを送信した後、バックグラウンド IO 処理スレッドを起動する必要があります。バックグラウンド スレッドが IO リクエストがないことを検出すると、待機状態 (os_event_wait) に入るからです。 。

    • この時点で、関数は戻り、プログラムは他のことを実行できるようになり、後続の IO 処理はバックグラウンド スレッドに渡されます。

      バックグラウンド IO スレッドがどのように処理されるかを紹介します。

      🎜🎜 InnoDB が開始されると、バックグラウンド IO スレッドが開始されます (io_handler_thread)。 os_aio_simulated_handle を呼び出してグローバル配列から IO リクエストを取り出し、その後、書き込みリクエストの場合、同期 IO を使用して処理を行う必要があります。の場合、対応するリクエストをバッファ プールに入れる必要があります。データ ページはダーティ ページ リストから削除されます。 🎜🎜🎜🎜os_aio_simulated_handle まず、実行する IO リクエストを配列から選択する必要があります。選択アルゴリズムは単純な先入れ先出しではなく、最小のオフセットを持つリクエストを選択します。これは、その後の IO マージの計算を容易にするためです。ただし、この問題を解決するために、InnoDB は、IO リクエストを選択する前に、最初にトラバーサルを実行します。リクエストが 2 秒前に見つかった場合、プッシュされた (つまり 2 秒間待機した) がまだ実行されていない場合、これらのリクエストが枯渇するのを防ぐために、最も古いリクエストが最初に実行されます (リクエストが 2 つある場合)。待ち時間が同じリクエストの場合、オフセットが小さいリクエストが選択されます。 🎜🎜🎜🎜os_aio_simulated_handle次に行うことは、IO マージを実行することです。たとえば、読み取りリクエスト 1 は、オフセット 100 から始まる 200 バイトのファイル 1 をリクエストし、読み取りリクエスト 2 は、オフセット 300 から始まる 100 バイトのファイル 1 をリクエストします。次に、これら 2 つのリクエストを 1 つのリクエスト (file1、offset100 から始まる 300 バイト) にマージできます。IO が戻ったら、データを元のリクエストのバッファにコピーするだけです。書き込みリクエストも同様で、書き込み操作の前に、書き込まれるデータが一時領域にコピーされてから、一度に書き込まれます。 IO はオフセットが連続している場合にのみマージされることに注意してください。中断または重複がある場合、それらはマージされないため、これは最適化ポイントと見なすことができます。 🎜🎜🎜🎜os_aio_simulated_handle現在IOリクエストが無いことが分かると待機状態に入り覚醒を待ちます🎜🎜
    🎜まとめるとこんな感じですIO リクエストは 1 つずつプッシュされます。反対に、バックグラウンド スレッドへのプッシュはすべて処理されます。バックグラウンド スレッドの優先順位が比較的高い場合、IO マージ効果が低下する可能性があります。この問題を解決するために、Simulate aio はグループ送信、つまり IO リクエスト送信のグループに似た機能。 最後に、バックグラウンド スレッドが起動されて均一に処理されるため、IO マージの効果がより良くなります。ただし、これにはまだ若干の問題があり、バックグラウンド スレッドが比較的ビジーな場合、待機状態にはなりません。つまり、リクエストがキューに入っている限り処理されます。この問題は、以下のネイティブ aio で解決できます。 🎜一般に、InnoDB によって実装されたシミュレーション メカニズムは比較的安全で信頼性が高く、プラットフォームがネイティブ aio をサポートしていない場合、このメカニズムはデータ ファイルの読み取りと書き込みに使用されます。 🎜

    Linux ネイティブ aio

    🎜 システムに libaio ライブラリがインストールされており、構成ファイルで innodb_use_native_aio=on が設定されている場合、起動時にネイティブ aio が使用されます。 🎜
      🎜🎜 デバッグ モードでは、エントリ関数は依然として os_aio_func であり、ファイルが正しいかどうかもチェックされません。 Linux ネイティブの aio を利用するには、O_DIRECT モードを使用してファイルを開く必要があることをユーザーが認識していない場合、これは少し危険です。パフォーマンスは期待どおりではありません。ここを確認して問題があればエラーログに出力することをお勧めします。 🎜🎜🎜🎜シミュレートされた aio と同様に、チェックに合格した後、os_aio_array_reserve_slot を呼び出して、IO リクエストをバックグラウンド スレッドに割り当てます。割り当てアルゴリズムでは、シミュレートされた aio と同様に、後続の IO マージも考慮されます。主な違いは、iocb 構造体を IO リクエストのパラメータで初期化する必要があることです。 iocb の初期化に加えて、IO リクエストの関連情報もグローバル配列のスロットに記録する必要があります。これは、主に os_aio_print_XXX 一連の関数での統計の便宜のためです。 🎜🎜🎜🎜 io_submit を呼び出してリクエストを送信します。 🎜🎜🎜🎜この時点で、関数は戻り、プログラムは他のことを実行できるようになり、後続の IO 処理はバックグラウンド スレッドに渡されます。 🎜次はバックグラウンド IO スレッドです。 🎜
  • Simulate aio と同様に、InnoDB の開始時にバックグラウンド IO スレッドも開始されます。 Linux ネイティブの aio の場合、後続の呼び出しは同様ですが、基本的な実装は io_getevents 関数を呼び出して IO リクエストが完了するのを待つだけです。タイムアウトは 0.5 秒です。これは、0.5 秒以内に IO リクエストが完了しない場合、関数は戻り、待機するために io_getevents を呼び出し続けます。もちろん、待機する前に、サーバーが閉じているかどうかを判断し、閉じている場合は、出口。 os_aio_linux_handle这个函数。这个函数的作用与os_aio_simulated_handle

IO スレッドを分散する場合、隣接する IO を 1 つのスレッドに配置するようにします。これは Simulate aio と似ていますが、後続の IO マージ操作は Simulate aio 自体によって実装されますが、Native aio はカーネルによって完了されます。コードは比較的シンプルです。

もう 1 つの違いは、IO リクエストがない場合、Simulate aio は待機状態になるのに対し、Native aio は 0.5 秒ごとに起動し、いくつかのチェックを行ってから待機し続けることです。したがって、新しいリクエストが来たとき、シミュレートされた aio はユーザー スレッドを起動する必要がありますが、ネイティブ aio は起動する必要がありません。さらに、サーバーのシャットダウン時に Simulate aio も起動する必要がありますが、Native aio は起動しません。

ネイティブ aio は Simulate aio と似ており、リクエストも 1 つずつ送信され、1 つずつ処理されるため、IO マージ効果が低下します。 Facebook チームは、ネイティブ aio グループ送信の最適化を提出しました。最初に IO リクエストをキャッシュし、次に io_submit 関数を呼び出して、以前のすべてのリクエストを一度に送信します (io_submit は一度に複数のリクエストを送信できます)。これにより、カーネルがより便利になります。 IO の最適化を実行します。 Simulate aio が IO スレッドに大きな負荷をかけている場合、グループ送信の最適化は失敗しますが、Native aio は失敗しません。グループ送信は最適化されており、aio 待機キューの長さを超えると、強制的に io_submit が開始されることに注意してください。

まとめ

この記事では、InnoDBにおけるIOサブシステムの実装と、利用する際に注意すべき点について詳しく紹介します。 InnoDB ログは同期 IO を使用し、データは非同期 IO を使用し、非同期 IO の書き込み順序は先入れ先出し方式ではないことに注意する必要があります。 Simulate aio には比較的大きな学習価値がありますが、最新のオペレーティング システムでは、Native aio を使用することをお勧めします。


上記は、MySQL、エンジン機能、InnoDB IO サブシステムの詳細な紹介です。その他の関連コンテンツについては、PHP 中国語 Web サイト (www.php.cn) に注目してください。



声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。