【Redis】线程模型

75 阅读3分钟

线程模型

  • 单线程的部分:接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端

  • 后台线程:

    • 关闭文件

    • AOF 刷盘

    • 4.0版本新增: lazyfree 线程,异步释放 Redis 内存

      • unlink key / flushdb async / flushall async 等命令

任务队列:

  • BIO_CLOSE_FILE 关闭文件任务队列:调用 close(fd) ,将文件关闭;
  • BIO_AOF_FSYNC,AOF刷盘任务队列:AOF 日志配置 everysec,后台线程会调用 fsync(fd),AOF 文件刷盘
  • BIO_LAZY_FREE,lazy free 任务队列:后台线程会 free(obj) 释放对象、 free(dict) 删除数据库所有对象、 free(skiplist) 释放跳表对象;

单线程模式 6.0前

Redis 初始化过程:

  • epoll_create() 创建 epoll 对象、socket() 创建一个服务端 socket
  • bind() 绑定端口、listen() 监听该 socket;
  • epoll_ctl() 将 listen socket 加入到 epoll,同时注册「连接事件」处理函数。

主事件循环函数过程

  • 调用处理发送队列函数,送队列里如果有发送任务,通过 write 函数将客户端发送缓存区里的数据发送出去,如果这一轮数据没有发送完,就会注册写事件处理函数,等待 epoll_wait 发现可写后再处理 。

  • 调用 epoll_wait 函数等待事件的到来:

    • 连接事件:调用连接事件处理函数:调用 accpet 获取已连接的 socket -> 调用 epoll_ctl 将已连接的 socket 加入到 epoll -> 注册「读事件」处理函数;
    • 读事件:调用读事件处理函数:调用 read 获取客户端发送的数据 -> 解析命令 -> 处理命令 -> 将客户端对象添加到发送队列 -> 将执行结果写到发送缓存区等待发送;
    • 写事件:调用写事件处理函数:通过 write 函数将客户端发送缓存区里的数据发送出去,如果这一轮数据没有发送完,就会继续注册写事件处理函数,等待 epoll_wait 发现可写后再处理 。

单线程为什么还这么快

  • 基于内存:读写速度快
  • 高效的数据结构:基于HashMap、SkipList,查找效率高
  • IO密集型:瓶颈可能是机器网络带宽,而并非 CPU
  • 单线程避免多线程之间的竞争,节省多线程上下文切换的开销,不会导致死锁。
  • I/O 多路复用:epoll非阻塞IO,处理大量的客户端 Socket 请求。

6.0 多线程

Why?

随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 I/O 的处理上。

How?

Redis 6.0 对于网络 I/O 采用多线程来处理,性能提升至少一倍以上。

  • 默认只有发送响应数据使用多线程,开启多线程处理客户端读请求:

    • io-threads-do-reads yes
  • IO 多线程个数的配置:

    • io-threads 4
  • 6.0 版本后,启动时,默认会额外创建 6 个线程:

    • Redis-server :主线程,主要负责执行命令;
    • bio_close_file、bio_aof_fsync、bio_lazy_free:三个后台线程,分别异步处理关闭文件任务、AOF刷盘任务、释放内存任务;
    • io_thd_1、io_thd_2、io_thd_3:三个 I/O 线程,io-threads 默认是 4 ,所以会启动 3(4-1)个 I/O 多线程,用来分担 Redis 网络 I/O 的压力。