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. 连接过的WiFi改了密码之后再次连接不让输入新密码还是用旧密码一直显示连接失败

    设置---网络和Internet---WLAN----管理已知网络----忘记    根据这个步骤就能忘记密码,重新输入新密码了.

  2. Visual Studio 2010下ASPX页面的TreeView控件循环遍历

    如果维护一个老系统就总会遇到各种问题,而这次是TreeView的循环遍历.对于Visual Studio2010上aspx页面的TreeView控件,我感受到了什么叫集微软之大智慧.与二叉树型不一样. ...

  3. Golang中GBK和UTF8编码格式互转

    Golang中GBK和UTF8编码格式互转 需求 已知byte数组的编码格式转换 实现代码 package utils import ( "bytes" "golang. ...

  4. 密码学系列之:twofish对称密钥分组算法

    简介 之前的文章我们讲到blowfish算法因为每次加密的块比较小只有64bits,所以不建议使用blowfish加密超过4G的文件.同时因为加密块小还会导致生日攻击等.所以才有了blowfish的继 ...

  5. Python基础之:Python的数据结构

    目录 简介 列表 列表作为栈使用 列表作为队列使用 列表推导式 del 元组 集合 字典 循环 简介 不管是做科学计算还是编写应用程序,都需要使用到一些基本的数据结构,比如列表,元组,字典等. 本文将 ...

  6. 用VSCode终端实现重定向比较程序输出和正确输出

    在刷 OJ 题目或者进行编程考试或比赛时,经常需要对编写好的程序进行测试,即运行编写好的程序,输入样例输入或者自己编写的输入数据,查看程序输出结果和样例输出或者正确输出是否一致.这种方法有很多弊端,当 ...

  7. Linux-远程服务ssh

    1.远程管理服务介绍 (1)SSH是(Secure Shell Protocol)的简写,由IETF网络工作小组制定:在进行数据传输之前,SSH先对联机数据包通过加密技术进行机密处理,加密后在进行文件 ...

  8. FlowNet:simple / correlation 与 相关联操作

    Flow Net : simple / correlation 与 相关联操作 ​ 上一篇文章中(还没来得及写),已经简单的讲解了光流是什么以及光流是如何求得的.同时介绍了几个光流领域的经典传统算法. ...

  9. Octal Fractions java秒 C++

    Octal Fractions 题目抽象:   将八进制小数转换成十进制小树.小数的为数很大. 可以用java  中的BigDeciaml 秒掉.  time:297ms 1 import java. ...

  10. GitLab升级(yum安装版v11.11.8~12.0.12)

    参考官方升级建议(注意升级路线:Example upgrade paths) 升级前请自行备份(测试可忽略此步骤) 生成备份文件,在/var/opt/gitlab/backups/目录下生成备份文件 ...