Redis 阻塞

Redis 是典型的单线程架构,所有的读写操作都是在一条主线程中完成,所以如果主线程被阻塞,就会直接影响到应用系统。本篇文章来盘点一下 Redis 中阻塞的常见情况

命令阻塞

虽然 Redis 是基于内存的数据库,但如果执行的命令时间复杂度过高,就会导致主线程阻塞。下面命令的时间复杂度均为O(n),随着数据规模的增加,执行时间也会变长

所以尽量使用时间复杂度低的命令!!!

RDB 阻塞

RDB 是 Redis 持久化的一种机制,详情可见 Redis 持久化机制 - RDB

生成 RDB 文件有两种命令:

所以尽量使用bgsave命令创建 RDB 文件!!!

AOF 阻塞

AOF 是 Rdis 持久化的一种机制,详情可见 Redis 持久化机制 - AOF

AOF 持久化一共有四步:命令写入、文件同步、文件重写、重启加载,在文件同步和文件重写时会发生阻塞

对于第一步的文件写入,其实只写到了 aof_buf 缓存,还没有写入到文件中;在文件同步时选择写入文件的策略,一共三种:always、everysec、no

当刷盘频率过高,导致磁盘 IO 开销大,后台刷盘线程需要阻塞到完成后才返回。如果主线程发现距离上一次的fsync成功超过 2s,为了数据安全性主线程会阻塞到后台线程执行完为止

对于第三步文件重写,会执行bgrewriteaof命令触发重写流程,调用fork()创建一个子进程完成文件重写,父进程可以正常处理其它命令,但会将其它命令写入 aof_rewrite_buf 缓冲区

等到子进程完成文件重写后,由父进程将 aof_rewrite_buf 缓冲中的命令追加到新的 aof 文件中,最后用新 aof 替换旧 aof 文件。父进程追加的步骤需要阻塞完成

但是后面的版本通过 pipe (管道) 对最后追加的步骤进行了优化,在子进程文件重写的后期,父进程可以分批将 aof_rewrite_buf 缓冲区中的数据通过管道传递给子进程,由子进程负责追加到 aof 文件

这样就可以避免父进程追加时阻塞,但可能存在无法将 aof_rewrite_buf 缓冲区所有内容都传递给子进程处理,所以最后父进程也需要阻塞式追加一部分命令,但相比于优化前少了很多

大 key 阻塞

如果一个 key 对应的 value 占用内存空间很大,就称为大 key。对于大 key 的查询、删除等操作都会消耗更多时间,导致阻塞

清空数据库时阻塞

可以使用flushdbflushdball命令清空数据库,该操作和删除大 key 原理一样,消耗时间多,导致阻塞

集群扩容时阻塞

Redis 集群的扩容或缩容处于半自动化的状态,需要人工介入,可以利用 redis-trib 实现数据的迁移

在扩容或缩容的时候,需要进行数据迁移,Redis 为了保证数据迁移的一致性,迁移的所有操作都是同步的。在执行迁移时,两边的 Redis 均会进入时长不等的阻塞状态

对于小 key,该时间可以忽略不计,但如果是大 key,严重的时候可能会触发故障转移,因为长时间得不到回复,会认为 Redis 节点下线

内存交换引起的阻塞

Redis 是基于内存的数据库,这是它保证高性能的前提。如果操作系统把 Redis 使用的部分内存换出到磁盘,就会严重影响 Redis 的速度,进而操作阻塞

CPU 竞争引起的阻塞

Redis 是典型的 CPU 密集型应用,如果 Redis 和其它多核 CPU 密集型应用部署在同一台服务器上,会造成 CPU 竞争,如果 Redis 没有竞争到 CPU 就会引起阻塞

网络问题引起的阻塞

在 Redis 分布式缓存中,为了使缓存具有高可用,都会将不同 Redis 节点部署在不同的服务器上,节点间的通信需要通过网络传输,如果网络不佳就会引起阻塞