redis如何避免单线程模型的阻塞?
mhr18 2024-11-21 18:07 20 浏览 0 评论
目录
一、Redis 实例有哪些阻塞点?
1. 和客户端交互时的阻塞点
1.1 查询
1.2 bigkey 删除操作
1.3 FLUSHDB 和 FLUSHALL
2. 和磁盘交互时的阻塞点
2.1 AOF 日志同步写(always)
3. 主从节点交互时的阻塞点
二、异步线程机制
三、哪些操作可以异步执行
什么是关键路径
四、异步的子线程机制
五、Redis的写操作(例如SET,HSET,SADD等)是在关键路径上吗?
Redis 性能的 5 大方面的潜在因素,
Redis 内部的阻塞式操作
CPU 核和 NUMA 架构的影响;
Redis 关键系统配置;
Redis 内存碎片;
Redis 缓冲区。
一、Redis 实例有哪些阻塞点?
客户端:网络 IO,键值对增删改查操作,数据库操作;
磁盘:生成 RDB 快照,记录 AOF 日志,AOF 日志重写;
主从节点:主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB 文件;
切片集群实例:向其他实例传输哈希槽信息,数据迁移。
?
1. 和客户端交互时的阻塞点
1.1 查询
集合全量查询和聚合操作。 例如hgetall、lrange key 0 -1、....
1.2bigkey 删除操作
删除操作的本质是要释放键值对占用的内存空间。你可不要小瞧内存的释放过程。释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序,所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞。
测试了不同元素数量的集合在进行删除操作时所消耗的时间
?
从这张表里,我们可以得出三个结论:
- 当元素数量从 10 万增加到 100 万时,4 大集合类型的删除时间的增长幅度从 5 倍上升到了近 20 倍;
- 集合元素越大,删除所花费的时间就越长;
- 当删除有 100 万个元素的集合时,最大的删除时间绝对值已经达到了 1.98s(Hash 类型)。Redis 的响应时间一般在微秒级别,所以,一个操作达到了近 2s,不可避免地会阻塞主线程。
1.3 FLUSHDB 和 FLUSHALL
2. 和磁盘交互时的阻塞点
2.1AOF 日志同步写(always)
Redis 直接记录 AOF 日志时,会根据不同的写回策略对数据做落盘保存。一个同步写(appendfsync always代表同步写)磁盘的操作的耗时大约是 1~2ms,如果有大量的写操作需要记录在 AOF 日志中,并同步写回的话,就会阻塞主线程了
3. 主从节点交互时的阻塞点
把 RDB 文件加载到内存,这个过程的快慢和 RDB 文件的大小密切相关,RDB 文件越大,加载过程越慢
二、异步线程机制
五个阻塞点:
- 集合全量查询和聚合操作;
- bigkey 删除;
- 清空数据库;
- AOF 日志同步写;
- 从库加载 RDB 文件。
如果在主线程中执行这些操作,必然会导致主线程长时间无法服务其他请求。为了避免阻 塞式操作,Redis 提供了异步线程机制。
什么是异步线程机制?
Redis 会启动一些子线程,然后把一些任务交给这些子线程,让它们在后台完成,而不再由主线程来执行这 些任务。使用异步线程机制执行操作,可以避免阻塞主线程。
三、哪些操作可以异步执行
什么是关键路径
客户端把请求发送给 Redis 后,等着 Redis 返回数据结果的操作。
?
主线程接收到操作 1 后,因为操作 1 并不用给客户端返回具体的数据,所以,主线程可以把它交给后台子线程来完成,同时只要给客户端返回一个“OK”结果就行。在子线程执行操作 1 的时候,客户端又向 Redis 实例发送了操作 2,而此时,客户端是需要使用操作 2 返回的数据结果的,如果操作 2 不返回结果,那么,客户端将一直处于等待状态。
在这个例子中,
操作 1 就不算关键路径上的操作,因为它不用给客户端返回具体数据,所以可以由后台子线程异步执行。
操作 2 需要把结果返回给客户端,它就是关键路径上的操作,所以主线程必须立即把这个操作执行完。
五个阻塞点:
- 集合全量查询和聚合操作;(关键路径)
- bigkey 删除;
- 清空数据库;
- AOF 日志同步写;
- 从库加载 RDB 文件。(关键路径)
由此可知,可以使用 Redis 的异步子线程机制来实现 bigkey 删除,清空数据库,以及 AOF 日志同步写。
四、异步的子线程机制
Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,分别由它们负责 AOF 日志写操作、键值对删除以及文件关闭的异步执行。
redis]# pstree -p 2090
redis-server(2090)─┬─{redis-server}(2101)
├─{redis-server}(2102)
└─{redis-server}(2103)
xxxxxxxxxxbr redis]# pstree -p 2090brredis-server(2090)─┬─{redis-server}(2101)br ├─{redis-server}(2102)br └─{redis-server}(2103)br
主线程通过一个链表形式的任务队列和子线程进行交互。当收到键值对删除和清空数据库的操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明删除已经完成。
但实际上,这个时候删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始实际删除键值对,并释放相应的内存空间。因此,我们把这种异步删除也称为惰性删除(lazy free)。
AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到任务队列中。后台子线程读取任务后,开始自行写入 AOF 日志,这样主线程就不用一直等待 AOF 日志写完了
?
Note : 异步的键值对删除和数据库清空操作是 Redis 4.0 后提供的功能
FLUSHDB ASYNC
FLUSHALL AYSNC
五、Redis的写操作(例如SET,HSET,SADD等)是在关键路径上吗?
1、如果客户端依赖操作返回值的不同,进而需要处理不同的业务逻辑,那么HSET和SADD操作算关键路径,而SET操作不算关键路径。因为HSET和SADD操作,如果field或member不存在时,Redis结果会返回1,否则返回0。而SET操作返回的结果都是OK,客户端不需要关心结果有什么不同。
2、如果客户端不关心返回值,只关心数据是否写入成功,那么SET/HSET/SADD不算关键路径,多次执行这些命令都是幂等的,这种情况下可以放到异步线程中执行。
3、但是有种例外情况,如果Redis设置了maxmemory,但是却没有设置淘汰策略,这三个操作也都算关键路径。因为如果Redis内存超过了maxmemory,再写入数据时,Redis返回的结果是OOM error,这种情况下,客户端需要感知有错误发生才行。
另外,我查阅了lazy-free相关的源码,发现有很多细节需要补充下:
1、lazy-free是4.0新增的功能,但是默认是关闭的,需要手动开启。
2、手动开启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:主从全量同步,从库清空数据库时异步释放内存
3、即使开启了lazy-free,如果直接使用DEL命令还是会同步删除key,只有使用UNLINK命令才会可能异步删除key。
4、这也是最关键的一点,上面提到开启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作者认为释放内存的代价比较低,就放在主线程做。如果释放的内存不连续(大量指针类型的数据),这个代价就比较高,所以才会放在异步线程中去执行。
?
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)