Threads and IO model in Redis
What is the Reactor pattern?
The origin of the "reaction" in the name of the "reactor" device:
"Reaction" means "inversion" and "reversal of control". The specific event handler does not call the reactor, but registers an event handler with the reactor, indicating that he is interested in certain events. When time comes, the specific event handler React to the occurrence of a specified event through an event handler; this inversion of control is also known as the "Hollywood Law" (don't call me, let me call you)
For example, passerby A goes to a men's spa, and the receptionist at the front desk receives passerby A. Passerby A is only interested in 10,000 technicians now, but passerby A goes earlier, so he tells the receptionist that the 10,000 technicians are on work or free. ,notice me. When passer-by A received the notice from the reception lady, he responded and occupied 10,000 technicians.
Then, passer-by A remembered that the room No. 10000 was good, with comfortable equipment and dim lighting, and told the reception lady at the front desk that I am very interested in room No. 10000. If the room is vacant, please tell me. The lady chatted about her life, and room 10000 was vacated. Passer-by A was notified by the reception lady again, and passer-by A responded again.
Passerby A is the specific event processing program. The receptionist at the front desk is the so-called reactor. "10000 technicians are at work" and "Room 10000 is free" are events. Passerby A is only interested in these two events. Others, such as 10001 It is also an event that technician number 1 or room 10002 is free, but passerby A is not interested.
The reception lady at the front desk not only serves passer-by A, but also serves passer-by B and C at the same time. The events that each person is interested in are different. The reception lady at the front desk will notify each person according to the event that each person is interested in. corresponding to each person.
Single-threaded Reactor mode process
The Reactor on the server side is a thread object that starts the event loop and uses the Acceptor event handler to pay attention to the ACCEPT event, so that the Reactor will listen to the connection request event (ACCEPT event) initiated by the client to the server.
The client initiates a connection request to the server, and the Reactor monitors the occurrence of the ACCEPT event and dispatches the ACCEPT event to the corresponding Acceptor processor for processing. After the connection is established, pay attention to the READ event, so that Reactor will listen to the READ event of the connection.
When Reactor detects that a READ event occurs, it dispatches the related event to the corresponding processor for processing. For example, the read processor will read the data, at this time, the read() operation can directly read the data without blocking and waiting for the readable data to arrive.
In the current single-thread Reactor mode, not only I/O operations are performed on the Reactor thread, but even non-I/O business operations are also processed on this thread, which may greatly delay the response of I/O requests. So we should offload non-I/O business logic operations from the Reactor thread to speed up the Reactor thread's response to I/O requests.
Single-threaded Reactor, worker thread pool
Different from the single-threaded Reactor mode, a worker thread pool is added, and non-I/O operations are moved from the Reactor thread to the worker thread pool for execution. This can improve the I/O response of the Reactor thread, so that the processing of subsequent I/O requests will not be delayed due to some time-consuming business logic.
However, for some small-capacity application scenarios, the single-threaded model can be used, but it is not suitable for application scenarios with high load, large concurrency, or large data volume. The main reasons are as follows:
① One NIO thread processes hundreds of links at the same time, which cannot be supported in terms of performance. Even if the CPU load of the NIO thread reaches 100%, it cannot satisfy the reading and sending of massive messages;
② When the NIO thread is overloaded, the processing speed will slow down, which will cause a large number of client connections to timeout, and will often be resent after the timeout, which will increase the load on the NIO thread, which will eventually lead to a large backlog of messages and processing timeout , become the performance bottleneck of the system;
Multi Reactor thread mode
Each Reactor thread in the Reactor thread pool will have its own Selector, thread, and event loop logic for distribution.
There can be only one mainReactor, but there are generally multiple subReactors. The mainReactor thread is mainly responsible for receiving the connection request from the client, and then passing the received SocketChannel to the subReactor, and the subReactor completes the communication with the client.
The multi-Reactor thread mode divides "accepting the client's connection request" and "communicating with the client" into two Reactor threads to complete. mainReactor completes the operation of receiving client connection requests. It is not responsible for communicating with the client, but transfers the established connection to the subReactor thread to complete the communication with the client. In this way, it will not cause too much data due to read() As a result, subsequent client connection requests cannot be processed immediately. And the multi-Reactor thread mode can distribute massive connections to multiple subReactor threads by implementing the subReactor thread pool in the case of a large number of concurrent client requests, which can greatly improve the application load and throughput in a multi-core operating system quantity.
Overview of threads and IO in Redis
Redis has developed its own network event handler - file event handler (hereinafter referred to as FEH) based on the Reactor model, and the processor is single-threaded, so redis is designed as a single-threaded model.
Use I/O multiplexing to monitor multiple sockets at the same time, and select the corresponding event handler for the socket according to the events currently executed by the socket.
When the monitored socket is ready to perform operations such as accept, read, write, close, etc., the file event corresponding to the operation will be generated. At this time, FEH will call the event handler associated with the socket to process the corresponding event.
Therefore, although FEH runs on a single thread, multiple sockets are monitored through I/O multiplexing, which not only realizes a high-performance network communication model, but also interacts with other modules running on the same single thread in the Redis server, ensuring that Redis internal Clean design for single-threaded model.
Let's look at several components of the file event handler.
socket
File events are the abstraction of socket operations. Whenever a socket is ready to perform operations such as connection accept, read, write, and close, a file event will be generated. A server usually connects multiple sockets, and multiple sockets may generate different operations concurrently, and each operation corresponds to a different file event.
I/O multiplexer
The I/O multiplexer will be responsible for listening to multiple sockets.
Although file events may occur concurrently, the I/O multiplexer puts all event-generating sockets into a queue, and transmits sockets to the file event dispatcher in an orderly, synchronous manner, one socket at a time, through the queue.
When the event generated by the previous socket is executed by the corresponding event handler, the I/O multiplexing program will send the next socket to the file event dispatcher, as follows:
Implementation of I/O multiplexing program
All the functions of Redis's I/O multiplexing program are realized by wrapping common I/O multiplexing function libraries such as select, epoll, evport and kqueue.
Each I/O multiplexing library corresponds to a separate file in the Redis source code:
Because Redis implements the same API for each I/O multiplexer library, the underlying implementations of I/O multiplexers are interchangeable. Redis defines the corresponding rules in the `ae.c` file of the source code of the I/O multiplexing program, so that the program automatically selects the I/O multiplexing function library with the highest performance in the system as the Redis Low-level implementation of an I/O multiplexer: in descending order of performance.
Note:
evport = Solaris 10
epoll = Linux
kqueue = OS X,FreeBSD
select = usually installed as a fallback on all platforms
Evport, Epoll, and KQueue have O(1) descriptor selection algorithm complexity, and they all use internal kernel space memory structures. They can also provide many (hundreds of thousands) of file descriptors.
Among other things, select can only give you at most 1024 descriptors, and it does a full scan of the descriptors (so iterates over all descriptors each time to select a usable one), so the complexity is O(n).
file event dispatcher
The file event dispatcher receives the socket from the I/O multiplexing program, and calls the corresponding event handler according to the event type generated by the socket.
file event handler
The server will associate different event handlers for sockets that perform different tasks. These handlers are functions that define the actions that the server should perform when an event occurs.
Redis has written multiple processors for various file event requirements. If the client connects to Redis and responds to each client connected to the server, it needs to map the socket to the connection response processor to write data to Redis and receive the data from the client. The command request needs to be mapped to the command request processor to read data from Redis and return the execution result of the command to the client. It needs to be mapped to the command reply processor. When the master server and the slave server perform replication operations, both the master and slave servers need to map to a copy processor written specifically for the copy function.
Types of file events
The I/O multiplexing program can monitor the ae.h/AE_READABLE event and ae.h/AE_WRITABLE event of multiple sockets. The correspondence between these two types of events and socket operations is as follows:
When the socket is readable (for example, the client performs a write/close operation on Redis), or when a new answerable socket appears (that is, the client performs a connect operation on Redis), the socket will generate an AE_READABLE event.
When the socket is writable (for example, the client performs a read operation on Redis), the socket will generate an AE_WRITABLE event.
The I/O multiplexing program can monitor both AE_REABLE and AE_WRITABLE events at the same time. If a socket generates these two events at the same time, the file event dispatcher will give priority to the AE_REABLE event. That is, when a socket is both readable and writable, the Redis server reads first and then writes to the socket.
Summarize
Finally, let's sort out the whole process of communication between the client and the Redis server:
When Redis starts initialization, it associates the connection response handler with the AE_READABLE event.
If a client initiates a connection, an AE_READABLE event will be generated, and then the connection response processor is responsible for establishing a connection with the client, creating a socket corresponding to the client, and at the same time associating the AE_READABLE event of this socket with the command request processor, so that the client Command requests can be sent to the master server.
When the client sends a request to Redis (regardless of read or write request), the client socket will generate an AE_READABLE event to trigger the command request processor. The processor reads the client's command content, and then passes it to the relevant program for execution.
When the Redis server is ready to respond to the client, it will associate the AE_WRITABLE event of the socket with the command reply processor. When the client is ready to read the response data, it will generate an AE_WRITABLE event on the socket, and the corresponding command will reply to the processor. Processing is to write the prepared response data to the socket for the client to read.
After the command reply processor has finished writing to the socket, the mapping between the socket's AE_WRITABLE event and the command reply processor will be deleted.