客户端阻塞点
1. 复杂度高的增删改查操作
例如集合元素全量查询操作 HGETALL、SMEMBERS,以及集合的聚合统计操作,例如求交、并和差集
2. 删除大量键值对
删除操作是要释放键值对占用的内存空间。为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序,所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞。
3. 清空数据库
清空数据库也涉及到删除和释放所有的键值对。
磁盘交互阻塞点
Redis采用子进程的方式生成 RDB 快照文件,以及执行 AOF 日志重写操作。这样一来,这两个操作由子进程负责执行,慢速的磁盘 IO 就不会阻塞主线程了。但是如果AOF是同步写磁盘的设置的话,就会阻塞主线程操作。
主从节点交互阻塞点
从库来说接收了RDB 文件后,需要使用 FLUSHDB 命令清空当前数据库,就会涉及到删除和释放所有的键值对。
切片集群实例交互阻塞点
如果使用了 Redis Cluster 方案,而且同时正好迁移的是 bigkey 的话,就会造成主线程的阻塞,因为 Redis Cluster 使用了同步迁移。这个在之后的文章会详细介绍。
对阻塞点进行异步处理
1. 读操作不能异步,客户端会等待结果。
2. 删除操作可用子进程异步处理
对于 “AOF 日志同步写”来说,为了保证数据可靠性,Redis 实例需要保证AOF 日志中的操作记录已经落盘,这个操作虽然需要实例等待,但它并不会返回具体的数据结果给实例。所以,也可以启动一个子线程来执行 AOF 日志的同步写,而不用让主线程等待 AOF 日志的写完成。
3. 从库加载 RDB 文件,。从库要想对客户端提供数据存取服务,就必须把 RDB 文件加载完成。不能异步处理。
异步的子线程机制
Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。
主线程通过一个链表形式的任务队列和子线程进行交互。当收到可以进行异步操作的命令时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明操作已经完成。
异步的键值对删除和数据库清空操作是 Redis 4.0 后提供的功能,Redis 也提供了新的命令来执行这两个操作。
- 键值对删除:当你的集合类型中有大量元素(例如有百万级别或千万级别元素)需要删除时,我建议你使用 UNLINK 命令。
- 清空数据库:可以在 FLUSHDB 和 FLUSHALL 命令后加上 ASYNC 选项,这样就可以让后台子线程异步地清空数据库,如下所示: