大 key:数据量比较大的 key-value 键值对

对 AOF 日志的影响

  • Always 写回策略:主线程执行 fsync() 函数阻塞的时间会比较久。因为当写入的数据量很大时,同步到硬盘的过程比较耗时
  • Everysec 写回策略:异步执行 fsync() 函数,不会影响主线程
  • No 写回策略:永不执行 fsync() 函数,不会影响主线程

当 AOF 日志写入大 key,文件会很快变大,就会触发 AOF 重写机制

对 AOF 重写和 RDB 快照的影响

fork 调用

AOF 重写和 RDB 快照(bgsave),都会通过 fork() 函数创建一个子进程来处理任务

在通过 fork() 函数创建子进程时,虽然不会复制父进程的物理内存,但是内核会把父进程的页表复制一份给子进程,如果页表很大,复制过程是耗时的,那么在执行 fork() 函数时就会阻塞主进程

可以执行 info 命令获取到 latest_fork_usec 指标:

> info
...
latest_fork_usec: 315 # 最近一次 fork 操作耗时
...

如果 fork 耗时很大,比如超过 1 秒,则需要做出优化调整:

  • 单个实例的内存占用控制在 10 GB 以下,这样 fork 就能很快返回
  • 如果 Redis 只是当作纯缓存使用,不关心数据安全性问题,可以考虑关闭持久化,就不会调用 fork
  • 在主从架构中,要适当调大 repl-backlog-size,避免因为 repl_backlog_buffer 不够大,导致主节点频繁全量同步,全量同步时会创建 RDB 文件,也就是会调用 fork

写时复制

创建完子进程后,父进程对共享内存中的大 key 进行了修改,内核就会发生写时复制,会把物理内存复制一份,比较耗时,父进程(主线程)就会发生阻塞

信号处理函数

子进程工作完成后,会向主进程发送一条信号(异步的),主进程收到该信号,会调用信号处理函数将 AOF 重写缓冲区中的所有内容追加到新的 AOF 的文件中,如果 AOF 重写缓冲区中有大 key,会比较耗时,阻塞主线程

如果 Linux 开启了内存大页,会影响 Redis 的性能

Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度执行的

如果采用了内存大页,那么即使只修改 100B 的数据,在发生写时复制后,Redis 也需要拷贝 2MB 的大页;相反,如果是常规内存页机制,只用拷贝 4KB

禁用内存大页(默认就是禁用的):

echo never >  /sys/kernel/mm/transparent_hugepage/enabled

其他影响

大 key 除了会影响持久化外,还有其他影响:

  • 工作线程阻塞、客户端阻塞:Redis 执行命令是单线程的,操作大 key 会比较耗时,从客户端视角看,就是很久没有响应
  • 引发网络阻塞:传输大 key 产生的网络流量较大,如果一个 key 的大小是 1MB,每秒访问量为 1000,每秒就会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的
  • 内存分布不均:集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大

如何避免大 key

  • 最好在设计阶段,就把大 key 拆分成一个一个小 key
  • 或者,定时检查 Redis 是否存在大 key ,如果该大 key 是可以删除的
  • 不要使用 DEL 命令删除,因为该命令删除过程会阻塞主线程
  • 而是用 unlink 命令(Redis 4.0+)删除,该命令的删除过程是异步的,不会阻塞主线程