How Redis Achieves High Performance with a Single‑Threaded Event Loop
This article explains how Redis uses Linux epoll and a single‑threaded event‑driven architecture to handle tens of thousands of connections efficiently, detailing the initialization of the server, the event‑loop mechanics, and the processing of client commands from accept to reply.
Understanding Multiplexing
Before diving into Redis, the article introduces the concept of I/O multiplexing with epoll . Traditional blocking network models waste CPU because each thread handles a single client. Multiplexing allows many connections to share a single thread, similar to a shepherd managing many sheep with one dog.
In Linux, epoll_create creates an epoll object, epoll_ctl registers or removes sockets, and epoll_wait waits for readable or writable events. This mechanism is the foundation of Redis's high‑throughput networking.
Redis Service Startup Initialization
Redis’s entry point is main in src/server.c, which calls initServer() and then enters the event loop via aeMain(). The source code can be obtained from the GitHub repository:
# git clone https://github.com/redis/redis
# cd redis
# git checkout -b 5.0.0 5.0.0The core of initServer performs three essential steps:
Create an epoll object.
Bind the configured listening port.
Register the listening socket with epoll.
int main(int argc, char **argv) {
initServer();
aeMain(server.el);
}
void initServer(void) {
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
listenToPort(server.port, server.ipfd, &server.ipfd_count);
for (j = 0; j < server.ipfd_count; j++) {
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler, NULL);
}
...
}The epoll object is created by aeCreateEventLoop, which allocates an aeEventLoop structure and calls aeApiCreate. The latter invokes the kernel epoll_create system call.
aeEventLoop *aeCreateEventLoop(int setsize) {
aeEventLoop *eventLoop = zmalloc(sizeof(*eventLoop));
eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
aeApiCreate(eventLoop);
return eventLoop;
}
static int aeApiCreate(aeEventLoop *eventLoop) {
aeApiState *state = zmalloc(sizeof(aeApiState));
state->epfd = epoll_create(1024);
eventLoop->apidata = state;
return 0;
}Redis Event Processing Loop
After initialization, Redis enters aeMain, an infinite loop that repeatedly:
Calls the beforesleep hook to flush pending write buffers.
Invokes aeProcessEvents, which internally calls epoll_wait to discover readable or writable events.
void aeMain(aeEventLoop *eventLoop) {
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}When a new client connects, the listening socket’s read callback acceptTcpHandler is triggered. It accepts the connection, creates a redisClient object, and registers a read event for the new socket.
void acceptTcpHandler(aeEventLoop *el, int fd, ...) {
int cfd = anetTcpAccept(server.neterr, fd, cip, ...);
acceptCommonHandler(cfd, 0);
}
static void acceptCommonHandler(int fd, int flags) {
redisClient *c = createClient(fd);
...
}
redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(redisClient));
aeCreateFileEvent(server.el, fd, AE_READABLE, readQueryFromClient, c);
return c;
}When data arrives on a client socket, readQueryFromClient parses the command, executes it via processCommand, and queues the reply.
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, ...) {
redisClient *c = (redisClient*)privdata;
processInputBufferAndReplicate(c);
}
void processInputBufferAndReplicate(redisClient *c) {
processInputBuffer(c);
}
void processInputBuffer(redisClient *c) {
processCommand(c);
}
int processCommand(redisClient *c) {
c->cmd = lookupCommand(c->argv[0]->ptr);
if (c->flags & CLIENT_MULTI) {
queueMultiCommand(c);
} else {
call(c, CMD_CALL_FULL);
}
return C_OK;
}The call function looks up the concrete command implementation (e.g., getCommand) and invokes it. The reply is added to the client’s output buffer via addReplyBulk and related helpers.
void addReplyBulk(redisClient *c, robj *obj) {
addReplyBulkLen(c, obj);
addReply(c, obj);
addReply(c, shared.crlf);
}
void addReply(redisClient *c, robj *obj) {
if (prepareClientToWrite(c) != C_OK) return;
if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c, obj->ptr, sdslen(obj->ptr)) != C_OK)
_addReplyStringToList(c, obj->ptr, sdslen(obj->ptr));
} else {
...
}
}
int prepareClientToWrite(redisClient *c) {
if (!clientHasPendingReplies(c) && !(c->flags & CLIENT_PENDING_READ))
clientInstallWriteHandler(c);
return C_OK;
}
void clientInstallWriteHandler(redisClient *c) {
c->flags |= CLIENT_PENDING_WRITE;
listAddNodeHead(server.clients_pending_write, c);
}Before each epoll_wait, beforesleep processes the server.clients_pending_write queue, sending buffered data to clients. If a write cannot finish because the socket’s send buffer is full, the client remains in the queue and a writable event is re‑registered.
void beforeSleep(aeEventLoop *eventLoop) {
handleClientsWithPendingWrites();
}
int handleClientsWithPendingWrites(void) {
listIter li;
listNode *ln;
listRewind(server.clients_pending_write, &li);
while ((ln = listNext(&li))) {
redisClient *c = listNodeValue(ln);
c->flags &= ~CLIENT_PENDING_WRITE;
listDelNode(server.clients_pending_write, ln);
writeToClient(c->fd, c, 0);
if (clientHasPendingReplies(c)) {
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c);
}
}
return 0;
}High‑Performance Redis Network Summary
Redis demonstrates that a single‑threaded server can handle tens of thousands of QPS by leveraging Linux’s epoll for efficient I/O multiplexing. The two core components are the initServer startup routine and the aeMain event loop. Understanding these gives a solid grasp of Redis’s networking performance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
