ちょこっとcode reading
ここまでのあらすじ稼働中のmemcachedでときどき反応がなくなることがあったので、ちょっとstatsの結果を見てみたところ、そのmemcachedだけconnection_structuresの値が異常に多かった。で、connection_structuresってどういう風に増えていくのか、Web上に情報が見つからなかったため、ざざっとコードを見てみることに。
まずは概要をとりあえず、良い機会なのでconnection_structuresにこだわらず、全体の構成をみてみる。とはいえTCP/IPの接続管理周り以外はあまり見ないかも。
- 目についたデータ(構造体)
- conn
- 接続ごとの状態や読み書き用バッファ、などなどを管理
- struct stats
- 統計情報。お目当てのconn_structsもここに
- struct settings
- よくある設定情報
- item
- データ管理用(多分)の双方向リスト
- まだ全然見ていない(あまり興味なし)けどslabsと絡んできそう
- conn
- 大まかな処理
- int main (int argc, char **argv)
- int server_socket(const int port, const bool is_udp)
- socket(), bind(), listen()でサーバー側のソケットを作成
- ちなみに作成したソケットはfcntl(O_NONBLOCK)
- そいつをconn_new(EV_READ | EV_PERSIST)に渡している
- conn *conn_new(const int sfd, const int init_state, const int event_flags, const int read_buffer_size, const bool is_udp, struct event_base *base)
- void drive_machine(conn *c)
- 接続状態:conn->state(初っ端はconn_listening)に応じていろいろやる
- 接続状態==conn_listening
- accept()実施. もしerrno == EAGAIN || errno == EWOULDBLOCKだったらdrive_machine()を抜ける(O_NONBLOCK済みなのでそのように判定)
- acceptしたソケットはconn_new(接続状態==conn_read)に渡される
- 接続状態==conn_read
- memcacheクライアントからのコマンドをread
- update_event(c, EV_READ | EV_PERSIST)が呼ばれイベントフラグを更新(後で調べる)
- 接続状態==conn_nread
- conn_readの場合と大体おなじような雰囲気なので後回し
- 接続状態==conn_swallow
- 何となく後回し
- 接続状態==conn_write
/* * We want to write out a simple response. If we haven't already, * assemble it into a msgbuf list (this will be a single-entry * list for TCP or a two-entry list for UDP). */ if (c->iovused == 0 || (c->udp && c->iovused == 1)) { if (add_iov(c, c->wcurr, c->wbytes) != 0 || (c->udp && build_udp_headers(c) != 0)) { : } /* fall through... */ case conn_mwrite:
-
-
-
- いつこの状態になるのかも含め、後で調べる
- 接続状態==conn_mwriteの場合
- transmit()呼び出し. その中では…
- memcacheクライアントにsendmsg()
- 送信情報(我ながら胡散臭い名称…)の更新
- errno == EAGAIN || errno == EWOULDBLOCKの場合はupdate_event(c, EV_WRITE | EV_PERSIST)
- transmit()戻り値 == TRANSMIT_COMPLETE
- 接続状態==conn_mwriteの場合、送信情報を更新し接続状態をconn_readに
- 接続状態==conn_writeの場合、接続状態をconn->write_and_goの設定値に(ここに何が入りうるかは後で調べる)
- transmit()戻り値 == TRANSMIT_INCOMPLETE || TRANSMIT_HARD_ERRORの場合、リトライ
- transmit()戻り値 == TRANSMIT_SOFT_ERRORの場合、一旦drive_machine()を抜ける
- transmit()呼び出し. その中では…
- 接続状態==conn_close
- TCP/IP切断
-
-
- freeconnsについて
空き接続状態を管理している入れ物(スタック)で関連する定義は以下
static conn **freeconns; static int freetotal; static int freecurr;
-
- 各変数についてまとめ
- freeconns
- 説明: connポインタ用スタック(拡張できるようにメモリはヒープ上)
- 初期値: sizeof(conn *) * freetotal分のmallocされたメモリ
- 変化する要因: do_conn_from_freelist()でconnをpop, do_conn_add_to_freelist()で新しいconnをpush. その際、freecurr >= freetotalの場合はreallocして倍のサイズに拡張.
- freetotal
- 説明: freeconnsに格納できるconnポインタの一応の上限値
- 初期値: 200
- 変化する要因: do_conn_add_to_freelist()でfreecurr >= freetotalの場合、倍加
- freecurr
- 説明: 現在freeconnsに格納されているconnポインタの数
- 初期値: 0
- 変化する要因: do_conn_from_freelist()の中でデクリメント、do_conn_add_to_freelist()の中でインクリメント
- freeconns
- do_conn_*_freelist()
- do_conn_from_freelist()
- conn_new()で関数の最初に呼ばれている
- do_conn_add_to_freelist()
- conn_close()でc->rsize <= READ_BUFFER_HIGHWATの場合に呼ばれている
- conn_new()でevent_add()呼び出しに失敗した場合に呼ばれている(異常時の後始末)
- c->rsize <= READ_BUFFER_HIGHWATとは?
- 定義は以下
- do_conn_from_freelist()
- 各変数についてまとめ
typedef struct conn conn; struct conn { : int rsize; /** total allocated size of rbuf */ : /** High water marks for buffer shrinking */ #define READ_BUFFER_HIGHWAT 8192
-
-
-
- conn->rsizeへのアクセス(後で調べる)
-
-
- conn_new()の呼び出しについて
- main()->server_socket()の流れでlisten()したソケットに対してconn_new(sfd, conn_listening, EV_READ | EV_PERSIST, 1, false, main_base)
- drive_machine()でクライアントからの接続をaccept()した後、そのソケットに対してdispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST, DATA_BUFFER_SIZE, false)
- TCP接続とfreeconns
- 起動直後のTCPサーバー構築時
- freecurrが0のまま、conn_new()内でconn_from_freelist()が呼ばれるが空なので、connが生成されstats.conn_structs++
- memcacheクライアントからの接続(初回)
- freecurrが0のまま、conn_new()内でconn_from_freelist()が呼ばれるが空なので、connが生成されstats.conn_structs++
- memcacheクライアントからの接続(二回目)
- freecurrが0のまま、conn_new()内でconn_from_freelist()が呼ばれるがが空なので、connが生成されstats.conn_structs++
- memcacheクライアントからの接続(N回目)←上記同様
- memcacheクライアント切断(初回分)
- freecurrは0、conn_close()内でdo_conn_add_to_freelist()が呼ばれるので、使用していたconnがfreeconnsに積み上げられfreecurrインクリメント
- memcacheクライアント切断(初回分)
- freecurrは1、conn_close()内でdo_conn_add_to_freelist()が呼ばれるので、使用していたconnがfreeconnsに積み上げられfreecurrインクリメント
- 起動直後のTCPサーバー構築時
- TCP接続関連のあっさりとしたまとめ
- memcacheクライアントから接続がある度に"connを生成&関連するバッファを確保"するのは無駄なので、使い終わったconnはfreeconnに積み上げておく
- memcacheクライアントから接続を受けた際、freeconnsにconnが積み上がっていたらそれをpopして使う(使い終わったらまたpush)
- connを生成した数がstats.conn_structsなので、これが増加したということは、何らかの理由により「再利用されるよりも早いスピードでクライアントからの接続を受けた」のでは?