How does Redis with outstanding performance use epoll?

c0b44862ec5b8d3bf8ac728e888cde5f.gif

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

bc3f6620349c72e6a8da89a0ce521fb6.png

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.

f2e29ff0adaba51a6ccb7e9560a0b8bf.png

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 .

502a9879382a745dc4ef868764efd196.png

At the same time, bind this file descriptor to a function acceptHandler , so that when a client connects, this function will be executed.

1ddfbf8a8380ac9c9bb76b0c50f5f849.png

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.

cedc3e64f2c54d312d4130716caf1c7b.png

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);
}

7a720951200b59398b1aaf0ae68aca14.gif

fc6afb27841769910a12073ac0b4dd33.png

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 .

542e23435454ed8dbd07ec94bf77a4d3.png

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.

fcc190c1ab3620585ea720cb894d1b46.png

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.

a258af58804e638dc2060b17298d5a0f.gif

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.

1809db805ac56e77dc88fbb9bbe9bc40.gif

When there is data from the client, the readQueryFromClient function will be triggered to complete the operation of this command.

a3ab62080cf9dc64fe8e7f89fb41b535.gif

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 .

c4cad8891e212562849c1ad8816dacca.gif

This is what we often say, redis is single-threaded , and commands are executed sequentially, without considering the issue of thread safety.

fcba724021cd409c9a195d5cdd924182.png

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 .

633194c064092424b963f7d7db36a5c6.png

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.

d0a0604cdb482c6b381a83872a22bd6c.png



ef1bd6685e64d5025b5914e1306773ca.png

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?

69c18e046eb2e51a447860a347ba3531.gif

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.

7f3dc345d1321257fefbe5d014466cdf.gif

35b6589e06ed7b16be24dc67864f3c8f.png

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"?

f86b7c8c9cb5498b5c7e0f270506aed9.gif

point to share

ea767ff9c4f0a921ec9e8e30f49de3cb.gif

Favorites

dc7b4e963069e3e670a94b29dd54fdaa.gif

Like

e3d90bba86b420b2debe9e0e7f09137b.gif

click to watch

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324138230&siteId=291194637