浅谈Redis的异步机制

目录
  • 前言
  • 一、Redis 的阻塞点
    • 4 类交互对象和具体的操作之间的关系:
    • 切片集群实例交互时的阻塞点
  • 二、可以异步执行的阻塞点
  • 三、异步的子线程机制
  • 总结

前言

命令操作、系统配置、关键机制、硬件配置等会影响 Redis 的性能,不仅要知道具体的机制,尽可能避免性能异常的情况出现,还要提前准备好应对异常的方案。

Redis 内部的阻塞式操作:

  • CPU 核和 NUMA 架构的影响;
  • Redis 关键系统配置;
  • Redis 内存碎片;
  • Redis 缓冲区。

一、Redis 的阻塞点

和 Redis 实例交互的对象,以及交互时会发生的操作:

  • 客户端:网络 IO,键值对增删改查操作,数据库操作;
  • 磁盘:生成 RDB 快照,记录 AOF 日志,AOF 日志重写;
  • 主从节点:主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB 文件;
  • 切片集群实例:向其他实例传输哈希槽信息,数据迁移。

4 类交互对象和具体的操作之间的关系:

和客户端交互时的阻塞点:

网络 IO 有时候会比较慢,但是 Redis 使用了 IO 多路复用机制,避免了主线程一直处在等待网络连接或请求到来的状态,所以网络 IO 不是导致 Redis 阻塞的因素。

键值对的增删改查操作是 Redis 和客户端交互的主要部分,也是 Redis 主线程执行的主要任务。复杂度高的增删改查操作肯定会阻塞 Redis。

判断操作复杂度高低的标准:看操作的复杂度是否为 O(N)

Redis 的第一个阻塞点:集合全量查询和聚合操作:

Redis 中涉及集合的操作复杂度通常为 O(N),使用时需重视起来。
例如集合元素全量查询操作 HGETALL、SMEMBERS,以及集合的聚合统计操作,例如求交、并和差集。

Redis 的第二个阻塞点 :bigkey 删除操作

集合自身的删除操作同样也有潜在的阻塞风险。删除操作的本质是要释放键值对占用的内存空间。 释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。

这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞。

释放大量内存的时机:在删除大量键值对数据的时候,删除包含了大量元素的集合,也称为 bigkey 删除

不同元素数量的集合在进行删除操作时所消耗的时间:

得出三个结论:

  • 当元素数量从 10 万增加到 100 万时,4 大集合类型的删除时间的增长幅度从 5 倍上升到了近 20 倍;
  • 集合元素越大,删除所花费的时间就越长;
  • 当删除有 100 万个元素的集合时,最大的删除时间绝对值已经达到了 1.98s(Hash 类 型)。Redis 的响应时间一般在微秒级别,一个操作达到了近 2s,不可避免地会阻塞主线程。

Redis 的第三个阻塞点:清空数据库

既然频繁删除键值对都是潜在的阻塞点了,在 Redis 的数据库级别操作中,清空数据库(例如 FLUSHDB 和 FLUSHALL 操作)也是一个潜在的阻塞风险,因为它涉及到删除和释放所有的键值对。

Redis 的第四个阻塞点:AOF 日志同步写

磁盘 IO 一般都是比较费时费力的,需要重点关注。 Redis 开发者早已认识到磁盘 IO 会带来阻塞,所以把 Redis 设计为采用子进程的方式生成 RDB 快照文件、执行 AOF 日志重写操作。由子进程负责执行,慢速的磁盘 IO 就不会阻塞主线程了。

Redis 直接记录 AOF 日志时,会根据不同的写回策略对数据做落盘保存。一个同步写磁盘的操作的耗时大约是 1~2ms,如果有大量的写操作需要记录在 AOF 日志中,并同步写回的话,会阻塞主线程。

Redis 的第五个阻塞点:从库加载 RDB 文件

在主从集群中,主库需要生成 RDB 文件,并传输给从库。

主库在复制的过程中,创建和传输 RDB 文件都是由子进程来完成的,不会阻塞主线程。
但是从库在接收了 RDB 文件后,需要使用 FLUSHDB 命令清空当前数据库,正好撞上了第三个阻塞点。

从库在清空当前数据库后,需要把 RDB 文件加载到内存,这个过程的快慢和 RDB 文件的大小密切相关,RDB 文件越大,加载过程越慢。

切片集群实例交互时的阻塞点

部署 Redis 切片集群时,每个 Redis 实例上分配的哈希槽信息需要在不同实例间进行传递,当需要进行负载均衡或者有实例增删时,数据会在不同的实例间进行迁移。不过哈希槽的信息量不大,而数据迁移是渐进式执行的,这两类操作对 Redis 主线程的阻塞风险不大。

如果使用了 Redis Cluster 方案,而且同时正好迁移的是 bigkey 的话,就会造成主线程的阻塞,因为 Redis Cluster 使用了同步迁移。

五个阻塞点:

  • 集合全量查询和聚合操作;
  • 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 日志的同步写。
  • “从库加载 RDB 文件”要想对客户端提供数据存取服务,就必须把 RDB 文件加载完成。这个操作也属于关键路径上的操作,必须让从库的主线程来执行。

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

三、异步的子线程机制

Redis 主线程启动后,会使用操作系统提供的 pthread_create 函数创建 3 个子线程,负责AOF 日志写操作、键值对删除、文件关闭的异步执行。

主线程通过一个链表形式的任务队列和子线程进行交互。

当收到键值对删除和清空数据库的操作时,主线程会把这个操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明删除已经完成。

但实际上,这个时候删除还没有执行,等到后台子线程从任务队列中读取任务后,才开始实际删除键值对,并释放相应的内存空间。这种异步删除也称为惰性删除 (lazy free)。

当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个任务,也放到任务队列中。后台子线程读取任务后,开始自行写入 AOF 日志,主线程就不用一直等待 AOF 日志写完了。

Redis 中的异步子线程执行机制:

异步的键值对删除和数据库清空操作是 Redis 4.0 后提供的功能,Redis 也提供了新的命令来执行这两个操作:

  • 键值对删除:集合类型中有大量元素(例如有百万级别或千万级别元素)需要删除时,建议使用 UNLINK 命令;
  • 清空数据库:可以在 FLUSHDB 和 FLUSHALL 命令后加上 ASYNC 选项,让后台子线程异步地清空数据库。
FLUSHDB ASYNC
FLUSHALL AYSNC

总结

Redis 实例运行时的 4 大类交互对象:客户端、磁盘、主从库实例、 切片集群实例

基于这 4 大类交互对象导致 Redis 性能受损的 5 大阻塞点:集合全量查询和聚合操作、bigkey 删除、清空数据库、AOF 日志同步写、从库加载 RDB 文件

bigkey 删除、清空数据库、AOF 日志同步写不属于关键路径操作, 可以使用异步子线程机制来完成。

Redis 在运行时会创建三个子线程,主线程会通过一个任务队列和三个子线程进行交互。子线程会根据任务的具体类型,来执行相应的异步操作。

异步删除操作是 Redis 4.0 以后才有的功能,如果使用的是 4.0 之前的版本,遇到 bigkey 删除时,建议:先使用集合类型提供的 SCAN 命令读取数据, 然后再进行删除。因为用 SCAN 命令可以每次只读取一部分数据并进行删除,这样可以避免一次性删除大量 key 给主线程带来的阻塞。
例如,对于 Hash 类型的 bigkey 删除,使用 HSCAN 命令,每次从 Hash 集合中获取一部分键值对(例如 200 个),再使用 HDEL 删除这些键值对,可以把删除压力分摊到多次操作中,每次删除操作的耗时就不会太长,也就不会阻塞主线程了。

集合全量查询和聚合操作、从库加载 RDB 文件是在关键路径上,无法使用异步操作来完成。
建议:

  • 集合全量查询和聚合操作:使用 SCAN 命令,分批读取数据,再在客户端进行聚合计算;
  • 从库加载 RDB 文件:把主库的数据量大小控制在 2~4GB 左右,以保证 RDB 文件能以较快的速度加载。

到此这篇关于浅谈Redis的异步机制的文章就介绍到这了,更多相关Redis 异步机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 异步redis队列实现 数据入库的方法

    业务需求 app客户端向服务端接口发送来json 数据 每天 发一次 清空缓存后会再次发送 出问题之前业务逻辑: php 接口 首先将 json 转为数组 去重 在一张大表中插入不存在的数据 该用户已经存在 和新增的id 入另一种详情表 问题所在: 当用户因特殊情况清除缓存 导致app 发送json串 入库并发高 导致CPU 暴增到88% 并且居高不下 优化思路: 1.异步队列处理 2.redis 过滤(就是只处理当天第一次请求) 3.redis 辅助存储app名称(验证过后批量插入数据app名

  • 高效异步redis客户端aredis优劣势原理解析

    背景 aredis 是一款由同步的 redis 客户端 redis-py 改写而成的高效的异步 redis 客户端,在最新的 1.0.7 版本中完成了对于 redis 集群的支持. 改动 主要重写了底部建立连接和读取数据部分的代码,接口部分都向下兼容,便于使用者从 redis-py 的同步代码迁移到 async 和 await 的协程版本,详细文档请看 aredis 文档 使用 安装 pip install aredis 具体姿势可以参阅项目文档和例子,接口向下兼容 redis-py,支持 Py

  • 浅谈Redis的异步机制

    目录 前言 一.Redis 的阻塞点 4 类交互对象和具体的操作之间的关系: 切片集群实例交互时的阻塞点 二.可以异步执行的阻塞点 三.异步的子线程机制 总结 前言 命令操作.系统配置.关键机制.硬件配置等会影响 Redis 的性能,不仅要知道具体的机制,尽可能避免性能异常的情况出现,还要提前准备好应对异常的方案. Redis 内部的阻塞式操作: CPU 核和 NUMA 架构的影响: Redis 关键系统配置: Redis 内存碎片: Redis 缓冲区. 一.Redis 的阻塞点 和 Redi

  • 浅谈redis的过期时间设置和过期删除机制

    目录 一:设置过期时间 二:保存过期时间 三:移除过期时间 四:计算并返回剩余生存时间 五:过期键的删除策略 六:redis使用的策略 一:设置过期时间 redis有四种命令可以用于设置键的生存时间和过期时间: EXPIRE <KEY> <TTL> : 将键的生存时间设为 ttl 秒 PEXPIRE <KEY> <TTL> :将键的生存时间设为 ttl 毫秒 EXPIREAT <KEY> <timestamp> :将键的过期时间设为

  • 浅谈Redis 中的过期删除策略和内存淘汰机制

    目录 前言 Redis 中 key 的过期删除策略 1.定时删除 2.惰性删除 3.定期删除 Redis 中过期删除策略 从库是否会脏读主库创建的过期键 内存淘汰机制 内存淘汰触发的最大内存 有哪些内存淘汰策略 内存淘汰算法 LRU LFU 为什么数据删除后内存占用还是很高 内存碎片如何产生 碎片率的意义 如何清理内存碎片 总结 参考 前言 Redis 中的 key 设置一个过期时间,在过期时间到的时候,Redis 是如何清除这个 key 的呢? 这来分析下 Redis 中的过期删除策略和内存淘

  • 浅谈Redis 中的过期删除策略和内存淘汰机制

    目录 前言 Redis 中 key 的过期删除策略 1.定时删除 2.惰性删除 3.定期删除 Redis 中过期删除策略 从库是否会脏读主库创建的过期键 内存淘汰机制 内存淘汰触发的最大内存 有哪些内存淘汰策略 内存淘汰算法 LRU LFU 为什么数据删除后内存占用还是很高 内存碎片如何产生 碎片率的意义 如何清理内存碎片 总结 参考 前言 Redis 中的 key 设置一个过期时间,在过期时间到的时候,Redis 是如何清除这个 key 的呢? 这来分析下 Redis 中的过期删除策略和内存淘

  • 浅谈Redis中的自动过期机制

    目录 Redis中的自动过期机制 一.使用Redis Key自动过期机制 二.SpringBoot整合key失效监听 Redis中的自动过期机制 实现需求:处理订单过期自动取消,比如下单30分钟未支付自动更改订单状态 1.使用Redis Key自动过期出发事件通知2.使用定时任务30分钟后检查3.按照每分钟轮训检查 CREATE TABLE `order_number` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_name` varchar(25

  • 浅谈Redis缓冲区机制

    目录 Redis缓冲区机制 客户端缓冲机制 应对输入缓冲区溢出 查看输入缓冲区信息 如何解决输入缓冲区溢出 应对输出缓冲区溢出 Monitor命令的执行 输出缓冲区设置不合理 主从集群中的缓冲区 复制缓冲区 复制缓冲区溢出 复制积压缓冲区 解决复制积压缓冲区溢出 Redis缓冲区机制 Redis中的缓冲区机制就是为了平衡客户端发送命令和服务端处理命令的速度差异,如果客户端写入过快或者服务端读取过慢这就会导致缓冲区溢出,缓冲区一旦溢出将引发一系列的性能问题,下面我们详细聊聊. 客户端缓冲机制 Re

  • 浅谈Redis跟MySQL的双写问题解决方案

    目录 写在前面 三种读写缓存策略 Cache-AsidePattern(旁路缓存模式) Read-Through/Write-Through(读写穿透) WriteBehindPattern(异步缓存写入) 旁路缓存模式解析 CacheAsidePattern的一些疑问 CacheAsidePattern的缺陷 项目中有遇到这个问题,跟MySQL中的数据不一致,研究一番发现这里面细节并不简单,特此记录一下. 写在前面 严格意义上任何非原子操作都不可能保证一致性,除非用阻塞读写实现强一致性,所以缓

  • 浅谈Redis常见延迟问题定位与分析

    目录 使用复杂度高的命令 存储bigkey 集中过期 实例内存达到上限 fork耗时严重 绑定CPU 使用Swap 网卡负载过高 使用复杂度高的命令 如果在使用Redis时,发现访问延迟突然增大,如何进行排查? 首先,第一步,建议你去查看一下Redis的慢日志.Redis提供了慢日志命令的统计功能,我们通过以下设置,就可以查看有哪些命令在执行时延迟比较大. 首先设置Redis的慢日志阈值,只有超过阈值的命令才会被记录,这里的单位是微妙,例如设置慢日志的阈值为5毫秒,同时设置只保留最近1000条慢

  • 浅谈Redis缓存更新策略

      内存淘汰 超时剔除 主动更新 说明 不用自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据.下次查询时更新缓存 给缓存数据添加TTL时间,到期后自动删除缓存,下次查询时更新缓存 编写业务逻辑,在修改数据的同时,更新缓存 一致性 差 一般 好 维护成本 无 低 高 业务场景需求: 在基本不会更新数据的情况下可以使用内存淘汰机制 在频繁更新数据的情况下可以使用主动更新,并以超时剔除作为兜底方案. 主动更新的三种方法 Cache Aside Pattern:由缓存的调用者,在更新

  • 浅谈Redis缓存有哪些淘汰策略

    目录 Redis过期策略 定时删除 惰性删除 定期删除 Redis的内存淘汰机制 LRU和LFU的区别 LRU LFU Redis重启如何恢复数据呢? 总结 Redis过期策略 我们首先来了解一下Redis的内存淘汰机制. 定时删除 概述     redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除.注意这里是随机抽取的.为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会

随机推荐