ストレージには主に6種類のスレッドが含まれます
ストレージスレッドスタック
gdbattach storage プロセス ID は、各スレッドのスタックを確認できます。
スレッドを受け入れる
メインスレッドは受け入れスレッドです
ニオスレッド
nio はネットワーク IO であり、主にネットワーク IO 処理を担当します
nio スレッドは storage_service_init、work_thread_entrance で作成されます
nio スレッドのデフォルト数は次のとおりです。 4 では、スレッド 2 から 5 までスタックが epoll_wait で停止することがわかります。
報告スレッド
スレッド 6 は tracker_report_thread_entrance で、トラッカーに情報を報告し、他のストレージ情報を取得するためのストレージとして使用されます。
スレッドのスケジュール設定
スレッド 7 はスケジューリング スレッド sched_thread_entrance で、主に、バイナリ バッファをバイナリ ファイルに定期的に書き込むなど、一部のタスクを定期的にスケジュールするために使用されます。
このスレッドには、ログの同期とタイム ホイール タイマーも含まれています。
ディオスレッド
dio はデータ IO で、主にファイルの読み取りと書き込みに使用されます。
スレッド 8 とスレッド 9 のスタックは同じで、どちらも dio スレッド dio_thread_entrance です。
ストレージのデフォルト設定ファイルによれば、一方のスレッドはディスクの読み取りスレッド、もう一方のスレッドはディスクの書き込みスレッドとなり、デフォルトでは読み取りと書き込みが分離されています。
ストレージの主な処理と通信 5スレッド
この記事ではレポート スレッドについては紹介しませんが、主に他の 5 種類のスレッドを紹介します。ファイルのアップロードを例に挙げると、各スレッドのフローチャートは以下のとおりです。
スレッドを受け入れる
受け入れスレッドの数を構成できます。デフォルトでは 1 つだけです。メイン スレッドは受け入れスレッドです。これは主に、接続を受け入れ、アイドル状態のタスク キュー (オブジェクト プール) から fast_task_info オブジェクトを取得するために使用されます。
オブジェクト プールは、スレッド数に応じて storage_service_init に事前に割り当てられます。
bytes = sizeof(struct storage_nio_thread_data) * g_work_threads;
g_nio_thread_data = (struct storage_nio_thread_data *)malloc(bytes);
fast_task_info は、各クライアント (fd) に対応するデータで、読み取りバッファと書き込みバッファ、および他のスレッドと nio スレッド間の通信用の 1 対のパイプが含まれます。nio スレッドはパイプの読み取り端にバインドされています。
クライアントがストレージに接続すると、accept スレッドは fast_task_info オブジェクトのアドレスをパイプに書き込み、nio スレッドが storage_recv_notify_read をコールバックします。クライアントが接続すると、初期化関数 storage_nio_init がこれは、epoll 読み取りイベントを登録するために呼び出されます。
ニオスレッド
一般にワーカー スレッドとして知られる nio スレッドは、主にネットワーク IO を担当し、中心となる関数は epoll_wait です。次に、イベント タイプに基づいてさまざまなコールバック関数を呼び出します。
- client_sock_read
- client_sock_write
- storage_recv_notify_read
nio スレッドは主に 3 つの機能を担当していることがわかります。
- クライアントからデータを読み取って処理する
- クライアントにデータを送信する
- パイプを介して他のスレッドから通知を受信する
クライアントがファイルをアップロードすると、コールバック client_sock_read が n 回トリガーされます。
初めて完全なパッケージを受信した後、コマンドに従ってタスク (storage_deal_task) を処理します。ファイルをアップロードするコマンドは STORAGE_PROTO_CMD_UPLOAD_FILE です。storage_write_to_file の最後に、storage_dio_queue_push が呼び出されます。 blocked_queue_push は storage_dio_queue_push で呼び出され、 fast_task_info をブロッキング キューに挿入し、後で dio スレッドが処理のためにブロッキング キューからタスクを取得します。
完全なパケットを受信するのが初めてではない場合、storage_dio_queue_push が直接呼び出されます。
ファイルがアップロードされたら、client_sock_write を呼び出してクライアントに通知します。
他のスレッドが nio スレッドに通知したい場合、パイプに書き込み、コールバック storage_recv_notify_read をトリガーします。
たとえば、accept スレッドは、fd の読み取りイベントを登録するように nio スレッドに通知します。
dio スレッドは、ファイルの一部を複数回書き込んだ後、データの読み取りを続けるように nio スレッドに通知します。ここでファイルが複数回書き込まれるのはなぜですか? 大きなファイルは 1 回の IO で処理できないためです。デフォルトでは、FastDFS は一度に最大 256K データの受信を設定します。クライアントが 1M ファイルをアップロードすると仮定すると、>1M データ (プロトコルを含む) headers) が実際に送信されるため、nio スレッドは recv を 5 回、blocked_queue_push を 5 回呼び出す必要があり、dio スレッドはタスクを 5 回フェッチし、データを 5 回書き込む必要があります。
ディオスレッド
dio スレッドは主にファイルの読み取りと書き込みを担当します。デフォルトでは、読み取りスレッドと書き込みスレッドが 1 つずつ存在します。
dio_thread_entrance 関数は非常に単純で、ブロッキング キューからタスクを取得して実行するだけです。
クライアントがファイルをアップロードした後、取り出されるタスク関数は dio_write_file です。まず dio_open_file を呼び出してファイルを開き、次に fc_safe_write を呼び出してファイルに書き込みます。
pFileContext->offset < pFileContext->end の場合、つまりファイルの書き込みが終了していない場合、nio スレッドはデータの受信を続行するように通知されます。
nio スレッドがデータを再度受信すると、タスクをブロッキング キューに再度書き込み、dio スレッドがタスクを再度フェッチしてファイルに書き込みます。
ファイルが書き込まれたことが pFileContext->offset >= pFileContext->end で示されている場合、dio スレッドは binlog を更新する必要があります。
ファイルが追加/削除/更新されるたびに、レコードが binlog に追加され、同じグループ内のすべてのストレージにファイルを保存および同期するために使用されます。
ストレージの同期については後で紹介します。この記事ではスレッドに焦点を当てます。
binlog_write_cache_buff と binlog_write_cache_len は storage_binlog_write_ex で更新されます
int storage_binlog_write_ex(const int timestamp, const char op_type, \
const char *filename, const char *extra)
{
int result;
int write_ret;
if ((result=pthread_mutex_lock(&sync_thread_lock)) != 0)
{
logError("file: "__FILE__", line: %d, " \
"call pthread_mutex_lock fail, " \
"errno: %d, error info: %s", \
__LINE__, result, STRERROR(result));
}
// 更新binlog_write_cache_buff和binlog_write_cache_len
if (extra != NULL)
{
binlog_write_cache_len += sprintf(binlog_write_cache_buff + \
binlog_write_cache_len, "%d %c %s %s\n",\
timestamp, op_type, filename, extra);
}
else
{
binlog_write_cache_len += sprintf(binlog_write_cache_buff + \
binlog_write_cache_len, "%d %c %s\n", \
timestamp, op_type, filename);
}
//check if buff full
if (SYNC_BINLOG_WRITE_BUFF_SIZE - binlog_write_cache_len < 256)
{
write_ret = storage_binlog_fsync(false); //sync to disk
}
else
{
write_ret = 0;
}
if ((result=pthread_mutex_unlock(&sync_thread_lock)) != 0)
{
logError("file: "__FILE__", line: %d, " \
"call pthread_mutex_unlock fail, " \
"errno: %d, error info: %s", \
__LINE__, result, STRERROR(result));
}
return write_ret;
}
dio スレッドとスケジューリング スレッドの両方が binlog バッファにアクセスするため、ここでは同期スレッド ロックが追加されることに注意してください。
binlog.000 ファイル形式:
タイムスタンプ + 操作タイプ + ファイル ID
binlog バッファーが更新され、ファイルのアップロードが完了すると、storage_nio_notify が呼び出され、クライアントに通知するパイプラインが作成されます。
スレッドのスケジュール設定
スレッドのスケジューリングの主な機能は次のとおりです。
- タイムホイールタイマーを使用してタイムアウトタスクを処理する
- 登録されたスケジュールされたタスクをループする
ログ同期、binglog 同期 (fdfs_binlog_sync_func)、ステータス ファイル同期、トランク binlog 同期、その他のスケジュール タスクは setupSchedules に登録されます。
上記のことから、アップロードされたファイルの処理後に dio スレッドが binlog バッファに書き込むことがわかります。このとき、binlog_write_cache_len が増加します。スケジューリング スレッドが fdfs_binlog_sync_func を再度呼び出すと、binlog_write_cache_len > 0 になります。条件が満たされると、 storage_binlog_fsync が呼び出されます。
int fdfs_binlog_sync_func(void *args)
{
if (binlog_write_cache_len > 0)
{
return storage_binlog_fsync(true);
}
else
{
return 0;
}
}
fdfs_binlog_sync_func では、永続性を実現するために、binlog バッファーが binlog ファイルに書き込まれます。 ++binlog_write_version を書き込んだ後の目的は、同期スレッドで同期を開始することです。
同期スレッド
同期スレッドは、レポート スレッド tracker_report_thread_entrance に作成されます。 n 個のストレージがあると仮定すると、各ストレージは n-1 個の同期スレッドを作成して、それぞれファイルを他のストレージに同期します。
同期スレッドは、binlog ステータスを検出するためにループします。storage_binlog_read 関数の storage_binlog_preread で、pReader->binlog_buff.version が binlog_write_version と異なることが検出されると、binlog ファイルが読み取られます。
if (pReader->binlog_buff.version == binlog_write_version &&
pReader->binlog_buff.length == 0)
{
// binlog文件没有更新则直接返回
return ENOENT;
}
binlog レコードの行が StorageBinLogRecord に正常に抽出されると、storage_binlog_read の戻り値 read_result は 0 になり、storage_sync_data を呼び出してファイルを同期できます。
ファイルを同期するときは、最初に tcpsenddata_nb を呼び出してプロトコルの前半 (つまり、プロトコル ヘッダー + ファイル名、ファイル サイズ、その他のフィールド) を送信し、次に tcpsendfile_ex を呼び出してファイルのコンテンツを送信します。 tcpsendfile_ex の中核は sendfile です。
同期ストレージ (コピー) がファイルと binlog を更新する方法については、後で説明します。