Redis内部的阻塞式操作以及优化


如何避免单线程模型的阻塞

影响 Redis 性能的 5 大方面的潜在因素,分别是:

  • Redis 内部的阻塞式操作

  • CPU 核和 NUMA 架构的影响

  • Redis 关键系统配置

  • Redis 内存碎片

  • Redis 缓冲区

 

本文仅介绍Redis内部的阻塞方式操作

 

redis内部的阻塞式操作(四类)

  1. 客户端:网络IO,增删改查操作,数据库操作

  2. 磁盘:生成RDB快照,记录AOF日志,AOF日志重写

  3. 主从节点:主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB文件;

  4. 切片集群实例:向其他实例传输哈希槽信息,数据迁移

 

1.客户端

  • 网络 IO 不是导致 Redis 阻塞的因素

  • 第一个阻塞点——集合全量查询和聚合操作

    • 集合元素全量查询操作:HGETALL、SMENBERS

    • 集合的聚合统计操作:求交、并、差集

  • Redis 的第二个阻塞点——bigkey 删除操作,redis删除得先释放内存,然后操作系统需要将释放的内存插入空闲内存块的链表,一下子释放太大可能会阻塞主线程

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

 

 

2.与磁盘交互

  • Redis 的第四个阻塞点——AOF 日志同步写。

 

 

3.主从节点交互

主从集群中,主库生成RDB文件是不会阻塞的(fork子进程完成),But!对于从库而言,它会:

  1. 首先,FLUSHDB清空当前数据库,这就撞上了第三个阻塞点(清库)

  2. 此外,清库后要把RDB文件加载到内存,这个过程与RDB文件大小有关,越大则越慢

Redis 的第五个阻塞点——加载 RDB 文件

 

 

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

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

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

 

 

哪些阻塞点可以异步执行?

 

 

 

 

 

 

在Redis中,提供了异步线程机制——redis会启动一些子线程,把一些任务交给子线程,让它们在后台完成。使用异步线程机制执行操作,可以避免阻塞主线程。But!不是五个阻塞点都能使用异步机制

哪些阻塞点可以异步执行?

如果一个操作能被异步执行,就意味着,它并不是 Redis 主线程的关键路径上的操作。我再解释下关键路径上的操作是啥。这就是说,客户端把请求发送给 Redis 后,等着 Redis返回数据结果的操作。

  • 读操作是典型的关键路径操作,因为客户端发送了读操作之后,就会等 待读取的数据返回,以便进行后续的数据处理。而redis第一个阻塞点“集合的全量查询和聚合操作”都涉及了读操作,所以它们不能异步操作

  • 删除操作不是关键路径操作可以异步执行,删除操作并不需要给客户端返回具体的数据结果,所以第二个阻塞点“bigkey删除”和第三个阻塞点“清空数据库”可以使用异步线程机制

  • 第四个阻塞点“AOF日志同步写”也可以异步执行,虽然为了保证数据可靠性,Redis实例需要保证AOF日志中操作已落盘,这个操作虽然需要实例等待,但不需要返回具体的数据给实例,所以不是关键路径操作,可以异步执行

  • 第五个阻塞点“从库加载 RDB 文件”是关键路径操作,不可异步,从库要想对客户端提供数据存取服务,就必须把 RDB 文件加载完成。

总结:除了“集合的全量查询和聚合操作”以及“从库加载RDB文件”不可以使用异步线程机制,其他阻塞点都可以使用这个机制来提升性能

 

异步线程机制

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

主线程通过一个链表形式的任务队列和子线程交互。例如:当收到删除键值对和清空数据库操作时,主线程会把这些操作封装成一个个任务,放到任务队列中,然后向客户端返回一个完成信息,表示删除已完成。

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

异步的键值对删除和数据库清空操作是 Redis 4.0 之后提供的功能

删除bigkey:

UNLINK

清空数据库:

FLUSHDB ASYNC
FLUSHALL AYSNC

 

总结

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

 

导致 Redis 性能受损的 5 大阻塞点,包括集合全量查询和聚合操作、bigkey 删除、清空数据库、AOF 日志同步写,以及从库加载 RDB 文件。

在这 5 大阻塞点中,bigkey 删除、清空数据库、AOF 日志同步写不属于关键路径操作,可以使用异步子线程机制来完成。Redis 在运行时会创建三个子线程,主线程会通过一个任务队列和三个子线程进行交互。(异步删除redis4.0后才有)

 

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

  • 前者,可以使用SCAN命令,分批读取数据,再在客户端进行和计算

  • 后者,从库加载 RDB 文件:把主库的数据量大小控制在 2~4GB 左右,以保证 RDB 文件能以较快的速度加载。