I feel that this part of the code is still quite interesting, and I will try to explain it in a more general way.
I remember my article on the Go HTTP Standard Library, which explains how to create a Server-side application for Go.
- First, register the processor.
- Start a loop to listen for ports, and create a Goroutine for each connection.
- Then the Goroutine waits in a loop to receive the request data, then matches the corresponding processor in the processor routing table based on the request address, and hands the request to the processor for processing.
In code, it looks like this.
Redis is a bit different because it is single-threaded and cannot use multiple threads to handle connections, so Redis chooses to use an event driver based on the Reactor pattern to implement concurrent processing of events.
The so-called Reactor mode in Redis is to listen to multiple fd’s through epoll, and whenever these fd’s respond, they will notify epoll in the form of events for callbacks, each of which has a corresponding event handler.
Each event has a corresponding event handler, such as accept for acceptTCPHandler, read & write for readQueryFromClient, and so on, and then the events are assigned to event handlers for processing in the form of circular dispatching of events.
So the above Reactor pattern is implemented through epoll, and there are three main methods for epoll.
So we can implement a simple server based on these three methods.
So, based on the above, you can see that an event loop for Redis is just a few steps.
- registering event listeners and callback functions.
- wait in the loop to get the event and process it.
- call the callback function to process the data logic.
- write back the data to the Client.
- register fd to epoll and set the callback function acceptTcpHandler, which will be called if there is a new connection.
- start a dead loop calling epoll_wait to wait and keep processing events, later we will return to the aeMain function to loop through the aeProcessEvents function.
- when a network event comes, it will follow the callback function acceptTcpHandler all the way to readQueryFromClient for data processing. readQueryFromClient will parse the client’s data and find the corresponding cmd function to execute;
- the Redis instance, after receiving the client request, will write the data to be returned to the client output buffer instead of returning it immediately after processing the client command.
- the beforeSleep function is then called at each loop of the aeMain function to write the data in the buffer back to the client; the beforeSleep function is then called at each loop of the aeMain function to write the data in the buffer back to the client.
The entire process of the event loop above has actually been written in very clear code steps, and there are many articles on the Internet about it, so I won’t say much.
Command execution process & write back to client
Let’s talk about something that many articles on the web don’t mention, and see how Redis executes commands, stores them in the cache, and writes the data back to the Client from the cache.
As we mentioned in the previous section, the readQueryFromClient function is called if a network event comes through, and it is where the command is actually executed. We’ll also follow this method all the way down to.
- readQueryFromClient will call the processInputBufferAndReplicate function to process the requested command.
- the processInputBufferAndReplicate function calls processInputBuffer and determines whether the command needs to be replicated to other nodes if it is in cluster mode.
- the processInputBuffer function will loop through the requested commands, call the processInlineBuffer function according to the requested protocol, and call the processCommand to execute the commands after the redisObject object.
- processCommand will lookupCommand to the
server.commandstable to find the corresponding execution function according to the command, then after a series of checks, call the corresponding function to execute the command, and call addReply to write the data to be returned to the client output buffer.
server.commands registers all Redis commands in the populateCommandTable function as a table of command functions based on command names.
For example, to execute the get command, the getCommand function would be called.
Find the data in the getCommand function and call addReply to write the data to be returned to the client’s output buffer.
Writing data back to Client
After the above command is written to the buffer, the data needs to be retrieved from the buffer and returned to the Client, which is actually done in the event loop of the server.
- first Redis will call the aeSetBeforeSleepProc function in the main function to register the beforeSleep function of the writeback package to the eventLoop; 2. then Redis will call the aeMain function for the event loop to see if beforesleep has been set.
- then Redis will determine if beforesleep has been set when calling the aeMain function for the event loop, and if it has, then it will call it.
- the beforesleep function will call the handleClientsWithPendingWrites function, which will call writeToClient to write the data back to the client from the buffer.
This article describes how the entire Redis request processing model really works. From registering to listen for fd events, to executing commands, to finally writing data back to the client, I’ve done a general analysis. Of course, this article is also a bit different from my previous articles, I didn’t post the code at length, mainly because I don’t think it’s necessary, but you can follow the flowchart to see the code.