ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
#if (NGX_HAVE_REUSEPORT)
if (ls[i].reuseport && ls[i].worker != ngx_worker) {
// 对应端口开启了reuseport,每个进程跳过不属于自己处理的套接字
continue;
}
#endif
c = ngx_get_connection(ls[i].fd, cycle->log);
概述
新版本的Linux支持了reuseport(注意区分reuseaddr),允许多个套接字bind 到相同的ip和port上了。内核为每个套接字分配一个链接队列。当有链接到达,内核根据tcp四元组hash到一个队列。 这样做的好处是:1,内核分配链接到一个链接队列,有效防止了惊群。2,内核根据四元组和bind的进程hash,其实做了一次负载均衡。3,为每个bind的套接字分配一个链接队列,无形中增大了backlog。
最近我们有个类似秒杀活动的页面,瞬间并发高达几十万。没开启reuseport的时候backlog配置6万。秒杀开始的时候80端口基本无响应,backlog满了,client的syn包被丢弃,此时服务器负载并不高。开启了reuseport每个链接队列等待accept的链接就高达2万。服务器负载已经非常高了。开启reuseport合理配置backlog是可以提高服务器性能的。
nginx的实现
配置
只要你linux服务器支持了reuseport这个特性,nginx配置非常简单。只需要在你要开启reuseport监听的命令后添加reuseport就可以开启了。 开启了以后你用ss看就发现每个进程都监听对应的端口。
代码分析
在ngx_http_block函数中,解析完毕http{} 的配置后,调用ngx_http_optimize_servers处理绑定监听的端口和ip地址,以及对应的虚拟主机。 ngx_http_optimize_servers调用了ngx_http_init_listening函数初始化监听的结构体。 ngx_http_init_listening又调用了ngx_clone_listening函数。该函数就是为开启了reuseport的监听复制监听结构体的。
在函数ngx_init_cycle中调用了ngx_open_listening_sockets函数,bind并listen上述添加到cf->cycle->listening里面的ip和port。上述clone函数会为开启reuseport的端口每个进程复制了一份,所以对于开启了reuseport的端口就会根据进程数bind了响应的次数。
初始化好配置后,main函数调用ngx_master_process_cycle函数启动工作进程,启动工作进程请参考nginx的master和worker模式。 最后在每个工作进程中调用了ngx_worker_process_init函数,该函数会循环调用每个模块的init_process方法。
而ngx_event_core_module模块的ngx_event_process_init会添加套接字监听事件,而开启了reuseport的端口会有多个套接字,而每个进程只添加属于自己的监听套接字。