Author | Flash
Source | Low Concurrency Programming
I'm a redis service and I'm about to start it
Because my master is console typing:
./redis-server
Take a macro look at my process
Suddenly, the owner pressed the enter key, which was incredible.
The shell program loaded my program into memory, started executing my main method, and that's where it all started.
int main(int argc, char **argv) {
...
initServer();
...
aeCreateFileEvent(fd, acceptHandler, ...);
...
aeMain();
...
}
Don't think I'm complicated here, in fact, there are three main steps.
In the first step, I created a TCP connection via the listenToPort() method.
This method of mine is really well-known, and if you expand it, you will find that there is nothing mysterious, that is, the socket bind listen standard takes three steps, establishes a TCP monitor, and returns a file descriptor fd.
In the second step, I added the file descriptor fd returned by the TCP connection created above to a linked list called aeFileEvent through the aeCreateFileEvent() method .
At the same time, bind this file descriptor to a function acceptHandler , so that when a client connects, this function will be executed.
In the third step, I use the aeMain() method to use the file descriptors in the above aeFileEvent linked list as the input parameters of select, which is the IO multiplexing mode.
Well, in fact, a TCP monitor is turned on, and then if a client comes in, let him execute the acceptHandler function.
After that I've been waiting for the client to connect.
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
Expand the experience of my specific work
At this point, another person started a redis-client and connected to me.
redis-cli -h host -p port
Then the fd on my head will sense that there is data read in, and execute the acceptHandler method.
static void acceptHandler(...) {
...
cfd = anetAccept(...);
...
c = createClient(cfd))
...
}
It can be seen that when a new client connects, it will call createClient to create a dedicated client to serve it.
static redisClient *createClient(int fd) {
...
aeCreateFileEvent(c->fd, readQueryFromClient, ...);
...
}
Here we can see that the so-called exclusive service is actually still the aeCreateFileEvent function.
As mentioned above, the function of this function is to hang the file descriptor on the linked list, and then assign a processing function.
Of course, the processing function this time is no longer acceptHandler that handles new client connections, but the function readQueryFromClient that handles redis commands from specific clients .
It is not difficult to imagine that if there is another client, another client... Then you can keep hanging the file descriptor of the new client, and the top file descriptor is always the one listening for the connection of the new client.
Well, the server has turned on the monitoring, and the client has also connected to the server. At this time, I am still in a dead waiting state, but I am not only waiting for the new client connection to arrive, but also waiting for the connected client to send it. Order.
Please note that there is only one thread in the dead wait here, and the aeProcessEvents function is called in a loop to listen to multiple file descriptors in the way of select. Put on the third step of the main method just now, to help you recall.
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
When a new client establishes a connection, the acceptHandler function is triggered to execute, adding a descriptor waiting for data.
When there is data from the client, the readQueryFromClient function will be triggered to complete the operation of this command.
Note that there is only one thread listening for these descriptors and doing processing. So even if the client sends commands concurrently, the commands are still taken out in sequence and executed sequentially .
This is what we often say, redis is single-threaded , and commands are executed sequentially, without considering the issue of thread safety.
In order to make it easier for everyone to brag, let me raise my eyebrows
Have you noticed that my startup process is actually divided into two major parts.
One is to monitor the client's request , which is to use IO multiplexing to monitor multiple file descriptors, just what the aeMain() method just did.
One is to execute the corresponding function to process the request . The specific function to be executed is bound by the aeCreateFileEvent() method that appears many times. This corresponding function is called an event handler .
The so-called connection response handler here is the function acceptHandler bound to the file descriptor that just listened for the connection.
The so-called command request handler is the function readQueryFromClient bound to the file descriptor that listens for client commands (read events).
The so-called command reply processor is the function sendReplyToClient that is bound to the file descriptor that listens to the client response (write event), which will be mentioned later.
This one is responsible for responding to IO events, and the other is responsible for handing over to the corresponding event handler for processing, which is called the Reactor mode .
Redis has developed its own file event handler based on the Reactor pattern, which implements a high-performance network communication model and maintains the simplicity of the single-threaded design within Redis .
I'm a little worried that the bragging force of this sentence is not enough. In fact, I refer to "Redis Design and Implementation", and I will show you the screenshots.
How to execute a Redis command
Now, let's issue a redis command through a connected client.
<client 6379> set dibingfa niubi
At this point the readQueryFromClient function will be executed.
This function will go to a table to find the function corresponding to the command. The coding technique used in this part is called command mode.
static struct redisCommand cmdTable[] = {
{"get",getCommand,2,REDIS_CMD_INLINE},
{"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"del",delCommand,-2,REDIS_CMD_INLINE},
{"exists",existsCommand,2,REDIS_CMD_INLINE},
...
}
Find the function corresponding to the set command is setCommand .
This function will eventually store the key and value separately step by step.
After processing the command, it is time to send the response to the client.
static void setCommand(redisClient *c) {
...
addReply(c, nx ? shared.cone : shared.ok);
}
This response is not directly written back synchronously, and certainly not by opening a thread to write it back asynchronously.
It still calls the evil aeCreateFileEvent function and hooks the sendReplyToClient function on the file descriptor of the client connection that needs to respond.
static void addReply(redisClient *c, robj *obj) {
...
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c, NULL) == AE_ERR);
}
Well, this time, the hole dug in the last section has finally been filled.
The above shit is my startup process, am I cute?
postscript
In the whole article, I don't seem to talk about why Redis is so fast, because I feel that this question is not well asked.
You can start from the perspective of IO multiplexing for receiving network requests, you can also start from the Reactor mode driven by event handlers, and you can start from the data structure when processing the command, such as the sds behind the string. In fact, a lot of clever designs have been done.
If I were the interviewer, I would specifically ask the interviewer to talk about the startup process of Redis, or the entire process of Redis processing commands.
There are a lot of points that can be dug up here. If you can talk and laugh, it is naturally a good technical level.
In addition, you will find a lot of bluffing terms in this article, such as Reactor mode, event handler, etc. After reading the Redis source code, you will find that it is really very simple.
It is rude to say that all articles or people who don't talk about specific implementation and pile up a lot of bluffing terms with you are playing hooligans.
In this article, I refer to the Redis3.0.0 source code, but the explanation code used in the writing is Redis1.0.0, and the design of the entire network module is exactly the same.
Recommended in the past
How to solve Redis cache breakdown (invalidation), cache penetration, and cache avalanche?
If you are asked about distributed locks, how should you answer?
Three minutes to teach you to write a WebSocket App with Scarlet
Basic knowledge of Java: what is a "bridge method"?
point to share
Favorites
Like
click to watch