Redis 服务器线程与事件循环解析

文章目录

事件循环线程

众所周知,Redis 服务器启动时会开启六个线程:

  1. Thread-1 [redis-server](主线程)
  2. Thread-2 [bio_close_file](后台 I/O 线程 - 文件关闭)
  3. Thread-3 [bio_aof](后台 I/O 线程 - AOF 刷盘)
  4. Thread-4 [bio_lazy_free](后台 I/O 线程 - 惰性删除)
  5. Thread-5 [jemalloc_bg_thd](内存整理线程)
  6. Thread-6 [jemalloc_bg_thd](内存整理线程)

而事件循环则位于 Thread-1 [redis-server](主线程)中。

事件循环相关源码文件

源码文件 说明
ae.c 事件驱动循环核心
ae_epoll.c Linux 系统实现(epoll)
ae_evport.c Solaris 专用
ae_kqueue.c macOS 专用
ae_select.c 老旧 Linux 或兼容模式使用
ebuckets.c 事件桶管理
eventnotifier.c 事件通讯器

事件主循环机制

Redis 的事件主循环位于 ae.c 中,核心代码如下:

c 复制代码
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|
                                   AE_CALL_BEFORE_SLEEP|
                                   AE_CALL_AFTER_SLEEP);
    }
}

但死循环非常消耗 CPU,Redis 是如何让线程进入睡眠等待的呢?

真正的"睡眠"发生在 aeApiPoll 中,其核心是调用系统 epoll_wait

c 复制代码
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    retval = epoll_wait(state->epfd, state->events, eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + (tvp->tv_usec + 999)/1000) : -1);
    if (retval > 0) {
        int j;
        numevents = retval;
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events + j;

            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;
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    } else if (retval == -1 && errno != EINTR) {
        panic("aeApiPoll: epoll_wait, %s", strerror(errno));
    }
    return numevents;
}
  • epoll_wait 就是实现"睡眠"的系统调用。
  • 当没有事件时,线程在此阻塞,不消耗 CPU。

睡眠钩子(Hooks)机制

为了在睡眠前后执行必要逻辑,Redis 提供了钩子函数。

initServer 函数中注册:

复制代码
aeSetBeforeSleepProc(server.el, beforeSleep);
aeSetAfterSleepProc(server.el, afterSleep);

aeProcessEvents 中调用:

复制代码
if (eventLoop->beforesleep != NULL && (flags & AE_CALL_BEFORE_SLEEP))
    eventLoop->beforesleep(eventLoop);

if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
    eventLoop->aftersleep(eventLoop);

作用

  • beforeSleep:处理客户端写回、检查阻塞超时、计算下一次 epoll_wait 的超时时间等。
  • afterSleep:清理或重置休眠期间的状态。

这些钩子避免了无限循环中 CPU 空转,是 Redis 高效的核心之一。


睡眠钩子的实现

beforeSleep钩子和afterSleep钩子的实现都在server.c中,分别是void beforeSleep(struct aeEventLoop *eventLoop)和void afterSleep(struct aeEventLoop *eventLoop)。

实现上beforeSleep远比afterSleep复杂。

Epoll 实战:键盘输入监听

我们可以编写代码练习 epoll 的使用,模拟 Redis 的事件监听机制。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>

// 简单的错误处理宏
#define panic(fmt, ...) \
    do { fprintf(stderr, "FATAL ERROR: " fmt " (%s:%d)\n", ##__VA_ARGS__, __FILE__, __LINE__); \
    exit(1); } while(0)

typedef struct epoll_event Event;

int main(void) {
    int epfd = epoll_create(1);
    if (epfd < 0) {
        panic("epoll_create");
        return 1;
    }

    // 设置监听标准输入(键盘)
    Event ev;
    ev.events = EPOLLIN;
    ev.data.fd = STDIN_FILENO;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) < 0) {
        panic("epoll_ctl");
        return 1;
    }

    Event events[10];
    char buf[1024];

    // 事件循环
    for (;;) {
        int nfds = epoll_wait(epfd, events, 10, -1);
        if (nfds < 0) {
            panic("epoll_wait");
            return 1;
        }

        // 处理就绪事件
        for (int i = 0; i < nfds; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                int nread = read(STDIN_FILENO, buf, sizeof(buf));
                if (nread > 0) {
                    buf[nread] = '\0';
                    printf("键盘输入: %s", buf);
                }
            }
        }
    }

    close(epfd);
    return 0;
}

编译运行

shell 复制代码
gcc epoll_test.c -o epoll_test
./epoll_test

这个程序会在没有输入时"睡眠",输入内容按回车后被唤醒,完美模拟了 Redis 的事件处理模型。

相关推荐
九章-3 小时前
一库平替,融合致胜:国产数据库的“统型”范式革命
数据库·融合数据库
FIT2CLOUD飞致云3 小时前
赛道第一!1Panel成功入选Gitee 2025年度开源项目
服务器·ai·开源·1panel
yanlou2333 小时前
[C++/Linux HTTP项目] HTTP服务器基于muduo高性能服务器搭载【深入详解】
运维·服务器·http·muduo库·http高性能服务器
2401_838472513 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python
u0109272713 小时前
使用Python进行网络设备自动配置
jvm·数据库·python
wengqidaifeng4 小时前
数据结构---顺序表的奥秘(下)
c语言·数据结构·数据库
what丶k4 小时前
SpringBoot3 配置文件使用全解析:从基础到实战,解锁灵活配置新姿势
java·数据库·spring boot·spring·spring cloud
天空属于哈夫克34 小时前
企微第三方 RPA API:非官方接口与官方接口的差异解析及选型建议
运维·服务器
niceffking4 小时前
linux 信号内核模型
linux·运维·服务器
Code blocks4 小时前
kingbase数据库集成Postgis扩展
数据库·后端