关于 nginx 的一些事
本文主要是记录最近碰到的几个 Nginx 模块开发时碰到的问题。
cancelable timer
之前用的是 1.6.x 版本的 nginx,有些年头了。我们的模块会每个四小时从硬盘读取数据缓存内存中。 但是当我们 nginx -s reload 时,旧进程会一直处于 nginx: worker process is shutting down. 几个小时才会彻底消失
模块中设置定时循环读取的代码类似于:
static void ngx_data_reload_event_handler(ngx_event_t *ev)
{
if (ev->timer_set) ngx_del_timer(ev);
if (!(ngx_quit || ngx_terminate || ngx_exiting)){
reload_data();
ngx_add_timer(ev, DEFAULT_RELOAD_INTERVAL*1000);
}
}
究其原因,这样设置的 timer 在进程重启时并没有被删除,需要等到之前设置的时刻到了才会释放,旧进程才能消失。
实际上1.7之后的 Nginx 中,加入了 cancelable timer 的功能。 只需要将 event 设置上 cancelable 属性即可。
ev->cancelable = 1;
处理逻辑的位置在 src/os/unix/ngx_process_cycle.c 中,相关函数自己去翻吧,我懒得放了。
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
ngx_int_t worker = (intptr_t) data;
ngx_process = NGX_PROCESS_WORKER;
ngx_worker = worker;
ngx_worker_process_init(cycle, worker);
ngx_setproctitle("worker process");
for ( ;; ) {
if (ngx_exiting) {
ngx_event_cancel_timers();
if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
{
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
ngx_process_events_and_timers(cycle);
if (ngx_terminate) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
if (ngx_quit) {
ngx_quit = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"gracefully shutting down");
ngx_setproctitle("worker process is shutting down");
if (!ngx_exiting) {
ngx_exiting = 1;
ngx_close_listening_sockets(cycle);
ngx_close_idle_connections(cycle);
}
}
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
ngx_reopen_files(cycle, -1);
}
}
}
log
今天某无良模块需要写多份日志,调研时无意中发现, 日志函数 ngx_log_error_core (src/core/ngx_log.c) 写日志处调用的 ngx_write_fd 实际上就是个 thin wrapper:
/* src/os/unix/ngx_files.h
/*
* we use inlined function instead of simple #define
* because glibc 2.3 sets warn_unused_result attribute for write()
* and in this case gcc 4.3 ignores (void) cast
*/
static ngx_inline ssize_t
ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
{
return write(fd, buf, n);
}
那么多进程 write 同一个文件是如何保证原子性的呢? 我们知道 Nginx 是通过 O_APPEND 方式打开日志文件的,Linux 的文件系统是保证 PIP_BUF(4KiB) 的写操作是原子的。不过, Nginx 的开发者说更大的值(32KiB)也可以,不知道他们的数据哪来的。