这篇我们来看下Redis持久化中常见的运维问题,主要从fork
操作、进程外开销、AOF追加阻塞这三个方面来学习。
fork操作
当 Redis 做 RDB 或者 AOF 重写时,必然要进行 fork 操作,对于 OS 来说,fork 都是一个重量级操作。而且,fork 还会拷贝一些数据,虽然不会拷贝主进程所有的物理空间,但会复制主进程的空间内存页表。对于 10GB 的 Redis 进程,需要复制大约 20MB 的内存页表,因此 fork 操作耗时跟进程总内存量息息相关,再加上,如果使用虚拟化技术,例如 Xen 虚拟机,fork 会更加耗时。
一个正常的 fork 耗时大概在 20毫秒左右。为什么呢,假设一个 Redis 实例的 OPS 在 5 万以上,如果 fork 操作耗时在秒级,那么僵拖慢几万条命令的执行,对生产环境影响明显。
我们可以在 Info stats 统计中查询 latestforkusec 指标获取最近一次 fork 操作耗时,单位微秒。
- 同步操作(执行期间 阻塞主进程)
- 与内存量息息相关:内存越大,耗时越长(与机器类型有关)
- info:latest_fork_usec (最近一次fork操作的时常,微秒)
子进程开销和优化
fork 完毕之后,会创建子进程,子进程负责 RDB 或者 AOF 重写,这部分过程主要涉及到 CPU,内存,硬盘三个地方的优化。
CPU:
写入文件的过程是 CPU 密集的过程,通常子进程对单核 CPU 利用率接近 90%。 如何优化呢?既然是 CPU 密集型操作,就不要绑定单核 CPU,因为这样会和父 CPU 进行竞争。同时,不要和其他 CPU 密集型服务不是在一个机器上。如果部署了多个 Redis 实例,尽力保证统一时刻只有一个子进程执行重写工作
开销: RDB和AOF文件生成,属于CPU密集型
优化: 不做CPU绑定,不和CPU密集型部署内存
子进程通过 fork 操作产生,占用内存大小等同于父进程,理论上需要两倍的内存完成持久化操作,但 Linux 有 copy on write 机制,父子进程会共享相同的物理内存页,当父进程处理写操作时,会把要修改的页创建对应的副本,而子进程在 fork 操作过程中,共享整个父进程内存快照。 即——如果重写过程中存在内存修改操作,父进程负责创建所修改内存页的副本。这里就是内存消耗的地方。 如何优化呢?尽量保证同一时刻只有一个子进程在工作;避免大量写入时做重写操作。
开销: fork内存开销,copy-on-write
优化: 不允许单机多部署时进行大量的重写;linux优化策略 echo never > /sys/kernel/mm/transparent_hugepage/enabled硬盘
硬盘开销分析:子进程主要职责是将 RDB 或者 AOF 文件写入硬盘进行持久化,势必对硬盘造成压力,可通过工具例如 iostat,iotop 等,分析硬盘负载情况。
如何优化:
不要和其他高硬盘负载的服务放在一台机器上,例如 MQ,存储。
AOF 重写时会消耗大量硬盘 IO,可以开启配置 no-appendfsync-on-rewrite,默认关闭。表示在 AOF 重写期间不做 fsync 操作。
当开启 AOF 的 Redis 在高并发场景下,如果使用普通机械硬盘,每秒的写速率是 100MB左右,这时,Redis 的性能瓶颈在硬盘上,建议使用 SSD。
对于单机配置多个 Redis 实例的情况,可以配置不同实例分盘存储 AOF 文件,分摊硬盘压力。
开销: AOF和RDB文件写入
优化: 不要和高硬盘负载服务部署一起:存储服务,消息队列等;
no-annpendfsync-on-rewrite=yes;
根据写入量决定磁盘类型:例如ssd;
单机多实例持久化文件目录可以考虑分盘
AOF 追加阻塞
当开启 AOF 持久化时,常用的同步硬盘的策略是“每秒同步” everysec,用于平衡性能和数据安全性,对于这种方式,redis 使用另一条线程每秒执行 fsync 同步硬盘,当系统资源繁忙时,将造成 Redis 主线程阻塞。
通过上图可以发现:everysec 配置最多可能丢失 2 秒数据,不是 1 秒;如果系统 fsync 缓慢,将会导致 Redis 主线程阻塞影响效率。
问题定位:
- 发生 AOF 阻塞时,会输入日志。用于记录 AOF fsync 阻塞导致拖慢 Redis 服务的行为。
- 每当 AOF 追加阻塞事件发生时,在 info Persistence 统计中,aofdelayedfsync 指标会累加,查看这个指标方便定位 AOF 阻塞问题。
- AOF 同步最多运行 2 秒的延迟,当延迟发生时说明硬盘存在性能问题,可通过监控工具 iotop 查看,定位消耗 IO 的进程。