Redis 实例有哪些阻塞点?
Redis 实例在运行时,要和许多对象进行交互,这些不同的交互就会涉及不同的操作,下
面我们来看看和 Redis 实例交互的对象,以及交互时会发生的操作。
客户端:网络 IO,键值对增删改查操作,数据库操作;
磁盘:生成 RDB 快照,记录 AOF 日志,AOF 日志重写;
主从节点:主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB
文件;
切片集群实例:向其他实例传输哈希槽信息,数据迁移。
1. 和客户端交互时的阻塞点
网络 IO 有时候会比较慢,但是 Redis 使用了 IO 多路复用机制,避免了主线程一直处在等
待网络连接或请求到来的状态,所以,网络 IO 不是导致 Redis 阻塞的因素
键值对的增删改查操作是 Redis 和客户端交互的主要部分,也是 Redis 主线程执行的主要
任务。所以,复杂度高的增删改查操作肯定会阻塞 Redis。
Redis 中涉及集合的操作复杂度通常为 O(N)
例如集合元素全量查询操作 HGETALL、SMEMBERS,以及集合的聚合统计操作,例如求交、并和差集。这些操作可以作为 Redis 的第一个阻塞点:集合全量查询和聚合操作。
集合自身的删除操作同样也有潜在的阻塞风险。你可能会认为,删除操作很简
单,直接把数据删除就好了,为什么还会阻塞主线程呢?
删除操作的本质是要释放键值对占用的内存空间。
应用程序释放内存时,操作系统会将释放的这块内存插入一个空闲内存块的链表,以便后续进行管理和再分配。而大量释放就会就会增加开销,然后阻塞这个地方
当删除了一个bigkey(也就是包含有大量元素的的时候)会有这种问题
bigkey 删除操作就是 Redis 的第二个阻塞点。
由此引出的第三个阻塞点清空数据库。
2. 和磁盘交互时的阻塞点
磁盘 IO 一般都是比较费时费。
然后呢redis 就有了这个aof 和这个 rdb 持久化机制。这两个持久化机制里面呢,就会引出第四个阻塞点AOF 日志同步写(一般说的时always)
3. 主从节点交互时的阻塞点
在主从集群中,主库需要生成 RDB 文件,并传输给从库。主库在复制的过程中,创建和传
输 RDB 文件都是由子进程来完成的,不会阻塞主线程。但是,对于从库来说,它在接收了
RDB 文件后,需要使用 FLUSHDB 命令清空当前数据库,这就正好撞上了刚才我们分析的
第三个阻塞点
从库在清空当前数据库后,还需要把 RDB 文件加载到内存,这个过程的快慢和
RDB 文件的大小密切相关,RDB 文件越大,加载过程越慢,所以,加载 RDB 文件就成为
了 Redis 的第五个阻塞点。
4. 切片集群实例交互时的阻塞点
这个点我只了解了一点,不要有bigkey
总结以上的堵塞点
集合全量查询和聚合操作;
bigkey 删除;
清空数据库;
AOF 日志同步写;
从库加载 RDB 文件
为了避免阻塞式操作,Redis 提供了异步线程机制。所谓的异步线程机制,就是指,Redis 会启动一些子线程,然后把一些任务交给这些子线程,让它们在后台完成,而不再由主线程来执行这
些任务。使用异步线程机制执行操作,可以避免阻塞主线程。
哪些阻塞点可以异步执行?
能被异步子线程执行的都不是关键路径上的操作
关键路径上的操作简单来说客户端把请求发送给 Redis 后,等着 Redis
返回数据结果的操作。
对于 Redis 来说,读操作是典型的关键路径操作,因为客户端发送了读操作之后,就会等
待读取的数据返回,以便进行后续的数据处理。
Redis 的第一个阻塞点“集合全量查询和聚合操作”都涉及到了读操作,所以,它们是不能进行异步操作
删除操作并不需要给客户端返回具体的数据结果,所以不算是关
键路径操作。而我们刚才总结的第二个阻塞点“bigkey 删除”,和第三个阻塞点“清空数
据库”,都是对数据做删除,并不在关键路径上。因此,我们可以使用后台子线程来异步
执行删除操作。
第四个阻塞点aof也不是,他不需要给用户返回结果
所以他也是异步的
第五个 从库加载 RDB 文件”这个阻塞点。从库要想对客户端提供数据存取
服务,就必须把 RDB 文件加载完成。所以,这个操作也属于关键路径上的操作,我们必须
让从库的主线程来执行。
不能用异步解决的就是集合全量查询和聚合操作”和“从库加载 RDB 文
件”。
那么异步又是怎么实现的呢
Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别
由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。
主线程通过一个链表形式的任务队列和子线程进行交互。当收到键值对删除和清空数据库
的操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回
一个完成信息,表明删除已经完成。
但实际上,这个时候删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始
实际删除键值对,并释放相应的内存空间。因此,我们把这种异步删除也称为惰性删除
(lazy free)。此时,删除或清空操作不会阻塞主线程,这就避免了对主线程的性能影
响。
和惰性删除类似,当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封
装成一个任务,也放到任务队列中。后台子线程读取任务后,开始自行写入 AOF 日志,这
样主线程就不用一直等待 AOF 日志写完了