なんかさっそく間隔が空いてしまったが気にしないことにする。
前回
nginxソースコードリーディング その1〜nginx起動〜
リビジョン
5422:89405deca1ad
イベント駆動
前回はmasterプロセスと各workerプロセスが起動するところまで書いた。
masterプロセスはシグナルが飛んでこない限り特に何もしないが、
workerプロセスはnginx自身が持つタイマーに基づいた間隔で絶えずイベントを処理している。
static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
・
・
・
for ( ;; ) {
・
・
・
// イベント処理
ngx_process_events_and_timers(cycle);
・
・
・
}
・
・
・
}
nginxはソケットへの接続やread、writeをはじめありとあらゆる処理を非同期にイベントとして処理するイベント駆動型のサーバである。
これに加えてnginxはノンブロッキング・非同期I/Oを駆使することで非常に高いスケーラビリティを実現している。
一般的にイベント駆動型のサーバプログラムは複数のcallbackを駆使したステートマシンを構築することになるのでprefork型のと比べて可読性が悪くなりやすいという欠点があるが、nginxは元々C10K問題を解決するために開発されたので、このようなアーキテクチャになっているのだろう。
nginxのイベント駆動エンジンは非常に抽象度が高く、callbackだらけで今までのようにトップダウンで読み進めるのがかなり大変なので若干拾い読み気味にいくことにする。(正直やってて挫折しそうになった)
さっき出てきたngx_process_events_and_timersの中でngx_process_eventsが呼ばれている。
(void) ngx_process_events(cycle, timer, flags);
これはこんな感じのマクロである。
#define ngx_process_events ngx_event_actions.process_events
ngx_event_actionsはngx_event_actions_tという型のグローバル変数で型の定義はこんな感じ。
typedef struct {
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*add_conn)(ngx_connection_t *c);
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
nginxが扱うイベントの動作は全てこのngx_event_actions_tの各callbackに基づいて抽象化される。
例えばselectやpoll、epoll、kqueueといったI/O multiplexingの処理もこの抽象化されたエンジンの上に乗って行われるので、libeventやlibevを使うことなく、I/O multiplexingにどのシステムコール(epoll, kqueueなど)を使うか選択することができる。
イベント駆動エンジンによるI/O multiplexingの抽象化
nginx.confの先頭によくこんなことを書くわけだが、
events {
worker_connections 1024;
use epoll;
}
use epoll」はI/O multiplexingにepollを使うことを示している。この場合、件のngx_event_actionsにはngx_epoll_module_ctx.actionsが代入される。
ngx_event_actions = ngx_epoll_module_ctx.actions;
ngx_epoll_module_ctxの定義はこんな感じになっていて、最後の括弧で囲まれている部分がngx_event_actions_tの各callbackへの関数ポインタである。
ngx_event_module_t ngx_epoll_module_ctx = {
&epoll_name,
ngx_epoll_create_conf, /* create configuration */
ngx_epoll_init_conf, /* init configuration */
{
ngx_epoll_add_event, /* add an event */
ngx_epoll_del_event, /* delete an event */
ngx_epoll_add_event, /* enable an event */
ngx_epoll_del_event, /* disable an event */
ngx_epoll_add_connection, /* add an connection */
ngx_epoll_del_connection, /* delete an connection */
NULL, /* process the changes */
ngx_epoll_process_events, /* process the events */
ngx_epoll_init, /* init the events */
ngx_epoll_done, /* done the events */
}
};
selectならこうなる。
ngx_event_module_t ngx_select_module_ctx = {
&select_name,
NULL, /* create configuration */
ngx_select_init_conf, /* init configuration */
{
ngx_select_add_event, /* add an event */
ngx_select_del_event, /* delete an event */
ngx_select_add_event, /* enable an event */
ngx_select_del_event, /* disable an event */
NULL, /* add an connection */
NULL, /* delete an connection */
NULL, /* process the changes */
ngx_select_process_events, /* process the events */
ngx_select_init, /* init the events */
ngx_select_done /* done the events */
}
};
というわけでnginxのイベント駆動エンジン部分について読み進めてみました。
ここはnginxのアーキテクチャの肝の一つで、正直まだ詳細に理解できてるわけではないのでしばらくしたらまた戻ってくるかもしれませんが、とりあえず今回はここまで。