Redis 大key对持久化有什么影响?

103 阅读5分钟

前言

Redis持久化方式有两种,AOF日志和RDB快照

Redis大key问题会造成客户端以及服务端的性能问题,例如:

  • 客户端超时阻塞:由于Redis执行命令过程是单线程处理,在操作大key时会比较耗时,导致很久没有响应
  • 网络阻塞:每次获取大key产生的网络流量比较大,如果一个key的大小是1MB,每秒访问量(QPS)为1000,那么每秒产生1000MB的流量,对于普通千兆网卡的服务器来说是灾难性的
  • 阻塞工作线程(主进程中):如果使用del命令删除大key,会耗时比较长,没办法处理后续命令
  • 内存分布不均:集群模型在slot分片均匀的情况下,大key会导致数据和查询倾斜,存有大key的Redis节点占用内存较大

实际上也有可能影响到持久化的性能,接下来具体分析

大Key对AOF日志的影响

我们知道Redis提供了3种AOF回写策略:

  • Always
  • Everysec
  • No

具体可参考 Redis持久化技术之AOF日志 学习笔记

这三种策略,在持久化大key时,分别有什么影响?

  • Always: 使用该策略时,主进程执行完命令后,会把数据写入到AOF日志,然后调用fsync()将内核缓冲区中的数据直接写入硬盘,等待硬盘写操作完成后,该函数才会返回。如果写入的是一个大key,执行fsync()的时间就会比较长,阻塞主进程,因为当写入数据量很大时,数据同步到硬盘这个过程是很耗时的。
  • Everysec: 由于是异步执行fsync(),所以大key持久化过程(数据同步到硬盘)不会影响主进程执行客户端命令
  • No: 永不执行fsync(),所以大key持久化过程也不会影响主进程执行客户端命令

大key对AOF重写的影响

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

AOF重写会fork一个子进程来处理任务,操作系统虽然不会直接复制父进程的物理内存,而是把父进程的页表复制一份给子进程,随着Redis存在越来越多的大key,就会占用很多内存,对应的页表就会越大,那么这个复制过程就会很耗时,而且由于fork函数是主进程调用的,发生阻塞就意味着阻塞Redis处理命令。

可以通过info 命令获取latest_fork_usec指标,表示Redis最近一次fork的耗时

大key对写时复制的影响

fork创建子进程期间,如果发生写操作会发生写时复制,这次如果父进程修改的是共享内存中的大key数据,那么被复制的物理内存也是比较大的,这个过程也是耗时的,会阻塞主进程

大key对RDB快照的影响

RDB持久化有两种方式,区别在于是否在主进程执行:

  • save
  • bgsave

如果采用bgsave命令,主进程会通过fork()创建子进程,此时父子进程共享同一片内存数据,页表执行同个物理内存空间。 具体可参考Redis持久化技术之RDB快照 学习笔记

那么一样在fork过程,如果是大key,同样会有两个阶段可能阻塞父进程:

  1. 创建子进程的过程,由于要负责父进程的页表等结构,大key对应的页表较大,阻塞时间较大
  2. 发生写时复制时,由于大key占用内存大,复制物理内存会比较耗时,阻塞父进程

如何优化?

如果fork耗时很大,例如超过1s,可以采取一些措施优化调整:

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

Linux内存大页

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

如果开启了内存大页,即使客户端请求只修改100B的数据,在发生写时复制时,也是会拷贝2MB的大页;而常规就只需要拷贝4KB,相比起来大页机制明显会拖慢写操作执行时间,影响Redis性能

内存大页机制默认是关闭,禁用方法如下:

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

如何避免大key

最好在设计阶段,就把大key拆分为一个一个小key,或者采用一些占用体积更小的编码格式,具体可参考这篇文章

或者定时检查Redis是否存在大key,如果大key是可以删除的,用unlink命令(Redis 4.0以上版本),因为该命令是异步的,不会阻塞主线程处理其它命令。

参考

《小林coding》