事件驱动是nginx高性能的基石也是核心, 正是基于事件驱动构造永不阻塞的线程运行机制, 减少了线程的上下文切换带来的损耗, 从而造就了nginx的高性能
除了事件部分, connection的创建销毁也是event模块关注的
配置解析
ngx_events_module
首先是event的NGX_CORE_MODULE类型模块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142ngx_module_t ngx_events_module = {
NGX_MODULE_V1,
&ngx_events_module_ctx, /* module context */
ngx_events_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_core_module_t ngx_events_module_ctx = {
ngx_string("events"),
NULL,
// 1.15.2之前只检查是否创建了配置结构体,无其他操作
// 因为event模块只有一个events指令
// 1.15.2之后增加新的代码
// 主要是对套接字进行了检查
ngx_event_init_conf
};
// 1.15.2之前只检查是否创建了配置结构体,无其他操作
// 因为event模块只有一个events指令
// 1.15.2之后增加新的代码
static char *
ngx_event_init_conf(ngx_cycle_t *cycle, void *conf)
{
// 1.15.2新增部分检查代码
ngx_uint_t i;
ngx_listening_t *ls;
// 要求必须有events{}配置块
// 1.15.2之前只有这一段
if (ngx_get_conf(cycle->conf_ctx, ngx_events_module) == NULL) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"no \"events\" section in configuration");
return NGX_CONF_ERROR;
}
// 检查连接数,需要大于监听端口数量
if (cycle->connection_n < cycle->listening.nelts + 1) {
/*
* there should be at least one connection for each listening
* socket, plus an additional connection for channel
*/
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"%ui worker_connections are not enough "
"for %ui listening sockets",
cycle->connection_n, cycle->listening.nelts);
return NGX_CONF_ERROR;
}
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
if (!ls[i].reuseport || ls[i].worker != 0) {
continue;
}
// reuseport专用的函数,1.8.x没有
// 拷贝了worker数量个的监听结构体, in ngx_connection.c
// 从ngx_stream_optimize_servers等函数处转移过来
if (ngx_clone_listening(cycle, &ls[i]) != NGX_OK) {
return NGX_CONF_ERROR;
}
/* cloning may change cycle->listening.elts */
ls = cycle->listening.elts;
}
return NGX_CONF_OK;
}
// 1.10新函数,专为reuseport使用
// 拷贝了worker数量个的监听结构体
// 在ngx_stream_optimize_servers等函数创建端口时调用
ngx_int_t
ngx_clone_listening(ngx_cycle_t *cycle, ngx_listening_t *ls)
{
// configure脚本可以检测系统是否支持reuseport
// 使用宏来控制条件编译
ngx_int_t n;
ngx_core_conf_t *ccf;
ngx_listening_t ols;
// 监听指令需要配置了reuseport
if (!ls->reuseport || ls->worker != 0) {
return NGX_OK;
}
// ls在之前已经正确设置了若干参数
// 例如type/handler/backlog等等
ols = *ls;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
// ccf->worker_processes是nginx的worker进程数
// 拷贝了worker数量个的监听结构体
//
// 注意从1开始,这样拷贝后总数就是worker数量个
// 最开始的一个就是被克隆的ls监听结构体
for (n = 1; n < ccf->worker_processes; n++) {
/* create a socket for each worker process */
ls = ngx_array_push(&cycle->listening);
if (ls == NULL) {
return NGX_ERROR;
}
// 完全拷贝结构体
*ls = ols;
// 设置worker的序号
// 被克隆的对象的worker是0
//
// worker的使用是在ngx_event.c:ngx_event_process_init
// 只有worker id是本worker的listen才会enable
ls->worker = n;
}
return NGX_OK;
}
命令
events
event模块只有一个events命令, 用来放其他事件模块的命令
ngx_event_core_module_t
这里出现了第一个ngx_event_module_t1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83// ngx_event_module_t
// 事件模块的函数指针表
// 核心是actions,即事件处理函数
typedef struct {
// 事件模块的名字,如epoll/select/kqueue
ngx_str_t *name;
// 事件模块的配置相关函数比较简单
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
// 事件模块访问接口,是一个函数表
ngx_event_actions_t actions;
} ngx_event_module_t;
// ngx_event_actions_t
// 全局的事件模块访问接口,是一个函数表
// 由epoll/kqueue/select等模块实现
// epoll的实现在modules/ngx_epoll_module.c
typedef struct {
// 添加事件,事件发生时epoll调用可以获取
// epoll添加事件
// 检查事件关联的连接对象,决定是新添加还是修改
// 避免误删除了读写事件的关注
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
// 删除事件,epoll不再关注该事件
// epoll删除事件
// 检查事件关联的连接对象,决定是完全删除还是修改
// 避免误删除了读写事件的关注
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
// 同add
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
// 同del
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
// 添加一个连接,也就是读写事件都添加
// epoll关注连接的读写事件
// 添加事件成功,读写事件都是活跃的,即已经使用
ngx_int_t (*add_conn)(ngx_connection_t *c);
// 删除一个连接,该连接的读写事件都不再关注
// epoll删除连接的读写事件
// 删除事件成功,读写事件都不活跃
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
// 目前仅多线程使用,通知
// 调用系统函数eventfd,创建一个可以用于通知的描述符,用于实现notify
// 模仿此用法也可以实现自己的通知机制
ngx_int_t (*notify)(ngx_event_handler_pt handler);
// 事件模型的核心功能,处理发生的事件
//
// epoll模块核心功能,调用epoll_wait处理发生的事件
// 使用event_list和nevents获取内核返回的事件
// timer是无事件发生时最多等待的时间,即超时时间
// 函数可以分为两部分,一是用epoll获得事件,二是处理事件,加入延后队列
// 在ngx_process_events_and_timers里被调用
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
// 初始化事件模块
// 调用epoll_create初始化epoll机制
// 参数size=cycle->connection_n / 2,但并无实际意义
// 设置全局变量,操作系统提供的底层数据收发接口
// 初始化全局的事件模块访问接口,指向epoll的函数
// 默认使用et模式,边缘触发,高速
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
// 事件模块结束时的收尾工作
// epoll模块结束工作,关闭epoll句柄和通知句柄,释放内存
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
// 全局的事件模块访问接口,是一个函数表
// 定义了若干宏简化对它的操作
// 常用的有ngx_add_event/ngx_del_event
// 在epoll模块的ngx_epoll_init里设置,指向epoll的函数
// ngx_event_actions = ngx_epoll_module_ctx.actions;
extern ngx_event_actions_t ngx_event_actions;
// 访问的时候就可以直接actions.add 访问具体的事件模块定义的方法
1 | // 但它不实现具体的事件模型,所以actions函数表全是空指针 |
1 | ngx_module_t ngx_event_core_module = { |
指令
worker_connections
单个进程所能创建的最大连接数, 注意不只是代理的连接, 是进程上用到的所有连接
use
用哪个事件响应机制
multi_accept
决定了一次accept能接收多少个请求, off一次接1个, on一次能接多少接多少
accept_mutex
负载均衡锁
accept_mutex_delay
负载均衡锁上等待最多多少时间
debug_connection
debug_connection address | CIDR | unix:;
对某些client打印debug日志