Ceph 整合性チェックツールのスクラブメカニズム

この章では、Ceph の整合性チェック ツールのスクラブ メカニズムを紹介します。最初にデータ検証の基礎知識を紹介し、次にスクラブの基本概念を紹介し、次にスクラブのスケジューリング メカニズムを紹介し、最後にスクラブの特定の実装のソース コード分析を紹介します。

1. エンドツーエンドのデータ検証

ストレージ システムでは、サイレント データ破損 (Silent Data Corruption) が発生することがあります。そのほとんどは、特定のデータ ビットの異常な反転 (ビット エラー レート) によって発生します。

下の図 12-1 は、一般的なストレージ システムのプロトコル スタックです。データ破損はシステムのすべてのモジュールで発生します。

セフ-第12章-1

  • メモリ、CPU、ネットワーク カードなどのハードウェア エラー。

  • SATA、FC、その他のプロトコルなどのデータ送信中の信号対ノイズ干渉。

  • RAID コントローラ、ディスク コントローラ、ネットワーク カードなどのファームウェアのバグ。

  • ソフトウェアのバグ(オペレーティング システムのカーネルのバグ、ローカル ファイル システムのバグ、SCSI ソフトウェア モジュールのバグなど)。

従来のハイエンド ディスク アレイでは、データの一貫性を達成するためにエンドツーエンドのデータ検証が一般的に使用されます。いわゆるエンドツーエンドのデータ検証とは、クライアント(アプリケーション層)がデータを書き込む際に、データブロックごとにCRC検証情報を計算し、検証情報とデータブロックをディスク(Disk)に送信することを意味します。ディスクはデータ パケットを受信すると、検証情報を再計算し、受信した検証情報と比較します。そうでない場合は、IO パス全体にエラーがあるとみなされ、IO 操作の失敗が返され、検証が成功した場合は、データ検証情報とデータがディスクに保存されます。同様に、データを読み取る場合、クライアントはデータ ブロックとディスク読み取り検証情報を取得する際に整合性を再度チェックする必要があります。

この方法により、アプリケーション層は IO リクエストのデータが一貫しているかどうかを明確に知ることができます。操作が成功した場合、ディスク上のデータは正しいはずです。

この方法では、IO パフォーマンスに影響を与えることなく、または比較的小さな影響で、データの読み取りと書き込みの整合性を向上させることができます。ただし、このアプローチにはいくつかの欠点もあります。

  • 間違った宛先アドレスによるデータ破損の問題を解決できません。

  • エンドツーエンドのソリューションでは、IO パス全体に沿った追加のパリティ情報が必要です。現在の IO プロトコル スタックには多くのモジュールが含まれており、モジュールごとにこのような検証情報を実装することは困難です。

この実装方法は Ceph の IO パフォーマンスに大きな影響を与えるため、Ceph はエンドツーエンドのデータ検証を実装しませんが、バックグラウンドでスキャンすることで Ceph の整合性チェックを解決するソリューションを使用して Ceph スクラブ メカニズムを実装します。

2. スクラブコンセプトの紹介

Ceph は、一貫性チェック用のツール Ceph Scrub を内部的に実装しています。原則は、各オブジェクト コピーのデータとメタデータを比較することにより、コピーの整合性チェックを完了することです。

この方法の利点は、ディスクの破損によって引き起こされるデータの不整合をバックグラウンドで検出できることです。欠点は、発見のタイミングが遅れることが多いことです。

スクラブは、スキャンした内容に応じて 2 つの方法に分けられます。

  • 1 つはスクラブと呼ばれるもので、オブジェクトの各コピーのメタデータを比較することによってデータの一貫性のみをチェックします。メタデータのみをチェックするため、読み込むデータ量や計算量が比較的少なく、比較的軽いチェックとなります。

  • もう 1 つはディープ スクラブと呼ばれるもので、オブジェクトのデータ内容が一貫しているかどうかをさらにチェックし、ディープ スキャンを実現し、ディスク上のほぼすべてのデータをスキャンして crc32 チェック値を計算するため、時間がかかり、時間がかかります。より多くのシステムリソース。

スクラブはスキャン方法に応じて 2 つのタイプに分類されます。

  • オンライン スキャン: システムの通常の業務には影響しません。

  • オフライン スキャン: システム全体を一時停止またはフリーズする必要があります

Ceph のスクラブ機能はオンライン検査を実装します。つまり、クライアントは、システムの現在の読み取りおよび書き込み要求を中断することなく、読み取りおよび書き込みアクセスを完了し続けることができます。システム全体は一時停止されませんが、バックグラウンドでスクラブされているオブジェクトは一時的にアクセスを防止するためにロックされ、オブジェクトがスクラブ操作を完了するまでロックが解除されてアクセスが許可されることはありません。

3. スクラブのスケジュール設定

スクラブ スケジューリングは、PG がスクラブ スキャン メカニズムを開始するときに解決します。主に以下のような方法があります。

  • 手動でスキャンをすぐに開始します。

  • バックグラウンドで一定の時間を設定し、その時間間隔に従って起動します。たとえば、デフォルトの時間は 1 日に 1 回実行されます。

  • 起動するまでの時間を設定します。一般に、スクラブ動作はシステム負荷が比較的軽い時間帯に設定されて実行されます。

3.1 関連するデータ構造

クラス PG (src/osd/osd.h) には、スクラブに関連する次のデータ構造があります。

class OSDService{
	...
public:
	Mutex sched_scrub_lock;           //Scrub相关变量的保护锁
	int scrubs_pending;               //资源预约已经成功,正等待Scrub的PG
	int scrubs_active;                //正在进行Scrub的PG
	set<ScrubJob> sched_scrub_pg;     //PG对应的所有ScrubJob列表
};

構造 ScrubJob は、PG スクラブ タスクに関連するパラメーターをカプセル化します。

struct ScrubJob {

	//Scrub对应的PG
	spg_t pgid;
	

	//Scrub任务的调度时间,如果当前负载比较高,或者当前的时间不在设定的Scrub工作时间段
	//内,就会延迟调度
	utime_t sched_time;
	
	//调度时间的上限,过了该时间必须进行Scrub操作,而不受系统负载和Scrub时间段的限制
	utime_t deadline;
}

3.2 スクラブスケジューリングの実装

OSD 初期化関数 OSD::init() では、スケジュールされたタスクが登録されます。

tick_timer_without_osd_lock.add_event_after(cct->_conf->osd_heartbeat_interval, new C_Tick_WithoutOSDLock(this));

タイミング タスクは、osd_heartbeat_interval 期間ごとにタイマーのコールバック関数 OSD::tick_without_osd_lock() をトリガーします (デフォルトは 1 秒です)。処理プロセスの関数呼び出し関係は、以下の図 12-2 に示されています。

セフ-第12章-2

上記の関数は、PG のスクラブ スケジューリング作業を実現します。処理プロセスにおける 2 つの重要な関数 OSD::sched_scrub() と PG::sched_scrub() の実装を以下に紹介します。

3.2.1 OSD::sched_scrub() 関数

この関数は、PG のスクラブ処理の開始タイミングを制御するために使用され、具体的な処理は次のとおりです。

1) can_inc_scrubs_pending() 関数を呼び出して、PG がスクラブ操作を開始できるようにするクォータがあるかどうかを確認します。変数 scrubs_pending は、リソース予約を完了し、スクラブを待機している PG の数を記録し、変数 scrubs_active は、スクラブされている PG の数を記録します。2 つの数値の合計は、システム構成パラメーター cct- の値を超えることはできません。 >_conf->osd_max_scrubs。この値は、同時にスクラブできる PG の最大数を設定します。

2) 関数 scrub_time_permit() を呼び出して、許可された期間内であるかどうかを確認します。cct->_conf->osd_scrub_begin_hour が cct->_conf->osd_scrub_end_hour より小さい場合、現在の時刻は 2 つで設定された時間範囲の間にある必要があります。cct->_conf->osd_scrub_begin_hour が cct->_conf->osd_scrub_end_hour 以上の場合、現在の時刻は 2 つで設定された時間範囲外でも許可されます。

3) 関数 scrub_load_below_threshold() を呼び出して、現在のシステム負荷が許可されているかどうかを確認します。関数 getloadavg() は、過去 1 分間、5 分間、および 15 分間のシステム負荷を取得します。

  a) 過去 1 分間の負荷が cct->_conf->osd_scrub_load_threshold の設定値未満の場合、実行が許可されます。

  b) 最後の 1 分間の負荷が daily_loadavg の値より小さく、最後の 1 分間の負荷が最後の 15 分間の負荷よりも小さい場合、実行は許可されます。

4) スクラブ操作の実行を待機している最初の ScrubJob のリストを取得します。その scrub.sched_time が現在時刻の値より大きい場合は、時間が経過していないことを意味し、PG をスキップして次のタスクを最初に実行します。

5) PG の pgbackend がスクラブをサポートしており、アクティブ状態にある場合は、PG に対応する PG オブジェクトを取得します。

  a) scrub.deadline が現在の値より小さい場合、つまり期限が過ぎている場合、スクラブ操作を開始する必要があります。

  b) または、この時点では time_permit およびload_is_low、つまり、時間と負荷の両方が許可されます。

上記の 2 つのケースでは、関数 pg->sched_scrub() が呼び出されてスクラブ操作が実行されます。

3.2.2 PG::sched_scrub() 関数

この関数は、スクラブ タスクの実行時に関連パラメーターの設定を実装し、必要なリソースの予約を完了します。その処理は次のとおりです。

1) まず PG のステータスを確認します。PG はメイン OSD である必要があり、アクティブでクリーンな状態にあり、進行中のスクラブ操作はありません。

2) deep_scrub_inerval の値を設定します。PG が配置されているプール オプションに値が設定されていない場合は、システム設定パラメータ cct->_conf->osd_deep_scrub_interval の値に設定します。

3) deep_scrub を開始するかどうかを確認し、現在の時間が info.history.last_deep_scrub_stamp と deep_scrub_interval の合計より大きい場合は、deep_scrub 操作を開始します。

4) scrubber.must_scrub の値が true の場合、ユーザーに対して deep_scrub 操作を手動で開始します。値が false の場合、システムは一定の確率で deep_scrub 操作を自動的に開始する必要があります。具体的な実装は次のとおりです: 乱数を自動的に生成し、乱数が cct->_conf->osd_deep_scrub_randomize_ratio 未満の場合、deep_scrub を開始します手術。

5) 最終的に deep_scrub を開始するかどうかを決定します。いずれか 1 つがステップ 3) とステップ 4) で設定されている限り、deep_scrub 操作を開始します。

6) osdmap またはプールに deep_scrub をサポートしないマークがある場合は、time_for_deep を false に設定し、deep_scrub 操作を開始しないでください。

7) osdmap またはプールにスクラブをサポートしないマークがあり、deep_scrub 操作が開始されていない場合は、戻って終了します。

8) cct->_conf->osd_scrub_auto_repair が自動修復に設定されており、pgbackend もサポートしており、deep_scrub 操作である場合、以下の判定処理が実行されます。

  a) ユーザーがmust_repair、must_scrub、またはmust_deep_scrubを設定した場合、このスクラブ操作はユーザーによってトリガーされ、システムはユーザーの選択を尊重し、scrubber.auto_repairの値を自動的にtrueに設定しないことを意味します。

  b) それ以外の場合、システムは自動的に修復するために scrubber.auto_repair の値を true に設定します。

9) スクラブ プロセスはリカバリ プロセスと似ており、どちらも大量のシステム リソースを消費する必要があり、PG が配置されている OSD 上でリソースを予約する必要があります。scrubber.reserved の値が false で、リソース予約が完了していない場合は、次の操作を実行する必要があります。

  a) 自分自身を scrubber.reserved_peers に追加します。

  b) 関数 scrub_reserve_replicas() を呼び出して CEPH_OSD_OP_SCRUB_RESERVE メッセージを OSD に送信し、リソースを予約します。

  c) scrubber.reserved_peers.size() がacting.size() と等しい場合、すべてのセカンダリ OSD リソース予約が成功し、PG が PG_STATE_DEEP_SCRUB 状態に設定されていることを意味します。関数 queue_scrub() を呼び出して PG をワーク キュー op_wq に追加し、スクラブ タスクの実行を開始します。

4. スクラブの実行

スクラブの具体的な実行プロセスは大まかに次のとおりです。メタデータとデータの検証は、オブジェクトの各 OSD 上のコピーのメタデータとデータを比較することによって完了します。その核となる処理フローは関数 PG::chunky_scrub() で制御され、完了します。

4.1 関連するデータ構造

スクラブ操作に関連する主なデータ構造は 2 つあります。1 つはスクラブ制御構造で、スクラブ操作のコンテキストに相当し、PG の操作プロセスを制御します。もう 1 つは、ScrubMap が、比較する必要があるオブジェクトの各コピーのメタデータとデータ概要情報を保存することです。

1)  Scrubber 構造 Scrubber は、PG (src/osd/pg.h) のスクラブ プロセスを制御するために使用されます。

// -- scrub --
struct Scrubber {
	// 元数据
	set<pg_shard_t> reserved_peers;                //资源预约的shard
	bool reserved, reserve_failed;                 //是否预约资源,预约资源是否失败
	epoch_t epoch_start;                           //开始Scrub操作的epoch
	
	// common to both scrubs
	bool active;                                   //Scrub是否开始
	
	/*
	 * 当PG有snap_trim操作时,如果检查Scrubber处于active状态,说明正在进行Scrub操作,那么
	 * snap_trim操作暂停,设置queue_snap_trim的值为true。当PG完成Scrub任务后,如果queue_snap_trim
	 * 的值为true,就把PG添加到相应的工作队列里,继续完成snap_trim操作
	 */
	bool queue_snap_trim;
	int waiting_on;                                //等待的副本计数
	set<pg_shard_t> waiting_on_whom;               //等待的副本
	int shallow_errors;                            //轻度扫描错误数
	int deep_errors;                               //深度扫描错误数
	int fixed;                                     //已修复的对象数

	ScrubMap primary_scrubmap;                     //主副本的ScrubMap
	map<pg_shard_t, ScrubMap> received_maps;       //接收到的从副本的ScrubMap
	OpRequestRef active_rep_scrub;
	utime_t scrub_reg_stamp;                       //stamp we registered for
	
	// For async sleep
	bool sleeping = false;
	bool needs_sleep = true;
	utime_t sleep_start;
	
	// flags to indicate explicitly requested scrubs (by admin)
	bool must_scrub, must_deep_scrub, must_repair;
	
	// Priority to use for scrub scheduling
	unsigned priority;
	
	// this flag indicates whether we would like to do auto-repair of the PG or not
	bool auto_repair;

	// Maps from objects with errors to missing/inconsistent peers
	map<hobject_t, set<pg_shard_t>, hobject_t::BitwiseComparator> missing;            //扫描出的缺失对象
	map<hobject_t, set<pg_shard_t>, hobject_t::BitwiseComparator> inconsistent;       //扫描出的不一致对象
	
	/*
	 * Map from object with errors to good peers
	 * 如果所有副本对象中有不一致的对象,authoritative记录了正确对象所在的OSD
	 */
	map<hobject_t, list<pair<ScrubMap::object, pg_shard_t> >, hobject_t::BitwiseComparator> authoritative;
	
	// Cleaned map pending snap metadata scrub
	ScrubMap cleaned_meta_map;
	
	// digest updates which we are waiting on
	int num_digest_updates_pending;                //等待更新digest的对象数
	
	// chunky scrub
	hobject_t start, end;                          //扫描对象列表的开始和结尾
	eversion_t subset_last_update;                 //扫描对象列表中最新的版本号
	
	// chunky scrub state
	enum State {
		INACTIVE,
		NEW_CHUNK,
		WAIT_PUSHES,
		WAIT_LAST_UPDATE,
		BUILD_MAP,
		WAIT_REPLICAS,
		COMPARE_MAPS,
		WAIT_DIGEST_UPDATES,
		FINISH,
	} state;
	
	std::unique_ptr<Scrub::Store> store;
	
	bool deep;                                    //是否为深度扫描
	uint32_t seed;                                //计算crc32校验码的种子
	
	list<Context*> callbacks;
};

2) スクラブマップ

データ構造 ScrubMap は、検証されるオブジェクトと対応する検証情報 (src/osd/osd_types.h) を保存します。

struct ScrubMap {
	struct object {
		map<string,bufferptr> attrs;           //对象的属性
		set<snapid_t> snapcolls;               //该对象所有的snap序号
		uint64_t size;                         //对象的size
		__u32 omap_digest;                     //omap的crc32c校验码
		__u32 digest;                          //对象数据的crc32校验码
		uint32_t nlinks;                       //snap对象(clone对象)对应的snap数量
		bool negative:1;                       
		bool digest_present:1;                 //是否计算了数据的校验码标志
		bool omap_digest_present:1;            //是否有omap的校验码标志
		bool read_error:1;                     //读对象的数据出错标志
		bool stat_error:1;                     //调用stat获取对象的元数据出错标志
		bool ec_hash_mismatch:1;               //
		bool ec_size_mismatch:1;
	};

	bool bitwise;                             // ephemeral, not encoded

	//需要校验的对象(hobject) -> 校验信息(object)的映射
	map<hobject_t,object, hobject_t::ComparatorWithDefault> objects;
	eversion_t valid_through;
	eversion_t incr_since;
};

内部クラス オブジェクトは、次の 5 つの側面を含む、オブジェクトが検証する必要がある情報を保存するために使用されます。

  • 物の大きさ(サイズ)

  • オブジェクトの属性 (attrs)

  • オブジェクトomapのチェックコード(ダイジェスト)

  • オブジェクトデータのチェックサム(ダイジェスト)

  • オブジェクトのすべてのクローンオブジェクトのスナップショットシーケンス番号

4.2 スクラブ制御プロセス

スクラブ タスクは OSD ワーク キュー OpWq によって完了し、対応する処理関数 pg->scrub(handle) が呼び出されて実行されます。

void PG::scrub(epoch_t queued, ThreadPool::TPHandle &handle){
	...
	chunky_scrub(handle);
}

PG::scrub() 関数は、最後に PG::chunky_scrub() 関数を呼び出して実装します。PG::chunky_scrub() 関数は、スクラブ操作の状態遷移とコア処理を制御します。

具体的な分析プロセスは次のとおりです。

1) Scrubber の初期状態は PG::Scrubber::INACTIVE であり、この状態の処理は次のようになります。

  a) scrubber.epoch_start の値を info.history.same_interval_since に設定します。

  b) scrubber.active の値を true に設定します。

  c) 状態 scrubber.state を PG::Scrubber::NEW_CHUNK に設定します。

  d)peer_featuresに従って、scrubber.seedのタイプを設定します。このシードは、crc32の初期化ハッシュ値を計算します。

2) PG::Scrubber::NEW_CHUNKステータスの処理は以下の通り

  a) get_pgbackend()->objects_list_partial() 関数を呼び出して、開始オブジェクトから開始するオブジェクトのグループをスキャンします。一度にスキャンされるオブジェクトの数は、次の 2 つの構成パラメータの間です: cct->_conf->osd_scrub_chunk_min (デフォルト値は 5) および cct ->_conf->osd_scrub_chunk_max (デフォルト値は 25)

  b) オブジェクトの境界を計算します。同一のオブジェクトは同じハッシュ値を持ちます。リストの最後から区切って、異なるハッシュ値を持つオブジェクトを探します。この目的は、オブジェクトのすべての関連オブジェクト (スナップショット オブジェクト、ロールバック オブジェクト) をスキャン検証プロセスに分割することです。

  c) 関数 _range_available_for_scrub() を呼び出してリスト内のオブジェクトを確認し、ブロックされたオブジェクトがある場合は、done の値を true に設定し、PG のスクラブ プロセスを終了します。

  d) pg_log に従ってオブジェクトの最初から最後までの最大更新バージョン番号を計算し、最新のバージョン番号は scrubber.subset_last_update に設定されます。

  e) 関数 _request_scrub_map() を呼び出してすべてのレプリカにメッセージを送信し、対応する ScrubMap の検証情報を取得します。

  f) ステータスを PG::Scrubber::WAIT_PUSHES に設定します。

3) PG::Scrubber::WAIT_PUSHES状態の処理は以下の通り

  a) active_pushes の値が 0 の場合、状態を PG::Scrubber::WAIT_LAST_UPDATE に設定し、次の状態の処理に入ります。

  b) active_pushes が 0 でない場合、PG が回復動作中であることを意味します。直接終了するには、done の値を true に設定します。chunky_scrub() に入るとき、PG は CLEAN 状態である必要があり、リカバリ操作はありません。リカバリ操作は、最後の chunky_scrub() 操作後の修復操作である可能性があります。

4) PG::Scrubber::WAIT_LAST_UPDATE 状態の処理は次のとおりです。

  a) last_update_applied の値が scrubber.subset_last_update の値より小さい場合、操作はログに書き込まれましたが、オブジェクトには適用されていないことを意味します。スクラブ操作以降のステップにはオブジェクトの読み取り操作があるため、ログ適用が完了するまで待つ必要があります。この PG のスクラブ プロセスを終了するには、done の値を true に設定します。

  b) それ以外の場合は、状態を PG::Scrubber::BUILD_MAP に設定します。

5) PG::Scrubber::BUILD_MAP 状態の処理は次のとおりです。

  a) 関数 build_scrub_map_chunk() を呼び出して、メイン OSD 上のオブジェクトの ScrubMap 構造を構築します。

  b) 構築が成功すると、scrubber.waiting_on カウントの値が 1 減らされ、scrubber.waiting_on_whom がキューから削除され、対応する状態が PG::Scrubber.WAIT_REPLICAS に設定されます。

6) PG::Scrubber::WAIT_REPLICAS 状態の処理は次のとおりです。

  a) scrubber.waiting_on がゼロでない場合は、レプリカ要求に対する応答がないことを意味し、done の値を true に設定し、終了して待機します。

  b) それ以外の場合は、PG::Scrubber::CAMPARE_MAPS 状態に入ります。

7) PG::Scrubber::COMPARE_MAPS ステータスの処理は次のとおりです。

  a) 関数 scrub_compare_maps() を呼び出して、各コピーの検証情報を比較します。

  b) パラメータ scrubber.start の値を scrubber.end に更新します。

  c) 関数 requeue_ops() を呼び出して、スクラブによってブロックされた読み取りおよび書き込み操作を実行する操作キューに再度追加します。

  d) 状態は PG::Scrubber::WAIT_DIGEST_UPDATES に設定されます。

8) PG::Scrubber::WAIT_DIGEST_UPDATES 状態の処理は次のとおりです。

  a) 待機中の scrubber.num_digest_updates_pending がある場合は、更新されたデータのダイジェストまたは omap のダイジェストを待ちます。

  b) scrubber.end が hobject_t::get_max() より小さい場合、この PG にはスクラブ操作を完了したオブジェクトがなく、ステータス scrubber::state を PG::Scrubber::NEW_CHUNK に設定し、追加を続けます。 PG から osd->scrub_wq へ。

  c) それ以外の場合は、ステータスを PG::Scrubber::FINISH の値に設定します。

9) PG::Scrubber::FINISH 状態の処理は次のとおりです。

  a) 関数 scrub_finish() を呼び出して、関連する統計情報を設定し、矛盾したオブジェクトの修復をトリガーします。

  b) 状態を PG::Scrubber::INACTIVE に設定します。

4.3 スクラブマップの構築

ScrubMap を構築するための関数実装は複数あります。これらについては以下で説明します。

1. build_scrub_map_chunk() 関数

build_scrub_map_chunk() 関数は、最初から最後まですべてのオブジェクトの検証情報を構築し、ScrubMap 構造に保存するために使用されます。

int PG::build_scrub_map_chunk(
  ScrubMap &map,
  hobject_t start, hobject_t end, bool deep, uint32_t seed,
  ThreadPool::TPHandle &handle);

処理分析は次のようになります。

1) map.valid_through の値を info.last_update に設定します。

2) get_pgbackend()->objects_list_range() 関数を呼び出して、開始と終了の範囲内のすべてのオブジェクトをリストします。ls キューにはヘッド オブジェクトとスナップ オブジェクトが格納され、rollback_obs キューにはロールバック用の ghobject_t オブジェクトが格納されます。

3) 関数 get_pgbackend()->be_scan_list() を呼び出してオブジェクトをスキャンし、ScrubMap 構造を構築します。

4) 関数 _scan_rollback_obs() を呼び出してロールバック オブジェクトを確認します。オブジェクトの生成が last_rollback_info_trimmed_to_applied 値より小さい場合は、オブジェクトを削除します。

5) _scan_snaps() を呼び出して、SnapMapper に保存されたスナップ情報を修復します。

2. _scan_snaps()

関数 _scan_snaps() は、ヘッド オブジェクトによって保存されたスナップ情報が、SnapMapper に保存されたオブジェクトのスナップ情報と一致するかどうかをスキャンします。前者で保存したオブジェクトスナップ情報を基準とし、SnapMapperで保存したオブジェクトスナップ情報を修復します。

void PG::_scan_snaps(ScrubMap &smap);

具体的な実装プロセスは次のとおりです。ScrubMap ループ内の各オブジェクトに対して、次の操作を実行します。

1) オブジェクトの hoid.snap の値が CEPH_MAXSNAP の値より小さい場合、オブジェクトはスナップ オブジェクトであり、object_info_t 情報は o.attrs[OI_ATTR] から取得されます。

2) オイのスナップを確認します。oi.snaps.empty() が 0 の場合は nlinks を 1 に設定し、io.snaps.size() が 1 の場合は nlinks を 2 に設定し、それ以外の場合は nlinks を 3 に設定します。

3) oi から oi_snaps を取得し、snap_mapper から cur_snaps を取得し、2 つのスナップ情報を比較すると、oi の情報が優先されます。

  a) 関数 snap_mapper.get_snaps(hoid, &cur_snaps) の結果が -ENOENT の場合、その情報を snap_mapper に追加します。

  b) 情報が矛盾している場合は、snap_mapper 内の矛盾しているオブジェクトを削除してから、そのオブジェクトのスナップ情報を snap_mapper に追加します。

3. be_scan_list()

関数 be_scan_list() は、ScrubMap 内のオブジェクトの検証情報を構築するために使用されます。

void PGBackend::be_scan_list(
  ScrubMap &map, const vector<hobject_t> &ls, bool deep, uint32_t seed,
  ThreadPool::TPHandle &handle);

具体的なプロセスは、ls ベクトル内のオブジェクトをループでスキャンすることです。

1) store->stat() を呼び出して、オブジェクトの統計情報を取得します。

  a) 取得が成功した場合、o.size の値を st.st_size に等しく設定し、store->getattrs() を呼び出してオブジェクトの属性情報を o.attrs に保存します。

  b) stat によって返された結果 r が -ENOENT の場合、オブジェクトを直接スキップします (このオブジェクトはこの OSD に存在しない可能性があり、後で結果を比較するときにチェックアウトされます)。

  c) stat によって返された結果 r が -EIO の場合、o.stat_error の値を true に設定します。

2) deep の値が true の場合、関数 be_deep_scrub() を呼び出してディープ スキャンを実行し、オブジェクトの omap とデータのダイジェスト情報を取得します。

4.be_deep_scrub()

関数 be_deep_scrub() は、オブジェクトのディープ スキャンを実装します。

void ReplicatedBackend::be_deep_scrub(
  const hobject_t &poid,                             //深度扫描的对象
  uint32_t seed,                                     //crc32的种子
  ScrubMap::object &o,                               //保存对应的校验信息
  ThreadPool::TPHandle &handle);

導入プロセスは次のように分析されます。

1) データと omap のバッファハッシュの初期値をシードに設定します。

2) 関数store->read()はオブジェクトのデータを読み取るために周期的に呼び出され、各読み取りの長さは構成パラメータcct->_conf->osd_deep_scrub_stride(512k)であり、crc32チェック値は次のようにカウントされます。バッファハッシュ。途中でエラーがある場合 (r==-EIO)、o.read_error の値を true に設定します。最後に、crc32 のチェック値を計算するために o.digest を設定し、o.digest_present の値を true に設定します。

3) 関数store->omap_get_header()を呼び出してヘッダを取得し、オブジェクトのomapのキーバリュー値を繰り返し取得し、ヘッダとキーバリューのダイジェスト情報を計算してo.omap_digestに設定します。そして、 o.omap_digest_present の値を true としてマークします。

要約すると、オブジェクトのメタデータは be_scan_list() 関数を通じて取得され、オブジェクトのデータと omap のダイジェスト情報は be_deep_scrub() 関数を通じて取得され、ScrubMap 構造に格納されます。

4.4 レプリカからの処理

スレーブレプリカは、オブジェクトの検証情報を取得するためにマスターレプリカから送信されたMOSDRepScrubタイプのメッセージを受信すると、関数replica_scrub()を呼び出して完了します。

void PG::replica_scrub(
  OpRequestRef op,
  ThreadPool::TPHandle &handle);

関数plica_scrub()の具体的な実装は次のとおりです。

1) まず、scrubber.active_rep_scrub が空でないことを確認します。

2) msg​​->map_epoch の値が info.history.same_interval_since の値より小さいかどうかを確認し、直接戻ります。ここでは、廃止された MOSDRepScrub リクエストがコピーから直接破棄されます。

3) last_update_applied の値が msg->scrub_to の値より小さい場合、つまり、コピーからのログ適用を完了する操作がメイン コピーのスクラブ操作のバージョンより遅れている場合、それらが完了するまで待つ必要があります。一貫性を保つこと。現在の操作操作を scrubber.active_rep_scrub に保存して待ちます。

4) active_pushes が 0 より大きい場合、進行中のリカバリ操作があることを示し、現在の操作操作を scrubber.active_rep_scrub に保存して待機します。

5) それ以外の場合は、関数 build_scrub_map_chunk() を呼び出して ScrubMap を構築し、それをマスター コピーに送信します。

待機中のローカル操作アプリケーションが完了したら、関数 ReplicatedPG::op_applied() をチェックインします。scrubber.active_rep_scrub が空でなく、操作のバージョンが msg->scrub_to に等しい場合、保存された操作操作が元に戻されます。 osd->op_wq リクエスト キューに追加し、リクエストの完了を続行します。

4.5 コピーの比較

オブジェクトのマスター コピーとスレーブ コピーの両方が検証情報の構築を完了し、対応する構造 ScrubMap に格納したら、次のステップでは、各コピーの検証情報を比較して整合性チェックを完了します。まず、オブジェクト自体の情報を通じて権限のあるオブジェクトを選択し、次にその権限のあるオブジェクトを使用して他のオブジェクトと比較してテストします。比較に使用する関数は以下のとおりです。

4.5.1 scrub_compare_maps()

scrub_compre_maps() 関数は、異なるコピーの情報が一致しているかどうかを実現するための処理フローです。

void PG::scrub_compare_maps();

1) まず、acting.size() が 1 より大きいことを確認します。PG に OSD が 1 つしかない場合、比較できません。

2) 動作中のバックフィルの OSD に対応する ScrubMap をマップに配置します。

3) be_compare_scrubmaps() 関数を呼び出して各コピーのオブジェクトを比較し、オブジェクトの完全なコピーが権限のあるシャードに保存されます。

4) _scrub() 関数を呼び出して、スナップ間のオブジェクトの一貫性の比較を続けます。

4.5.2 be_compare_scrubmaps()

be_compare_scrubmaps() 関数は、オブジェクトの各コピーの整合性を比較するために使用され、具体的な処理プロセスは次のように分析されます。

void PGBackend::be_compare_scrubmaps(
  const map<pg_shard_t,ScrubMap*> &maps,
  bool repair,
  map<hobject_t, set<pg_shard_t>, hobject_t::BitwiseComparator> &missing,
  map<hobject_t, set<pg_shard_t>, hobject_t::BitwiseComparator> &inconsistent,
  map<hobject_t, list<pg_shard_t>, hobject_t::BitwiseComparator> &authoritative,
  map<hobject_t, pair<uint32_t,uint32_t>, hobject_t::BitwiseComparator> &missing_digest,
  int &shallow_errors, int &deep_errors,
  Scrub::Store *store,
  const spg_t& pgid,
  const vector<int> &acting,
  ostream &errorstream);

1) まず、すべてのレプリカ OSD 上のオブジェクトの結合であるマスター セットを構築します。

2) マスター セット内の各オブジェクトに対して次の操作を実行します。

  a) 関数 be_select_auth_object() を呼び出して、権限のあるオブジェクトを使用したコピー認証を選択します。権限のあるオブジェクトが選択されていない場合は、変数 Shallow_errors に 1 を追加して、このエラーを記録します。

  b) 関数 be_compare_scrub_objects() を呼び出して、各シャード上のオブジェクトと権限のあるオブジェクトを比較します。それぞれ、データのダイジェスト、omap_digest、および omap の属性を比較します。

* 如果结果为clean,表明该对象和权威对象的各项比较完全一致,就把该shard添加到auth_list列表中;

* 如果结果不为clean,就把该对象添加到cur_inconsistent列表中,分别统计shallow_errors和deep_errors的值;

* 如果该对象在该shard上不存在,添加到cur_missing列表中,统计shallow_errors的值;

  c) オブジェクトのすべての比較結果を確認します。cur_missing が空でない場合は、それを欠落キューに追加します。cur_inconsistent オブジェクトがある場合は、不整合なオブジェクトに追加します。オブジェクトに不完全なコピーがある場合は、レコードをコピーせずに配置します。権威の問題。

  d) 権威オブジェクト object_info に記録されたデータのダイジェストと omap の omap_digest が実際のスキャンデータの計算結果と矛盾する場合、更新モードを FORCE に設定して強制的に修復します。object_info にデータ ダイジェストと omap ダイジェストがない場合、修復モード更新は MAYBE に設定されます。

  e) 最後に、更新モードが FORCE であるか、オブジェクトの経過時間が構成パラメータ g_conf->osd_deep_scrub_update_digest_min_age の値より大きいかどうかを確認し、それを missing_digest リストに追加します。

4.5.3 be_select_auth_object()

関数 be_select_auth_object() は、各 OSD 上のレプリカ オブジェクトの中から権限のあるオブジェクト、つまり auth_obj オブジェクトを選択するために使用されます。原理は、保持する冗長情報に基づいて自身の完全性を検証することであり、具体的なプロセスは次のとおりです。

map<pg_shard_t, ScrubMap *>::const_iterator
  PGBackend::be_select_auth_object(
  const hobject_t &obj,
  const map<pg_shard_t,ScrubMap*> &maps,
  object_info_t *auth_oi,
  map<pg_shard_t, shard_info_wrapper> &shard_map,
  inconsistent_obj_wrapper &object_error);

1) まず、オブジェクトの read_error と stat_error が設定されていないことを確認します。つまり、オブジェクトのデータとメタデータを取得するプロセスにエラーがないことを確認します。そうでない場合は、直接スキップします。

2) 取得した属性 OI_ATTR 値が空ではないことを確認し、データ構造 object_info_t を正しくデコードし、現在のオブジェクトを auth_obj オブジェクトとして設定します。

3) object_info_t に格納されているサイズ値がスキャンされたオブジェクトのサイズ値と一致していることを確認します。一致していない場合は、より適切な auth_obj オブジェクトの検索を続けます。

4) 複製タイプの PG の場合、object_info_t に格納されているデータと omap のダイジェスト値がスキャン プロセス中に計算された値と一致するかどうかを検証します。一貫性がない場合は、より適切な auth_obj オブジェクトを探し続けます。

5) 上記がすべて一致する場合、ループは直接終了し、満足のいく auth_obj オブジェクトが見つかったことになります。

上記の選択プロセスから、権限のあるオブジェクトを選択するための条件は次のとおりであることがわかります。

  • ステップ 1)、2) の 2 つの条件に基づいて、オブジェクトのデータと属性を正しく読み取ることができます。

  • 手順 3)、4) では、object_info_t に格納されているオブジェクト サイズと、omap と data のダイジェストの冗長情報を使用します。この情報を、オブジェクト スキャンから読み取られたデータから計算された情報と比較して検証します。

4.5.4 _scrub()

関数 _scrub() は、オブジェクトとスナップショット オブジェクト間の一貫性をチェックします。

void ReplicatedPG::_scrub(
  ScrubMap &scrubmap,
  const map<hobject_t, pair<uint32_t, uint32_t>, hobject_t::BitwiseComparator> &missing_digest);

プールにキャッシュ プール レイヤーがある場合、一部のオブジェクトがまだキャッシュ プールに存在し、フラッシュされていない可能性があるため、オブジェクトをコピーして不整合な状態にすることができます。これは、関数 pool.info.allow_incomplete_clones() によって決定されます。

実際には、コードはさらに複雑で、次の例はその実装の基本プロセスを示しています。

例12-1 _scrub() 関数の実装プロセスの例を以下の表 12-1 に示します。

_scrub() の実装プロセスは次のように説明されます。

1) オブジェクト obj1 snap1 はスナップショット オブジェクトであるため、head または snapdir オブジェクトが必要です。しかし、スナップショット オブジェクトに対応する head または snapdir オブジェクトがない場合、そのオブジェクトは予期しないオブジェクトとしてマークされます。

2) オブジェクト obj2 head は、期待されるオブジェクトである head オブジェクトです。head オブジェクトを通じて、[6,4,2,1] として設定されたスナップショットのクローン リストを取得します。

3) オブジェクト obj2 snap7 がオブジェクト obj2 のスナップセットのクローン リストにないことを確認します。これは異常なオブジェクトです。

4) オブジェクト obj2 のスナップショット オブジェクト snap6 がスナップセットのクローン リストにあります。

5) オブジェクト obj2 のスナップショット オブジェクト snap4 がスナップセットのクローン リストにあります。

6) obj3 ヘッド オブジェクトが見つかった場合、期待されるオブジェクト obj2 内に欠落オブジェクトとしてスナップショット オブジェクト snap2 および snap1 が存在するはずです。引き続き、snap3 のスナップセットのクローン値をリスト [3,1] として取得します。

7) オブジェクト obj3 のスナップショット オブジェクト snap3 は、予期されたオブジェクトと一致します。

8) オブジェクト obj3 のスナップショット オブジェクト snap1 は、予期されたオブジェクトと一致します。

9) 予想どおり、オブジェクト obj4 の snapdir オブジェクト。オブジェクトを取得するスナップセットのクローン リストは [4] です。

10) スキャンされたオブジェクトのリストは終了しましたが、予期されたオブジェクトは obj4 のスナップショット オブジェクト snap4 であり、オブジェクト obj4 snap4 がありません。

現在、予期されるオブジェクトと予期しないオブジェクトは、それ以上の処理を行わずにログ内でマークされるだけです。

最後に、ダイジェストが正しくないが他のデータ オブジェクトが正しいデータ オブジェクト、つまり、missing_digest でダイジェストを更新する必要があるオブジェクトに対して、ダイジェスト更新要求が送信されます。

4.6 スクラブプロセスの終了

scrub_finish() 関数はスクラブ プロセスを終了するために使用され、その処理は次のとおりです。

void PG::scrub_finish();

1) 関連する PG のステータスと統計情報を設定します。

2) 関数 scrub_process_inconsistent を呼び出して、スクラバでマークされた欠落オブジェクトや矛盾したオブジェクトを修復します。最後に、repair_object 関数を呼び出します。これは、peer_missing で欠落しているオブジェクトをマークするだけです。

3) 最後に、DoRecovery イベントがトリガーされて PG ステート マシンに送信され、実際のオブジェクト回復操作が開始されます。

5. 章の概要

この章では、スクラブの基本原理とスクラブ プロセスのスケジューリング メカニズムを紹介し、その後、検証情報の構築と検証の比較の具体的なプロセスを紹介します。

最後に、Ceph の整合性チェック スクラブ機能の重要なポイントをまとめます。

  • スクラブ操作チェックのオブジェクト範囲の開始と終了の間に書き込み中のオブジェクトがある場合は、スクラブ プロセスを終了します。スクラブが開始されている場合は、開始と終了の間のオブジェクトの書き込み操作はスクラブ操作の終了を待つ必要があります。 ;

  • チェックは、マスター/スレーブ コピーのメタデータ (サイズ、attrs、omap) がデータと一致しているかどうかを比較することです。権限のあるオブジェクトの選択は、オブジェクト自体によって保存された情報 (オブジェクト情報) が、読み取られたオブジェクトの情報と一致するかどうかに基づいて行われます。次に、権限のあるオブジェクトとオブジェクトの他のコピーを照合します。

おすすめ

転載: blog.csdn.net/weixin_43778179/article/details/132718238