redis开启多线程IO

redis版本

redis 6.0+

关键点

  1. 配置修改。
  2. 有足够的IO并发压力。

一、配置修改

设置io-thread的值为想要的io线程数,设置io-threads-do-reads yes打开读事件处理的多线程。


redis配置修改

二、造成足够的压力

2.1 开启并发写IO的条件参数
写IO的堆栈图

通过写IO的堆栈图依次寻找写IO的条件判断函数,找到handleClientsWithPendingWritesUsingThreads函数。

int handleClientsWithPendingWritesUsingThreads(void) {
    int processed = listLength(server.clients_pending_write);
    if (processed == 0) return 0; /* Return ASAP if there are no clients. */

    /* If I/O threads are disabled or we have few clients to serve, don't
     * use I/O threads, but thejboring synchronous code. */
    if (server.io_threads_num == 1 || stopThreadedIOIfNeeded()) {
        return handleClientsWithPendingWrites();
    }

    /* Start threads if needed. */
    if (!io_threads_active) startThreadedIO()
    // 多线程处理写数据
}

int stopThreadedIOIfNeeded(void) {
    int pending = listLength(server.clients_pending_write);

    /* Return ASAP if IO threads are disabled (single threaded mode). */
    if (server.io_threads_num == 1) return 1;

    if (pending < (server.io_threads_num*2)) {
        if (io_threads_active) stopThreadedIO();
        return 1;
    } else {
        return 0;
    }
}

void stopThreadedIO(void) {
    /* We may have still clients with pending reads when this function
     * is called: handle them before stopping the threads. */
    handleClientsWithPendingReadsUsingThreads();
    for (int j = 1; j < server.io_threads_num; j++)
        pthread_mutex_lock(&io_threads_mutex[j]);
    io_threads_active = 0;
}

根据上面的源码,我们可以很清晰的看到,如果只开启了单线程或者要处理的写事件penging < server.io_threads_num * 2,io_threads_num是前面配置中设置的值,则不开启多线程处理模式,并且要关闭多线程处理。
PS:无论是否开启多线程IO,redis都是采用异步的方式发送数据,在命令处理过程中,将数据缓存在缓冲区,然后统一在handleClientsWithPendingWritesUsingThreads该函数中将所有client的缓存数据发送出去。

2.2 开启并发读事件的条件

开启并发读需要在读事件处理时,不直接处理而是将所有的读事件缓存在server.clients_pending_read中。

void readQueryFromClient(connection *conn) {
    client *c = connGetPrivateData(conn);
    int nread, readlen;
    size_t qblen;

    /* Check if we want to read from the client later when exiting from
     * the event loop. This is the case if threaded I/O is enabled. */
    if (postponeClientRead(c)) return;
}

int postponeClientRead(client *c) {
    if (io_threads_active && // 在上面写事件处理过程中 如果忙则会被设置为开启
        server.io_threads_do_reads && // 上面配置修改对应的字段的值
        !ProcessingEventsWhileBlocked && // 默认值是0
        !(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ))) // 避免重复重发
    {
        c->flags |= CLIENT_PENDING_READ;
        listAddNodeHead(server.clients_pending_read,c); // 将client加入待处理队列
        return 1;
    } else {
        return 0;
    }
}

由上面的源码可知,如果写事件繁忙, 则设置io_threads_active = 1, 而且配置也配置了开启多线程读事件, 则在处理读事件时, 将client添加到server.clients_pending_read, 延后处理.
再来看多线程并行处理读事件的函数handleClientsWithPendingReadsUsingThreads.

int handleClientsWithPendingReadsUsingThreads(void) {
    if (!io_threads_active || !server.io_threads_do_reads) return 0;
    int processed = listLength(server.clients_pending_read);
    if (processed == 0) return 0;
    // 并行处理IO读事件

这里并行处理IO读事件的前提条件和上面将读事件延后处理的条件是一样的.

2.3 分析总结
  1. 首先必须修改配置, 开启多线程IO和设置开启多线程处理读事件.
  2. 写事件必须满足并发写的数量concurrent > 2 * io_threads_num, 才会开启多线程处理模式.
  3. 在并发写不满足上述条件的情况下, 写处理时会主动关闭多线程处理模式.
  4. 在写事件处理时, 开启了多线程模式的前提下, 才会开启多线程处理读事件.
  5. 多线程处理模式具有一定的弹性, 只有在读写繁忙时才会触发, 在不繁忙时主动关闭.
2.4 修改源码开启多线程处理模式

上面提到, 要开启多线程处理模式需要并发压力大于 2 * io_threads_num时才能触发, 对于断点调试多线程处理模式的源码是很不方便. 因此考虑调整源码, 强行开启多线程处理模式.
修改stopThreadedIOIfNeeded函数中的判断条件. 修改如下:

// 每次判断是否要关闭多线程模式时都选择否
int stopThreadedIOIfNeeded(void) {
    return 0;
    int pending = listLength(server.clients_pending_write);

    /* Return ASAP if IO threads are disabled (single threaded mode). */
    if (server.io_threads_num == 1) return 1;

    if (pending <= 0)) {
        if (io_threads_active) stopThreadedIO();
        return 1;
    } else {
        return 0;
    }
}

附上多线程下处理setCommond的堆栈图, 因为只有一个连接, 因此被分给了主线程(也是第一个IO线程)进行处理, 没有分配给其它IO线程.


多线程模式下处理读事件堆栈

多线程模式下Set命令的堆栈

总结

  1. 问: 为什么在单线程的情况下也选择在处理完所有事件后统一写数据?
    答: 能提高整体的速度吗? 不能! 但是每个事件的耗时会相对平均, 如果处理一个事件就发送一个, 那么在前面的事件延迟总是更低, 但是后面的事件的延迟总是更高.
  2. 问: 为什么是在写事件处理时检测繁忙状态, 然后选择性的开启多线程处理模式?
    答: 还有一个可选方案是在apiPollEvent后, 获得所有触发的读事件, 然后根据读事件的数量选择是否开启多线程处理模式.
  3. redis根据繁忙状态弹性的开启多线程处理模式, 这个做法非常值得借鉴, 并且开启多线程处理模式是在主线程触发, 避免了并发的可能性.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351