如果从Redis的核心网络模型来看,从 Redis 的 v1.0 到 v6.0 版本之前,Redis 的核心网络模型一直是一个典型的单 Reactor 模型:利用 epoll/select/kqueue 等多路复用技术,在单线程的事件循环中不断去处理事件(客户端请求),最后回写响应数据到客户端。这个单线程网络模型一直到 Redis v6.0 才改造成多线程模式。但是从Redis整个数据库服务器而言,这并不意味着整个 Redis 一直都只是单线程。
1. Redis 3.0前多线程
(1)Redis 3.0以前,在执行BGSAVE和BGREWRITEAOF这两条命令时,就会fork一个子进程进行RDB后台持久化和AOF的后台重写。
(2)此外,Redis还创建一个线程数为2的线程池:
bioInit(); // 初始化线程池;
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3); // 将任务加入线程池:
专门处理2类异步任务,分别是:
- REDIS_BIO_CLOSE_FILE:异步关闭文件
- REDIS_BIO_AOF_FSYNC:异步将缓冲区冲洗到磁盘文件中
2. Redis 4.0多线程
Redis 在 v4.0 版本的时候就已经引入了的多线程来做一些异步操作,此举主要针对的是那些非常耗时的命令,通过将这些命令的执行进行异步化,避免阻塞单线程的事件循环。
我们知道 Redis 的 DEL 命令是用来删除掉一个或多个 key 储存的值,它是一个阻塞的命令,大多数情况下你要删除的 key 里存的值不会特别多,最多也就几十上百个对象,所以可以很快执行完,但是如果你要删的是一个超大的键值对,里面有几百万个对象,那么这条命令可能会阻塞至少好几秒,又因为事件循环是单线程的,所以会阻塞后面的其他事件,导致吞吐量下降。
于是,在 Redis v4.0 之后增加了一些的非阻塞命令如 UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC。UNLINK 命令其实就是 DEL 的异步版本,它不会同步删除数据,而只是把 key 从 keyspace 中暂时移除掉,然后将任务添加到一个异步队列,最后由后台线程去删除,不过这里需要考虑一种情况是如果用 UNLINK 去删除一个很小的 key,用异步的方式去做反而开销更大,所以它会先计算一个开销的阀值,只有当这个值大于 64 才会使用异步的方式去删除 key,对于基本的数据类型如 List、Set、Hash 这些,阀值就是其中存储的对象数量。
Redis 在 v4.0 版本的时候就异步任务由两类增加到了3类,此时需要初始化线程数为3的线程池,需要处理的3类异步任务为:
- BIO_CLOSE_FILE:异步关闭文件
- BIO_AOF_FSYNC :异步将缓冲区冲洗到磁盘文件中
-
BIO_LAZY_FREE :异步删除键值对
线程池提供的外部接口函数为:
void bioCreateCloseJob(int fd); // 创建关闭文件的异步任务
void bioCreateFsyncJob(int fd); // 创建冲洗文件缓冲区的异步任务
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...); // 创建删除键值对的异步任务