[Turn] Nginx achieve high concurrency principles

Nginx

First of all to understand, Nginx uses a multi-process (single-threaded) & multi-channel multiplexed IO model. Use the I / O multiplexing technology Nginx, it becomes a "concurrent event-driven," the server.

Asynchronous non-blocking (AIO) of the Detailed http://www.ibm.com/developerworks/cn/linux/l-async/

Write pictures described here

Multi-process mode of operation

  1.  
    1, Nginx after the start, there will be a master process and several independent worker process.
  2.  
    2, a signal received from the outside, a transmission signal to each worker process, which are likely to handle the connection.
  3.  
    3, master process can monitor the operation status of worker processes, when the worker process exits (exceptional circumstances), will start a new worker process automatically.

Note the number of worker processes, usually set to the machine cpu audit. Because the more the number of worker, the process will only lead to competing cpu, leading to unnecessary context switches.

Use multi-process model, not only can increase the complication rate, and are independent processes, a process linked to the worker will not affect other worker processes.

Thundering herd phenomenon

The main process (master process) First, let's create a file descriptor for listening through the sock socket (), and then generates a child process fork (workers process), the child process will inherit the parent process sockfd (socket file descriptor), after the child after accept () creates descriptor connected (connected descriptor)), then the descriptor to communicate with clients connected via.

Well, since all child processes inherit sockfd parent process, then when the connection came in, all children will receive a notification and "scrambling" to establish a connection with it, which is called "thundering herd phenomenon." Large number of processes is activated and suspended, only one process can accept () connection to this, which of course will consume system resources.

Nginx handling of thundering herd phenomenon

Nginx provides a accept_mutex this thing, which is a plus on the accept a shared lock. That is, each worker process needs to acquire the lock before executing accept, get less than give up the implementation of accept (). Once you have the lock, the same time, it will only have a process to accpet (), so there will be thundering herd problem. accept_mutex is a controllable option, we can turn off the display, is enabled by default.

Nginx process Detailed

 

Nginx after the start, there will be a master process and multiple worker processes.

master process

Mainly used to manage worker process, comprising: receiving a signal from the outside, a signal is sent to each worker process, monitor the operation status of worker processes, when the worker process exits (exceptional circumstances), it will automatically re-start a new worker process.

master the whole process acts as a process group interaction with the user interface, while on the guardianship process. It does not handle network events, is not responsible for the implementation of the business, will only be achieved by managing worker process to restart the service, smooth upgrade, replace the log files, configuration files and other functions with immediate effect.

We have to control nginx, only need to kill to send signals to the master process on the line. Such as kill -HUP pid, it is to tell nginx, calmly restart nginx, we generally use this signal to restart nginx, or reload the configuration, because it is calmly restart, so the service is not interrupted. master process after receiving a HUP signal is how to do it? First master process after receiving the signal, will first reload the configuration file, and then start a new worker process, and sends a signal to all the old worker process, they can tell the honorable retirement. After starting a new worker, he began receiving new requests, while older worker after receiving a signal from the master, it is no longer receiving new requests, and any pending completion of the request in the current process after the completion of treatment , then exit. Of course, the signal is sent directly to the master process, which is relatively old mode of operation, nginx after version 0.8, introduces a series of command-line parameters to facilitate our management. For example,. / Nginx -s reload, is to restart nginx,. / Nginx -s stop, nginx is to stop the run. How to do it? We still take reload, we see that, when executing orders, we are starting a new process nginx, and nginx process after the new resolve to reload parameters, we know our purpose is to control nginx reload configuration files it will send a signal to the master process, and then the next action, and we will send signals directly to master the process of the same.

worker process

The basic network event, it is on the worker process to deal with. Between multiple worker processes are equal, they are the same competition from the client's request, the processes are independent of each other between. A request can only be processed in a process worker, a worker process, other processes can not handle the request. The number of worker processes that can be set, usually we will set up a machine with the same number of cpu core, the reason there with a process model and event handling model nginx are inseparable.

Equality between worker processes, each process, the opportunity to process the request is the same. When we provide services http 80 port, a connection request over each process are likely to deal with this connection, how to do it? First, each worker process are coming from the master process fork, inside the master process, need to listen better after a socket (listenfd) first established, then fork out more worker processes. All listenfd worker process becomes readable when the arrival of a new connection, in order to ensure that only one process handling this connection, all the worker processes grab accept_mutex before registering listenfd read event, grab a mutex that the process of registration listenfd read event, A call to accept to accept the connection in the event in reading. When a worker process after accept this connection, they begin to read the request, parse the request, processes the request, after generating the data, and then returned to the client, and finally disconnected, such a complete request is like this. We can see a request handled entirely by the worker process, and deal only in a worker process. Equality between worker processes, each process, the opportunity to process the request is the same. When we provide services http 80 port, a connection request over each process are likely to deal with this connection, how to do it? First, each worker process are coming from the master process fork, inside the master process, need to listen better after a socket (listenfd) first established, then fork out more worker processes. All listenfd worker process becomes readable when the arrival of a new connection, in order to ensure that only one process handling this connection, all the worker processes grab accept_mutex before registering listenfd read event, grab a mutex that the process of registration listenfd read event, A call to accept to accept the connection in the event in reading. When a worker process after accept this connection, they begin to read the request, parse the request, processes the request, after generating the data, and then returned to the client, and finally disconnected, such a complete request is like this. We can see a request handled entirely by the worker process, but only at a worker process

worker process workflow

When a process worker accept () after the connection, to start a read request, parses the request, processing the request, generating the data, and then returned to the client, and finally disconnected, a full request. A request is handled entirely by the worker process, and can only be treated in a worker process.

The benefits of doing so:

1, save money lock brings. Each worker process is a separate process, do not share resources without locking. At the same time when the investigation on the programming and the problems will be a lot easier.

2, independent process, reduce the risk. With a separate process, can make do not affect each other, after a process exits, another process was still working, the service will not be interrupted, master process is quickly re-start a new worker process. Of course, worker processes can occur unexpectedly quit.

Multi-process model for each process / thread can handle all the way IO, then Nginx is how to deal with multiple IO it?

If you do not use IO multiplexing, so in one process, and can only handle a request, such as the implementation of accept (), if the connection is not over, the program will back up here until there is a connection over, to continue down carried out.

The multiplexer allows us only when the event returns control to the program, while other times kernel processes are suspended, on standby.

Core: IO multiplexing model used epoll Nginx

epoll by applying a simple file system (? What file system data structure is generally implemented with a B + tree) in the Linux kernel, its working process is divided into three parts:

  1.  
    1, call int epoll_create ( int size) to establish a epoll object eventpoll kernel creates a structure for storage by epoll_ctl ( ) to add in epoll object
  2.  
    The events that will be mounted in the red-black tree.
  3.  
    2, call int epoll_ctl ( int epfd, int OP, int fd, struct epoll_event * Event) registered for the event in the epoll fd objects, all added to the epoll in pieces
  4.  
    And will build device drivers callback relations, that is, when the corresponding event calls the callback method that sockfd, add sockfd to eventpoll in the doubly-linked list
  5.  
    3, call int epoll_wait ( int epfd, struct epoll_event * Events, int maxevents, int timeout) to wait for the occurrence of an event, timeout is -1, the
  6.  
    Call blocks know events
  7.  
     

In this way, after registering a good event, as long as the fd events, epoll_wait () can be detected and returned to the user, the user will be able to "non-blocking" to the I / O up.

epoll () in the kernel maintains a list, epoll_wait direct examination of the list is not empty know whether there is a file descriptor is ready. (Epoll biggest advantages compared to select is not going to grow as the number sockfd reduce efficiency, the use of select (), the kernel method in rotation to see if fd ready, which saved sockfd is similar array of data structure fd_set, key as fd, value is 0 or 1.)

To achieve this effect, because the kernel implementation epoll is implemented in accordance with established with a callback function for each device driver sockfd above. So, when an event occurs on a sockfd, with its corresponding callback function is called, sockfd to join this list, the other is in the "idle" state will not. At this point, we epoll achieve a "pseudo" AIO. But if most of the I / O are "active", each socket usage is high, then, epoll efficiency is not necessarily higher than select (probably to maintain the queue complex).

As it can be seen, because a process has only one thread, at the same time a process can only do one thing, but you can "simultaneously" to handle multiple requests by constantly switching to.

Examples: Nginx will register an event: "If you come from a new client connection request arrives, and then informed me," then only the connection request arrives, the server will execute accept () to receive requests. Another example is when the server forwards the request to the upstream (such as PHP-FPM), and wait for the request to return, the worker will not be processed at this obstruction, it will be after sending a request to register an event: "If the buffer to receive the data Let me know, I read it and then come in ", then the process will be idle waiting for events to occur.

In this way, based on multi-process + epoll, Nginx can achieve high concurrency.

Epoll using a framework to handle events, switched codes: http://www.cnblogs.com/fnlingnzb-learner/p/5835573.html

  For. 1 (;;) 
 2 { 
 . 3 NFDs = epoll_wait (the epfd, Events, 20,500); 
 . 4 for (I = 0; I <NFDs; I ++) 
 . 5 { 
 . 6 IF (Events [I] == .data.fd listenfd) // new connection 
 . 7 { 
 . 8 connfd = Accept (listenfd, (the sockaddr *) & clientaddr, & clilen); // Accept the connection 
 . 9 ev.data.fd = connfd; 
10 ev.events = EPOLLIN | EPOLLET; 
. 11 epoll_ctl (epfd, EPOLL_CTL_ADD, connfd, & ev); // add the new fd to the epoll monitor queue 
12 is} 
13 is the else IF (Events [I] .events & EPOLLIN) // receiving data, read Socket 
14 {  
15 n-= read (sockfd, line, MAXLINE) ) <0 // read
16 EV. data.ptr = md; // md custom type, adding data 
. 17 ev.events = EPOLLOUT | EPOLLET; 
18 is epoll_ctl (the epfd, EPOLL_CTL_MOD, sockfd, & EV); // modify the identifier, a wait loop when transmitting data, the essence of asynchronous processing 
. 19} 
20 is the else IF (Events [I] .events & EPOLLOUT) // there is data to be transmitted, the write Socket 
21 is { 
22 is struct myepoll_data * MD = (myepoll_data *) Events [I] .data.ptr ; // fetch data 
23 is the MD-sockfd => FD; 
24 send (sockfd, the MD-> PTR, strlen ((char *) the MD-> PTR), 0); // send data 
25 ev.data.fd = sockfd ; 
26 is ev.events = EPOLLIN | EPOLLET; 
27 epoll_ctl (the epfd, EPOLL_CTL_MOD, sockfd, & EV); // modify identifier, waiting for the next cycle of the received data 
28} 
29 the else 
30 { 
31 is another processing //
32             }
33         }
34     }

Here's an example of complete server side:

 1 #include <iostream>
  2 #include <sys/socket.h>
  3 #include <sys/epoll.h>
  4 #include <netinet/in.h>
  5 #include <arpa/inet.h>
  6 #include <fcntl.h>
  7 #include <unistd.h>
  8 #include <stdio.h>
  9 #include <errno.h>
 10 
 11 using namespace std;
 12 
 13 #define MAXLINE 5
 14 #define OPEN_MAX 100
 15 #define LISTENQ 20
 16 #define SERV_PORT 5000
 17 #define INFTIM 1000
 18 
 19 void setnonblocking(int sock)
 20 {
 21     int opts;
 22     opts=fcntl(sock,F_GETFL);
 23     if(opts<0)
 24     {
 25         perror("fcntl(sock,GETFL)");
 26         exit(1);
 27     }
 28     opts = opts|O_NONBLOCK;
 29     if(fcntl(sock,F_SETFL,opts)<0)
 30     {
 31         perror("fcntl(sock,SETFL,opts)");
 32         exit(1);
 33     }
 34 }
 35 
 36 int main(int argc, char* argv[])
 37 {
 38     int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
 39     ssize_t n;
 40     char line[MAXLINE];
 41     socklen_t clilen;
 42 
 43 
 44     if ( 2 == argc )
 45     {
 46         if( (portnumber = atoi(argv[1])) < 0 )
 47         {
 48 fprintf (stderr, "the Usage:% S portnumber / A / n-", the argv [0]); 
 49 return. 1; 
 50} 
 51 is} 
 52 is the else 
 53 is { 
 54 is fprintf (stderr, "the Usage:% S portnumber / A / n- ", the argv [0]); 
 55 return. 1; 
 56 is} 
 57 is 
 58 
 59 
 60 // declare variables epoll_event structure, EV register for events, event for return arrays to be processed 
 61 is 
 62 is struct epoll_event EV, events [ 20]; 
 63 // generates a dedicated process accept epoll file descriptor 
 64 
 65 epoll_create the epfd = (256); 
 66 clientaddr the sockaddr_in struct; 
 67 ServerAddr the sockaddr_in struct; 
 68 = Socket listenfd (AF_INET, SOCK_STREAM, 0);
 // set the socket 69 nonblocking 
 70 
 71 is setnonblocking // (listenfd); 
 72 
 73 is provided to be processed // file descriptor associated with the event 
 74 
 90 the bind (listenfd, (the sockaddr *) & ServerAddr, the sizeof (ServerAddr)) ; 
 75 ev.data.fd = listenfd; 
 76 // set the event type to be processed
 77 
 78 ev.events = EPOLLIN | EPOLLET; 
 79 //ev.events=EPOLLIN; 
 80 
 81 // Register event epoll 
 82 
 83 epoll_ctl (the epfd, EPOLL_CTL_ADD, listenfd, & EV); 
 84 bzero (& ServerAddr, the sizeof (ServerAddr)); 
 serveraddr.sin_family = AF_INET 85; 
 86 LOCAL_ADDR char * = "127.0.0.1"; 
 87 the inet_aton (LOCAL_ADDR, & (serveraddr.sin_addr)); // the htons (portnumber); 
 88 
 89 serveraddr.sin_port = the htons (portnumber); 
 91 is the listen (listenfd, LISTENQ); 
 92 Maxi = 0; 
 93 for (;;) { 
 94 // wait for events occurring epoll 
 95 
 96 NFDs = epoll_wait (the epfd, events, 20,500); 
 97 // handling of all events that occurred
 98 
 99 for (I = 0; I <NFDs; I ++) 
100 { 
101 IF (Events [I] == .data.fd listenfd) // If a new monitoring a user connected to SOCKET SOCKET bound port, establish a new connection. 
102 
103 { 
104 connfd = Accept (listenfd, (the sockaddr *) & clientaddr, & clilen); 
105 IF (connfd <0) { 
106 perror ( "connfd <0"); 
107 Exit (. 1); 
108} 
109 // setnonblocking ( connfd); 
110 
115 ev.data.fd = connfd; 
111 * char STR = inet_ntoa (clientaddr.sin_addr);
<< COUT 112 "from accapt A Connection" << endl << STR; 
113 // file descriptor is provided for read operations 
114 
128 COUT << "EPOLLIN" << endl; 
129 IF ((sockfd = Events [I] .data.fd) <0) 
130. Continue;
117 
1 18 ev.events = EPOLLIN | EPOLLET; 
119 //ev.events=EPOLLIN; 
120 
121 // Register EV 
122 
123 epoll_ctl (the epfd, EPOLL_CTL_ADD, connfd, & EV); 
124} 
125 the else IF (Events [I] & EPOLLIN .events ) // If the user is connected, and data is received, then the read. 
126 
127 { 
131 is IF ((n-= Read (sockfd, Line, MAXLINE)) <0) { 
132 IF (errno == ECONNRESET) { 
133 Close (sockfd); 
134 Events [I] .data.fd = -1; 
the else} 135 
136 STD :: COUT << "the readline error" STD :: << endl;
IF the else} 137 (n-== 0) { 
138 Close (sockfd); 
139 Events [I] = -1 .data.fd; 
140} 
141 is Line [n-] = '/ 0'; 
142 COUT << "Read" Line << endl <<; 
143 // write operation is provided for the file descriptor 
144 
145                 ev.data.fd=sockfd;
146 // NOTE provided for sensing a write event 
147 
148 ev.events = EPOLLOUT | EPOLLET; 
149 // modify sockfd to be processed the event EPOLLOUT 
150 
151 // epoll_ctl (the epfd, EPOLL_CTL_MOD, sockfd, & EV); 
152 
153} 
154 the else IF (events [I] .events & EPOLLOUT) // If data is sent 
155 
156 { 
Events sockfd = 157 [I] .data.fd; 
158 Write (sockfd, Line, n-); 
159 // file descriptor is provided for a read operation 
160. 
161 ev.data.fd = sockfd; 
162 // provided for Note measured read event 
163 
164 is ev.events = EPOLLIN | EPOLLET; 
165 // modify the event to be processed is sockfd Epolin 
166 
167 is epoll_ctl (the epfd, EPOLL_CTL_MOD, sockfd, & EV); 
168} 
169} 
170.} 
171 is return 0 ; 
172}

Nginx and Apache multi-process mode of comparison:

For Apache, each request will be exclusively a worker thread, when the number of concurrent arrival of thousands, they also have thousands of threads in the process the request. This is for the operating system, memory occupied by very large, the thread context switching overhead cpu brings great performance would be difficult to go up, but these costs are completely pointless.
     For Nginx is concerned, a process has only one main thread, through a non-blocking asynchronous event handling mechanism, to achieve the recycling process multiple events ready to achieve lightweight and high concurrency.

Suitable for event-driven I / O-intensive services, multi-process or thread is suitable for CPU-intensive services: 
1, Nginx but mainly as a reverse proxy, rather than the Web server. Its model is event-driven. 
2, event-driven server, this is the most suitable for I / O-intensive work, such as a reverse proxy, which plays a role in the transfer of data between the client and server WEB purely I / O operation itself does not It involves complex calculations. Because when the process is calculated in one place, then this process can not handle the other events. 
3, Nginx need only a small amount with the event-driven process, several processes run libevent, unlike the number of Apache processes at every turn as hundreds of multi-process model. 
5, Nginx static files was also very effective, it is because reading and writing files and network communications are actually I / O operations, the same process.

 

Reference article: https://blog.csdn.net/m0_38110132/article/details/75126316

Guess you like

Origin www.cnblogs.com/atai/p/11809910.html