redis 为什么会阻塞

目录

前言

客户端交换时的阻塞

redis 磁盘交换的阻塞

主从节点交互的阻塞

切片集群交互时的阻塞

异步执行的演变

redis 异步执行如何实现的


前言

大家对redis 比较熟悉吧,只要做项目都会用到redis,提高系统的吞吐。小米商城抢购高峰18k的qps,redis 在其中扮演着非常重要的角色。有时我们操作不当,redis 阻塞了,影响了整个业务。我记得2018年的时候,顺丰就出现了一个事故,有同学在线上对redis的一个操作,直接阻塞了,影响公司的整个业务,后面这位同学被辞退了。如果他对redis比较了解的话,也不会出现这样的事故。

redis 为什么会阻塞,与它的本身设计有一定关系。大家都知道redis 是单线程的,这个并完全正确,只是说我们对数据的读写是在主线程中完成的。但RDB,aof 重写,删除都是在子线程完成的。我们说的阻塞就是阻塞的主线程,redis 为什么会这么设计,抽时间我会详细讲解。这篇文章我主要从客户端,磁盘,主从节点,切片集群实例 多方面取阐述这个问题。

客户端交换时的阻塞

客户端的功能,与redis 服务器建立连接就有对网络IO的影响,对数据库的增删改查操作。redis 采用的是多路复用机制,避免了主线程一直处在等待网络连接或请求到来的状态,所以,网络 IO 不是导致 Redis 阻塞的因素。剩下的也就是增删改查对redis 的影响,这部分功能也是redis 主要的任务,复杂度高的肯定会阻塞redis。

那么怎么判断操作复杂度高不高?就是看复杂度的O(N),这个复杂度也可以看作是空间复杂度。N越大复杂度越高,越容易阻塞。我们可以对照redis 官网的命令,进行查看。一般集合的操作都是O(N) 的复杂度。比如HGETALL,SMEMBERS,以及集合的聚合统计操作,交,并,差集。这些操作特殊是集合的全量查询和聚合操作。

我们看了查询,删除会不会造成redis 阻塞了。当然会了,删除操作的本质是释放键值对占用的内存空间。它不仅释放内存空间,在Redis 释放内存时,操作系统会把释放掉的内存块插入到一个空闲内存块链表,以便后续进行管理和分配。这个过程需要时间并且会阻塞当前释放内存的应用程序。元素数量越大,这个操作消耗的时间越多,越容易阻塞,这就是我们所说的bigkey 删除。

还有就是清空数据库的操作例如( FLUSHDB 和 FLUSHALL 操作)必然也是一个潜在的阻塞风险,因为它涉及到删除和释放所有的键值对。

redis 磁盘交换的阻塞

磁盘的操作一直是系统的瓶颈,一次读盘需要经过寻道,旋转,传输 这么复杂的操作。很多数据库都用了各种技术来避免经常操作磁盘,比如mysql 用了WAL技术,关于这方面技术描述,可以阅读我的普通索引和唯一索引详解。redis 最大的卖点还是还性能,它保证的是操作的高效性,就没有用这么复杂的技术。redis 与磁盘操作的功能都是放在子线程操作,这样就避免了主线程的阻塞。redis 生成的 RDB 快照,aof 日志重写。但是有一个文件比较特殊,aof 日志,会根据不同写回策略做落盘保存。一个同步写磁盘的操作的耗时大约是 1~2ms,如果有大量的写操作需要记录在 AOF 日志中,并同步写回的话,就会阻塞主线程了。大家可以看到redis 磁盘交换的阻塞主要发生在aof 日志同步写。

主从节点交互的阻塞

一般的架构都是一主多从,主节点写,从节点读。主从同步的大概过程是,主节点身材RDB 快照,这个操作上面已经讲过,是通过子线程生成的,不会阻塞。对于从节点来说就是两个步骤,一个是FLUSHDB 清空当前数据库,这个肯定会阻塞,从节点的redis 会回放RDB 的数据,这个操作会阻塞从节点。加载RDB 文件也会阻塞,最终影响的从节点的读。

切片集群交互时的阻塞

使用 Redis Cluster 作为集群方案,当我们增加实例或删除实例时,数据会在不同实例进行迁移。一般会是渐进式的迁移,如果遇到bigkey 会阻塞节点

异步执行的演变

综上所述:我们讲到了几个阻塞点:集合全量查询和聚合操作、bigkey 删除、FLUSHDB 和 FLUSHALL 、AOF 日志同步写,从库加载RDB 文件。这些都是Redis 的性能的瓶颈,这些也一直困扰着redis 的开发人员。随着版本的递进,发生了一些变化。有些已经放在子线程去执行了,就意味着,它并不是 Redis 主线程的关键路径上的操作。

那么什么是主线程的关键路径上的操作:这就是说,客户端把请求发送给 Redis 后,等着 Redis 返回数据结果的操作,比如获取数据,进行接下来的业务。

按照这个定义来说的话:集合全量查询和聚合操作依旧是关键路径上的操作,依旧会阻塞,这个是无法避免的。

bigkey 删除、FLUSHDB 和 FLUSHALL 并不需要给客户端返回具体数据结果,不算关键路径上的操作。它们算不算阻塞点呢,这个其实挺复杂的。这个叫做惰性删除(lazy free),这个功能 Redis 4.0 以后才有的功能,并不是所有的key 都能异步删除。

关于异步删除我需要补充几点,希望大家做个理性的判断:

  1. lazy-free是4.0新增的功能,但是默认是关闭的,需要手动开启。

  1. 手动开启lazy-free时,有4个选项可以控制,分别对应不同场景下,要不要开启异步释放内存机制:

    a) lazyfree-lazy-expire:key在过期删除时尝试异步释放内存

    b) lazyfree-lazy-eviction:内存达到maxmemory并设置了淘汰策略时尝试异步释放内存

    c) lazyfree-lazy-server-del:执行RENAME/MOVE等命令或需要覆盖一个key时,删除旧key尝试异步释放内存

    d) replica-lazy-flush:主从全量同步,从库清空数据库时异步释放内存

  2. 即使开启了lazy-free,如果直接使用DEL命令还是会同步删除key,只有使用UNLINK命令才会可能异步删除key 而 FLUSHDB ASYNC、FLUSHALL AYSNC 才会异步清空库

  3. 这也是最关键的一点,上面提到开启lazy-free的场景,除了replica-lazy-flush之外,其他情况都只是可能去异步释放key的内存,并不是每次必定异步释放内存的。 开启lazy-free后,Redis在释放一个key的内存时,首先会评估代价,如果释放内存的代价很小,那么就直接在主线程中操作了,没必要放到异步线程中执行(不同线程传递数据也会有性能消耗)。 什么情况才会真正异步释放内存?这和key的类型、编码方式、元素数量都有关系(详细可参考源码中的lazyfreeGetFreeEffort函数):

    a) 当Hash/Set底层采用哈希表存储(非ziplist/int编码存储)时,并且元素数量超过64个

    b) 当ZSet底层采用跳表存储(非ziplist编码存储)时,并且元素数量超过64个

    c) 当List链表节点数量超过64个(注意,不是元素数量,而是链表节点的数量,List的实现是在每个节点包含了若干个元素的数据,这些元素采用ziplist存储) 只有以上这些情况,在删除key释放内存时,才会真正放到异步线程中执行,其他情况一律还是在主线程操作。 也就是说String(不管内存占用多大)、List(少量元素)、Set(int编码存储)、Hash/ZSet(ziplist编码存储)这些情况下的key在释放内存时,依旧在主线程中操作。 可见,即使开启了lazy-free,String类型的bigkey,在删除时依旧有阻塞主线程的风险。所以,即便Redis提供了lazy-free,我建议还是尽量不要在Redis中存储bigkey。

个人理解Redis在设计评估释放内存的代价时,不是看key的内存占用有多少,而是关注释放内存时的工作量有多大。从上面分析基本能看出,如果需要释放的内存是连续的,Redis作者认为释放内存的代价比较低,就放在主线程做。如果释放的内存不连续(大量指针类型的数据),这个代价就比较高,所以才会放在异步线程中去执行。

所以我虽然在以后的版本删除有可能是异步删除,还是不要存储bigkey,对bigkey 进行删除。

如果真的要对bigkey 删除呢,我给你个小建议:先使用集合类型提供的 SCAN 命令读取数据,然后再进行删除。因为用 SCAN 命令可以每次只读取一部分数据并进行删除,这样可以避免一次性删除大量 key 给主线程带来的阻塞。

例如,对于 Hash 类型的 bigkey 删除,你可以使用 HSCAN 命令,每次从 Hash 集合中获取一部分键值对(例如 200 个),再使用 HDEL 删除这些键值对,这样就可以把删除压力分摊到多次操作中,那么,每次删除操作的耗时就不会太长,也就不会阻塞主线程了。

AOF 日志呢,如果 AOF 日志配置成 everysec 选项后,也不会去阻塞,异步执行。

从库加载RDB 文件 呢,这个在从库上需要在主线程执行,这个是不能异步的。为了避免阻塞,在这个地方给大家一个建议:从库加载 RDB 文件:把主库的数据量大小控制在 2~4GB 左右,以保证 RDB 文件能以较快的速度加载。

redis 异步执行如何实现的

有些操作需要异步执行,redis 主线程通过一个链表形式的任务队列和子线程进行交互,等到后台子线程从任务队列中读取任务进行操作。

好了就讲到这些了,其他的希望大家补充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/715913.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

KubeSphere平台安装系列之三【Linux多节点部署KubeSphere】(3/3)

**《KubeSphere平台安装系列》** 【Kubernetes上安装KubeSphere(亲测–实操完整版)】(1/3) 【Linux单节点部署KubeSphere】(2/3) 【Linux多节点部署KubeSphere】(3/3) **《KubeS…

一句话讲清楚数据库中事务的隔离级别(通俗易懂版)

为什么我只说通俗易懂版不说严谨版? 因为严谨版遍地都是, 但是他们却有一个缺点就是让人看得云里雾里, 所以这就是我写通俗易懂版的初衷! 但是既然是通俗易懂版就必然有缺陷, 只为了各位在开发过程中头脑更加清晰, 如有错误还望兄弟们不吝赐教! 在MySQL数据库中,事务一共有4…

C语言之strcmp函数,strlen函数

strcmp函数是比较两个字符串ASCII大小的函数。 比较方式是自左向右比较&#xff0c;直到出现不同字符或者\0为止 语法格式 strcmp(字符串1,字符串2&#xff09; 如果两个字符串相同&#xff0c;会返回数值0 如果字符串1>字符串2,会返回一个正数 如果字符串1<字符串2…

新一代电话机器人开源PHP源代码

使用easyswoole 框架开发的 新一代电话机器人开源PHP源码 项目地址&#xff1a;https://gitee.com/ddrjcode/robotphp 代理商页面演示地址 http://119.23.229.15:8080 用户名&#xff1a;c0508 密码&#xff1a;123456 包含 AI外呼管理&#xff0c;话术管理&#xff0c;CR…

每日一题 — 复写零

1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 首先找到最后一个复写的数&#xff1a; 双指针算法&#xff1a; 1、先判断 cur 位置上的值 2、然后决定 dest 移动一步还是两步 3、然后判断 dest 是否到终点了 4、最后 cur 处理越界的情况 arr[n-1] …

使用sourceCompatibility = 11不匹配解决方法

运行springbootgradle项目报错。 原因&#xff1a;在生产该项目时&#xff0c;选择的JDK是11版本的&#xff0c;但是本地电脑只安装了1.8版本。不兼容所以报错。 解决办法&#xff1a; 找到build.gradle配置文件—>找到sourceCompatibility ‘11’—>把11改成自己本地…

思维题(蓝桥杯 填空题 C++)

目录 题目一&#xff1a; ​编辑 代码&#xff1a; 题目二&#xff1a; 代码&#xff1a; 题目三&#xff1a; 代码&#xff1a; 题目四&#xff1a; 代码&#xff1a; 题目五&#xff1a; 代码&#xff1a; 题目六&#xff1a; 代码七&#xff1a; 题目八&#x…

用python和pygame库实现刮刮乐游戏

用python和pygame库实现刮刮乐游戏 首先&#xff0c;确保你已经安装了pygame库。如果没有安装&#xff0c;可以通过以下命令安装&#xff1a; pip install pygame 示例有两个。 一、简单刮刮乐游戏 准备两张图片&#xff0c;一张作为背景bottom_image.png&#xff0c;一张作…

Leetcoder Day35| 动态规划part02

62.不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff…

如何在Linux配置C、C++、Go语言的编译环境?

在 Linux 系统上配置 C、C、Go 语言的编译环境可以通过安装相应的编译器和相关工具来实现。以下是在 Linux 系统上配置这些语言的编译环境的一般步骤&#xff1a; 1. C 和 C 编译环境配置&#xff1a; 安装 GCC 编译器&#xff08;一般 Linux 发行版都会包含&#xff09;&…

Android 显示系统框架

一.FrameBuffer FrameBuffer 介绍&#xff1a; FrameBuffer中文译名为帧缓冲驱动&#xff0c;它是出现在2.2.xx内核中的一种驱动程序接口。主设备号为29&#xff0c;次设备号递增。 Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。FrameBuffer机制模仿显卡的功能…

Day11:信息打点-Web应用企业产权指纹识别域名资产网络空间威胁情报

目录 Web信息收集工具 业务资产-应用类型分类 Web单域名获取-接口查询 Web子域名获取-解析枚举 Web架构资产-平台指纹识别 思维导图 章节知识点&#xff1a; Web&#xff1a;语言/CMS/中间件/数据库/系统/WAF等 系统&#xff1a;操作系统/端口服务/网络环境/防火墙等 应用…

dart中的事件队列与微任务

dart在每个事件循环中&#xff0c;会先执行同步任务代码&#xff0c;然后分别检查两个任务队列&#xff1a;微任务队列和事件队列。dart总是先执行微任务队列中的代码&#xff0c;然后才是事件队列中的代码。当两个队列中的任务都执行完成后&#xff0c;线程进入休眠状态&#…

Stable Diffusion WebUI API http://127.0.0.1:7860/docs空白

在尝试调用Stable Diffusion WebUI API的时候&#xff0c;打开http://127.0.0.1:7860/docs遇到了以下页面 网络诊断是这样的原因&#xff1a; 修bug&#xff0c;改来改去遇到了以下两种页面&#xff1a; 此时http://127.0.0.1:7860可以如下正常显示&#xff1a; 查资料的时候找…

vue+springboot项目部署服务器

项目仓库&#xff1a;vuespringboot-demo: vuespringboot增删改查的demo (gitee.com) ①vue中修改配置 在public文件夹下新建config.json文件&#xff1a; {"serverUrl": "http://localhost:9090"//这里localhost在打包后记得修改为服务器公网ip } 然后…

[NSSCTF 2nd] web复现

1.php签到 <?phpfunction waf($filename){$black_list array("ph", "htaccess", "ini");$ext pathinfo($filename, PATHINFO_EXTENSION);foreach ($black_list as $value) {if (stristr($ext, $value)){return false;}}return true; }if(i…

nginx 配置浏览器不缓存文件 每次都会从服务器 请求新的文件

目录 解决问题方法说明 测试html环境js环境第一步然后修改内容 打开带有js缓存的页面强制刷新 配置nginx 每次打开页面都会重新请求index.js 文件重启nginx再次修改index.js 总结设置为全局 解决问题 适用于实时更新数据的&#xff0c;网页 可以让用户每次都是重新请求&#x…

C语言中的套娃——函数递归

目录 一、什么是递归 1.1.递归的思想 1.2.递归的限制条件 二、举例体会 2.1.求n的阶乘 2.2.顺序打印整数的每一位 2.3.斐波那契数列 三、递归与迭代 一、什么是递归 在学习C语言的过程中&#xff0c;我们经常会跟递归打交道&#xff0c;什么是递归呢&#xff1f;它其实…

LNMP 架构

环境准备&#xff1a;lnmp 需要安装 nginx mysql php 论坛/博客 软件 使用LNMP架构搭建 论坛 1. 关闭防火墙和和核心防护 systemctl disable --now firewalld setenforce 0 2. 编译安装 nginx 安装依赖包 yum -y install pcre-devel zlib-devel gcc gcc-c make 创建…

在Redhat 7 Linux上安装llama.cpp [ 错误stdatomic.h: No such file or directory]

前期准备 在github上下载llama.cpp或克隆。 GitHub - ggerganov/llama.cpp: LLM inference in C/C ​ git clone https://github.com/ggerganov/llama.cpp.gitcd llama.cpp 执行make命令编译llama.cpp make 在huggingface里下载量化了的 gguf格式的llama2模型。 https:/…