Cephソースコード分析:読み取りおよび書き込みプロセス
Ceph Source Code Analysis
1.OSDモジュールの紹介
1.1メッセージのカプセル化 :OSDに関する情報を送受信します。
cluster_messenger-他のOSDおよびモニターと通信します
client_messenger-クライアントと通信する
1.2メッセージのスケジューリング :
主にメッセージ分類を担当するディスパッチャクラス
1.3作業キュー:
1.3.1 OpWQ:ops(クライアントから)およびsub ops(他のOSDから)を処理します。 op_tpスレッドプールで実行します。
1.3.2 PeeringWQ:op_tpスレッドプールで実行されるピアリングタスクの処理。
1.3.3 CommandWQ:cmdコマンドを処理し、command_tpで実行します。
1.3.4 RecoveryWQ:データ回復。recovery_tpで実行します。
1.3.5 SnapTrimWQ:スナップショット関連、disk_tpで実行。
1.3.6 ScrubWQ:disk_tpで実行されるスクラブ。
1.3.7 ScrubFinalizeWQ:disk_tpで実行されているスクラブ。
1.3.8 RepScrubWQ:disk_tpで実行されているスクラブ。
1.3.9 RemoveWQ:古いpgディレクトリを削除します。 disk_tpで実行します。
1.4スレッドプール:
OSDスレッドプールには次の4種類があります。
1.4.1 op_tp:opsおよびsubopsを処理します
1.4.2 recovery_tp:修復タスクを処理する
1.4.3 disk_tp:ディスクを大量に消費するタスクを処理する
1.4.4 command_tp:プロセスコマンド
1.5主な目的:
ObjectStore * store
OSDSuperblockスーパーブロック主にバージョン番号などの情報
OSDMapRef osdmap
1.6主な操作プロセス: 参考記事
1.6.1クライアントがリクエストプロセスを開始します
1.6.2op_tpスレッドはデータの読み取りを処理します
1.6.3オブジェクト操作のプロセス
1.6.4変更操作の処理
1.6.5ログの書き込み
1.6.6書き込み操作処理
1.6.7トランザクションの同期プロセス
1.6.8ログの回復
1.7全体的なプロセス図
次に、クライアントとストレージフォームにデータを書き込む一般的なプロセス
2.1読み取りと書き込みのフレームワーク
2.2クライアントの書き込みプロセス
クライアントでrbdを使用するには、一般的に2つの方法があります。
- 1つ目はカーネルrbdです。 rbdデバイスを作成した後、rbdデバイスをカーネルにマップして、仮想ブロックデバイスを形成します。現時点では、このブロックデバイスは他の一般的なブロックデバイスと同じです。一般的なデバイスファイルは/ dev / rbd0であり、ブロックデバイスは将来直接使用されます。ファイルで十分です。 / dev / rbd0は、フォーマット後にディレクトリにマウントすることも、rawデバイスとして直接使用することもできます。このとき、rbdデバイスの操作はカーネルrbd操作方式で行います。 A
- 2番目の方法はlibrbdです。 rbdデバイスを作成した後、librbdおよびlibradosライブラリを使用して管理ブロックデバイスにアクセスできます。このメソッドはカーネルにマップされず、librbdによって提供されるインターフェイスを直接呼び出します。これにより、rbdデバイスへのアクセスと管理が可能になりますが、クライアント上にブロックデバイスファイルは生成されません。
RBDブロックデバイスへの書き込みプロセス:
- アプリケーションはlibrbdインターフェースを呼び出すか、Linuxカーネル仮想ブロックデバイスにバイナリブロックを書き込みます。例としてlibrbdを取り上げましょう。
- librbdはバイナリブロックをブロックに分割します。デフォルトのブロックサイズは4Mで、各ブロックには名前があり、オブジェクトになります。
- librbdはlibradosを呼び出して、Cephクラスターにオブジェクトを書き込みます
- libradosは分割されたバイナリデータブロックをメインOSDに書き込みます(最初にTCP / IP接続を確立し、次にメッセージをOSDに送信し、OSDはそれを受信してディスクに書き込みます)
- プライマリOSDは、1つ以上のセカンダリOSDに同時にコピーを書き込む責任があります。ここでは、返されるようにジャーナル(Journal)に書き込まれるため、SSDをジャーナルとして使用すると、応答速度を向上させて、サーバーがクライアントをすばやく同期して書き込み結果(ack)を返すことができることに注意してください。
- プライマリOSDとセカンダリOSDの両方の書き込みが完了すると、プライマリOSDは書き込みが成功したことをクライアントに返します。
- 一定期間(おそらく数秒)後、ジャーナル内のデータがディスクに正常に書き込まれた後、Cephは、イベントを介してデータがディスクに正常に書き込まれた(コミット)ことをクライアントに通知します。この時点で、クライアントはデータを書き込みキャッシュClearedに完全に書き込むことができます。
- デフォルトでは、Cephクライアントは、クラスターのコミット通知を受信するまで、書き込まれたデータをキャッシュします。この段階(書き込みメソッドがコミット通知の受信に戻るまで)でOSDが失敗し、ファイルシステムへのデータ書き込みが失敗した場合、Cephはクライアントがコミットされていない操作(再生)をやり直すことを許可します。したがって、PGのステータスはリプレイと呼ばれます。「OSDがクラッシュした後、プレースメントグループはクライアントが操作をリプレイするのを待っています。」
つまり、ファイルシステムはファイル処理を担当し、librbdはブロック処理を担当し、libradosはオブジェクト処理を担当し、OSDはジャーナルとディスクへのデータの書き込みを担当します。
2.3RBD保存形式
次の図に示すように、Cephシステムのさまざまなレベルのコンポーネント/ユーザーから見たデータの形式は異なります。
- Cephクライアントに表示されるのは、完全な連続バイナリデータブロックです。そのサイズは、作成されたRBDイメージの設定サイズまたはサイズ変更であり、クライアントは最初から、または特定の場所からバイナリデータを書き込むことができます。
- libradosは、プールの順序によってサイズが決定されるRADOSでオブジェクトを作成する役割を果たします。デフォルトでは、order = 22でオブジェクトサイズは4MBであり、クライアントから渡されたバイナリブロックをいくつかのストリップにストライピングする役割を果たします。 (ストライプ)。
- libradosは、どのストライプがどのOSDによって書き込まれるかを制御します(ストライプ---書き込まれる---->オブジェクト----どの----> OSD)
- OSDは、ファイルシステムにファイルを作成し、libradosから渡されたデータをデータに書き込む役割を果たします。
CephクライアントはバイナリデータをRBDイメージに書き込みます(プールコピーの数が3であると想定)。
(1)Cephクライアントがlibradosを呼び出してRBDイメージを作成します。現時点では、ストレージスペースは割り当てられていませんが、メタデータ情報を保存するためにいくつかのメタデータオブジェクトが作成されています。
(2)Cephクライアントがlibradosを呼び出してデータの書き込みを開始します。 libradosは、ストライプやオブジェクトなどを計算してから、特定のターゲットオブジェクトへの最初のストライプの書き込みを開始します。
(3)CRUSHアルゴリズムに従って、libradosはオブジェクトに対応するメインOSD IDを計算し、それにバイナリデータを送信します。
(4)メインOSDは、ファイルシステムインターフェイスを呼び出して、ディスク上のファイルにバイナリデータを書き込む役割を果たします(各オブジェクトはファイルに対応し、ファイルの内容は1つ以上のストライプです)。
(5)メインODSは、データの書き込みが終了した後、CRUSHを使用して2番目のOSD(2次OSD)と3番目のOSD(3次OSD)の位置を計算し、これら2つのOSDにオブジェクトをコピーします。すべてが完了すると、オブジェクトが保存されたことをcephクライアントに報告します。
(6)次に、すべての書き込みが完了するまで2番目のストライプを書き込みます。すべてが完了したら、libradosは、新しいサイズの書き込みなど、メタデータも更新する必要があります。
完全なプロセス( ソース ):
このプロセスには、強い一貫性という特徴があります。
- Cephは、読み取りおよび書き込み操作にプライマリレプリカモデルを使用します。クライアントは、オブジェクトに対応するOSDセットのプライマリに対してのみ読み取りおよび書き込み要求を開始します。これにより、強力なデータ整合性が保証されます。
- 各オブジェクトにはプライマリOSDが1つしかないため、オブジェクトの更新は順次行われ、同期の問題は発生しません。
- プライマリはオブジェクト書き込み要求を受信すると、他のレプリカにデータを送信する責任があります。データがすべてのOSDに保存されている限り、プライマリはオブジェクト書き込み要求に応答します。これにより、コピーの一貫性が保証されます。これはまた、いくつかの副作用をもたらします。 Swiftなど、最終的な整合性のみを実現するストレージシステムと比較すると、Cephは3つのコピーすべてが書き込まれた後にのみ書き込みを行います。これは書き込み完了と見なされ、ディスクの破損が発生した場合の書き込み待ち時間が長くなります。
- OSDでは、データストレージ命令を受信した後、2〜3個のディスクシーク操作を生成します。
-
- 書き込み操作をOSDのジャーナルファイルに書き込みます(ジャーナルは書き込み操作のアトミック性を確保するためのものです)。
- オブジェクトに対応するファイルへの書き込み操作を更新します。
- 書き込み操作をPGログファイルに書き込みます。
第三に、クライアント要求プロセス(転送 小さな川 ブログ投稿、よく書かれています)
RADOS読み取りオブジェクトフロー
RADOS書き込みオブジェクト操作フロー
例:
#!/ usr / bin / env python
sys、rados、rbdをインポートします
def connectceph():
cluster = rados.Rados(conffile = '/ root / xuyanjiangtest / ceph-0.94.3 / src / ceph.conf')
cluster.connect()
ioctx = cluster.open_ioctx( 'mypool')
rbd_inst = rbd.RBD()
サイズ= 4 * 1024 ** 3#4 GiB
rbd_inst.create(ioctx、 'myimage'、size)
image = rbd.Image(ioctx、 'myimage')
データ= 'foo' * 200
image.write(data、0)
image.close()
ioctx.close()
cluster.shutdown()
if __name__ == '__ main __':
connectceph()
1.まず、cluster = rados.Rados(conffile = 'ceph.conf')、現在のceph構成ファイルを使用してradosを作成します。ここでは、主にceph.confのクラスター構成パラメーターを解析します。次に、これらのパラメーターの値をradosに保存します。
2. cluster.connect()、radosclient構造がここに作成され、この構造には主にいくつかの汎用モジュールが含まれます:メッセージ管理モジュールMessager、データ処理モジュールObjector、フィニッシャースレッドモジュール。
3. ioctx = cluster.open_ioctx( 'mypool')、mypoolという名前のストレージプールのioctxを作成します。 ioctxは、radosclientモジュールとObjectorモジュールを示し、プールのパラメーターを含むmypoolの情報も記録します。
4. rbd_inst.create(ioctx、 'myimage'、size)、myimageという名前のrbdデバイスを作成してから、このデバイスにデータを書き込みます。
5. image = rbd.Image(ioctx、 'myimage')、画像構造を作成します。この構造はmyimageとioctxを接続し、iocxは後で画像構造から直接見つけることができます。ここで、iocxはdata_ioctxとmd_ctxに分割されて2つにコピーされます。知っていることと知っていることを参照してください。1つはrbdのストレージデータを処理するために使用され、もう1つはrbdの管理データを処理するために使用されます。
フローチャート:
1. image.write(data、0)、書き込み要求の有効期間の開始は、imageを介して開始されます。リクエストの2つの基本要素は、ここでbuffer = dataとoffset = 0で指定されています。ここから、cephの世界だけでなく、c ++の世界にも参入し始めました。
image.write(data、0)からlibrbd.ccファイルのImage :: write()関数に、この関数の主な実装を見てみましょう。
ssize_t Image::write(uint64_t ofs, size_t len, bufferlist& bl) { ImageCtx *ictx = (ImageCtx *)ctx int r = librbd::write(ictx, ofs, len, bl.c_str(), 0) return r }
2.この関数は、librbd :: wrte関数に直接配布されます。 librbd :: writeの実装を見てみましょう。この関数の特定の実装は、internal.ccファイルにあります。
ssize_t write(ImageCtx *ictx, uint64_t off, size_t len, const char *buf, int op_flags) { Context *ctx = new C_SafeCond(&mylock, &cond, &done, &ret) //---a AioCompletion *c = aio_create_completion_internal(ctx, rbd_ctx_cb)//---b r = aio_write(ictx, off, mylen, buf, c, op_flags) //---c while (!done) cond.Wait(mylock) // ---d }
--- a。この文は、この操作のコールバック操作に適用する必要があります。いわゆるコールバックは、いくつかの仕上げ作業、信号ウェイクアップ処理です。
--- b。この文は、ioが完了したときに実行される操作を申請するためのものです。 ioが完了すると、rbd_ctx_cb関数が呼び出され、関数は引き続きctx-> complete()を呼び出します。
--- c。関数aio_writeは、引き続きこの要求を処理します。
--- d。 cセンテンスがこのioをosdに送信し、osdがまだリクエスト処理を完了していない場合、最下層がリクエストの処理を完了するまでdを待機し、bによって適用されたAioCompletionをコールバックし、>でctx-を呼び出し続けます。 complete()、ここで待機信号をウェイクアップすると、プログラムは引き続き下向きに実行されます。
3.要求されたオフセットとバッファを取得したときにaio_writeが何をするかを見てみましょう。
int aio_write(ImageCtx *ictx, uint64_t off, size_t len, const char *buf, AioCompletion *c, int op_flags) {// The request will be split according to the object vector extents if (len> 0) If (len> 0) Striper::file_to_extents(ictx->cct, ictx->format_string, extents) //---a} //Process the request data on each object for (vector::iterator p = extents.begin() p != extents.end() ++p) {_AioWrite *req_comp = new C_AioWrite(cct, c) //----b AioWrite *req = new AioWrite(ictx, p->oid.name, p->objectno, p->offset,bl,... , req_comp) //---c c r = req->send() //---d} }
リクエストのサイズに応じて、このリクエストはオブジェクトに応じて分割する必要があります。オブジェクトは関数file_to_extentsによって処理されます。処理が完了すると、オブジェクトに応じたエクステントに保存されます。 file_to_extents()には同じ名前の関数がたくさんあります。これらの関数の主な内容は、元のリクエストの分割という1つのことを行います。
rbdデバイスは多くのオブジェクトで構成されています。つまり、rbdデバイスはブロックに分割され、各ブロックはオブジェクトと呼ばれ、各オブジェクトのサイズはデフォルトで4Mですが、自分で指定することもできます。 file_to_extents関数は、この大きなリクエストをオブジェクトにマップし、以下に示すように、それを多くの小さなリクエストに分割します。最終的なマッピングの結果はObjectExtentに保存されます。
元のオフセットとは、rbd(rbdが書き込まれる場所)内のオフセットを指します。 file_to_extentsの後、1つ以上のオブジェクトの内部オフセットoffset0に変換されます。この変換後、このオブジェクト内のリクエストのバッチが処理されます。
4. aio_write関数に戻ると、分割後の各オブジェクト要求を処理する必要があります。
--- b。書き込み要求にコールバックハンドラーを適用します。
--- c。オブジェクトの内部要求に基づいて、AioWriteという構造を作成します。
--- d。このAioWritereqをsend()に送信します。
5.ここで、AioWriteはAbstractWriteを継承し、AbstractWriteはAioRequestクラスを継承し、AbstractWriteクラスではsendメソッドを定義します。sendの特定のコンテンツを参照してください。
int AbstractWrite::send() { if (send_pre()) //---a } #Enter the send_pre() function bool AbstractWrite::send_pre() {
m_state = LIBRBD_AIO_WRITE_PRE // ----a FunctionContext *ctx = //----b new FunctionContext( boost::bind(&AioRequest::complete, this, _1)) m_ictx->object_map.aio_update(ctx) //-----c }
--- a。 m_stateの状態をLIBRBD_AIO_WRITE_PREに変更します。
--- b。コールバック関数を申請し、実際にAioRequest :: complete()を呼び出します
--- c。 object_map.aio_updateリクエストの発行を開始します。これはステータス更新機能であり、それほど重要なリンクではありません。ここではこれ以上は言いませんが、更新要求が完了すると、bによって適用されたコールバック関数が自動的にコールバックされます。
6. AioRequest :: complete()関数を入力します。
void AioRequest::complete(int r) { if (should_complete(r)) //---a }
--- a.should_complete関数は、継承されたクラスAbstractWriteに実装する必要がある純粋仮想関数です。7になります。AbstractWrite:: should_complete()を見てください。
bool AbstractWrite::should_complete(int r) { switch (m_state) { case LIBRBD_AIO_WRITE_PRE: //----a { send_write() //----b
---- a。 send_preでm_stateの状態がLIBRBD_AIO_WRITE_PREに設定されているため、この分岐が実行されます。
---- b。 send_write()関数では、処理が続行されますが、
7.1。このsend_write関数を見てみましょう
void AbstractWrite::send_write() { m_state = LIBRBD_AIO_WRITE_FLAT //----a add_write_ops(&m_write) // ----b int r = m_ictx->data_ctx.aio_operate(m_oid, rados_completion, &m_write) }
--- a。 m_stateの状態をLIBRBD_AIO_WRITE_FLATにリセットします。
--- b。 m_writeに入力し、リクエストをm_writeに変換します。
--- c。 m_writeを送信し、data_ctx.aio_operate関数を使用して処理します。引き続きio_ctx_impl-> aio_operate()関数を呼び出し、引き続きobjecter-> mutate()を呼び出します。
8.object-> mutate()
ceph_tid_t mutate(……..) { Op *o = prepare_mutate_op(oid, oloc, op, snapc, mtime, flags, onack, oncommit, objver) //----d return op_submit(o) }
--- d。リクエストをOpリクエストに変換し、引き続きop_submitを使用してリクエストを発行します。引き続きop_submitで_op_submit_with_budgetを呼び出して、リクエストを処理します。引き続き_op_submit処理を呼び出します。
8.1_op_submitの処理。ここで詳しく見る価値があります
ceph_tid_t Objecter::_op_submit(Op *op, RWLock::Context& lc) {
check_for_latest_map = _calc_target(&op->target, &op->last_force_resend); //---a int r = _get_session(op->target.osd, &s, lc) //---b _session_op_assign(s, op) //----c _send_op(op, m) //----d
}
---- a。 _calc_targetは、現在のオブジェクトの保存されたosdを計算し、次にメインosdをターゲットに保存することにより、rbd書き込みデータが最初にメインosdに送信され、次にメインosdがデータを他のコピーosdに送信します。 osdセットとメインosdの間の関係を選択する方法についての話はもうありません。このプロセスの原理は「cephのデータストレージへの道(3)」で説明されており、コード部分を理解するのは難しくありません。
---- b。 _get_session、この関数はメインosdとの通信を確立するために使用されます。通信が確立されると、このチャネルを介してメインOSDに送信できます。この関数がどのように処理されるかを見てみましょう
9. _get_session
int Objecter::_get_session(int osd, OSDSession **session, RWLock::Context& lc) { map::iterator p = osd_sessions.find(osd) //----a OSDSession *s = new OSDSession(cct, osd) //----b osd_sessions[osd] = s//--c s->con = messenger->get_connection(osdmap->get_inst(osd))//-d
}
---- a。最初に、直接使用できる接続がosd_sessionsにすでに存在するかどうかを確認しますが、最初の通信は存在しません。
---- b。 OSDSessionを再申請し、osdおよびその他の情報を使用して初期化します。
--- c。新しく適用されたOSDSessionをosd_sessionsに追加し、将来の使用のために保存します。
---- d。メッセージ送信者のget_connectionメソッドを呼び出します。この方法では、ターゲットosdとの接続を確立する方法を引き続き見つけます。
10.メッセージはsimpleMessagerサブクラスによって実装されます。 SimpleMessagerでのget_connectionの実装を見てみましょう
ConnectionRef SimpleMessenger::get_connection(const entity_inst_t& dest) { Pipe *pipe = _lookup_pipe(dest.addr) //-----a if (pipe) { } else { pipe = connect_rank(dest.addr, dest.name.type(), NULL, NULL) //----b }
}
---- a。まず、このパイプを見つけなければなりません。最初の通信、当然このパイプは存在しません。
---- b。 connect_rankは、このターゲットosdのアドレスに従って作成されます。 connect_rankが行ったことを確認してください。
11. SimpleMessenger :: connect_rank
Pipe *SimpleMessenger::connect_rank(const entity_addr_t& addr, int type, PipeConnection *con, Message *first) {
Pipe *pipe = new Pipe(this, Pipe::STATE_CONNECTING, static_cast(con)) //----a pipe->set_peer_type(type) //----b pipe->set_peer_addr(addr) //----c pipe->policy = get_policy(type) //----d pipe->start_writer() //----e return pipe //----f }
---- a。最初にこのパイプを作成する必要があり、パイプはパイプコンに関連付けられています。
---- b、---- c、----- d。すべていくつかのパラメータを設定します。
---- e。パイプの書き込みスレッドの開始を開始します。ここでは、パイプ書き込みスレッドの処理関数pipe-> writer()で、この関数はosdへの接続を試みます。そして、ソケット接続チャネルを確立します。
現在のリソース統計によると、書き込み要求は、ターゲットのメインosdに従ってOSDSessionを検索または確立できます。このOSDSessionには、データチャネルを管理するパイプ構造があります。次に、この構造でメッセージを送信する処理スレッドライターがあります。このスレッドは、ターゲットOSDとのソケット通信を維持します。
12.これらのリソースを確立して取得してから、_op_submit関数に戻ります。
ceph_tid_t Objecter::_op_submit(Op *op, RWLock::Context& lc) {
check_for_latest_map = _calc_target(&op->target, &op->last_force_resend); //---a int r = _get_session(op->target.osd, &s, lc) //---b _session_op_assign(s, op) //----c MOSDOp *m = _prepare_osd_op(op) //-----d _send_op(op, m) //----e }
--- c、現在のopリクエストをこのセッションにバインドすると、後でリクエストを送信するときに使用するセッションを知ることができます。
--d、opをMOSDopに変換してから、MOSDOpをオブジェクトとして扱います。
--- e、_send_opは、以前に確立された通信チャネルに従って、このMOSDOpを送信します。 _send_opでop-> session-> con-> send_message(m)を呼び出します。このメソッドは、SimpleMessager-> send_message(m)を呼び出し、次に_send_message()を呼び出し、次にsubmit_message()を呼び出します。 submit_messageで、前のパイプを見つけます。次に、pipe-> sendメソッドを呼び出し、最後に、pipe-> writerスレッドを介してターゲットosdに送信します。
それ以来、お客様はosd処理が完了して結果が返されるのを待っています。
1.左上隅のrados構造を確認し、最初にio環境を作成し、rados情報を作成し、構成ファイルのデータをradosに構造化します。
2.radosに基づいてradosclientクライアント構造を作成します。この構造には、finiserコールバック処理スレッド、Messagerメッセージ処理構造、およびObjectorデータ処理構造の3つの重要なモジュールが含まれています。最終的なデータはメッセージにカプセル化され、Messagerを介してターゲットosdに送信されます。
3.プール情報とradosclientに基づいてioctxを作成します。ここにプール関連の情報があり、この情報を取得した後、データ処理で使用します。
4.次に、ioctxがimagectxにコピーされ、data_ioctxおよびmd_ioctxデータ処理チャネルになり、最後にimagectxを画像構造にカプセル化します。その後、すべての書き込み操作はこのイメージを介して実行されます。イメージの構造に従って、作成されて使用できるデータ構造を見つけます。
5.右上隅の画像を介して読み取りおよび書き込み操作。読み取り/書き込み操作のオブジェクトがイメージの場合、イメージはリクエストの処理を開始し、処理後にリクエストはオブジェクトオブジェクトに分割されます。分割後、ターゲットosdを見つけるための処理のためにオブジェクトに渡されます。もちろん、ここではクラッシュアルゴリズムを使用して、ターゲットosdセットとメインosdを検索します。
6.リクエストopをMOSDOpメッセージにカプセル化してから、SimpleMessagerに渡します。 SimpleMessagerは、既存のosd_sessionで検索を試みます。対応するセッションが見つからない場合は、OSDSessionを再作成し、このOSDSessionのデータチャネルパイプを作成します。データチャネルをSimpleMessagerに保存すると、次回使用できます。
7.パイプは、ターゲットosdとのソケット通信チャネルを確立し、パイプには、ソケット通信を担当する特別な書き込みスレッドライターがあります。スレッドライターでは、ターゲットIPが最初に接続され、通信が確立されます。 SimpleMessagerからメッセージを受信すると、パイプのoutqキューに保存されます。ライタースレッドのもう1つの用途は、outqキューを監視することです。送信を待機しているメッセージがキューにある場合、メッセージはソケットに書き込まれ、ターゲットOSDに送信されます。
8. OSDがデータメッセージを処理するのを待った後、コールバックを実行し、実行結果をフィードバックしてから、呼び出し元に結果を段階的に通知します。
4、Ceph読み取りプロセス
OSDはメッセージの配布プロセスを終了します
OSD終了読み取り操作処理フロー
全体的なフローチャート:
int read(inodeno_t ino、
file_layout_t * layout、
snapid_t snap、
uint64_tオフセット、
uint64_t len、
bufferlist * bl、//データへのptr
intフラグ、
コンテキスト* onfinish、
int op_flags = 0)-------------------------------- Filer.h
Striper :: file_to_extents(cct、ino、layout、offset、len、truncate_size、extends)//アクセスするオブジェクトに読み込まれるデータの長さとオフセットを変換します。エクステントはbrtfsファイルシステムの概念に従います。
objecter-> sg_read_trunc(extents、snap、bl、flags、truncate_size、truncate_seq、onfinish、op_flags)// osdへのリクエストを開始します
読み取り操作の場合:
1.クライアントは、保存されたデータが属するメインosdを直接計算し、メッセージをメインosdに直接送信します。
2.メッセージを受信した後、メインosdはFilestoreを呼び出して、基になるファイルシステムのメインpgのコンテンツを直接読み取り、クライアントに返すことができます。特定の呼び出し関数は、ReplicatePG :: do_osd_opsに実装されています。
CEPH_OSD_OP_MAPEXT || CEPH_OSD_OP_SPARSE_READ
r = osd-> store-> fiemap(coll、soid、op.extent.offset、op.extent.length、bl)
CEPH_OSD_OP_READ
r = pgbackend-> objects_read_sync(soid、miter-> first、miter-> second、&tmpbl)
5、Cephの書き込みプロセス
OSD書き込みプロセスフロー
書き込み操作の場合、データ書き込みの同期を確保することははるかに複雑です。
1.最初に、クライアントはデータをメインosdに送信します。
2.マスターOSDは、最初に書き込み操作の前処理も実行する必要があり、完了すると、他のスレーブOSDに書き込みメッセージを送信して、コピーpgを変更できるようにします。
3. osdからFileJournalを介してジャーナルへの書き込み操作が完了したら、メッセージを送信して、メインのosdに完了したことを通知します。5を入力します。
4.マスターosdがスレーブosdからすべてのメッセージを受信して書き込み操作を完了すると、FileJournalを介してジャーナルへの独自の書き込み操作が完了します。完了すると、書き込み操作が完了したことがクライアントに通知されます。
5.メインosdはosdスレッドから動作を開始し、Filestoreを呼び出して、Journalのデータを基になるファイルシステムに書き込みます。
書かれた論理フローチャートは次のとおりです。
この図から、書き込み操作が次のステップに分割されていることがわかります。
1.この記事の冒頭の図で説明されているように、OSD :: op_tpスレッドはOSD :: op_wqから取り出されます。特定のコードフローは次のとおりです。
ReplicatePG :: apply_repopは、コールバッククラスC_OSD_OpCommitおよびC_OSD_OpAppliedを作成します
コールバッククラスC_JournaledAheadがFileStore :: queue_transactionsに作成されました
2. FileJournal :: write_threadスレッドはFileJournal :: writeqから取り出されて動作し、主に特定のジャーナルにデータを書き込み、特定のコードフローを実行します。
3. Journal :: Finisher.finisher_threadスレッドがJournal :: Finisher.finish_queueから取り出されます。 C_JournalAheadが残したコールバック関数FileStore:_journaled_aheadを呼び出すことにより、スレッドが機能し始めます。 2つのこと:最初に基になるFileStore :: op_wq通知を入力して書き込みを開始し、FileStore :: ondisk_finisher.finisher_queue通知を再入力して戻ります。特定のコードフロー:
4.FileStore :: ondisk_finisher.finisher_threadスレッドはFileStore :: ondisk_finisher.finisher_queueから操作を取り出し、C_OSD_OpCommitが残したコールバック関数ReplicatePG :: op_commitを呼び出して、書き込み操作が成功したことをクライアントに通知します。
5. FileStore :: op_tpスレッドプールはFileStore :: op_wqから操作を受け取ります(ここでのOP_WQは親クラスThreadPool :: WorkQueueを継承し、_processや_process_finishなどの関数を書き換えるため、OSD :: op_wqとは異なります。独自のワークフローがあります。 )、最初にFileStore :: _ do_opを呼び出し、終了したらFileStore :: _finish_opを呼び出します。
6. FileStore :: op_finisher.finisher_threadスレッドがFileStore :: op_finisher.finisher_queueから取り出され、C_OSD_OpAppliedによって残されたコールバック関数ReplicatePG :: op_appliedを呼び出すことにより、通知データが読み取り可能になります。
特定のOSDのソースコードは、文ごとに分析できます。 XiaoJiangによるブログ投稿
この記事は、主に参考資料のceph clientの読み書きプロセス、OSDの終了の読み書きプロセスなどを、参考資料の内容を使用してまとめたものです。参考資料の作成者の権利が侵害された場合は、私に連絡してください。関連するコンテンツを時間内に削除します。
参考資料:
http://blog.sina.com.cn/s/blog_c2e1a9c7010151xb.html
著者: ywy463726588 http://blog.csdn.net/ywy463726588/article/details/42676493
http://blog.csdn.net/ywy463726588/article/details/42679869
著者:劉志民(サミー劉) http://www.cnblogs.com/sammyliu/p/4836014.html
著者:小さな川 http://my.oschina.net/u/2460844/blog/532755
http://my.oschina.net/u/2460844/blog/534390?fromerr=PnkKCbYU
上記の作者の無私の共有に感謝します!