nginx启动流程之work初始化

        上一篇分析了master进程的初始化流程,接着来分析work进程的初始化流程。work进程初始化流程包括:

        1、work进程创建;

        2、ngx_worker_process_init初始化过程

        3、master-work进程通信

        4、work进程的事件机制

        5、work进程负载均衡实现

一、work进程的创建

        在master进程的ngx_start_worker_processes函数中,会调用ngx_spawn_process函数开始创建work进程。创建完成后master进程、work进程同时工作,分别执行不同的业务逻辑。work进程用来处理来自客户端的读写事件,或者将客户端的请求反向代理到上游服务器等。函数ngx_spawn_process用来创建一个work进程,而work进程的处理函数为ngx_worker_process_cycle.

ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
{
	//创建work进程
    pid = fork();
    switch (pid)
	{
	    case -1:
	        ngx_close_channel(ngx_processes[s].channel, cycle->log);
	        return NGX_INVALID_PID;

	    case 0:
			//子进程处理逻辑
	        ngx_pid = ngx_getpid();
	        proc(cycle, data);
	        break;
	    default:
	        break;
    }
    return pid;
}

二、ngx_worker_process_init初始化过程

        ngx_worker_process_cycle函数为work进程的入口函数,在该函数内,会调用ngx_worker_process_init函数,对work进程进行初始化操作。

初始化操作主要是为work进程的运行提供必要的环境。例如设置work进程能够打开socket的最大个数;预先分配连接池空间,当有客户端请求时,直接从连接池中获取一个空闲连接;创建epoll对象,使得能够监听客户端的读写事件等。下面以一张思维导图来概括ngx_worker_process_init初始化过程。


1、函数会对work进程的一些基本环境进行初始化操作。例如:

        (1)设置work进程的优先级; 

        (2)设置work进程最大能打开多少文件;

        (3)设置work进程core文件的最大值;

        (4)设置work进程绑定到特定的cpu

//设置work进程的优先级
    if (priority && ccf->priority != 0) 
	{
        if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) 
		{
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "setpriority(%d) failed", ccf->priority);
        }
    }
        而像ccf->priority这些要设置的值,都是解析nginx.conf配置后得到,保存到core核心模块的上下文中。

2、接下来work进程将调用各个模块实现的init_process方法,用来初始化所有的模块

//调用所有模块的init_process方法,目前只有ngx_event_core_module模块实现了方法。
	//方法名为ngx_event_process_init
    for (i = 0; ngx_modules[i]; i++) 
	{
        if (ngx_modules[i]->init_process) 
		{
            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) 
			{
                /* fatal */
                exit(2);
            }
        }
    }
        目前只有ngx_event_core_module模块实现init_process方法,方法名为ngx_event_process_init。函数内部主要创建work事件模型所需要的epoll对象;以及使用红黑树数实现定时器,用来管理超时事件;注册SIGALRM信号,在信号回调中获取系统时间;预先开辟连接池空间,注册客户端连接事件的回调等等。

        2.1、创建红黑树实现的定时器

        nginx内部使用红黑树实现定时器功能,用来管理所有超时事件。函数ngx_event_timer_init就是用来初始化一颗红黑树。

//初始化红黑树定时器
    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) 
	{
        return NGX_ERROR;
    }
        初始化好红黑树定时器后,就可以调用ngx_event_add_timer函数往红黑树中插入一个事件,待事件超时后,就会触发超时回调。

如果不想使用超时事件了,则可以调用ngx_event_del_timer函数将超时事件从红黑树中删除,删除后相应的超时回调就不会被触发。

        2.2、work进程会对所有事件模块进行初始化操作。

        在编译nginx时,系统会优先使用一种事件模型进行编译,一般linux下事件模型为epoll。在每一个平台只会有一个事件模块编译,例如:epoll, select中的一个。

各个事件模块实现的init方法,就是为了创建相应的事件对象。以epoll为例,init函数将创建epoll对象,同时指定epoll每次最多可以从内核中返回多少个事件。

for (m = 0; ngx_modules[m]; m++) 
	{
        if (ngx_modules[m]->type != NGX_EVENT_MODULE) 
		{
            continue;
        }
		//使用use配置项指定的事件驱动模块,linux下为epoll
        if (ngx_modules[m]->ctx_index != ecf->use) 
		{
            continue;
        }
        module = ngx_modules[m]->ctx;
		//调用epoll事件模块的init方法,创建epoll对象与epoll事件方法。
		//因为目前只有ngx_epoll_module模块的上下文实现了actions方法,init方法为ngx_epoll_init
        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK)
		{
            exit(2);
        }
        break;
    }
        epoll事件模块实现的init方法为ngx_epoll_init。函数内部会创建epoll对象,同时指定epoll每次最多可以从内核中返回多少个事件。
//epoll模块初始化,创建epoll句柄,同时创建epoll队列
static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
	//创建epoll对象
    if (ep == -1) 
	{
        ep = epoll_create(cycle->connection_n / 2);
        if (ep == -1) 
		{
       
            return NGX_ERROR;
        }
    }
	//创建epoll返回队列,也就是epoll每次从epoll_wait返回时,最多可以返回多少个事件
    if (nevents < epcf->events)
	{
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,cycle->log);
       
    }
	//返回队列元素个数
    nevents = epcf->events;
	//保存epoll事件的10个方法
    ngx_event_actions = ngx_epoll_module_ctx.actions;
    return NGX_OK;
}

有了epoll对象后,就可以将读写事件注册到epoll中,当客户端连接时将触发读事件回调。

        2.3 设置定时器,在定时时间到后,会触发SIGALRM信号。这个定时器是用来避免每次事件循环返回都调用gettimeofday函数,而是定时调用,减少调用系统调用频率

   itv.it_value.tv_sec = ngx_timer_resolution / 1000;
        itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
        setitimer(ITIMER_REAL, &itv, NULL);

定时器的回调为ngx_timer_signal_handler,函数内部只是给变量ngx_event_timer_alarm打上标记

//信号处理函数,用于更新标记。目的是为了减少频繁的系统调用更新时间,而采用信号定时器方式获取
//ngx_event_timer_alarm更新后,表示需要进行一次gettimeofday系统调用,获取时间
void ngx_timer_signal_handler(int signo)
{
    ngx_event_timer_alarm = 1;
}

这样每次epoll返回时,会根据这个变量决定是否需要进行一次系统调用,从而获取系统时间。ngx_epoll_process_events为ngx_epoll_module模块上下文结构实现的process_events方法

ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
	//等待事件返回
    events = epoll_wait(ep, event_list, (int) nevents, timer);
	//更新缓存时间
    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) 
	{
        ngx_time_update();
    }
}

     2.4 接下来将会创建连接池空间。每一个连接对象ngx_connection_t,都会关联一个读事件,一个写事件ngx_event_t结构。也就是说,nginx服务器对于每一个客户端连接,都可以处理该客户端的读事件,写事件。nginx通过预先分配一个连接池空间,当有客户端连接时,直接从连接池中获取一个空闲连接。

	//预分配连接池
    cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);

	//预分配读事件池
    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);

	//预先分配写事件池
    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,cycle->log);
	//将连接池,读事件池,写事件池一一关联起来
    do 
	{
        i--;
        c[i].data = next;		//data成员组成一个空闲连接池链表
        c[i].read = &cycle->read_events[i];
        c[i].write = &cycle->write_events[i];
        c[i].fd = (ngx_socket_t) -1;

        next = &c[i];
    } while (i);

        nginx使用数组方式实现了一个连接池链表,使用data成员将各个连接对象ngx_connection_t给连接起来。这样从连接池中获取一个连接后,也相应得到连接对应的读事件,写事件。最后使用cycle->free_connections指针指向空闲连接池链表头;

 cycle->free_connections = next; //空闲连接池链表头

        以下是可能存在的连接池、读事件池、写事件池的内存布局图

        在创建好连接池后,如果有客户端连接,就可以调用ngx_get_connection函数,从连接池中获取一个连接。

        2.5 为所有监听socket,从空间连接池中获取一个连接。同时将注册监听socket读事件回调设置为ngx_event_accept。当有客户端连接时,该函数会被调用,用于建立与客户端的连接。

	//对于每一个监听端口,从连接池中取出一个连接对象(也将从读时间,写事件池取出对象,
	//使得连接,读、写保持一一对应关系),负责监听来自客户端的连接
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) 
    {
        c = ngx_get_connection(ls[i].fd, cycle->log);
        c->listening = &ls[i];
        ls[i].connection = c;
        rev = c->read;
	//设置连接回调,当有客户端连接时,将触发回调
	//这里不需要注册写事件回调,应该在没有建立连接时,nginx不可能向客户端写入数据
        rev->handler = ngx_event_accept;

	//如果work进程之间使用枷锁,则暂时不把读事件加入epoll中,在别处加入。这种方式下,只会有一个work进程将所有的监听socket加入到epoll事件管理器中
        if (ngx_use_accept_mutex) 
        {
            continue;
        }

	//如果work进程之间没有使用枷锁,则把读事件加入epoll中。这种情况下各个work进程将该socket的读连接事件。
 	    if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) 
            {
	        return NGX_ERROR;
	        } 
     }
        如果work进程之间不使用同步,则各个work进程都可以将所有监听socket加入到epoll中。这样当某个监听socket有客户端连接时,各个work将采用抢占方式,最终只会有一个work进程获取该socket的连接权限,处理客户端的连接请求。这容易造成"惊群"现象。在这种方式下,上面函数会将监听socket加入到epoll中。ngx_add_event是一个宏,添加事件的方法为ngx_epoll_add_event。

static ngx_int_t
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
	//data指的是该事件对于的是哪个连接
    c = ev->data;
	//活跃事件,则说明之前已经注册过事件,现在则应是修改操作
    if (e->active) 
	{
        op = EPOLL_CTL_MOD;
        events |= prev;

    }
	else 
	{
        op = EPOLL_CTL_ADD;
    }

	//注册或者修改事件
    epoll_ctl(ep, op, c->fd, &ee);
    return NGX_OK;
}

        如果work进程之前使用同步方式,则只会有一个work进程能够将所有监听socket加入到epoll中。其它work进程因为获取不到监听socket,从而不会处理来自客户端的连接。因此,同一时刻只会有一个work进程在监听客户端的连接事件。当然如果已经连接的请求,则不影响,各个work进程正常接收客户端的读写事件。在使用同步方式时,上面函数并不会将监听socket加入到epoll中。那什么时候会将监听socket加入到epoll中呢? 在ngx_enable_accept_events函数中,会将监听socket加入到epoll中。

ngx_worker_process_cycle

   ---->ngx_process_events_and_timers

        ---->ngx_trylock_accept_mutex

            ---->ngx_enable_accept_events

static ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle)
{
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) 
	{

        c = ls[i].connection;
         ngx_add_event(c->read, NGX_READ_EVENT, 0);
    }

    return NGX_OK;
}
3、work进程继承了master进程的进程表。进程表中存放了各个work进程的信息。work进程使用自己的channe[1]管道读取master进程或者其它work进程发来的数据。因此就不需要其它work进程的channel[1]读管道,因此可以关闭。

    for (n = 0; n < ngx_last_process; n++) 
	{
		//本work进程只会从自己的channel[1]读取来自master进程或者其它work进程的数据,因此可以关闭其它
		//work进程的读端
        close(ngx_processes[n].channel[1]);
    }
work进程自己不需要channel[0]写管道,因此可以关闭写管道。
	//work进程关闭写端,也就是说work进程从channel[1]读取来自master进程或者其它work进程写入管道的数据
	//work进程不会写数据给master进程,但work进程会写数据给其它的work进程,则可以在进程表中找到相应work进程,
	//并向channel[0]写入数据
    if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) 
	{
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "close() channel failed");
    }
4、work进程间通信、以及master进程与work进程通信,使用管道来通信。在上面我们已经创建了epoll对象,同时也预先分配了连接池空间。现在可以将管道加入到epoll中监听。这样就将管道与epoll事件关联起来了。 ngx_add_channel_event函数将从连接池中获取一个连接,并加事件加入到epoll中。
//将管道加入到epoll事件
ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
    										ngx_event_handler_pt handler)
{
    ngx_event_t       *ev, *rev, *wev;
    ngx_connection_t  *c;
	//从空闲连接队列中获取一个连接对象
    c = ngx_get_connection(fd, cycle->log);

    c->pool = cycle->pool;
    //关联连接对应的读写事件
    rev = c->read;
    wev = c->write;

    rev->channel = 1;
    wev->channel = 1;
    ev = (event == NGX_READ_EVENT) ? rev : wev;
	//设置事件回调
    ev->handler = handler;
	//将事件添加到epoll中
    ngx_add_event(ev, event, 0);
    return NGX_OK;
}

        将读管道注册到epoll中,读管道的回调为ngx_channel_handler。在master进程向管道写入数据时,epoll被触发,从而可以ngx_channel_handler函数被调用。在该函数内可以读取到数据。下面以master-work进程通信的例子来分析ngx_channel_handler函数的使用。

三、master-work进程通信

        下面以重新打开文件为例,说明master-work进程的通信过程。当执行./nginx -s reopen时。这个新执行的程序会查找master进程所在的pid文件,进而向master进程发送USR1信号,也就是reopen信号。master进程中,信号函数ngx_signal_handler会被调用,给ngx_reopen打上标记。

void ngx_signal_handler(int signo)
{
	//master进程中执行
	switch (signo)
	{
		case ngx_signal_value(NGX_REOPEN_SIGNAL):		//用户信号,用来reopen
		ngx_reopen = 1;									//打上标记
		action = ", reopening logs";
		break;
	}
}
        之后master进程的事件循环函数ngx_master_process_cycle,会循环检测是否有reopen标记。如果有,master进程首先将所有已经打开文件缓存中的数据保存到文件中。然后在重新打开所有文件。接着向channe[1]写管道,向work进程写入数据,通知work进程重新打开所有文件。

    //master进程工程流程
void ngx_master_process_cycle(ngx_cycle_t *cycle)
{
    for ( ;; ) 
	{
		//收到重新打开所有文件信号
        if (ngx_reopen) 
		{
            ngx_reopen = 0;
            ngx_reopen_files(cycle, ccf->user);
            ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_REOPEN_SIGNAL));
        }
    }
}
void ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
{
	ch.command = NGX_CMD_REOPEN;
	ngx_write_channel(ngx_processes[i].channel[0],&ch, sizeof(ngx_channel_t), cycle->log)
}  
        在上面已经将work进程的读管道channel[1]已经注册到epoll中。这样master进程通过channel[0]向管道写入数据时,epoll会被触发,进而调用读管道的回调ngx_channel_handler。该函数会从管道中读取数据,并将ngx_reopen打上标记。
static void ngx_channel_handler(ngx_event_t *ev)
{
    for ( ;; ) 
	{
		//work进程从读管道channel[1]中读取数据,数据内容为ngx_channel_t
        n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
        switch (ch.command) 
		{
			case NGX_CMD_REOPEN:
				ngx_reopen = 1;		//work进程给该变量打上标记
				break;
        }
    }
}
        work进程的事件循环函数ngx_worker_process_cycle会检测ngx_reopen标记是否为1。如果是,则work进程将所有已经打开文件的缓存写入到文件中保存。然后重新打开文件。

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    for ( ;; ) 
	{
		//重新打开所有文件
        if (ngx_reopen) 
		{
            ngx_reopen = 0;
            ngx_reopen_files(cycle, -1);
        }
    }
}
四、work进程的事件机制

        nginx采用epoll来管理来自客户端的读写事件。work进程的事件循环函数中会调用ngx_process_events_and_timers函数,处理来自客户端的网络事件。

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    for ( ;; ) 
	{
		ngx_process_events_and_timers(cycle);  //处理epoll网络事件
    }
}
        在上面2.4小节中,如果work进程需要使用互斥锁来进行同步,那时候并没有将所有监听socket加入到epoll中。使用互斥所情况下ngx_use_accept_mutex这个标记将为1,这个时候应该将所有监听socket加入到epoll中。当前work进程这个时候如果获取到了锁,则将所有监听socket加入到epoll中。如果work进程没有获取到锁,那应该将所有监听socket从epoll中移除。这样就保证了在同一时候,只会有一个work进程监听来自客户端的连接。函数ngx_trylock_accept_mutex就是来实现这个功能。
//获取互斥锁
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
	//获取到锁
    if (ngx_shmtx_trylock(&ngx_accept_mutex)) 
	{
		//如果进程已经获取到锁了,则直接返回,不需要把监听fd加入到事件中
		//什么时候会走入这样逻辑? 进程第一次获取锁成功,并在epoll_wait返回后释放了锁,但没有将
		//ngx_accept_mutex_held标记清0。这样所有子进程都可以抢占锁,如果还是本进程先获取到锁,会进入
		//这个逻辑?
        if (ngx_accept_mutex_held
            && ngx_accept_events == 0
            && !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
        {
            return NGX_OK;
        }
		//将所有监听fd加入监听事件中
        ngx_enable_accept_events(cycle);

		//标示进程拥有锁
        ngx_accept_events = 0;
        ngx_accept_mutex_held = 1;

        return NGX_OK;
    }
	//未获取到锁,但进程拥有了锁,则是不正确的。应该把所有监听连接都关闭,并标示进程没有拥有锁。
	//什么时候会走入这样逻辑? 进程第一次获取锁成功,并在epoll_wait返回后释放了锁,但没有将
	//ngx_accept_mutex_held标记清0。这样所有子进程都可以抢占锁,在其他子进程先获取到情况下,会进入
	//这个逻辑。
    if (ngx_accept_mutex_held) 
	{
        if (ngx_disable_accept_events(cycle) == NGX_ERROR) 
		{
            return NGX_ERROR;
        }

        ngx_accept_mutex_held = 0;
    }

    return NGX_OK;
}

        ngx_enable_accept_events负责将所有监听socket加入到epoll, ngx_disable_accept_events则将所有监听socket从epoll中移除。

        接下来执行ngx_process_events,等待事件发生。在有事件时从epoll中返回,或者超时时间到了,即便没有事件也会返回。这是一个宏,实际函数为ngx_epoll_process_events。看下这个函数的处理逻辑。

static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, 
													   ngx_msec_t timer, 
													   ngx_uint_t flags)
{
	//等待事件返回
    events = epoll_wait(ep, event_list, (int) nevents, timer);
	//更新缓存时间
    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) 
	{
        ngx_time_update();
    }
	//遍历所有已经发生的事件
    for (i = 0; i < events; i++) 
	{
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
        rev = c->read;
        revents = event_list[i].events;
		//如果是读事件且事件时活跃的
        if ((revents & EPOLLIN) && rev->active) 
		{
			//标示这个事件要延后处理
            if (flags & NGX_POST_EVENTS) 
			{
				//如果要在post队列中延后处理事件,首先要判断它是新连接还是普通事件,从而决定要把
				//事件放到哪个队列。连接队列:存放来自客户端的连接。
				//普通队列: 存放已经建立连接的客户端,进行读写事件通信
                queue = (ngx_event_t **) (rev->accept ?
                               &ngx_posted_accept_events : &ngx_posted_events);

				//将事件放入相应的延后队列
                ngx_locked_post_event(rev, queue);
            } 
			else 
			{
				//不是延迟事件,则立即调用读事件回调
                rev->handler(rev);
            }
        }
        wev = c->write;
		//取出写事件
        if ((revents & EPOLLOUT) && wev->active) 
		{
			//将事件添加到延迟队列中
            if (flags & NGX_POST_EVENTS) 
			{
                ngx_locked_post_event(wev, &ngx_posted_events);
            } 
			else 
			{
				//立即调用写事件的回调
                wev->handler(wev);
            }
        }
    }
    return NGX_OK;
}
        work进程阻塞在epoll_wait系统调用中。如果有事件发生,则work进程被唤醒。进而判断事件是否需要延迟处理。如果不需要延迟处理,则立即调用事件的处理函数。如果需要延迟处理,则将事件加入到队列中,稍后再对队列中的事件进行处理。nginx使用了两个队列,一个连接队列,存放新客户端的连接。另一个普通队列,存放所有已经建立连接后的客户端。这两个队列中,连接队列的优先级更高。也就是说在同一个时刻,有新客户端的连接,也有老客户的读写事件请求。则优先处理新客户端的连接事件。

	//调用epoll_wait等待事件
    (void) ngx_process_events(cycle, timer, flags);

	//计算处理事件消耗多长时间
    delta = ngx_current_msec - delta;

    //优先处理连接队列中的所有连接
    if (ngx_posted_accept_events) 
	    {
        ngx_event_process_posted(cycle, &ngx_posted_accept_events);
    }
	//处理完连接队列后,要立即释放互斥锁,避免长事件占用锁,而不能在普通读写事件处理完后才释放锁。
	//因为有可能普通事件处理的时间很长,导致锁没释放,其它子进程无法处理新的连接
    if (ngx_accept_mutex_held) 
	    {
		//此处最好也调用ngx_disable_accept_events清除监听socket,而不需要等待循环把监听socket清除
		//因为释放锁后,其它子进程有可能马上获取到了锁,并把监听socket加入到epoll。
		//这样导致多个work进程都在监听同一个socket
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

	//消耗了时间,有可能红黑树中有超时事件,则处理红黑树中所有超时事件,并把超时事件从红黑树中删除
    if (delta) 
	    {
        ngx_event_expire_timers();
    }
	//最后才处理普通事件队列中的读写事件
    if (ngx_posted_events) 
	    {
       
         ngx_event_process_posted(cycle, &ngx_posted_events);

    }

        而ngx_event_process_posted实际上只是调用事件的处理函数。

//事件post事件队列中的事件,处理完后并将事件从队列中删除
//注意:删除事件并没有真正把ngx_event_t删除,而是将prev,next指针从post队列中断开
void ngx_event_process_posted(ngx_cycle_t *cycle,
    ngx_thread_volatile ngx_event_t **posted)
{
    ngx_event_t  *ev;
    for ( ;; ) 
	{
        ev = (ngx_event_t *) *posted;
        ngx_delete_posted_event(ev);
        ev->handler(ev);
    }
}
五、work进程间负载均衡

        每一个work进程都可以监听来自客户的连接,使用互斥锁方式实现work进程间的同步。在某些情况下,有可能某个work进程处理着大量的客户端连接,处于高负载工作状态。而其他work进程可能只处理着少量的客户端连接,这样就造成负载不均,影响整体性能。那nginx是如何处理work进程之间的负载均衡呢?

        在2.4中将所有监听socket加入到epoll时,注册了读事件的回调为ngx_event_accept;在函数中会对ngx_accept_disabled变量进行统计。如果work进程处理的客户端连接数达到了work进程连接的7/8时,说明work进程处于高负载运行状态,此时ngx_accept_disabled将大于0,不能在处理新客户端的连接事件了。

  ngx_accept_disabled = ngx_cycle->connection_n / 8
                              - ngx_cycle->free_connection_n;

        c = ngx_get_connection(s, ev->log);
        那work进程根据这个ngx_accept_disabled这个变量,什么时候确定是否还能允许新客户端连接呢? 在函数ngx_process_events_and_timers中会对这个变量值进行判断。如果该变量值大于0,说明work进程处于高负载运行状态,此时不能处理新客户端连接。函数中当值大于0时,只是把变量值减1操作。并不会尝试去获取锁,也就不会将所有监听socket加入到epoll中。因为没有把监听socket加入到epoll中,因此该work进程也就不会处理新客户端的连接。这样本work进程不参数互斥锁的抢占,其它的work进程可以获取到锁后,处理新客户端的连接,从而实现work进程之间的负载均衡。当然,在变量值递减到0以下时,本work进程还是会再次尝试获取锁操作。

void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
	//nginx.conf配置了accept_mutex on, 标示使用锁
    if (ngx_use_accept_mutex) 
	{
		//当值为正数时,本work进程不处理新连接事件,而是将值递减1。
		//此处怀疑有bug,最好调用ngx_disable_accept_events清除监听socket
        if (ngx_accept_disabled > 0) 
		{
            ngx_accept_disabled--;

        } 
		else 
		{
			//尝试获取锁
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) 
			{
                return;
            }
        }
    }
}
        到此work进程的初始化操作就全部分析完成了。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值