Redis的网络IO和键值对读写都是由主线程完成的。
Redis实例都有哪些阻塞点
- 客户端:网络IO,键值对增删改查,数据库操作
- 磁盘:生成RDB快照,记录AOF日志,AOF日志重写
- 主从节点:主库生成、传输RDB文件、从库接收RDB文件,清空数据库,加载RDB文件
- 切片集群实例:向其他实例传输哈希槽信息,数据迁移
和客户端交互的阻塞点
网络IO使用了IO多路复用机制,避免了主线程一直等待网络请求或请求到来的状态,所以网络IO不是阻塞点
键值对的增删改查是对外提供的主要功能,操作复杂度为O(N)的会阻塞Redis
集合的全量查询和聚合操作
Redis中涉及集合的操作复杂度通常为O(N),比如HGETALL、SMEMBERS,以及集合的聚合统计操作,求交,求并,求差集。
bigkey删除操作
- 删除的本质是要释放键值对占用的内存空间,释放掉的内存块需要插入一个空闲的内存块的链表,以便后续的管理和再分配,这个过程需要耗费一定的时间,阻塞当前释放内存的应用程序
- 释放大量内存的时候,必然会空闲内存块的链表的操作时间变长,造成Redis阻塞
不同元素、不同大小删除key时结果如下:
集合元素越大,删除时间越长,必定会阻塞Redis
清空数据库
频繁删除键值对是潜在的阻塞点,清空数据库(例如 FLUSHDB 和 FLUSHALL 操作)必然也是一个潜在的阻塞风险
磁盘交互的阻塞点
磁盘IO一般都是比较费时费力的。
Redis采用子进程的方式生成RDB快照、执行AOF日志重写,那么就不会去阻塞主线程了
Redis 记录AOF日志时,根据不同的写回策略对数据做落盘保存,若是同步写回的话,会阻塞主线程。
主从节点交互时的阻塞点
主从集群中,主库生成RDB文件,传输给从库,主库在复制过程中,创建和传输RDB文件都是子进程来完成的,不会阻塞主线程
- 从库接收RDB文件后,需要FLUSHDB清空数据库,就会产生阻塞
- 加载RDB的过程中,RDB文件越大,加载过程会越慢,这也是Redis的一个阻塞点
切片集群实例交互时的阻塞点
Redis部署切片集群时,每个Redis实例分配的哈希槽信息需要在不同的实例间进行传递,需要负载均衡或有实例增加的时候,数据会在不同的实例间进行渐进式的迁移,一般不会阻塞主线程。
Redis Cluster方案,同时正好迁移的是bigkey的话,因为采用的是同步迁移,所以就会造成主线程的阻塞
可以异步执行的阻塞点
如果一个操作能被异步执行,说明它不是Redis主线程的关键路径。
- 集合的全量查询和聚合操作,读操作是典型的关键路径,不能异步
- bigkey删除操作、清空数据库 非关键路径,可以异步
- AOF日志同步写,也不需要返回给客户端结果,也可以异步执行
- 从库加载RDB,属于关键路径,不能异步
异步的子线程
Redis会通过主线程创建3个子线程,分别负责AOF日志写操作、键值对删除、文件关闭异步执行。
主线程通过一个链表形式的任务队列和子线程进行交互,收到键值对删除和清空数据库操作时,主线程会把这个操作封装成一个任务,放入任务队列中,然后给客户端返回完成信息。
后台子进程从任务队列里读取任务,开始删除键值对,释放空间的过程就是异步删除,也叫惰性删除
当把AOF日期配成everysec后,主线程会把AOF写日志操作封装成一个任务放入任务队列,后台子线程读取,开始自己写入AOF日志。
Redis 4.0 提供了新的命令来删除操作
- 键值对删除:当集合类型有大量元素需要删除时,用UNLINK命令
- 清空数据库:可以在FLUSHDB和FLUSHALL命令后加上ASYNC选项,可以让后台异步子线程清空数据库
总结
- 集合的全量查询和聚合操作可以使用SCAN命令,分批读取数据,在客户端做聚合计算
- 从库加载RDB文件中,把主库的数据量控制在2-4GB,保证RDB能以较快速度加载