redis 6.0多线程核心代码分析

本文深入探讨Redis的多线程处理机制,重点分析了如何通过独立子线程进行网络请求解析和响应处理,有效提升处理效率。文章详细介绍了主线程与子线程的协调工作流程,以及客户端请求在多线程环境下的分配策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一部分,主流程分析

1.main
1.1 initServer 事件处理器
1.2 InitServerLast  
1.2.1 initThreadedIO 创建io子线程, 注意这里的

    /* Spawn and initialize the I/O threads. */
    for (int i = 0; i < server.io_threads_num; i++) {
        /* Things we do for all the threads including the main thread. */
        io_threads_list[i] = listCreate();
        if (i == 0) continue; /* Thread 0 is the main thread. */

        /* Things we do only for the additional threads. */
        pthread_t tid;
        pthread_mutex_init(&io_threads_mutex[i],NULL);
        io_threads_pending[i] = 0;
        pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
        if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
            serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
            exit(1);
        }
        io_threads[i] = tid;
    }


1.2.2  注意这里创建子线程时的处理,在index=0时,直接continue,猜测这里是为了保持在conf文件中配置的线程数就是总的线程数,而不是io线程数,index为0的就是当前线程,即主线程

2.主循环   beforeSleep
3.事件beforeSleep中   handleClientsWithPendingReadsUsingThreads
3.1 server.clients_pending_read轮转分配到N个io_threads_list队列中

    /* Distribute the clients across N different lists. */
    listIter li;
    listNode *ln;
    listRewind(server.clients_pending_read,&li);
    int item_id = 0;
    while((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        int target_id = item_id % server.io_threads_num;
        listAddNodeTail(io_threads_list[target_id],c);
        item_id++;
    }

3.2 主线程  通过将原子开关io_threads_op置位为IO_THREADS_OP_READ,开启各个io线程的读操作,然后主线程处理自己的io_threads_list[0],并通过原子开关io_threads_pending来启动io子线程

    /* Give the start condition to the waiting threads, by setting the
     * start condition atomic var. */
    io_threads_op = IO_THREADS_OP_READ;
    for (int j = 1; j < server.io_threads_num; j++) {
        int count = listLength(io_threads_list[j]);
        io_threads_pending[j] = count;
    }

3.3 主线程  自旋检测所有ios线程的处理队列对否都已清空

    /* Wait for all the other threads to end their work. */
    while(1) {
        unsigned long pending = 0;
        for (int j = 1; j < server.io_threads_num; j++)
            pending += io_threads_pending[j];
        if (pending == 0) break;
    }

3.4 主线程  在db中查询client上来的读请求

3.5 io子线程  IOThreadMain 自旋等待,通过原子开关 io_threads_pending 来判断

    while(1) {
        /* Wait for start */
        for (int j = 0; j < 1000000; j++) {
            if (io_threads_pending[id] != 0) break;
        }

        /* Give the main thread a chance to stop this thread. */
        if (io_threads_pending[id] == 0) {
            pthread_mutex_lock(&io_threads_mutex[id]);
            pthread_mutex_unlock(&io_threads_mutex[id]);
            continue;
        }

3.6 io子线程  通过原子开关io_threads_pending和io_threads_op启动客户端请求的解析和查询结果的返回

        listIter li;
        listNode *ln;
        listRewind(io_threads_list[id],&li);
        while((ln = listNext(&li))) {
            client *c = listNodeValue(ln);
            if (io_threads_op == IO_THREADS_OP_WRITE) {
                writeToClient(c,0);
            } else if (io_threads_op == IO_THREADS_OP_READ) {
                readQueryFromClient(c->conn);
            } else {
                serverPanic("io_threads_op value is unknown");
            }
        }
        listEmpty(io_threads_list[id]);
        io_threads_pending[id] = 0;

4.事件beforeSleep中   handleClientsWithPendingWritesUsingThreads, 具体策略类似读操作的io多线程处理

第二部分,关键模块分析

1.readQueryFromClient(connection *conn)

1.1  在多线程模式下会启用postponeClientRead

1.1.1 postponeClientRead, 如果client还没有被加入到server.clients_pending_read队列中,则加入这个全局队列,设置client的CLIENT_PENDING_READ状态,放到下一轮的多线程处理读请求的时候再处理

int postponeClientRead(client *c) {
    if (io_threads_active &&
        server.io_threads_do_reads &&
        !ProcessingEventsWhileBlocked &&
        !(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
    {
        c->flags |= CLIENT_PENDING_READ;
        listAddNodeHead(server.clients_pending_read,c);
        return 1;
    } else {
        return 0;
    }
}

1.1.2  如果一个client在一个时间片内被加入到了server.clients_pending_read,那么不会被加入第二次, 这样可以保证一个时间片内,不管cleint有多少次请求,都会被同一个io thread处理完,不会存在client的多个请求被分散在多个io thread中导致的数据不一致问题

1.2  在单线程模式下或者client已经在CLIENT_PENDING_READ状态下, 直接解析读请求,查询db

第三部分 总结

redis多线程的核心思想,就是不改动之前的db处理部分,只对前阶段的网络请求解析和后阶段的网络回包做多线程处理,按照作者的说法,前后两部分才是耗时大户。


 

<think>嗯,用户深入询问了Redis 6.0多线程模型下如何保证数据操作的原子性,这确实是个关键问题。从对话历史看,用户已经对Redis数据结构有基础认知,现在转向底层机制探究,可能是系统架构师或后端开发人员。 用户提供的引用材料很有价值,特别是[^3]和[^5]直接涉及多线程实现细节。需要重点解释清楚两个层面:1)为什么多线程不破坏原子性;2)具体实现机制。用户引用中提到的“主线程逐条处理命令”和“原子开关io_threads_op”是核心线索。 注意到用户特别强调“不是真正的需求”,说明ta需要的是原理性解释而非配置指导。考虑从三个维度展开:线程分工(网络IO多线程 vs 命令执行单线程)、同步机制(原子开关/任务队列)、设计哲学(保持单线程优势)。技术细节上要突出io_threads_op这个原子变量的作用,就像用户引用的代码片段所示。 用户可能存在的隐藏疑问是:这种设计是否真能提升性能?是否需要担心线程安全问题?准备在回答中穿插性能对比数据,并强调“所有命令仍串行执行”这一关键特性。对于ACID特性的保证,可以补充WAL日志的配合机制。 最后要关联到用户之前问过的数据结构问题——比如说明多线程不影响Sorted Set的ZADD等操作的原子性,保持知识体系的连贯性。</think>Redis 6.0多线程模型通过**网络 I/O 多线程化 + 命令执行单线程化**的设计,在提升吞吐量的同时严格保证了数据操作的原子性。具体实现机制如下: --- ### 一、多线程模型的核心分工 1. **网络 I/O 多线程处理(并行)** - 主线程负责监听连接请求,接收新连接后将其分配给 I/O 线程池 - I/O 线程池并行处理连接的**读写操作**(解析请求/封装响应) ```c // 引用[5]代码片段:主线程分配读任务 io_threads_op = IO_THREADS_OP_READ; // 设置原子操作标志 for (int j = 1; j < server.io_threads_num; j++) { io_threads_pending[j] = listLength(io_threads_list[j]); // 分配任务量 } ``` - **关键点**:I/O 线程仅处理网络数据传输,不涉及命令逻辑[^3][^4] 2. **命令执行单线程化(串行)** - 所有解析后的命令**按顺序进入主线程队列** - 主线程**单线程执行所有命令**(与 Redis 6.0 之前完全一致) - 执行过程保持原子性:一个命令执行完才会处理下一个[^1][^3] --- ### 二、原子性保障的核心机制 | 阶段 | 执行线程 | 是否并行 | 原子性保障方式 | |--------------|----------------|----------|--------------------------------| | 网络数据读取 | I/O 线程池 | ✅ 并行 | 仅传输数据,不解析命令 | | 命令解析 | 主线程 | ❌ 串行 | 单线程顺序处理 | | **命令执行** | **主线程** | ❌ 串行 | **单线程顺序执行(关键)** | | 结果返回 | I/O 线程池 | ✅ 并行 | 仅传输结果,不修改数据 | > 如图: > ``` > 客户端请求 → I/O线程(并行读) → 主线程队列 → 主线程(串行执行命令) → I/O线程(并行写) → 客户端响应 > ``` --- ### 三、关键技术实现 1. **原子任务分配开关** - 通过原子变量 `io_threads_op` 控制 I/O 线程状态(读/写/空闲)[^5] - 确保主线程与 I/O 线程间无竞争条件 2. **任务队列隔离** - 每个 I/O 线程有独立 socket 队列(`io_threads_list[j]`)[^5] - 主线程通过轮询分配连接,避免资源争抢 3. **序列化命令执行** - 命令执行阶段禁用多线程,保持单线程模型的所有特性: - 无需锁机制 - 天然避免竞态条件 - 所有操作满足 ACID 中的原子性[^1][^3] --- ### 四、性能与安全的平衡 | 场景 | 单线程模型 | 多线程模型 | 原子性影响 | |---------------------|--------------------|--------------------------|------------| | 网络 I/O 吞吐量 | 低(受限于单线程) | 高(多线程并行传输) | ✅ 无 | | 命令执行 | 单线程 | 单线程 | ✅ 保持 | | 内存操作 | 无锁 | 无锁 | ✅ 保持 | | 10万 QPS 网络密集型 | 可能成为瓶颈 | 吞吐量提升 2-3 倍[^1][^4] | ✅ 无影响 | > ⚠️ **注意**:需在配置中显式启用多线程(默认关闭) > `io-threads 4` # 启用4个I/O线程 --- ### 总结 Redis 6.0多线程**本质是网络 I/O 卸载**(Offloading),将耗时的网络数据传输交给多线程并行处理,而核心的数据操作仍由单线程串行执行。这种设计既规避了多线程并发控制的复杂性,又显著提升了高并发场景下的吞吐量,完美平衡了性能与原子性需求[^1][^3][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值