16 | 异步机制:如何避免单线程模型的阻塞?
蒋德钧
该思维导图由 AI 生成,仅供参考
你好,我是蒋德钧。
Redis 之所以被广泛应用,很重要的一个原因就是它支持高性能访问。也正因为这样,我们必须要重视所有可能影响 Redis 性能的因素(例如命令操作、系统配置、关键机制、硬件配置等),不仅要知道具体的机制,尽可能避免性能异常的情况出现,还要提前准备好应对异常的方案。
所以,从这节课开始,我会用 6 节课的时间介绍影响 Redis 性能的 5 大方面的潜在因素,分别是:
Redis 内部的阻塞式操作;
CPU 核和 NUMA 架构的影响;
Redis 关键系统配置;
Redis 内存碎片;
Redis 缓冲区。
这节课,我们就先学习了解下 Redis 内部的阻塞式操作以及应对的方法。
在第 3 讲中,我们学习过,Redis 的网络 IO 和键值对读写是由主线程完成的。那么,如果在主线程上执行的操作消耗的时间太长,就会引起主线程阻塞。但是,Redis 既有服务客户端请求的键值对增删改查操作,也有保证可靠性的持久化操作,还有进行主从复制时的数据同步操作,等等。操作这么多,究竟哪些会引起阻塞呢?
别着急,接下来,我就带你分门别类地梳理下这些操作,并且找出阻塞式操作。
Redis 实例有哪些阻塞点?
Redis 实例在运行时,要和许多对象进行交互,这些不同的交互就会涉及不同的操作,下面我们来看看和 Redis 实例交互的对象,以及交互时会发生的操作。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
Redis是一个高性能的数据库,但其单线程模型可能会受到阻塞式操作的影响。本文介绍了Redis内部的阻塞式操作及相应的应对方法。文章首先介绍了Redis实例与客户端、磁盘、主从节点和切片集群实例之间的交互关系,并分析了可能引起阻塞的操作。针对这些阻塞点,文章提出了五大阻塞式操作,包括集合全量查询和聚合操作、bigkey删除、清空数据库、AOF日志同步写以及从库加载RDB文件。为了避免这些阻塞式操作影响主线程的服务能力,Redis提供了异步线程机制,将一些任务交给子线程后台完成,从而避免阻塞主线程。文章还介绍了Redis的异步子线程机制,以及对应的操作建议。总的来说,通过本文的总结,读者可以快速了解Redis内部阻塞式操作的特点以及相应的解决方法。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Redis 核心技术与实战》,新⼈⾸单¥68
《Redis 核心技术与实战》,新⼈⾸单¥68
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(49)
- 最新
- 精选
- KaitoRedis的写操作(例如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作者认为释放内存的代价比较低,就放在主线程做。如果释放的内存不连续(大量指针类型的数据),这个代价就比较高,所以才会放在异步线程中去执行。 如果我的理解有偏差,还请老师和大家指出!2020-09-1481559
- 注定非凡1,作者讲了什么? Redis有哪些可能导致阻塞的操作,以及解决机制 2,作者是怎么把这件事讲明白的? 1,分类:通过Redis实例的交互关系,拆分了四类:客户端,磁盘,主从集群,分片集群 2,提出关键路径操作概念,作为是否可以异步处理的判断标准 3,为了讲明白,作者讲了哪些要点?有哪些亮点? 1,亮点1:将Redis的可能阻塞点划分了四块,与客户端交互,与磁盘交互,主从实例交互,分片集群实例交互 2,亮点2:通过关键路径上操作概念,建立了判断是否适合异步操作的标准 3,要点1:与客户端交互时的阻塞点:高复杂度的增删改操作(集合全量的查询和聚合操作,bigKey 删除操作,清空数据库) 4,要点2:与磁盘交互的阻塞点:aof日志实时同步写回 5,要点3:主从节点交互时的阻塞点:从库加载RDB文件 6,要点4:分片集群的阻塞点:哈希槽bigkey数据迁移 7,要点5:关键路径操作概念:客户端把请求发给Redis后,要等着Redis返回数据结果的操作 8,要点6:异步的子线程机制:主线程通过一个链表形式的任务队列和子线程进行交互 4,对于作者所讲,我有哪些发散性思考? 给自己提了几个问题: 1,Redis至少有几个线程? 2,网络IO有时会比较慢,网络IO是否为Redis的阻塞点? 3,删除操作为什么会阻塞Redis 5,将来在哪些场景里,我能够使用它? 6,留言收获 什么时候Redis会真正的异步释放内存?(答案来自@kaito 大佬) lazy free机制:Redis收到键值对删除和清空数据库的指令时,主线线程会把这个操作封装成一个任务,放入任务队列中,然后给客户端返回一个完成信息,但实际上,这个删除还没有执行,需要等待后台子线程从任务队列中读取到这个任务后,才开始实际删除键值对,并释放相应的内存空间。 但是:lazy-free是4.0新增功能,默认关闭。开启这个配置后, 除了replica-lazy-flush之外,其他情况都只是*可能*去异步释放key的内存,并不是每次必定异步释放内存的。是否会真正异步释放内存,取决于key的类型,编码方式,元素数量,所以 即使开启了lazy-free,String类型的bigkey,在删除时依旧有阻塞主线程的风险2020-10-2139
- Spring4JRedis的异步子线程机制就跟java里面的线程池原理差不多,都是主线程封装任务到队列中,子线程到队列中拉取任务异步执行,运用了生产者消费者的模型2020-09-1411
- 奕在删除一个 大 key 的时候,redis 把这个任务放到队列,实际还没有执行删除操作,这时候马上来个查询查刚才删除的 key. 这时候会查询到吗? Redis 是怎么处理这种情况的?2020-09-1539
- 奕释放掉的内存块插入一个空闲内存块的链表 =================== 这个过程怎么会耗时间呢? 插入链表的时候,不就是在链表尾部一放不就可以了吗? 时间复杂度位 O(1)2020-09-1556
- 漫步oo0云端今天学习了5种阻塞点,请问老师,后面会学习,当redis发生阻塞时如何分析是什么操作导致的这个技能吗?2020-09-1416
- Geek_1b4c8异步删除key时,如果删除操作在任务队列里面还未被子线程执行,此时如果来了get查询操作,是否会返回数据吗,异步删除时是否会先标识为删除吗2022-04-0812
- test写操作是否在关键路径,需要看使用方是否需要确认写入已经完成。2020-09-142
- 那时刻当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到任务队列中。后台子线程读取任务后,然后写入AOF日志。请问老师,如果写入操作比较频繁,是否也会引起redis延迟增大呢?2020-09-1412
- Geek_77c5bf老师我一直有个问题,你上面说,如果是读操作。读操作是主线程来处理查询并返回的吗?意思是同步的?我的理解,客户端的确是一直在等待结果,但是redis是异步的,是基于事件通知回调机制,等redis查到结果,再socket通知客户端。希望老师帮忙解答,困扰我很久2021-03-3121
收起评论