Redis实例在运行的时候,要和许多对象进行交互,这些不同的交互对象会有不同的操作。下面我们来看看,这些不同的交互对象以及相应的主要操作有哪些。

  • 客户端:键值对的增删改查操作。

  • 磁盘:生成RDB快照、记录AOF日志、AOF日志重写。

  • 主从节点:主库生成、传输RDB文件,从库接受RDB文件、清空数据库、加载RDB文件。

下面我们来分析一下哪些操作会引起主线程阻塞。

1.和客户端交互时的阻塞点。

键值对的增删改查操作是Redis和客户端交互的主要部分,也是Redis主线程执行的主要任务。所以复杂度高的增删改查操作肯定会阻塞Redis。在Redis中复杂度高的操作都是对集合的操作,通常时间复杂度为O(N)。所以在使用的过程中需要注意起来,例如对集合的全量查询操作HGETALL以及对集合的聚合统计操作,例如求交、并和差集。所以集合全量查询和聚合操作是Redis的一个阻塞点。

除此之外,对集合删除操作也会有潜在的阻塞风险。因为删除操作的本质是要释放键值对占用的内存空间。释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序,所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞。所以删除bigkey也是Redis的一个阻塞点。

因为清空数据库涉及到删除和释放所有的键值对。所以清空数据库也是Redis的一个阻塞点。

2.和磁盘交互时的阻塞点。

Redis是采用后台子进程的方式生成RDB快照文件,以及执行AOF日志重写操作。所以这两个操作由子进程负责执行,因此不会成为Redis的阻塞点。

但是Redis直接记录AOF日志时,会根据不同的写回策略对数据做落盘保存。

如果有大量的写操作需要记录在AOF日志中,并写回策略设置成同步写回的话,就会阻塞主线程了。所以AOF日志同步写也是Redis的一个阻塞点。

3.主从节点交互时的阻塞点

在主从集群中,主库需要生成RDB文件,并传输给从库。主库在复制的过程中,创建和传输 RDB 文件都是由后台子进程来完成的,不会阻塞主线程的执行。但是,对于从库来说,它在接收了RDB 文件后,需要使用 FLUSHDB 命令清空当前数据库,然后需要把 RDB 文件加载到内存,这个过程的快慢和 RDB 文件的大小密切相关,RDB 文件越大,加载过程越慢,所以,加载 RDB 文件就成为了 Redis 又一个阻塞点。

所以总结一下,Redis中阻塞主线程的操作主要有以下五种。

  • 集合全量查询和聚合操作。

  • bigkey 删除。

  • 清空数据库。

  • AOF 日志同步写。

  • 从库加载 RDB 文件。

那有什么方式可以避免阻塞式操作呢?Redis提供了异步线程机制。为了避免一些阻塞主线程的操作,Redis会启动一些子线程,然后把一些任务交给这些子线程去在后台完成,而不再由主线程来执行这些任务。下面我们来看一下哪些阻塞主线程的操作可以采用异步执行呢?

如果一个操作能被异步执行,就意味着,它并不是Redis主线程的关键路径上的操作。所谓的关键路径是指客户端把请求发送给Redis后等着Redis返回数据结果的操作。如下图所示。

主线程接收到操作1后,因为操作1并不用给客户端返回具体的数据,所以,主线程可以把它交给后台子线程来完成,同时只要给客户端返回一个“OK”结果就行。在子线程执行操作1的时候,客户端又向Redis实例发送了操作2,而此时,客户端是需要使用操作2返回的数据结果的,如果操作2不返回结果,那么,客户端将一直处于等待状态。在这个例子中,操作 1 就不算关键路径上的操作,因为它不用给客户端返回具体数据,所以可以由后台子线程异步执行。而操作 2 需要把结果返回给客户端,它就是关键路径上的操作,所以主线程必须立即把这个操作执行完。

对于 Redis 来说,读操作是典型的关键路径操作,因为客户端发送了读操作之后,就会等待读取的数据返回。而Redis 的集合全量查询和聚合操作都涉及到了读操作,所以,它们是不能进行异步操作了。

我们再来看看删除操作。删除操作并不需要给客户端返回具体的数据结果,所以不是关键路径操作。所以“bigkey 删除”和“清空数据库”,都是对数据做删除,所以我们可以用后台子线程来异步执行删除操作。

对于“AOF 日志同步写”来说,为了保证数据可靠性,Redis 实例需要保证 AOF 日志中的操作记录已经落盘,这个操作虽然需要实例等待,但它并不会返回具体的数据结果给实例。所以,我们也可以启动一个子线程来执行 AOF 日志的同步写,而不用让主线程等待 AOF 日志的写完成。

最后,我们再来看下“从库加载 RDB 文件”这个操作。从库要想对客户端提供数据存取服务,就必须把 RDB 文件加载完成。所以,这个操作也属于关键路径上的操作,我们必须让从库的主线程来执行。

对于 Redis 的五大阻塞点来说,除了“集合全量查询和聚合操作”和“从库加载 RDB 文件”,其他三个阻塞点涉及的操作都不在关键路径上,所以,我们可以使用 Redis 的异步子线程机制来实现 bigkey 删除,清空数据库,以及 AOF 日志同步写。

4.异步的子线程机制

Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。主线程通过一个链表形式的任务队列和子线程进行交互。当收到键值对删除和清空数据库的操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明删除已经完成。但实际上,这个时候删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始实际删除键值对,并释放相应的内存空间。因此,我们把这种异步删除也称为惰性删除(lazy free)。此时,删除或清空操作不会阻塞主线程,这就避免了对主线程的性能影响。和惰性删除类似,当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到任务队列中。后台子线程读取任务后,开始自行写入 AOF 日志,这样主线程就不用一直等待 AOF 日志写完了。

当你的集合中有大量的元素要删除时,我建议你使用异步删除命令 UNLINK 。而对于清空数据库来说,可以在 FLUSHDB 和 FLUSHALL 命令后加上 ASYNC 选项,这样就可以让后台子线程异步地清空数据库。

FLUSHDB ASYNC
FLUSHALL AYSNC

更多硬核知识,请关注公众号"程序员学长"。

Redis内部阻塞式操作有哪些?的更多相关文章

  1. 关于redis内部的数据结构

    最大感受,无论从设计还是源码,Redis都尽量做到简单,其中运用到的原理也通俗易懂.特别是源码,简洁易读,真正做到clean and clear, 这篇文章以unstable分支的源码为基准,先从大体 ...

  2. redis内部数据结构深入浅出

    最大感受,无论从设计还是源码,Redis都尽量做到简单,其中运用到的原理也通俗易懂.特别是源码,简洁易读,真正做到clean and clear, 这篇文章以unstable分支的源码为基准,先从大体 ...

  3. jQuery链式操作[转]

    用过jQuery的朋友都知道他强大的链式操作,方便,简洁,易于理解,如下 $("has_children").click(function(){ $(this).addClass( ...

  4. 并发式IO的解决方案:多路非阻塞式IO、多路复用、异步IO

    在Linux应用编程中的并发式IO的三种解决方案是: (1) 多路非阻塞式IO (2) 多路复用 (3) 异步IO 以下代码将以操作鼠标和键盘为实例来演示. 1. 多路非阻塞式IO 多路非阻塞式IO访 ...

  5. Java IO(3)非阻塞式输入输出(NIO)

    在上篇<Java IO(2)阻塞式输入输出(BIO)>的末尾谈到了什么是阻塞式输入输出,通过Socket编程对其有了大致了解.现在再重新回顾梳理一下,对于只有一个“客户端”和一个“服务器端 ...

  6. [译]async/await中使用阻塞式代码导致死锁 百万数据排序:优化的选择排序(堆排序)

    [译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Cleary的 ...

  7. PHP设计模式:类自动载入、PSR-0规范、链式操作、11种面向对象设计模式实现和使用、OOP的基本原则和自动加载配置

    一.类自动载入 SPL函数 (standard php librarys) 类自动载入,尽管 __autoload() 函数也能自动加载类和接口,但更建议使用 spl_autoload_registe ...

  8. [译]async/await中使用阻塞式代码导致死锁

    原文:[译]async/await中使用阻塞式代码导致死锁 这篇博文主要是讲解在async/await中使用阻塞式代码导致死锁的问题,以及如何避免出现这种死锁.内容主要是从作者Stephen Clea ...

  9. 探索Redis设计与实现2:Redis内部数据结构详解——dict

    本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...

随机推荐

  1. 【Android编程】android平台的MITM瑞士军刀_cSploit源码解析及中间人攻击复现

    /文章作者:Kali_MG1937 作者博客ID:ALDYS4 QQ:3496925334 未经允许,禁止转载/ 何为MITM欺骗,顾名思义,中间人攻击的含义即为在局域网中充当数据包交换中间人的角色 ...

  2. SpringBoot 自动配置原理,翻源码看一下

    ​ 初始化一个Springboot项目,在主启动类会有这么一个注解:@SpringBootApplication,自动装配的秘密全在主启动类这个注解里面了 点进去一层会发现有三个子注解组成,分别是 @ ...

  3. Webflux请求处理流程

    spring mvc处理流程 在了解SpringMvc的请求流程源码之后,理解WebFlux就容易的多,毕竟WebFlux处理流程是模仿Servlet另起炉灶的. 下面是spring mvc的请求处理 ...

  4. 【NLP学习其三】在学习什么是嵌入之前,你应该了解什么是词语表征

    在了解什么是嵌入(embeddings)之前,我们需要先搞清楚一个词语在NLP中是如何被表示的 注:本次不涉及任何具体算法,只是单纯对概念的理解 词汇表征 One-Hot 词汇的表示方法有很多,最有名 ...

  5. vue中$nextTick详细讲解保证你一看就明白

    1.功能描述 今天我们要实现这个一个小功能: 页面渲染完成后展示一个div元素: 当点击这个div元素后: div元素消失: 出现一个input元素:并且input元素聚焦 想必大家我觉得简单,我们一 ...

  6. Pycharm破解版_安装从失败到成功

    前言: 入门学习的时候一直用的是社区版的,现在想换个专业版的玩玩. 本文使用的环境说明: win10系统 安装过pycharm社区版,已卸载 已安装python 3.8.5 (建议看完整篇文章后再自行 ...

  7. 聊一聊我在 B 站自学 Java 的经历吧

    Hey guys ,这里是 cxuan,欢迎你收看我最新一期的文章. 这是一篇鸽了很久的文章...... 事情还要从上回说起...... 我爱 B 站! 这篇文章我汇总了 B 站上计算机基础(操作系统 ...

  8. Redis 过期时间解析

    文章参考:<Redis 设计与实现>黄建宏 设置过期时间 通过 EXPIRE 或者 PEXPIRE 命令,客户端可以以秒或毫秒精度为数据库中的某个键设置生存时间 TTL (Time To ...

  9. HTML骨架解析

    在VSCode中按下"!"感叹号便可得到如下的html骨架,没有这些骨架浏览器便不能正常渲染,但是这些骨架分别代表什么含义呢?不急,我们一行一行的看. 1 <!DOCTYPE ...

  10. java实现遍历文件目录,根据文件最后的修改时间排序,并将文件全路径存入List集合

    package com.ultra.aliyun.control.main; import java.io.File; import java.util.ArrayList; import java. ...