链条传动

砥砺前行,不忘初心!

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

fork操作

当Redis做RDB或AOF重写时, 一个必不可少的操作就是执行fork操作创建子进程, 对于大多数操作系统来说fork是个重量级错误。

虽然fork创建的子进程不需要拷贝父进程的物理内存空间, 但是会复制父进程的空间内存页表。

例如对于10GB的Redis进程,需要复制大约20MB的内存页表,因此fork操作耗时跟进程总内存量息息相关,如果使用虚拟化技术, 特别是Xen虚拟机, fork操作会更耗时。

 

fork耗时问题定位:

对于高流量的Redis实例OPS可达5万以上,如果fork操作耗时在秒级别将拖慢Redis几万条命令执行,对线上应用延迟影响非常明显。 
正常情况下fork耗时应该是每GB消耗20毫秒左右。 可以在info stats统计中查latest_fork_usec指标获取最近一次fork操作耗时, 单位微秒。

 

如何改善fork操作的耗时:

1) 优先使用物理机或者高效支持fork操作的虚拟化技术, 避免使用Xen。
2) 控制Redis实例最大可用内存, fork耗时跟内存量成正比, 线上建议每个Redis实例内存控制在10GB以内。
3) 合理配置Linux内存分配策略, 避免物理内存不足导致fork失败。
4) 降低fork操作的频率, 如适度放宽AOF自动触发时机, 避免不必要的全量复制等

 

子进程开销监控和优化

子进程负责AOF或者RDB文件的重写,它的运行过程主要涉及CPU、 内存、 硬盘三部分的消耗。

CPU

CPU开销分析

子进程负责把进程内的数据分批写入文件,这个过程属于CPU密集操作, 通常子进程对单核CPU利用率接近90%.

CPU消耗优化

Redis是CPU密集型服务, 不要做绑定单核CPU操作。由于子进程非常消耗CPU, 会和父进程产生单核资源竞争。
不要和其他CPU密集型服务部署在一起,造成CPU过度竞争。
如果部署多个Redis实例, 尽量保证同一时刻只有一个子进程执行重写工作

 

内存

内存消耗分析
子进程通过fork操作产生,占用内存大小等同于父进程,理论上需要两倍的内存来完成持久化操作,但Linux有写时复制机制(copy-on-write) 。 父子进程会共享相同的物理内存页, 当父进程处理写请求时会把要修改的页创建副本, 而子进程在fork操作过程中共享整个父进程内存快照。

内存消耗优化

1) 同CPU优化一样, 如果部署多个Redis实例, 尽量保证同一时刻只有一个子进程在工作。
2)避免在大量写入时做子进程重写操作, 这样将导致父进程维护大量页副本, 造成内存消耗。

注:
Linux kernel在2.6.38内核增加了Transparent Huge Pages( THP) , 支持huge page( 2MB) 的页分配, 默认开启。
当开启时可以降低fork创建子进程的速度, 但执行fork之后, 如果开启THP, 复制页单位从原来4KB变为2MB, 会大幅增加重写期间父进程内存消耗。
建议设置“sudo echo never>/sys/kernel/mm/transparent_hugepage/enabled”关闭THP。


硬盘

硬盘开销分析

子进程主要职责是把AOF或者RDB文件写入硬盘持久化。 势必造成硬盘写入压力。 根据Redis重写AOF/RDB的数据量, 结合系统工具如sar、 iostat、 iotop等, 可分析出重写期间硬盘负载情况。

硬盘开销优化

a) 不要和其他高硬盘负载的服务部署在一起。 如: 存储服务、 消息队列服务等。
b) AOF重写时会消耗大量硬盘IO, 可以开启配置no-appendfsync-onrewrite, 默认关闭。 表示在AOF重写期间不做fsync操作。
c) 当开启AOF功能的Redis用于高流量写入场景时, 如果使用普通机械磁盘, 写入吞吐一般在100MB/s左右, 这时Redis实例的瓶颈主要在AOF同步硬盘上。
d) 对于单机配置多个Redis实例的情况, 可以配置不同实例分盘存储AOF文件, 分摊硬盘写入压力。

注:配置no-appendfsync-on-rewrite=yes时, 在极端情况下可能丢失整个AOF重写期间的数据, 需要根据数据安全性决定是否配置。

 

AOF追加阻塞

当开启AOF持久化时, 常用的同步硬盘的策略是everysec, 用于平衡性能和数据安全性。 对于这种方式, Redis使用另一条线程每秒执行fsync同步硬盘。 当系统硬盘资源繁忙时, 会造成Redis主线程阻塞。

 

 

 

阻塞流程分析:

1) 主线程负责写入AOF缓冲区。
2) AOF线程负责每秒执行一次同步磁盘操作, 并记录最近一次同步时间。
3) 主线程负责对比上次AOF同步时间:
   ·如果距上次同步成功时间在2秒内, 主线程直接返回。
   ·如果距上次同步成功时间超过2秒, 主线程将会阻塞, 直到同步操作完成。

 

通过对AOF阻塞流程可以发现两个问题:

1) everysec配置最多可能丢失2秒数据, 不是1秒。
2) 如果系统fsync缓慢, 将会导致Redis主线程阻塞影响效率。

 

AOF阻塞问题定位:

1) 发生AOF阻塞时, Redis输出如下日志, 用于记录AOF fsync阻塞导致拖慢Redis服务的行为:
    Asynchronous AOF fsync is taking too long (disk is busy). Writing the AOF buffer
    without waiting for fsync to complete, this may slow down Redis
2) 每当发生AOF追加阻塞事件发生时, 在info Persistence统计中,aof_delayed_fsync指标会累加, 查看这个指标方便定位AOF阻塞问题。
3) AOF同步最多允许2秒的延迟, 当延迟发生时说明硬盘存在高负载问题, 可以通过监控工具如iotop,定位消耗硬盘IO资源的进程。优化AOF追加阻塞问题主要是优化系统硬盘负载。

 

多实例部署

Redis单线程架构导致无法充分利用CPU多核特性,通常的做法是在一台机器上部署多个Redis实例。 当多个实例开启AOF重写后, 彼此之间会产生对CPU和IO的竞争。

对于单机多Redis部署, 如果同一时刻运行多个子进程, 对当前系统影响将非常明显, 因此需要采用一种措施, 把子进程工作进行隔离。

Redis在info Persistence中为我们提供了监控子进程运行状况的度量指标。

基于以上指标, 可以通过外部程序轮询控制AOF重写操作的执行

 

流程说明:

1) 外部程序定时轮询监控机器(machine) 上所有Redis实例。
2) 对于开启AOF的实例, 查看(aof_current_sizeaof_base_size) /aof_base_size确认增长率。
3) 当增长率超过特定阈值(如100%) , 执行bgrewriteaof命令手动触发当前实例的AOF重写。
4) 运行期间循环检查aof_rewrite_in_progress和aof_current_rewrite_time_sec指标, 直到AOF重写结束。
5) 确认实例AOF重写完成后, 再检查其他实例并重复2)~4) 步操作。从而保证机器内每个Redis实例AOF重写串行化执行。

 

posted on 2020-12-07 18:39  链条君  阅读(161)  评论(0编辑  收藏  举报