event driven
Redis server is an event driver, which is divided into file events and time events
- File events: readable and writable events of socket
- Timed task
They are all encapsulated in the aeEventLoop structure
typedef struct aeEventLoop { int stop; // Identifies whether the event ends aeFileEvent *events; // File event array to store registered file events aeFireEvent *fired; // Store triggered file events aeTimeEvent *timteEventHead; // Linked list formed by multiple time events void *apidata; // Encapsulation of I/O model aeBeforeSleepProc *beforesleep; // Execute before process blocking aeBeforeSleepProc *aftersleep; // The process is executed after being awakened } aeEventLoop;
The event driver actually waits for events through the while/for loop
while (! eventLoop->stop) { if (eventLoop->beforesleep != NULL) eventLoop->beforesleep(eventLoop) aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP); }
aeProcessEvents is the main function of event processing
epoll
Redis client interacts with the server through TCP socket. File events refer to the readable and writable events of the socket. Non blocking mode is generally used. The related I/O multiplexing includes select/epoll/kqueue, etc. different operating systems have different implementations.
Taking epoll as an example, it is a solution proposed by the Linux kernel to deal with a large number of concurrent network connections. Epoll provides three API s
- epoll_create creates an epoll specific file descriptor for subsequent epoll related API calls
int epoll_create(int size) // size tells the kernel program the number of network connections expected to register. After Linux 2.6.8, it is changed to kernel dynamic allocation // The return parameter is a file descriptor dedicated to epoll
- epoll_ctl function registers, modifies or deletes events to be monitored with epoll
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) // epfd function epoll_ Epoll file descriptor returned by create // op operation type EPOLL_CTL_ADD: registration event; EPOLL_CTL_MOD: modify network connection events; EPOLL_CTL_DEL: delete event // socket file descriptor of fd network connection // event events to be monitored
- epoll_ The wait function blocks the process until an event occurs on several monitored network connections
int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout) // epfd function epoll_ Epoll file descriptor returned by create // epoll_event is used as an output parameter to return the triggered event array // maxevents is the maximum number of events that can be processed at a time // timeout epoll_ The wait function blocks the timeout time. If no event occurs after the timeout time, the function will no longer block and return directly; When timeout equals 0, the function returns immediately. When timeout equals - 1, the function blocks until an event occurs
File event
Instead of directly using epoll's API, IDS supports four I/O multiplexing models at the same time, and encapsulates the APIs of these models. Then check the I/O multiplexing model supported by the operating system in the compilation stage, and decide which model to reuse according to the policy.
Take epoll as an example, Redis has the following encapsulation
// Corresponding epoll_create static int aeApiCreate(aeEventLoop *eventLoop) // Corresponding epoll_ctl add event static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) // Corresponding epoll_ctl delete event static int aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) // Corresponding epoll_wait static int aeApiPool(aeEventLoop *eventLoop, struct timeval *tvp)
Recall the eventLoop structure mentioned above. Its member apidata points to four I/O multiplexing model objects; Events stores the event array to be monitored, and takes the socket file descriptor as the array index access element; fired stores an array of triggered events.
The structure of file events is defined as follows:
typedef struct aeFileEvent { int mask; // File event type AE_READABLE readable event; AE_WRITEABLE writeable event aeFileProc *rfileProc; // Read event handler pointer aeFileProc *wfileProc; // Write event handler pointer void *clientData; // Point to the corresponding client object } aeFileEvent;
Take a look at the implementation of the create file event aeCreateFileEvent
int aeCreateFileEvent (aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData) { aeFileEvent *fe = &eventLoop->evnts[fd]; if (aeApiAddEvent(eventLoop, fd, mask) == -1) return AE_ERR; fe->mask |= mask; if (mask & AE_READABLE) fe->rfileProc = proc; if (mask & AE_WRITABLE) fe->wfileProc = proc; fe->clientData = clientData; return AE_OK; }
Redis server will handle transactions by creating various file events, such as:
- When starting, create a socket and listen, waiting for the client to connect
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler, NULL);
- After the client establishes a socket connection with the server, the server will wait for the command request of the client
aeCreateFileEvent(server.el, fd, AE_READABLLE, readQueryFromClient, c);
- After the server processes the client's command request, the command reply will be temporarily cached in the buf buffer of the client structure. The command reply will not be sent to the client until the writable event of the client's file descriptor occurs
aeCreateFileEvent(server.el, c->fd, AE_READABLLE, sendReplyToClient, c);
The execution of all Redis events is controlled through the aeProcessEvents function. Among them, epoll_wait occurs when executing file events. If the blocking event is too long, it will hinder the execution of time events (timing). In order to avoid this situation, the incoming waiting time when implementing file events is obtained by calculating the earliest time event
int aeProcessEvents(aeEventLoop *eventLoop, int flags) { shortest = aeSearchNearestTimer(eventLoop); long long ms = (shortest->when_sec - now_sec) * 1000 + \ shortest->when_ms - now_ms; // Blocking event occurs numevents = aeApiPoll(eventLoop, ms); for (j=0; j < numevents; j++) { aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j]].fd]; // Handle file events, that is, rfileProc or wfileProc is executed according to the type } // Processing time events processed += processTimeEvents(eventLoop); }
summary
Now let's take a general look at the corresponding command flow of Redis server
The aeMain function schedules and executes file events and time events by calling the aeProcessEvents function. aeEventLoop records event related information. First, we get the shortest time interval execution time interval n through the aeSearchNearestTimer function, then call the aeApiPoll function to get the monitored socket, then perform the corresponding event handling function rfileProc and wfileProc with the socket, and finally execute the time event function processTimeEvents.
A complete client server connection event:
-
AE Monitor Kit word_ The readable event generates AE when the client sends a connection request_ With the readable event, the server will respond to the connection request of the client and send the AE of the client socket_ Readable event and command request processing function (aeFileProc). The client can send command requests to the server
-
The client sends a command request to the server, and the client socket will generate AE_ The readable event causes the command processor to execute, the execution of the command will generate the corresponding command reply, and the server will send the AE of the client socket_ The writeable event is associated with the command reply handler (aeFileProc)
-
When the client tries to read the command reply, the client socket will generate AE_ The writeable event triggers the command reply processor to execute. When the command reply processor writes all the command replies to the socket, the server will contact the AE of the client socket_ The association between the writeable event and the command reply handler (aeFileProc)