[Redis 01] Redis threading model

Introduction to Redis

Redis introduction on the official website:

Redis is a memory-based storage system that can be used as a database, cache, and message middleware. Redis provides multiple objects and data structures such as strings, hashes, lists, sets, sorted sets, bitmaps, hyperloglogs, geo, etc.

Redis has built-in replication, lua scripts, LRU drivers, transactions, and different levels of disk persistence functions, and ensures the high availability of Redis through the Sentinel and Redis Cluster clusters.

Redis threading model

The Redis service is implemented in a similar way to Reactor, and each network request corresponds to a file descriptor FD. The I/O multiplexing module monitors multiple FDs at the same time. When file events such as accept, read, write, close, etc. are generated, the IO multiplexing program will first activate the FD corresponding event and place it in the event queue. The processor calls the corresponding callback function for processing, and finally returns the result.

[External link image transfer failed. The source site may have an anti-hotlinking mechanism. It is recommended to save the image and upload it directly (img-hKHvGS3O-1616052833634)(../../../../Library/Application%20Support/typora -user-images/image-20210318104628076.png)]

  • 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 ready to perform the listening socket accept, read, write, closeetc. In operation, socket I / O multiplexing program will generate all events in the queue, and in an orderly manner to a socket file transfer Dispatcher event;
  • The file event processor calls the corresponding processor to process the event according to the scoket event type from the I/O multiplexing;
  • The file event processor includes a command request processor, a command reply processor, a connection reply processor, etc., and the result is returned after the processing is completed.

Therefore, the single-threaded model of Redis mainly means that network IO event processing and key-value pair reading and writing are completed by one thread, and other operations such as persistence and asynchronous deletion are completed by additional threads.

Why is Redis using the single-threaded model so fast?

1. Why use the single-threaded model

  • Multi-threaded! = Fast. When there are multiple threads accessing resources in the system, a similar locking mechanism is needed to ensure the correctness of shared resources, which brings additional overhead;
  • Frequent context switching and competition among multiple threads will cause additional resource consumption;
  • Redis is based on memory operations, CPU is not the main problem, mainly because the network IO will take more time. So using a single thread is enough to process millions of commands.

2. Why is the single-threaded model so fast

  • Pure memory operation; Redis is based on memory operation, the response time is within ns, QPS can reach 10W+;
  • Efficient data structure: Provides data structures for efficient operations such as hash tables and jump tables;
  • Single thread avoids the problem of resource contention and frequent thread context switching among multiple threads;
  • IO multiplexing mechanism: enable it to concurrently process a large number of client requests in network IO operations to achieve high throughput;

Redis 6 multi-threaded model

Redis 6 provides multi-threaded operations, but note that Redis is still a single-threaded model.

Redis's multi-threaded mode is mainly used for receiving network requests, parsing commands, and outputting command execution results, which can be configured as multi-threaded execution. Redis officially considers these to be the main time-consuming points. But the execution of each command is still single-threaded.

[External link image transfer failed. The source site may have an anti-hotlinking mechanism. It is recommended to save the image and upload it directly (img-HB87lf36-1616052833636)(../../../../Library/Application%20Support/typora -user-images/image-20210318111425468.png)]

(Image source: "Talking about Redis Multithreading with Gang Jing")

Linux multiplexing

Linux multiplexing technology refers to the subsequent monitoring of multiple file descriptors FD generated by the socket after the socket connection is established. When a file descriptor is ready for reading/writing, the corresponding data is copied to the user space, and then read Write operation. When processing network requests, the process of calling the select function is blocked.

Linux provides implementation methods such as select, poll, and epoll, all of which are essentially synchronous IO methods, and all need to be responsible for reading and writing after the read and write event is ready. This reading and writing process is blocked.

The select call process is as follows:

  • First copy the fd_set that needs to be monitored from the user space to the kernel, and register the callback function
  • Traverse all fd, call the poll method (such as socket_poll), if there is a readable and writable mask, assign this mask to fd_set
  • Finally, copy fd_set from kernel space to user space
    Insert picture description here

The difference between select and epoll:

  • The maximum number of connections that can be opened by select a process is limited (32-bit machine size is 3232), but epoll 1G memory machine can open about 100,000 connections
  • select needs to copy the FD collection from the user mode to the kernel mode, because every time select is called, all connections are traversed linearly. When the FD collection is large, the traversal speed is very slow; while epoll registers the callback function on the FD, this Mechanism mechanism can bring efficiency improvement, that is, increasing the number of FDs will not reduce efficiency, because only active and available FDs will call the callback function.

(For the difference between select and epoll, you can read this article: In- depth understanding of select, poll and epoll and the difference )

Redis I/O multiplexing source code analysis


​ Redis IO multiplexing model (epoll)

Redis uses an event-driven mechanism, providing four event-driven models: evport, epoll, kqueue, and select.

Let's take a look at the epoll model implementation source code (in the ae_epoll.c file).

1. Create an epoll instance:

static int aeApiCreate(aeEventLoop *eventLoop) {
   // 省略部分代码
    state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
    // 调用
    eventLoop->apidata = state;
    return 0;
}

2. Add monitoring events, add file descriptors and corresponding events to the corresponding IO multiplex

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    // 省略部分代码
    mask |= eventLoop->events[fd].mask; /* Merge old events */
  // 添加AE读写事件
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.fd = fd;
    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    return 0;
}

3. Wait for the event to be triggered, and save the event after receiving the event

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    // 省略部分代码
   for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;
						// 将epoll事件转换成AE事件
            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
     
			     //  将事件保存在fired数组中,后续处理会用到该数组
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }	
    return numevents;
}

4. Event handling

The main function is monitored by calling the aeMain() function:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
  // 循环监听
    while (!eventLoop->stop) {
      	// 调用IO多路复用,如果返回事件,就激活事件处理器进行处理
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|
                                   AE_CALL_BEFORE_SLEEP|
                                   AE_CALL_AFTER_SLEEP);
    }
}

The aeProcessEvents function calls the IO multiplexing API to monitor; when the IO multiplexing returns an event, aeProcessEvents executes the callback function of each activation event for processing.

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    	// 省略部分代码
       // 调用IO多路复用 API,获取激活事件。事件保存在eventLoop->fired[]数组中
        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int fired = 0; /* Number of events fired for current fd. */

            int invert = fe->mask & AE_BARRIER;

            // 先调用回调函数执行可读事件
            if (!invert && fe->mask & mask & AE_READABLE) {
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                fired++;
                fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
            }

            // 再调用回调函数执行可写事件
            if (fe->mask & mask & AE_WRITABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }

            /* If we have to invert the call, fire the readable event now
             * after the writable one. */
            if (invert) {
                fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
                if ((fe->mask & mask & AE_READABLE) &&
                    (!fired || fe->wfileProc != fe->rfileProc))
                {
                    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }

            processed++;
        }
    }
    // 处理超时事件
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);

    return processed; /* return the number of processed file/time events */
}

Guess you like

Origin blog.csdn.net/noaman_wgs/article/details/114981325