Redis 是典型的单线程架构,所有的读写操作都是在一条主线程中完成,所以如果主线程被阻塞,就会直接影响到应用系统。本篇文章来盘点一下 Redis 中阻塞的常见情况
虽然 Redis 是基于内存的数据库,但如果执行的命令时间复杂度过高,就会导致主线程阻塞。下面命令的时间复杂度均为O(n)
,随着数据规模的增加,执行时间也会变长
keys *
:获取所有的 key
hgetall
:获取哈希表中所有 <key, value>
smembers
:返回集合中的所有成员
...
所以尽量使用时间复杂度低的命令!!!
RDB 是 Redis 持久化的一种机制,详情可见 Redis 持久化机制 - RDB
生成 RDB 文件有两种命令:
save
:阻塞主线程,直至 RDB 文件创建完毕为止,阻塞期间 Redis 不可以处理任何其它命令
bgsave
:执行fork()
创建子进程,由子进程负责创建 RDB 文件,服务器没有被阻塞,可以正常处理其它命令
所以尽量使用bgsave
命令创建 RDB 文件!!!
AOF 是 Rdis 持久化的一种机制,详情可见 Redis 持久化机制 - AOF
AOF 持久化一共有四步:命令写入、文件同步、文件重写、重启加载,在文件同步和文件重写时会发生阻塞
对于第一步的文件写入,其实只写到了 aof_buf 缓存,还没有写入到文件中;在文件同步时选择写入文件的策略,一共三种:always、everysec、no
always:对于每条写入命令,由后台线程立即调用fsync
阻塞式写入到磁盘中
everysec:对于每条写入命令,主线程先执行write
系统调用写入到 aof_buf 缓存,然后由后台线程按每秒一次的频率调用fsync
阻塞式将缓存写入到磁盘中
no:对于每条写入命令,执行write
系统调用写入到 aof_buf 缓存,同步到磁盘由操作系统决定,一般 30s 一次
当刷盘频率过高,导致磁盘 IO 开销大,后台刷盘线程需要阻塞到完成后才返回。如果主线程发现距离上一次的fsync
成功超过 2s,为了数据安全性主线程会阻塞到后台线程执行完为止
对于第三步文件重写,会执行bgrewriteaof
命令触发重写流程,调用fork()
创建一个子进程完成文件重写,父进程可以正常处理其它命令,但会将其它命令写入 aof_rewrite_buf 缓冲区
等到子进程完成文件重写后,由父进程将 aof_rewrite_buf 缓冲中的命令追加到新的 aof 文件中,最后用新 aof 替换旧 aof 文件。父进程追加的步骤需要阻塞完成
但是后面的版本通过 pipe (管道) 对最后追加的步骤进行了优化,在子进程文件重写的后期,父进程可以分批将 aof_rewrite_buf 缓冲区中的数据通过管道传递给子进程,由子进程负责追加到 aof 文件
这样就可以避免父进程追加时阻塞,但可能存在无法将 aof_rewrite_buf 缓冲区所有内容都传递给子进程处理,所以最后父进程也需要阻塞式追加一部分命令,但相比于优化前少了很多
如果一个 key 对应的 value 占用内存空间很大,就称为大 key。对于大 key 的查询、删除等操作都会消耗更多时间,导致阻塞
可以使用flushdb
或flushdball
命令清空数据库,该操作和删除大 key 原理一样,消耗时间多,导致阻塞
Redis 集群的扩容或缩容处于半自动化的状态,需要人工介入,可以利用 redis-trib 实现数据的迁移
在扩容或缩容的时候,需要进行数据迁移,Redis 为了保证数据迁移的一致性,迁移的所有操作都是同步的。在执行迁移时,两边的 Redis 均会进入时长不等的阻塞状态
对于小 key,该时间可以忽略不计,但如果是大 key,严重的时候可能会触发故障转移,因为长时间得不到回复,会认为 Redis 节点下线
Redis 是基于内存的数据库,这是它保证高性能的前提。如果操作系统把 Redis 使用的部分内存换出到磁盘,就会严重影响 Redis 的速度,进而操作阻塞
Redis 是典型的 CPU 密集型应用,如果 Redis 和其它多核 CPU 密集型应用部署在同一台服务器上,会造成 CPU 竞争,如果 Redis 没有竞争到 CPU 就会引起阻塞
在 Redis 分布式缓存中,为了使缓存具有高可用,都会将不同 Redis 节点部署在不同的服务器上,节点间的通信需要通过网络传输,如果网络不佳就会引起阻塞