Understanding Nginx Architecture in One Article
[Copy link]
1. Nginx infrastructure
After Nginx is started, it runs in the background as a daemon. The background process includes a master process and multiple worker processes. As shown in the following figure:
Nginx is a multi-process model with a master management process and multiple worker processes handling work. The basic architecture design is shown in the following figure:
The master is responsible for managing the worker process, and the worker process is responsible for handling network events. The entire framework is designed to be event-driven, asynchronous, and non-blocking.
The advantages of this design are:
Can fully utilize multi-core machines and enhance concurrent processing capabilities.
Load balancing can be achieved among multiple workers .
· The Master monitors and uniformly manages the behavior of the workers. When a worker fails, the Master process can be started to improve the reliability of the system. The Master process also controls program upgrades and configuration item modifications during service operation, thus enhancing the overall dynamic scalability and hot-change capabilities.
2. Master Process
- Core Logic
The main logic of the Master process is in ngx_master_process_cycle, and the core focus is on the source code:
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,
"wake up, sigio %i", sigio);
if (ngx_reap) {
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
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));
}
}
}
From the above code, we can understand that the master process is mainly used to manage the worker process, which includes the following four main functions:
1) Receive signals from the outside world. The flags in the master loop correspond to various signals, such as ngx_quit represents the QUIT signal, which means shutting down the entire service gracefully.
2) Send a signal to each worker process. For example, ngx_noaccept represents the WINCH signal, indicating that all child processes will no longer accept new connections, and the master sends a QUIT signal to all child processes.
3) Monitor the running status of the worker process. For example, ngx_reap represents the CHILD signal, which means that a child process ends unexpectedly. At this time, it is necessary to monitor the running status of all child processes, which is mainly done by ngx_reap_children.
4) When the woker process exits (in abnormal circumstances), a new woker process will be automatically restarted. This is mainly done in ngx_reap_children.
2. Hot Update
- Hot reload - hot configuration
When Nginx hot-changes configuration, you can update the configuration smoothly while it is running. The specific process is as follows:
Update the nginx.conf configuration file, send a SIGHUP signal to the master or execute nginx -s reload
The Master process uses the new configuration and starts a new worker process
The worker process using the old configuration no longer accepts new connection requests and exits after completing the existing connection
2) Hot upgrade - hot program update
The Nginx hot upgrade process is as follows:
Replace the old Nginx file with the new Nginx file (note the backup)
Send USR2 signal to the master process (smoothly upgrade to the new version of Nginx program)
The master process modifies the pid file number and adds the suffix .oldbin
The master process starts a new master process with the new Nginx file. At this time, the old and new masters/workers exist at the same time.
Send a WINCH signal to the old master, shut down the old worker process, and observe the working status of the new worker process. If the upgrade is successful, send a QUIT signal to the old master process and shut down the old master process; if the upgrade fails, you need to roll back, send a HUP signal to the old master (reread the configuration file), send a QUIT signal to the new master, and shut down the new master and worker.
3. Worker Process
1. Core Logic
The main logic of the Worker process is in ngx_worker_process_cycle, and the core focus is on the source code:
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_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
ngx_process_events_and_timers(cycle);
if (ngx_terminate) {...}
if (ngx_quit) {...}
if (ngx_reopen) {...}
}
}
From the above code, we can understand that the worker process mainly handles network events, which is implemented through the ngx_process_events_and_timers method. The events mainly include: network events and timer events.
2. Event-driven - epoll
When processing network events, the Worker process relies on the epoll model to manage concurrent connections, realizing event-driven, asynchronous, and non-blocking features. As shown in the following figure:
Usually, during a massive number of concurrent connections, at each moment (a relatively short period of time), only a small number of connections with events, i.e., active connections, need to be processed. Based on the above phenomenon, epoll achieves efficient and stable network IO processing capabilities by separating connection management from active connection management.
Among them, epoll uses the efficient addition, deletion and query efficiency of the red-black tree to manage connections, and uses a bidirectional linked list to maintain active connections.
3. Shock the herd
Since workers are all forked from the master process, they all listen to the same port, which causes competition among multiple child processes when establishing connections through accept, leading to the famous "herd panic" problem.
The core code of Worker core processing logic ngx_process_events_and_timers is as follows:
void ngx_process_events_and_timers(ngx_cycle_t *cycle){
//This will handle the listening socket
...
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
//Get the lock and join the wait set,
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
...
//Set the network read and write event delay processing flag, that is, process after releasing the lock
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
}
}
...
// Here epollwait waits for network events
//Network connection event, put it into the ngx_posted_accept_events queue
//Network read and write events, put into ngx_posted_events queue
(void) ngx_process_events(cycle, timer, flags);
...
//Process the network connection event first. Only when the lock is acquired will there be a connection event here.
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
//Release the lock so that other processes can also get it
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
//Handle network read and write events
ngx_event_process_posted(cycle, &ngx_posted_events);
}
From the above code, we can see that Nginx solves the herd panic:
1) Separate connection events from read and write events. Connection events are stored as ngx_posted_accept_events, and read and write events are stored as ngx_posted_events.
2) Set the ngx_accept_mutex lock. Only the process that obtains the lock can handle the connection event.
4. Load Balancing
The key to the load between Workers lies in how many connections each of them has connected. The prerequisite for connecting and grabbing the lock is ngx_accept_disabled > 0, so ngx_accept_disabled is the key threshold for implementing the load balancing mechanism.
ngx_int_t ngx_accept_disabled;
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
Therefore, when nginx starts, the value of ngx_accept_disabled is a negative number, which is 7/8 of the total number of connections. When the number of connections of the process reaches 7/8 of the total number of connections, the process will no longer process new connections.
At the same time, each time 'ngx_process_events_and_timers' is called, ngx_accept_disabled is reduced by 1 until its value is lower than the threshold, and then an attempt is made to reprocess new connections.
Therefore, the load balancing between the worker subprocesses of nginx will only be triggered when the number of connections processed by a worker process reaches 7/8 of its maximum total number of connections. The load balancing is not satisfied under any conditions. As shown in the following figure:
The process with pid 1211 is the master process, and the others are worker processes.
1. Why not use a multi-threaded model to manage connections?
Stateless service, no need to share process memory
· Using independent processes can prevent them from affecting each other. If one process crashes abnormally, the services of other processes will not be interrupted, which improves the reliability of the architecture.
· Processes do not share resources and do not need to lock, so the overhead caused by locking is saved.
2. Why not use multi-threading to process logical business?
The number of processes is already equal to the number of cores. Creating new threads to handle tasks will only preempt existing processes and increase the switching cost.
As the access layer, it is basically a data forwarding service. The waiting time-consuming part of the network IO task has been processed into a non-blocking/fully asynchronous/event-driven mode. In the absence of more CPUs, it is meaningless to use multi-threaded processing. And if there is blocking processing logic in the process, it should be solved by each service. For example, openResty uses Lua coroutines to optimize blocking services.
丨The article is organized to spread relevant technologies, the copyright belongs to the original author丨
丨If there is any infringement, please contact us to delete丨
Click here to listen to more Linux video course materials: http://www.makeru.com.cn/live/1758_310.html?s=11
|