• notice
  • Congratulations on the launch of the Sought Tech site

Redis is single-threaded, why is it so fast?

1. Redis is based on memory, and the reading and writing speed of memory is very fast;
2. Redis is single-threaded, which saves a lot of time for context switching threads;
3. Redis uses multiplexing technology to handle concurrent connections;

Briefly explain the second point: context switching means that the cpu executes in turn between multiple threads (shooting CPU resources), while redis is single-threaded, so it avoids tedious multi-threaded context switching.

Focus on explaining multiplexing:
multiplexing - refers to multiple socket connections, multiplexing - refers to multiplexing a thread.
Currently, there are three main technologies for multiplexing: select, poll, and epoll. The order in which they appear is after the yoxi press, and the technology after the more ranks corrects the shortcomings of the previous technology. epoll is the latest and the best multiplexing technology available.
For example: a bar waiter, there are many drunks in front of them, epoll is equivalent to a drunk man yelling for a drink, the waiter will pour him a drink after hearing it, and you can play when these drunks don't ask for it. Play with your phone, etc. But the select and poll techniques are such a scenario: the waiters take turns asking each drunk if they want to pour the wine, and there is no free time. The meaning of io multiplexing is to be a drunkard and share a waiter.

  • select:
    1. It will modify the incoming parameters, which is very unfriendly to multiple calling functions;
    2. If the sock (the io stream has data), select can only poll this to find the data. It is said that the overhead is very large;
    3. It is not thread-safe, which is horrible;
    4. Only 1024 connections can be monitored;

  • poll:
    1. It is not thread-safe...
    2. The limit of 1024 connections is removed;
    3. The incoming parameters are not modified;

  • epoll:
    1. Thread-safe; 2. epoll
    can not only tell you which sock has data, but also which sock has data, no need to poll;
    3. however, only supports linux system;



Author: superxcp
Link: https://www.jianshu.com/p/b08c1f3bb256
Source:
The copyright of Jianshu belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

 

Multiplexing technology:

 

Linux IO multiplexing has epoll, poll, select, epoll performance is better than several others.

 

The nouns are more convoluted, and it is good to understand the meaning. An epoll scene: a bartender (a thread) with a group of drunks lying in front, suddenly one roars "Pour wine" (event), you trot over to pour him a drink, then let him go, suddenly another one is about to pour Wine, you go over and pour it again, just like a waiter serving a lot of people, sometimes no one drinks, the waiter is in an idle state, you can do something else and play with your mobile phone. As for epoll and select, the difference between poll is that in the latter two scenes, the drunk man doesn't speak. You have to ask one by one if you want alcohol, and you don't have time to play with your phone. io multiplexing probably means that these drunkards share a waiter.

In fact, the stupid translation of "I/O multiplexing" may be the reason why this concept is so difficult to understand in Chinese. The so-called I/O multiplexing is actually called I/O multiplexing in English. If you search for the meaning of multiplexing, you will basically get this picture:


So most people directly think of the concept of "one network cable, multiple sock multiplexing", including the above answers. In fact, no matter you use multi-process or I/O multiplexing, there is only one network cable. cut. The function of multiple Socks multiplexing one network cable is implemented in the kernel + driver layer.

The important thing to say again: I/O multiplexing The multiplexing here refers to the fact that a single thread manages at the same time by recording and tracking the status of each Sock (I/O stream) (corresponding to the Fight progress strip slot in the ATC tower). Multiple I/O streams. The reason for its invention is to increase the throughput of the server as much as possible.

 

Does it sound awkward, just look at the picture and you'll understand.

 



In the same thread, multiple I/O streams are transmitted at the same time by flipping switches (people who have studied EE can now stand up and say this is called "time division multiplexing").

 

What, you haven't figured out "a request comes, what is the process of nginx using epoll to receive the request", just look at this picture to understand. As a reminder, ngnix will have a lot of links coming in, epoll will monitor them all, and then, like a toggle switch, whoever has data will dial to whom, and then call the corresponding code for processing.

-------------------------------------------------- -------------------------------------------------- ---------------------

 

Once you understand this basic concept, the rest is easy to explain.

select, poll, epoll are all specific implementations of I/O multiplexing. The reason why these three ghosts exist is that they appear in order.

After the concept of I/O multiplexing was proposed, select was the first implementation (implemented in BSD around 1983).

1. After select was implemented, many problems were quickly exposed.

  • select will modify the incoming parameter array, which is very unfriendly to a function that needs to be called many times.

  • select If there is data in any sock (I/O stream), select will only return, but it will not tell you that there is data on that sock, so you can only find one by one yourself, 10 or more socks may be fine , If tens of thousands of socks were searched every time, this unnecessary expense would have the arrogance of a grand banquet.

  • select can only monitor 1024 links, this has nothing to do with grass, linux is defined in the header file, see FD_SETSIZE.

  • Select is not thread-safe. If you add a sock to select, and suddenly another thread finds out, Nima, this sock is not used and needs to be withdrawn. Sorry, this select is not supported. If you turn off this sock in a frenzy, the standard behavior of select is. Uh. Unpredictable, this is written in the documentation.

"If a file descriptor being monitored by select() is closed in another thread, the result is unspecified
"

2. So 14 years later (in 1997) a group of people implemented poll, and poll fixed many problems of select, such as

  • poll removes the limit of 1024 links, so how many links do you need, just as long as you are happy, master.

  • From the design point of view, poll does not modify the incoming array anymore, but this depends on your platform, so it is better to be careful when walking in the rivers and lakes.

In fact, the delay of 14 years is not a problem of efficiency, but the hardware of that era is too weak, a server processing more than 1,000 links is like a god, and select has met the needs for a long time.

But poll is still not thread-safe, which means that no matter how powerful the server is, you can only handle one set of I/O streams in one thread. You can of course cooperate with multiple processes, but then you have all kinds of problems with multiple processes.

So 5 years later, in 2002, the great god Davide Libenzi realized epoll.

3. epoll can be said to be the latest implementation of I/O multiplexing. epoll fixes most of the problems of poll and select, such as:

  • epoll is now thread safe.

  • epoll now not only tells you the data in the sock group, but also tells you which sock has data, so you don't have to find it yourself. 

But epoll has a fatal flaw, only linux supports it. For example, the corresponding implementation on BSD is kqueue.

In fact, I will take the initiative to tell you that some well-known domestic manufacturers have cut epoll from Android. What, you said that no one uses Android as a server, Nima, you look down on p2p software.

In the design principle of ngnix, it will use the most efficient I/O multiplexing model on the target platform, so there is this setting. In general, if possible, try to use epoll/kqueue.

Details here:
Connection processing methods

PS: All of the above comparative analyses are based on large concurrency. If your concurrency is too small, it makes no difference which one to use. If it is like the transcoding server in the Oppen data center, there are tens of thousands and hundreds of thousands of concurrent transactions, and I can directly hit the wall without epoll.

===============================Implementation of IO multiplexing ============= ===============

3. IO multiplexing (Reactor)

The IO multiplexing model is based on the demultiplexing function select provided by the kernel. Using the select function can avoid the problem of polling and waiting in the synchronous non-blocking IO model.

142332187256396.png

Figure 3 Demultiplexing function select

As shown in Figure 3, the user first adds the socket that needs IO operation to select, and then blocks and waits for the select system call to return. When data arrives, the socket is activated and the select function returns. The user thread officially initiates a read request, reads the data and continues execution.

From the process point of view, there is not much difference between using the select function for IO requests and the synchronous blocking model, and even adding monitoring sockets and additional operations of calling the select function, the efficiency is even worse. However, the biggest advantage of using select is that users can process IO requests of multiple sockets simultaneously in one thread. Users can register multiple sockets, and then continuously call select to read the activated sockets, so as to achieve the purpose of processing multiple IO requests at the same time in the same thread. In the synchronous blocking model, this purpose must be achieved through multi-threading.

The pseudo-code description of the user thread using the select function is:

{

select(socket);

while(1) {

sockets = select();

for(socket in sockets) {

if(can_read(socket)) {

read(socket, buffer);

process(buffer);

}

}

}

}

The socket is added to the select monitoring before the while loop, and then the select is called all the time to obtain the activated socket. Once the socket is readable, the read function is called to read the data in the socket.

 

However, the advantages of using the select function don't stop there. Although the above method allows multiple IO requests to be processed in a single thread, the process of each IO request is still blocked (blocked on the select function), and the average time is even longer than the synchronous blocking IO model. If the user thread only registers the socket or IO request that it is interested in, then does its own thing, and waits until the data arrives before processing it, the CPU utilization can be improved.

The IO multiplexing model implements this mechanism using the Reactor design pattern.

142332350853195.png

Figure 4 Reactor design pattern

As shown in Figure 4, the EventHandler abstract class represents the IO event handler, which has the IO file handle Handle (obtained by get_handle), and the Handle operation handle_event (read/write, etc.). Subclasses that inherit from EventHandler can customize the behavior of the event handler. The Reactor class is used to manage the EventHandler (registration, deletion, etc.), and uses handle_events to implement the event loop, constantly calling the demultiplexer function select of the synchronous event demultiplexer (usually the kernel), as long as a file handle is activated (readable /write, etc.), select returns (blocks), and handle_events calls the handle_event of the event handler associated with the file handle to perform related operations.

 

Figure 5 IO multiplexing

As shown in Figure 5, through the Reactor method, the work of polling the IO operation status of the user thread can be uniformly handed over to the handle_events event loop for processing. After the user thread registers the event handler, it can continue to perform other work (asynchronously), while the Reactor thread is responsible for calling the kernel's select function to check the socket status. When a socket is activated, it will notify the corresponding user thread (or execute the callback function of the user thread), and execute handle_event to read and process data. Since the select function is blocking, the multiplexed IO multiplexing model is also called the asynchronous blocking IO model. Note that the blocking here refers to the thread being blocked when the select function is executed, not the socket. Generally, when using the IO multiplexing model, the socket is set to NONBLOCK, but this will not have an impact, because when the user initiates an IO request, the data has already arrived, and the user thread will not be blocked.

The pseudo-code description of the user thread using the IO multiplexing model is:

void UserEventHandler::handle_event() {

if(can_read(socket)) {

read(socket, buffer);

process(buffer);

}

}

 

{

Reactor.register(new UserEventHandler(socket));

}

The user needs to rewrite the handle_event function of the EventHandler to read and process the data. The user thread only needs to register its own EventHandler with the Reactor. The pseudocode of the handle_events event loop in Reactor is roughly as follows.

Reactor::handle_events() {

while(1) {

sockets = select();

for(socket in sockets) {

get_event_handler(socket).handle_event();

}

}

}

The event loop continuously calls select to obtain the activated socket, and then according to the EventHandler corresponding to the obtained socket, the executor handle_event function can be used.

IO multiplexing is the most commonly used IO model, but its asynchrony is not "thorough" because it uses the select system call that blocks threads. Therefore, IO multiplexing can only be called asynchronous blocking IO, not true asynchronous IO.

 

4. Asynchronous IO (Proactor)

 

"True" asynchronous IO requires stronger support from the operating system. In the IO multiplexing model, the event loop notifies the user thread of the status event of the file handle, and the user thread reads and processes the data by itself. In the asynchronous IO model, when the user thread receives the notification, the data has been read by the kernel and placed in the buffer specified by the user thread, and the kernel can notify the user thread to use it directly after the IO is completed.

The asynchronous IO model implements this mechanism using the Proactor design pattern.

151608309061672.jpg

Figure 6 Proactor design pattern

As shown in Figure 6, the Proactor mode and the Reactor mode are similar in structure, but differ greatly in the way users (Client) use them. In the Reactor mode, the user thread registers interested event listeners with the Reactor object, and then calls the event handler when the event is triggered. In the Proactor mode, the user thread registers the AsynchronousOperation (read/write, etc.), the Proactor, and the CompletionHandler when the operation is completed to the AsynchronousOperationProcessor. AsynchronousOperationProcessor uses the Facade mode to provide a set of asynchronous operation APIs (read/write, etc.) for users to use. After the user thread calls the asynchronous API, it continues to execute its own tasks. AsynchronousOperationProcessor will open independent kernel threads to perform asynchronous operations to achieve true asynchronous. When the asynchronous IO operation is completed, the AsynchronousOperationProcessor takes out the Proactor and CompletionHandler registered with the user thread and the AsynchronousOperation, and then forwards the CompletionHandler and the result data of the IO operation to the Proactor. The Proactor is responsible for calling back the event completion handler handle_event of each asynchronous operation. Although each asynchronous operation in the Proactor mode can bind a Proactor object, generally in the operating system, the Proactor is implemented as a Singleton mode to facilitate the centralized distribution of operation completion events.

142333511475767.png

Figure 7 Asynchronous IO

As shown in Figure 7, in the asynchronous IO model, the user thread directly uses the asynchronous IO API provided by the kernel to initiate a read request, and returns immediately after initiation to continue executing the user thread code. However, at this time, the user thread has registered the called AsynchronousOperation and CompletionHandler to the kernel, and then the operating system starts an independent kernel thread to process the IO operation. When the data requested by read arrives, the kernel is responsible for reading the data in the socket and writing it into the buffer specified by the user. Finally, the kernel distributes the read data and the CompletionHandler registered by the user thread to the internal Proactor, and the Proactor notifies the user thread of the IO completion information (generally by calling the completion event handler registered by the user thread) to complete the asynchronous IO.

The pseudo-code description of the user thread using the asynchronous IO model is:

void UserCompletionHandler::handle_event(buffer) {

process(buffer);

}

 

{

aio_read(socket, new UserCompletionHandler);

}

The user needs to rewrite the handle_event function of the CompletionHandler to process the data. The parameter buffer represents the data that the Proactor has prepared. The user thread directly calls the asynchronous IO API provided by the kernel and registers the rewritten CompletionHandler.

Compared with the IO multiplexing model, asynchronous IO is not very commonly used. Many high-performance concurrent service programs use the IO multiplexing model + multi-threaded task processing architecture to basically meet the needs. Moreover, the current operating system's support for asynchronous IO is not particularly complete, and more is to use the IO multiplexing model to simulate asynchronous IO (the user thread is not directly notified when an IO event is triggered, but the data is stored in the data after reading and writing is completed. user-specified buffer). Asynchronous IO has been supported since Java7, and interested readers can try to use it.


Tags

Technical otaku

Sought technology together

Related Topic

0 Comments

Leave a Reply

+