FastDFS ソース コード分析 - ストレージ スレッド分析

ストレージには主に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 つの機能を担当していることがわかります。

  1. クライアントからデータを読み取って処理する
  2. クライアントにデータを送信する
  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 が呼び出され、クライアントに通知するパイプラインが作成されます。
画像の説明を追加してください

スレッドのスケジュール設定

スレッドのスケジューリングの主な機能は次のとおりです。

  1. タイムホイールタイマーを使用してタイムアウトタスクを処理する
  2. 登録されたスケジュールされたタスクをループする

ログ同期、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 を更新する方法については、後で説明します。

おすすめ

転載: blog.csdn.net/ET_Endeavoring/article/details/124104810