Dive Deep Redis Internals ~ GETコマンド実行時の動作 ~
Redis 4.0.10をソースからビルドしてデフォルト設定時、GETコマンド実行時の動作をソースコードで確認します。 https://github.com/antirez/redis
GETコマンド実行時の挙動
以下、2つの段階に分けて処理が実行されます。
1. クライアントから送られてきたリクエストを処理してClient Output Bufferに保存する処理
2. Client Output Bufferに保存されたリプライ内容をクライアントへ送る処理概要
0. main関数からaeMain関数までの処理
aeMain関数までは、server.cでmain関数実行後、大きく以下の処理が実行される。パラメータ内容の読み込み後、Redisサーバの初期化を行う。
1 loadServerConfig 2 initServer 3 aeMain
1. クライアントから送られてきたリクエストを処理してClient Output Bufferに保存する処理
0 aeMain at ae.c
1 aeProcessEvents at ae.c
2 aeSearchNearestTimer at ae.c
2 aeApiPoll at ae.c
2 afterSleep at server.c
2 rfileProc -> readQueryFromClient at networking.c
3 processInputBuffer at networking.c
4 processMultibulkBuffer at networking.c
5 createStringObject at object.c
6 createEmbeddedStringObject at object.c
5 createStringObject at object.c
4 processMultibulkBuffer at networking.c
4 processCommand at server.c
5 lookupCommand at server.c
6 dictFetchValue at dict.c
7 dictFind at dict.c
8 dictHashKey -> dictSdsCaseHash at server.c
4 processCommand at server.c
5 call at server.c
6 proc -> getCommand at t_string.c
7 getGenericCommand at t_string.c
8 lookupKeyReadOrReply at db.c
9 lookupKeyRead at db.c
10 lookupKeyReadWithFlags at db.c
8 addReplyBulk at networking.c
4 resetClient at networking.c
2 processTimeEvents at ae.c
2. Client Output Bufferに保存されたリプライ内容をクライアントへ送る処理
0 aeMain at ae.c
1 beforeSleep at server.c
2 activeExpireCycle at expire.c
2 moduleHandleBlockedClients at module.c
2 flushAppendOnlyFile at aof.c
2 handleClientsWithPendingWrites at networking.c
3 writeToClient at networking.c
4 clientHasPendingReplies at networking.c
詳細
クライアントから送られてきたリクエストを処理してClient Output Bufferに保存する処理
0 aeMain at ae.c
aeMain関数中のwhileで待機。aeProcessEvents関数が呼ばれる
1 aeProcessEvents at ae.c
aeSearchNearestTimer関数で直近のタイマーイベントを調べる
2 aeSearchNearestTimer at ae.c
1 aeProcessEvents at ae.c
2 aeApiPoll at ae.c
numeventsには1の値が代入された状態で関数を抜ける
1 aeProcessEvents at ae.c
2 afterSleep at server.c
1 aeProcessEvents at ae.c
ここでは GET なので AE_READABLE で rfileProc 関数が呼ばれる
2 rfileProc -> readQueryFromClient at networking.c
processInputBuffer関数が呼ばれる
3 processInputBuffer at networking.c
Redisではリクエストの種類にINLINEとMULTIBULKの2種類があり、リクエスト内容が"*"で始まっていることからMULTIBULKが選択される。
その後、processMultibulkBuffer関数が実行される
4 processMultibulkBuffer at networking.c
このとき、c->querybuf には、sds にRESP形式で"*2\r\n$3\r\nget\r\n$4\r\nhoge\r\n"の文字列が格納されている
ちょっと処理を進めて、c->querybuf+pos+1 には、"3\r\nget\r\n$4\r\nhoge\r\n" が格納されている
オブジェクト作成の文字列がバッファに含まれるので最適化処理を行わず、createStringObject関数を実行
5 createStringObject at object.c
Redisで文字列をembstrかrawのエンコーディングで保存するか、格納対象の文字列とパラメータ値に基づいて実行。ここでは、createEmbeddedStringObject関数を実行
6 createEmbeddedStringObject at object.c
文字列をembstrのエンコーディングで作成していく。sh->bufには"get"が格納される
ここで作成しているオブジェクトは以下の形式の構造体
4 processMultibulkBuffer at networking.c
c->querybuf+pos+1 には、先程"3\r\nget\r\n$4\r\nhoge\r\n" が格納されていたが、"4\r\nhoge\r\n"が今は格納されている。
c->querybuf+posを進め、"hoge\r\n"が格納された状態で、createStringObject関数を再度実行
5 createStringObject at object.c
sh->bufには"hoge"が格納された状態で値を返す。
4 processMultibulkBuffer at networking.c
3 processInputBuffer at networking.c
4 processCommand at server.c
lookupCommand関数が呼ばれる
5 lookupCommand at server.c
6 dictFetchValue at dict.c
7 dictFind at dict.c
dictHashKey関数を実行すると、dictSdsCaseHash関数が呼ばれる。
8 dictHashKey -> dictSdsCaseHash at server.c
4 processCommand at server.c
c->cmd には、以下の値へのポインタが格納された状態で処理を続行。
クラスターモード有効時で、対象のキーが今いるノードにスロットが無い場合等にはリダイレクト処理。ここでは特に必要ないのでスキップ。
その後、call 関数を実行して、実際に入力されたコマンドの内容を処理。
5 call at server.c
c->cmd->proc(c)が実行される。proc関数は、redisCommandTable変数で各コマンドに対して定義している実行する関数が呼ばれる。 https://github.com/antirez/redis/blob/4.0.10/src/server.c#L75-L308
6 proc -> getCommand at t_string.c
7 getGenericCommand at t_string.c
addReplyBulkを実行
8 lookupKeyReadOrReply at db.c
lookupKeyRead関数を実行。addReply関数は実行せず、robjオブジェクトへのポインタを返す
9 lookupKeyRead at db.c
10 lookupKeyReadWithFlags at db.c
8 addReplyBulk at networking.c
MULTI_BULK形式でリプライを生成。
5 call at server.c
call関数の処理続行。必要に応じてスローログの記録処理。statsの記録にも反映。
AOFやレプリケーションにも反映。
4 processCommand at server.c
call関数実行後、processCommand関数の処理に戻ってくる。handleClientsBlockedOnLists関数は実行せず、そのまま、C_OKを返す。
3 processInputBuffer at networking.c
processCommand関数実行後、processInputBuffer関数の処理に戻ってくる。ブロックされていない処理なので、resetClient関数を実行。その後、while文の最後まで達する。その後、processInputBuffer関数を抜け、readQueryFromClient関数を抜け、aeProcessEvents関数の処理に戻ってくる。
4 resetClient at networking.c
1 aeProcessEvents
Read処理実行後、Write処理等は特に無いため、そのまま処理継続。最後にタイマーイベントを確認。processTimeEvents関数実行後、aeProcessEvents関数の処理を終え、aeMain関数に戻ってくる。
2 processTimeEvents at ae.c
Client Output Bufferに保存されたリプライ内容をクライアントへ送る処理
0 aeMain at ae.c
aeMain関数に戻ってきた後、beforesleep関数が呼ばれる。
1 beforeSleep at server.c
handleClientsWithPendingWrites関数でClient Output Bufferに格納された結果をクライアントに返す。その後、再びaeMain関数のループ処理に戻ってきて、一連の処理は終了。
2 activeExpireCycle at expire.c
2 moduleHandleBlockedClients at module.c
2 flushAppendOnlyFile at aof.c
AOFの設定は特にしてないので、ここでは特に何も処理を行わない。
2 handleClientsWithPendingWrites at networking.c
writeToClient関数を実行
3 writeToClient at networking.c
clientHasPendingReplies関数を実行。write関数実行後、クライアントには結果が返される。
その後、clientHasPendingReplies関数でクライアントに返すpending中のリプライが無いことを確認して、aeDeleteFileEvent関数を実行
4 clientHasPendingReplies at networking.c
クライアントに返す結果があるのでtrueを返す。
事前準備
Redis
デバッグ用にビルドしておく。また、最適化は無効化しておく。
別Terminal
もしくは brew install gdb cgdb でインストールして、gdb の代わりに cgdb を利用
別Terminal2
Docker
もしくは用意されているDockerコンテナを利用する方法もOK
別Terminal
別Terminal2
cgdb
https://cgdb.github.io/
-std=c++11をつけないと、make時に"kui.cpp:310:15: error: ‘it’ does not name a type"のエラー https://github.com/cgdb/cgdb/issues/184
gdb
Redis のデバッグで、コマンド実行ごとにprocessCommand関数を通過するので、コマンド実行の際の挙動をこちらを追うためにはこちらにブレークポイントを貼っておくと良い。
Last updated