线程模型
-
单线程的部分:接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端
-
后台线程:
-
关闭文件
-
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 的压力。