1024programmer Nginx Why is the performance of Nginx much higher than that of Apache?

Why is the performance of Nginx much higher than that of Apache?

This is due to Nginx using the latest epoll (Linux 2.6 kernel) and kqueue (freebsd) network I/O model,
Apache, on the other hand, uses the traditional select model.
At present, Squid and Memcached, which can withstand high concurrent access under Linux, all use the epoll network I/O model.

To deal with the read and write of a large number of connections, the select network I/O model adopted by Apache is very inefficient.
Let’s use a metaphor to analyze the difference between the select model adopted by Apache and the epoll model adopted by Nginx:

Nginx Nginx configuration apache Apache configuration

Suppose you are studying in a university, and there are many rooms in the dormitory building where you live, and your friends want to come to you.
The dormitory aunt in the select version will take your friends to search from room to room until they find you.
The dormitory aunt of the epoll version will first write down the room number of each student,
When your friends come, you only need to tell your friends which room you live in, instead of taking your friends all over the building to find someone.
If 10,000 people come and they all want to find their classmates who live in this building, which one is more efficient, the select version or the epoll version, is self-explanatory.
Similarly, in a high-concurrency server, polling I/O is one of the most time-consuming operations. It is also very clear which one has better performance, select or epoll.

epoll – I/O event notification facility

In Linux network programming, select has been used for event triggering for a long time.
In the new Linux kernel, there is a mechanism to replace it, which is epoll.
Compared with select, the biggest advantage of epoll is that it will not reduce efficiency as the number of monitored fds increases.
Because in the select implementation in the kernel, it is processed by polling. The more fds polled, the more time-consuming it will be.
And, there is such a statement in the linux/posix_types.h header file:
#define __FD_SETSIZE 1024
It means that select can monitor up to 1024 fds at the same time. Of course, this number can be expanded by modifying the header file and recompiling the kernel, but this does not seem to be a permanent cure.

The interface of epoll is very simple, with a total of three functions:
1. int epoll_create(int size);
Create an epoll handle, and size is used to tell the kernel how big the total number of listeners is.
This parameter is different from the first parameter in select(), giving the value of fd+1 for the maximum listener.
It should be noted that when the epoll handle is created, it will occupy a fd value. If you view /proc/process id/fd/ under linux,
It is possible to see this fd, so after using epoll, you must call close() to close, otherwise the fd may be exhausted.

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
The event registration function of epoll, which is different from select(), tells the kernel what type of event to listen to when listening to the event,
Instead, first register the event type to be monitored here. The first parameter is the return value of epoll_create(),
The second parameter represents the action, represented by three macros:
EPOLL_CTL_ADD: register new fd to epfd;
EPOLL_CTL_MOD: Modify the listening event of the registered fd;
EPOLL_CTL_DEL: delete a fd from epfd;
The third parameter is the fd that needs to be monitored, and the fourth parameter is to tell the kernel what to monitor. The structure of struct epoll_event is as follows:
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events can be a collection of the following macros:
EPOLLIN: Indicates that the corresponding file descriptor can be read (including the normal closing of the peer SOCKET);
EPOLLOUT: indicates that the corresponding file descriptor can be written;
EPOLLPRI: Indicates that the corresponding file descriptor has urgent data readable (this should indicate that there is out-of-band data coming);
EPOLLERR: Indicates that the corresponding file descriptor has an error;
EPOLLHUP: indicates that the corresponding file descriptor is hung up;
EPOLLET: Set EPOLL to Edge Triggered (Edge Triggered) mode, which is relative to Level Triggered (Level Triggered).
EPOLLONESHOT: only listen to the event once, after listening to this event,
If you still need to continue listening to this socket, you need to add this socket to the EPOLL queue again

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
Wait for the event to be generated, similar to the select() call.
The parameter events is used to get the collection of events from the kernel, maxevents tells the kernel how big the events are,
The value of this maxevents cannot be greater than the size when creating epoll_create(),
The parameter timeout is the timeout time (in milliseconds, 0 will return immediately, -1 will be uncertain, and it is also said that it is permanently blocked).
This function returns theThe number of events, if it returns 0, it means it has timed out.

4. Regarding the two working modes of ET and LT:
It can be concluded that:
ET mode is notified only when the state changes, the so-called state change here does not include unprocessed data in the buffer,
In other words, if you want to use ET mode, you need to read/write until an error occurs,
Many people have reported why they can no longer get notifications after only receiving part of the data in ET mode, mostly because of this;
In LT mode, as long as there is data that has not been processed, it will continue to be notified.

So how to use epoll? It’s actually very simple.
By including a header file #include and a few simple APIs you can greatly increase the number of people your web server can support.

First create an epoll handle through create_epoll(int maxfds), where maxfds is the maximum number of handles supported by your epoll.
This function will return a new epoll handle, and all subsequent operations will be performed through this handle.
After using it, remember to use close() to close the created epoll handle.

Then in your network main loop,
Each frame calls epoll_wait(int epfd, epoll_event events, int max events, int timeout)
To query all network interfaces to see which one can be read and which one can be written. The basic syntax is:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
Where kdpfd is the handle created with epoll_create, events is a pointer to epoll_event*,
When the epoll_wait function is successfully operated, all read and write events will be stored in epoll_events.
max_events is the current number of socket handles that need to be monitored. The last timeout is the timeout of epoll_wait,
When it is 0, it means to return immediately, when it is -1, it means to wait until there is an event range, when it is any positive integer, it means to wait for such a long time,
Scope if there have been no events. Generally, if the network main loop is a separate thread, you can use -1 to wait, which can ensure some efficiency.
If it is in the same thread as the main logic, you can use 0 to ensure the efficiency of the main loop.

After the scope of epoll_wait should be a loop, all the events of interest.

Almost all epoll programs use the following framework:
for( ; ; )
{
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i<nfds;++i)
{
If(events[i].data.fd==listenfd) //There is a new connection
{
         cOnnfd= accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept this connection
ev.data.fd=connfd;
    ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //add new fd to epoll’s listening queue
}
“ else if( events[i].events&EPOLLIN ) // Receive data, read socket
{
n = read(sockfd, line, MAXLINE)) <0 //read
    ev.data.ptr = md; //md is a custom type, add data
        ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//Modify the identifier, wait for the next cycle to send data, the essence of asynchronous processing
}
“ else if(events[i].events&EPOLLOUT) //There is data to be sent, write to socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //get data
sockfd = md->fd;
        send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); // send data
ev.data.fd=sockfd;
         ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //Modify the identifier and wait for the next cycle to receive data
}
else
{
//Other processing
}
}

}



sp; send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); // send data
ev.data.fd=sockfd;
         ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //Modify the identifier and wait for the next cycle to receive data
}
else
{
//Other processing
}
}

}


This article is from the internet and does not represent1024programmerPosition, please indicate the source when reprinting:https://www.1024programmer.com/why-is-the-performance-of-nginx-much-higher-than-that-of-apache/

author: admin

Previous article
Next article

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Us

Contact us

181-3619-1160

Online consultation: QQ交谈

E-mail: [email protected]

Working hours: Monday to Friday, 9:00-17:30, holidays off

Follow wechat
Scan wechat and follow us

Scan wechat and follow us

Follow Weibo
Back to top
首页
微信
电话
搜索