1。Nginx基础设施启动后,nginx作为守护进程运行在后台,后台进程包括一个master进程和多个worker进程。如下图所示:master和workernginx是多进程模型,一个master管理进程,多个worker进程处理工作。Infrastructuredesign,如下图所示:infrastructuredesignmaster负责管理worker进程,worker进程负责处理网络事件。整个框架设计为事件驱动、异步、非阻塞模式。这种设计的优点:1.可以充分利用多核机器,增强并发处理能力。2.可以在多个worker之间实现负载均衡。3.Master对worker行为进行统一监控和管理。worker异常后,可以主动拉起worker进程,从而提高系统的可靠性。此外,Master进程控制服务运行过程中的程序升级、配置项修改等操作,从而增强整体的动态扩展和热更新能力。2.Master进程2.1核心逻辑Master进程的主要逻辑在ngx_master_process_cycle中,核心关注源码:ngx_master_process_cycle(ngx_cycle_t*cycle){...ngx_start_worker_processes(cycle,ccf->worker_processes,NGX_PROCESS_RESPAWN);...for(;;){if(delay){...}ngx_log_debug0(NGX_LOG_DEBUG_EVENT,cycle->log,0,"sigsuspend");sigsuspend(&set);ngx_time_update();ngx_log_debug1(NGX_LOG_DEBUG_EVENT,cycle->log,0,"wakeup,sigio%i",sigio);if(ngx_reap){ngx_reap=0;ngx_log_debug0(NGX_LOG_DEBUG_EVENT,cycle->log,0,"reapchildren");live=ngx_reap_children(cycle);}if(!live&&(ngx_terminate||ngx_quit)){...}if(ngx_terminate){...}if(ngx_quit){...}if(ngx_reconfigure){...}if(ngx_restart){...}if(ngx_reopen){...}if(ngx_change_binary){...}if(ngx_noaccept){ngx_noaccept=0;ngx_noaccepting=1;ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL));}}}从上面的代码可以看出了解到master进程主要是用来管理worker进程的,包括f以下四个主要功能:1.接受来自外界的信号。mastercycle中的flags对应着各种信号,比如:ngx_quit代表QUIT信号,表示整个服务优雅关闭。2.给每个worker进程发信。例如ngx_noaccept代表WINCH信号,表示所有子进程不再接受和处理新的连接,master向所有子进程发送QUIT信号量。3.监控worker进程的运行状态。比如ngx_reap代表CHILD信号,表示一个子进程意外结束。这时候就需要监控所有子进程的运行状态,主要由ngx_reap_children完成。4.当woker进程退出时(异常情况下),会自动重启一个新的woker进程。主要还是在ngx_reap_children2.2热更新2.2.1hotreload-configurationhotupdatehotreloadnginx热更新配置的过程中,能够保持运行并顺利更新配置。具体过程如下:1.更新nginx.conf配置文件并上传给master发送SIGHUP信号或者执行nginx-sreload2.master进程使用新的配置,启动一个新的worker进程3.使用旧配置的工作进程不再接受新的连接请求并在完成现有连接后退出2.2。2热升级-程序热升级nginx热升级流程如下:1.用新的Nginx文件替换旧的Nginx文件(注意备份)2.向master进程发送USR2信号(顺利升级到新版本Nginx程序)3.master进程修改pid文件号,增加后缀.oldbin4。master进程使用新的Nginx文件启动一个新的master进程。此时,新老master/worker同时存在。5、向老master发送WINCH信号,关闭老worker进程,观察新worker进程的工作状态。如果升级成功,向旧master进程发送QUIT信号,关闭旧master进程;如果升级失败,需要回滚,向旧master发送HUP信号(重新读取配置文件),向新master发送QUIT信号,关闭新master和worker。3.Worker进程3.1核心逻辑worker进程的主要逻辑在ngx_worker_process_cycle中,核心关注源码:worker;ngx_worker_process,init();ngx_setproctitle("workerprocess");for(;;){if(ngx_exiting){...}ngx_log_debug0(NGX_LOG_DEBUG_EVENT,cycle->log,0,"workercycle");ngx_process_events_and_timers(周期);if(ngx_terminate){...}if(ngx_quit){...}if(ngx_reopen){...}}}从上面的代码可以理解worker进程主要是处理网络事件,通过ngx_process_events_and_timers方法实现,事件主要包括:网络事件,定时器事件。3.2事件驱动——epollworker进程在处理网络事件时,依赖epoll模型管理并发连接,实现了事件驱动、异步、非阻塞等特性。如下图所示:infographic-Inside-NGINX_nonblocking通常在海量并发连接的过程中,在每个时刻(一个比较短的时间段),只有少量有事件的连接需要处理,即active连接。基于以上现象,epoll通过将连接管理与主动连接管理分离,实现了高效稳定的网络IO处理能力。网络模型对比其中,epoll利用红黑树高效的增删改查效率来管理连接,使用双向链表维护活动连接。epoll数据结构3.3shockinggroup由于worker是master进程的fork产生的,worker会监听同一个端口。这样,当accept建立连接时,多个子进程就会发生争抢,从而产生著名的“shockinggroup”问题。worker核心处理逻辑ngx_process_events_and_timers的核心代码如下:voidngx_process_events_and_timers(ngx_cycle_t*cycle){//这会处理监听socket...if(ngx_accept_disabled>0){ngx_accept_disabled--;}else{//获取锁并加入waitset,if(ngx_trylock_accept_mutex(cycle)==NGX_ERROR){return;}...//设置网络读写事件延迟处理标志,即释放锁后处理if(ngx_accept_mutex_held){flags|=NGX_POST_EVENTS;}}...//这里epollwait等待网络事件//网络连接事件,放入ngx_posted_accept_events队列//网络读写事件,放入ngx_posted_events队列(void)ngx_process_events(cycle,timer,flags);...//先处理网络连接事件,只有获取到锁后才会有连接事件{ngx_shmtx_unlock(&ngx_accept_mutex);}//处理网络读写事件ngx_event_process_posted(cycle,&ngx_posted_events);}从上面的代码可以看出,Nginx解决了惊群:1.将连接事件和读写事件分离。连接事件存储为ngx_posted_accept_events,读写事件存储为ngx_posted_events。2.设置ngx_accept_mutex锁,只有获得锁的进程才能处理连接事件。3.4负载均衡worker之间负载的关键是有多少连接相互连接。访问连接锁的前提是ngx_accept_disabled>0,所以ngx_accept_disabled是负载均衡机制实现的关键阈值。ngx_int_tngx_accept_disabled;ngx_accept_disabled=ngx_cycle->connection_n/8-ngx_cycle->free_connection_n;因此,nginx启动时,ngx_accept_disabled的值为负数,即连接总数的7/8。当进程的连接数达到总连接数的7/8时,进程将不再处理新的连接。同时,每调用一次'ngx_process_events_and_timers',ngx_accept_disabled就会减1,直到它的值低于阈值。在尝试重新处理新连接之前。因此,nginxworker子进程之间的负载均衡只有在一个worker进程处理的连接数达到其最大连接总数的7/8时才会触发,其负载均衡不满足任何条件。如下图所示:实际工作情况,'pid'为1211的进程为master进程,其余为worker进程独立的进程可以防止相互影响。如果一个进程异常崩溃,其他进程的服务不会中断,提高了架构的可靠性。3、进程间不共享资源,不需要锁,省去了锁带来的开销。4.2为什么不用多线程处理逻辑业务?1、进程数已经等于核心数,再创建新线程处理任务只会抢占已有进程,增加切换成本。2.作为接入层,基本是数据转发业务。网络IO任务的等待耗时部分已经处理成非阻塞/全异步/事件驱动的方式。在没有更多CPU的情况下,多线程没有多大意义。而如果流程中有阻塞处理逻辑,则需要各业务自行解决。比如openResty使用Lua协程来优化阻塞业务。
