www.cnblogs.com/yum777/p/62…

github.com/nginx/nginx

\

The reason why Nginx is popular among code farmers is that, in addition to its high performance, it also has its elegant system architecture. Compared to Memcached’s classic multithreaded model, Nginx is a classic multiprocess model. 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:



Figure 1. Nginx multi-process model

Master process is mainly used to manage worker process, including the following four main functions: (1) Receiving signals from the outside world. (2) Send signals to each worker process. (3) Monitor the running status of the Woker process. (4) When the Woker process exits (in abnormal cases), the new Woker process will be restarted automatically. The Woker process is mainly used to process network events. Each Woker process is peer and independent from each other. They compete equally for requests from clients. Nginx processes are controlled by the master process in two main ways: As can be seen from Figure 1, the master receives signals to manage multiple woker processes. Then, you can send signals to the master process by kill, such as kill -hUP PID, to notify Nginx to restart gracefully. A leisurely restart is one that does not interrupt service: the master process reloads the configuration after receiving the signal, then starts a new process to receive new requests, sends a signal to all the old processes to stop receiving new requests, and exits automatically after processing all outstanding requests. /nginx -s reload To start a new nginx process./nginx -s reload The new process will send a signal to the master process after the Reload parameter is resolved (the new process will automatically do the manual signaling for us). /nginx -s stop to stop nginx Nginx uses an asynchronous, non-blocking approach to handle network events, similar to Libevent (single process, single thread).



Figure 2 Nginx network events

The master process builds the socket that needs listen, and then forks multiple Woker processes so that each work process can accept the socket. All accept work processes are notified when a client connection arrives, but only one process can accept successfully, while the others will fail to accept. Nginx provides a shared lock accept_mutex to ensure that only one work process is accepting at a time. When a worker process accepts the connection, it starts to read the request, parse the request, process the request, generate the data, and then return it to the client. Finally, the connection is disconnected, and a completed request ends.

\

4. Nginx architecture

What is Nginx? Nginx (” Engine X “) is a high-performance HTTP and reverse proxy server, as well as an IMAP/POP3/SMTP proxy server. Nginx runs in the background as a daemon on Unix systems. Daemon processes include a master process and multiple worker processes. Of course, Nginx also supports multi-threading, but our main mode is multi-process mode, which is the default mode of Nginx. The master process is mainly used to manage worker processes, including: receiving signals from the outside, sending signals to all worker processes, monitoring the running status of worker processes. When the worker process exits (in abnormal cases), it will automatically restart the new worker process. Worker processes handle basic network events. Multiple worker processes are peer. They compete equally for requests from clients and are independent of each other. A request can only be processed in one worker process, and it is impossible for a worker process to process requests from other processes. The number of worker processes can be set, which is generally consistent with the number of CPU cores of the machine. More workers will only cause processes to compete for CPU resources, leading to unnecessary context switches. In addition, nginx has the CPU binding option to take advantage of the multi-core feature, so that a process can be bound to a certain core, so that there is no cache invalidation due to process switching. Every worker process forks from the master process. In the master process, the socket that needs listen is first established, and then multiple worker processes are fork out, so that each worker process can accept the socket(not the same socket, of course). However, each process of the socket will monitor at the same IP address and port, which is allowed in the network protocol. Generally, when a connection comes in, all processes that accept on the socket are notified, and only one process can accept the connection. The others fail to accept. Seven, compared with threads, the advantages of using processes between processes do not share resources, do not need to lock, so avoid the overhead of locking. The use of independent processes can not affect each other. After one process exits, other processes are still working and the service will not be interrupted. The master process will restart the new worker process soon. Programmatically easier. Eight, the problem of multithreading and multithreading in the case of multiple concurrent, the memory occupation of the thread is large, the thread context switch caused a large amount of CPU overhead. If you think about the common way apache works (there is also an asynchronous non-blocking version of Apache, but it doesn’t work very often because it conflicts with some of its own modules), each request has an exclusive worker thread, and when the number of concurrent requests is in the thousands, there are thousands of threads processing the request at the same time. This is a big challenge for the operating system. Threads take up a lot of memory, and context switching costs a lot of CPU, which makes no sense. Asynchrony non-blocking Asynchrony is the opposite of synchronization, that is, events that do not occur simultaneously. The concept of non-blocking is the counterpart of blocking, where events are executed in sequence and each event waits for the last event to complete, as opposed to non-blocking, where if an event is not ready, the event can simply return and wait a while before processing the query, during which time other things can be done. However, multiple inquiries can also incur additional overhead. Overall, the benefits of Nginx adopting asynchronous non-blocking are: · No threads need to be created, only a small amount of memory per request · No context switches, and very light event handling

The Taobao Tengine team said the test results were “up to 2 million concurrent requests on a machine with 24GB of memory”.

\

10. Recommend good books

Nginx Complete Development Guide: Using C, C++, and OpenResty

Understanding Nginx in Depth: Module Development and Architecture Parsing (Version 2)

\

Libevent Friendship knowledge: github.com/libevent/li…

The main loop that supports Libevent is the event_base_loop (event.c /1533) function, which is executed as follows: \



Figure 1 event_base_loop main loop

A simple description of the figure above is: (1) Correct the current system time. (2) Compare the current time with the time in the minimum heap, and remove all timer events whose time is less than the current time from the heap and add them to the active event queue. (3) Invoke I/O encapsulation (e.g. The timeout value of Epoll/epoll_wait (epoll.c /dispatch/407) is the difference between the current time and the minimum value in the time heap (the minimum heap takes the minimum complexity O(1)). Where triggered I/O and signal events are added to the active event queue. \

(4) Call the function event_process_active (event.c /1406) to traverse the active Event queue and call the registered callback function in turn to process the corresponding events.

The event_base_loop source code is as follows: \

int event_base_loop(struct event_base *base, int flags) { const struct eventop *evsel = base->evsel; struct timeval tv; struct timeval *tv_p; int res, done, retval = 0; /* Grab the lock. We will release it inside evsel.dispatch, and again * as we invoke user callbacks. */ EVBASE_ACQUIRE_LOCK(base, th_base_lock); if (base->running_loop) { event_warnx("%s: reentrant invocation. Only one event_base_loop" " can run on each event_base at once.", __func__); EVBASE_RELEASE_LOCK(base, th_base_lock); return -1; } base->running_loop = 1; clear_time_cache(base); if (base->sig.ev_signal_added && base->sig.ev_n_signals_added) evsig_set_base(base); done = 0; #ifndef _EVENT_DISABLE_THREAD_SUPPORT base->th_owner_id = EVTHREAD_GET_ID(); #endif base->event_gotterm = base->event_break = 0; while (! done) { base->event_continue = 0; /* Terminate the loop if we have been asked to */ if (base->event_gotterm) { break; } if (base->event_break) { break; } timeout_correct(base, &tv); tv_p =&tv;if (! N_ACTIVE_CALLBACKS(base) && ! (flags & EVLOOP_NONBLOCK)) { timeout_next(base, &tv_p); } else { /* * if we have active events, we just poll new events * without waiting. */ evutil_timerclear(&tv); } /* If we have no events, we just exit */ if (! event_haveevents(base) && ! N_ACTIVE_CALLBACKS(base)) { event_debug(("%s: no events registered.", __func__)); retval = 1; goto done; } /* update last old time */ gettime(base, &base->event_tv); clear_time_cache(base); res = evsel->dispatch(base, tv_p); if (res == -1) { event_debug(("%s: dispatch returned unsuccessfully.", __func__)); retval = -1; goto done; } update_time_cache(base); timeout_process(base); if (N_ACTIVE_CALLBACKS(base)) { int n = event_process_active(base); if ((flags & EVLOOP_ONCE) && N_ACTIVE_CALLBACKS(base) == 0 && n ! = 0) done = 1; } else if (flags & EVLOOP_NONBLOCK) done = 1; } event_debug(("%s: asked to terminate loop.", __func__)); done: clear_time_cache(base); base->running_loop = 0; EVBASE_RELEASE_LOCK(base, th_base_lock); return (retval); }Copy the code

\