“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”

event-driven

Redis server is an event driver, divided into file events and time events

  • File events: readable and writable events of the socket
  • Timing task

They are all encapsulated in the aeEventLoop structure

typedef struct aeEventLoop {
	int stop; // Indicates whether the event ends
	aeFileEvent *events; // File event array, which stores registered file events
	aeFireEvent *fired; // Store triggered file events
	aeTimeEvent *timteEventHead; // A linked list of multiple temporal events
	void *apidata; // Encapsulate the I/O model
	aeBeforeSleepProc *beforesleep; // Execute before the process blocks
	aeBeforeSleepProc *aftersleep; // The process is awakened and executed
} aeEventLoop;
Copy the code

Event drivers actually wait for events to occur through a while/for loop

while (! eventLoop->stop) {
	if(eventLoop->beforesleep ! =NULL)
		eventLoop->beforesleep(eventLoop)
	aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
Copy the code

AeProcessEvents is the main event handler function

epoll

The Redis client interacts with the server through TCP sockets. File events refer to socket readable and writable events. The general use of non-blocking mode, related I/O multiplexing such as SELECT /epoll/kqueue, different operating system different implementation.

Epoll, for example, is the Linux kernel’s solution for handling a large number of concurrent network connections. Epoll provides three apis

  1. Epoll_create Creates an epoll-specific file descriptor for subsequent epoll-related API calls
int epoll_create(int size)
// size indicates the number of network connections the kernel expects to register. Linux 2.6.8 changed to dynamic kernel allocation
// The return argument is the epoll-specific file descriptor
Copy the code
  1. The 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)
// Epoll file descriptor returned by the epfd function epoll_create
// op operation type EPOLL_CTL_ADD: register event; EPOLL_CTL_MOD: modifies network connection events. EPOLL_CTL_DEL: delete event
// Socket file descriptor for fd network connection
// event Indicates the event to be monitored
Copy the code
  1. The epoll_wait function blocks the process until an event occurs on a number of monitored network connections
int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout)
// Epoll file descriptor returned by the epfd function epoll_create
// epoll_event is used as an output parameter to return an array of triggered events
// maxEvents Indicates the maximum number of events that can be processed at a time
// timeout The epoll_wait function blocks the timeout period. If no event has occurred after the timeout period, the function will stop blocking and return directly. The function returns immediately when timeout equals 0, and blocks until an event occurs when timeout equals -1
Copy the code

File events

Instead of using EPoll’s API directly, Reids supports four I/O multiplexing models simultaneously, encapsulating their APIS. The I/O multiplexing model supported by the operating system is then examined at compile time and the policy is used to decide which model to reuse.

Again, taking Epoll as an example, Redis encapsulates as follows

/ / the corresponding epoll_create
static int aeApiCreate(aeEventLoop *eventLoop)

// Add events corresponding to epoll_ctl
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
// Corresponds to the epoll_ctl deletion event
static int aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask)

/ / the corresponding epoll_wait
static int aeApiPool(aeEventLoop *eventLoop, struct timeval *tvp)
Copy the code

Recall the eventLoop structure mentioned above, whose member Apidata points to four I/O multiplexing model objects; Events stores an array of events to be monitored and accesses elements using socket file descriptors as array indexes. Fired stores an array of events fired.

The file event structure is defined as follows:

typedef struct aeFileEvent {
	int mask; // The file event type is AE_READABLE; AE_WRITEABLE Writeable event
	aeFileProc *rfileProc; // Read the event handler pointer
	aeFileProc *wfileProc; // Write the event handler pointer
	void *clientData; // Point to the corresponding client object
} aeFileEvent;
Copy the code

Take a look at the implementation of creating the 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;
}
Copy the code

The Redis server handles transactions by creating various file events, such as:

  • The socket is created at startup and listens, waiting for the client to connect
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler, NULL);
Copy the code
  • After the client establishes a socket connection with the server, the server waits for the command request from the client
aeCreateFileEvent(server.el, fd, AE_READABLLE, readQueryFromClient, c);
Copy the code
  • After the server processes the command request from the client, the command reply is temporarily cached in the BUF buffer of the client structure until the writable event of the client file descriptor occurs
aeCreateFileEvent(server.el, c->fd, AE_READABLLE, sendReplyToClient, c);
Copy the code

The execution of all events in Redis is controlled by the aeProcessEvents function. In this case, the execution of file events is blocked (epoll_wait). If the blocking event is too long, it will prevent the execution of time events (timed). To avoid this situation, the wait time passed in the implementation of file events is calculated by calculating the earliest time events

int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
	shortest = aeSearchNearestTimer(eventLoop);
	long long ms = (shortest->when_sec - now_sec) * 1000 + \
		shortest->when_ms - now_ms;

	// A blocking event occurred
	numevents = aeApiPoll(eventLoop, ms);

	for (j=0; j < numevents; j++) {
		aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j]].fd];
		// Handles file events, that is, rfileProc or wfileProc, depending on the type
	}

	// Handle time events
	processed += processTimeEvents(eventLoop);
}
Copy the code

conclusion

Now let’s take a look at the flow of Redis server commands as a wholeThe aeMain function schedules and executes file and time events by calling the aeProcessEvents function. AeEventLoop records event-related information. Then aeApiPoll is called to get the monitored socket. Finally, the socket direction event handlers rfileProc and wfileProc are executed. Finally, the time event function processTimeEvents is executed.

A complete client-server connection event:

  • When a client sends a connection request and generates an AE_READABLE event, the server responds to the client’s connection request by combining the AE_READABLE event of the client socket with the command request handler (aeFileProc), The client can now send command requests to the server

  • When the client socket sends a command request to the server, the client socket will generate AE_READABLE events, which will trigger the command processor to execute. Executing the command will generate the corresponding command reply. The server associates the AE_WRITABLE event of the client socket with the command reply handler (aeFileProc)

  • When the end attempts to read the command reply, the client socket will generate AE_WRITABLE event, which triggers the command reply processor to execute. After the command reply processor writes all the command reply to the socket, The server is exposed to the association between the AE_WRITABLE event of the client socket and the command reply handler (aeFileProc)