05:持久化

05:持久化

1、本章知识

1.1 本章内容

  • 介绍RDB、 AOF的配置和运行流程, 以及控制持久化的相关命令, 如bgsave和bgrewriteaof。
  • 其次对常见持久化问题进行分析定位和优化。
  • 最后结合Redis常见的单机多实例部署场景进行优化
  • RDB和AOF 混合模式

1.2 重点

  • 1) Redis提供了两种持久化方式: RDB和AOF。
  • 2) RDB使用一次性生成内存快照的方式, 产生的文件紧凑压缩比更高, 因此读取RDB恢复速度更快。 由于每次生成RDB开销较大, 无法做到实时持久化, 一般用于数据冷备和复制传输。
  • 3) save命令会阻塞主线程不建议使用, bgsave命令通过fork操作创建子进程生成RDB避免阻塞。
  • 4) AOF通过追加写命令到文件实现持久化, 通过appendfsync参数可以控制实时/秒级持久化。 因为需要不断追加写命令, 所以AOF文件体积逐渐变大, 需要定期执行重写操作来降低文件体积。
  • 5) AOF重写可以通过auto-aof-rewrite-min-size和auto-aof-rewritepercentage参数控制自动触发, 也可以使用bgrewriteaof命令手动触发。
  • 6) 子进程执行期间使用copy-on-write机制与父进程共享内存, 避免内存消耗翻倍。 AOF重写期间还需要维护重写缓冲区, 保存新的写入命令避免数据丢失。
  • 7) 持久化阻塞主线程场景有: fork阻塞和AOF追加阻塞。 fork阻塞时间跟内存量和系统有关, AOF追加阻塞说明硬盘资源紧张。
  • 8) 单机下部署多个实例时, 为了防止出现多个子进程执行重写操作,建议做隔离控制, 避免CPU和IO资源竞争。

2、RDB

RDB (redis database snapshot redis数据库快照)

RDB持久化是把当前进程数据生成快照保存到硬盘的过程, 触发RDB持久化过程分为手动触发自动触发

2.1 触发机制

  • 手动触发分别为 save 和 bgsave 命令:
    • save 命令:阻塞当前redis服务器,所以线上环境不建议使用

      19317:M 22 Nov 16:24:30.418 * DB saved on disk

    • bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。 阻塞只发生在fork阶段, 一般时间很短

      3970:C 22 Nov 16:37:48.704 * RDB: 6 MB of memory used by copy-on-write
      19317:M 22 Nov 16:37:48.715 * Background saving terminated with success

两个命令的不同之处,save用redis主进程进行同步转储,而bgsave则由子进程在后台进行转储

2.2 流程说明

bgsave是主流的触发RDB持久化方式, 下图了解它的运作流程

  • 运作流程
    • 1) 执行bgsave命令, Redis父进程判断当前是否存在正在执行的子进程, 如RDB/AOF子进程, 如果存在bgsave命令直接返回。

    • 2) 父进程执行fork操作创建子进程, fork操作过程中父进程会阻塞, 通过info stats命令查看latest_fork_usec选项, 可以获取最近一个fork操作的耗时, 单位为微秒

    • 3) 父进程fork完成后, bgsave命令返回“Background saving started”信息并不再阻塞父进程, 可以继续响应其他命令。

      ps命令会看到redis-rdb-bgsave子进程

    • 4) 子进程创建RDB文件, 根据父进程内存生成临时快照文件, 完成后对原有文件进行原子替换。 执行lastsave命令可以获取最后一次生成RDB的时间, 对应info统计的rdb_last_save_time选项。

      使用写时复制(copy-on-write)机制来实现快照持久化

    • 5) 进程发送信号给父进程表示完成, 父进程更新统计信息, 具体见info Persistence下的rdb_*相关选项。

19317:M 22 Nov 16:37:48.669 * Background saving started by pid 3970
3970:C 22 Nov 16:37:48.703 * DB saved on disk
3970:C 22 Nov 16:37:48.704 * RDB: 6 MB of memory used by copy-on-write
19317:M 22 Nov 16:37:48.715 * Background saving terminated with success
192.168.49.171:6392> bgsave
Background saving started  --父进程fork完成

192.168.49.171:6392> info persistence
# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1574411868  --执行lastsave命令可以获取最后一次生成RDB的时间,转成日期为(2019-11-22 16:37:48)
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
aof_enabled:1
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:0
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_current_size:17661740
aof_base_size:3046839
aof_pending_rewrite:0
aof_buffer_length:0
aof_rewrite_buffer_length:0
aof_pending_bio_fsync:0
aof_delayed_fsync:0

192.168.49.171:6392> info stats
# Stats
total_connections_received:37505
total_commands_processed:4151046
instantaneous_ops_per_sec:1
total_net_input_bytes:187212173
total_net_output_bytes:2059852448
instantaneous_input_kbps:0.07
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:2
sync_partial_ok:0
sync_partial_err:0
expired_keys:1
evicted_keys:0
keyspace_hits:597427
keyspace_misses:2815
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:5267  --最近一个fork操作的耗时5267毫秒
migrate_cached_sockets:0
192.168.49.171:6392> 

2.3 RDB文件处理

  • 存储文件:

RDB文件保存在dir配置指定的目录下, 文件名通过dbfilename配置指定。
可以通过执行config set dir{newDir}config setdbfilename{newFileName}运行期动态执行, 当下次运行时RDB文件会保存到新目录

当遇到坏盘或磁盘写满等情况时, 可以通过config set dir{newDir}在线修改文件路径到可用的磁盘路径, 之后执行bgsave进行磁盘切换, 同样适用于AOF持久化文件。

  • 校验:

如果Redis加载损坏的RDB文件时拒绝启动, 并打印如下日志:# Short read or OOM loading DB. Unrecoverable error, aborting now.这时可以使用Redis提供的redis-check-dump工具检测RDB文件并获取对应的错误报告.

①、save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave(这个命令下面会介绍,手动触发RDB持久化的命令)

默认如下配置:

save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存
当然如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。可以直接一个空字符串来实现停用:save ""

②、stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了

③、rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。
虽然压缩RDB会消耗CPU, 但可大幅降低文件的体积, 方便保存到硬盘或通过网络发送给从节点, `因此线上建议开启`。

④、rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

⑤、dbfilename :设置快照的文件名,默认是 dump.rdb

⑥、dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默认是和当前配置文件保存在同一目录。

也就是说通过在配置文件中配置的 save 方式,当实际操作满足该配置形式时就会进行 RDB 持久化,将当前的内存快照保存在 dir 配置的目录中,文件名由配置的 dbfilename 决定。

2.4 演示

192.168.49.171:6392> config get save
1) "save"
2) ""
192.168.49.171:6392> flushall
OK
192.168.49.171:6392> keys *
(empty list or set)
192.168.49.171:6392> config get save
1) "save"
2) ""
192.168.49.171:6392> config set save "900 1 300 10 60 100000"
OK
192.168.49.171:6392> config get save
1) "save"
2) "900 1 300 10 60 100000"
shell >cat redis_import_key_10w.py                            
# coding: utf-8
import redis

client = redis.StrictRedis(host='192.168.49.171',port=6392)
for i in range(11000): --导入11000个key
    client.pfadd("ids","user%d" % i)
    total = client.pfcount("ids")
    # if total != i 1:
    #     print total,i 1
    #     break
    print 11000,client.pfcount("ids")
您在 /var/spool/mail/root 中有邮件
[root@yace-slave1 python]# 
19317:M 22 Nov 18:04:33.058 * 10 changes in 300 seconds. Saving...    --触发了300 秒内如果至少有 10 个 key 的值变化则保存这一条
19317:M 22 Nov 18:04:33.059 * Background saving started by pid 12362
12362:C 22 Nov 18:04:33.082 * DB saved on disk
12362:C 22 Nov 18:04:33.083 * RDB: 8 MB of memory used by copy-on-write
19317:M 22 Nov 18:04:33.159 * Background saving terminated with success
  • 安装redis-rdb-tools
git clone https://github.com/sripathikrishnan/redis-rdb-tools
cd redis-rdb-tools
sudo python setup.py install
[root@node01 redis-rdb-tools]# rdb --command json  /opt/cachecloud/data/dump-6392.rdb      
[{
"a":"1",
"b":"2",
"c":"3"}]
[root@node01 redis-rdb-tools]# 

2.5 RDB的优缺点

  • RDB的优点:
    • RDB是一个紧凑压缩的二进制文件, 代表Redis在某个时间点上的数据快照。 非常适用于备份, 全量复制等场景。
      比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs) ,用于灾难恢复。
    • Redis加载RDB恢复数据远远快于AOF的方式。
  • RDB的缺点:
    • RDB方式数据没办法做到实时持久化/秒级持久化。 因为bgsave每次运行都要执行fork操作创建子进程, 属于重量级操作, 频繁执行成本过高。
    • RDB文件使用特定二进制格式保存, Redis版本演进过程中有多个格式的RDB版本, 存在老版本Redis服务无法兼容新版RDB格式的问题。
    • RDB当redis进程崩溃时或者出现硬件故障,两次RDB之间转存储的数据会永久丢失。

3、 AOF( append only file) 持久化

AOF持久化: 以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的 , AOF的主要作用是解决了数据持久化的实时性, 目前已经是Redis持久化的主流方式 。

3.1 使用AOF

开启AOF功能需要设置配置: appendonly yes, 默认不开启。 AOF文件名通过appendfilename配置设置, 默认文件名是appendonly.aof。 保存路径同RDB持久化方式一致, 通过dir配置指定。

配置参数

appendonly no 
appendfilename "appendonly.aof"

3 .2 AOF工作流程

AOF的工作流程操作: 命令写入(append) 、 文件同步(sync) 、 文件重写(rewrite) 、 重启加载(load)

  • 具体流程如下:
    • 1) 所有的写入命令会追加到aof_buf( 缓冲区)中。
    • 2) AOF缓冲区根据对应的策略向硬盘做同步操作。
    • 3) 随着AOF文件越来越大, 需要定期对AOF文件进行重写, 达到压缩的目的。
    • 4) 当Redis服务器重启时, 可以加载AOF文件进行数据恢复。

3.3 AOF 命令写入

AOF命令写入内容是采用文本协议格式。

3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n

  • 采用理由:

    • 1)文本协议兼容性好
    • 2)开启AOF,命令是追加操作,避免二次开销
    • 3)文本协议有可读性,方便修改和处理
  • AOF命令追加到aof_buf中?

    • Redis使用单线程响应命令, 如果每次写AOF文件命令都直接追加到硬盘, 那么性能完全取决于当前硬盘负载。
    • Redis可以提供多种缓冲区同步硬盘的策略, 在性能和安全性方面做出平衡。

3.4 文件同步

Redis提供了多种AOF缓冲区同步文件策略, 由参数appendfsync控制,不同值的含义。

  • 系统调用write和fsync说明:

    • write操作会触发延迟写(delayed write) 机制。

      Linux在内核提供页缓冲区用来提高硬盘IO性能。 write操作在写入系统缓冲区后直接返回。
      同步硬盘操作依赖于系统调度机制, 例如: 缓冲区页空间写满或达到特定时间周期。 同步文件之前, 如果此时系统故障宕机, 缓冲区内数据将丢失。

    • fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回, 保证了数据持久化。

    • 配置为always时, 每次写入都要同步AOF文件, 在一般的SATA硬盘上, Redis只能支持大约几百TPS写入, 显然跟Redis高性能特性背道而驰,不建议配置。

      • 配置为no, 由于操作系统每次同步AOF文件的周期不可控, 而且会加大每次同步硬盘的数据量, 虽然提升了性能, 但数据安全性无法保证。
      • 配置为everysec, 是建议的同步策略, 也是默认配置, 做到兼顾性能和数据安全性。 理论上只有在系统突然宕机的情况下丢失1秒的数据

配置参数

#appendfsync always
appendfsync everysec
# appendfsync no

3.5 重写机制(AOF rewrite)

随着命令不断写入AOF, 文件会越来越大, 为了解决这个问题, Redis引入AOF重写机制压缩文件体积。 AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。

  • AOF重写数据压缩的原理思想:

    redis中有某些键超时或者已过期,键值多次更新保留新值,多条写命令和并成一条(lpush list a,lpush list b);好处数据文件变小,重启加快别redis重载。

  • AOF重写过程可以手动触发和自动触发:

    • 手动触发: 直接调用bgrewriteaof命令。
    • 自动触发: 根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。

配置参数

auto-aof-rewrite-min-size 64MB  --AOF文件重写最小的文件大小
auto-aof-rewrite-percentage 100 --当前AOF文件比上次重写的增长比例大小
no-appendfsync-on-rewrite no  --在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,
no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。
设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。`Linux的默认fsync策略是30秒``可能丢失30秒数据`。默认值为no。
  • 默认触发规则:
    • 当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

自动触发时机=aof_current_size>auto-aof-rewrite-minsize&&( aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewritepercentage
其中aof_current_sizeaof_base_size可以在info Persistence统计信息中查看。

192.168.49.171:6392> info persistence
# Persistence
aof_current_size:18234821
aof_base_size:3046839
  • AOF重写运作流程

  • 流程说明:

    • 1) 执行AOF重写请求。
      如果当前进程正在执行AOF重写, 请求不执行并返回如下响应:

    ERR Background append only file rewriting already in progress

    如果当前进程正在执行bgsave操作, 重写命令延迟到bgsave完成之后再执行, 返回如下响应:

    Background append only file rewriting scheduled

    • 2) 父进程执行fork创建子进程, 开销等同于bgsave过程。
    • 3.1) 主进程fork操作完成后, 继续响应其他命令。 所有修改命令依然写入AOF缓冲区并根据appendfsync策略同步到硬盘, 保证原有AOF机制正确性。
    • 3.2) 由于fork操作运用写时复制技术, 子进程只能共享fork操作时的内存数据。 由于父进程依然响应命令, Redis使用“AOF重写缓冲区”保存这部分新数据, 防止新AOF文件生成期间丢失这部分数据。
    • 4) 子进程根据内存快照, 按照命令合并规则写入到新的AOF文件。 每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制, 默认为32MB, 防止单次刷盘数据过多造成硬盘阻塞。
    • 5.1) 新AOF文件写入完成后, 子进程发送信号给父进程, 父进程更新统计信息, 具体见info persistence下的aof_*相关统计。
    • 5.2) 父进程把AOF重写缓冲区的数据写入到新的AOF文件。
    • 5.3) 使用新AOF文件替换老文件, 完成AOF重写。

3.6 重启加载

redis 3.2版本

AOF和RDB文件都可以用于服务器重启时的数据恢复

  • 流程说明:

    • 1) AOF持久化开启且存在AOF文件时, 优先加载AOF文件, 打印如下日志:
    • DB loaded from append only file: 5.841 seconds
    • 2) AOF关闭或者AOF文件不存在时, 加载RDB文件, 打印如下日志:
    • DB loaded from disk: 5.586 seconds
    • 3) 加载AOF/RDB文件成功后, Redis启动成功。
    • 4) AOF/RDB文件存在错误时, Redis启动失败并打印错误信息。

    3.7 文件校验

    加载损坏的AOF文件时会拒绝启动, 并打印如下日志:

    Bad file format reading the append only file: make a backup of your AOF file,then use edis-check-aof --fix <filename>

对于错误格式的AOF文件, 先进行备份, 然后采用redis-check-aof–fix命令进行修复, 修复后使用diff-u对比数据的差异, 找出丢失的数据, 有些可以人工修改补全。
AOF文件可能存在结尾不完整的情况, 比如机器突然掉电导致AOF尾部文件命令写入不全。
Redis为我们提供了aof-load-truncated配置来兼容这种情况, 默认开启。 加载AOF时, 当遇到此问题时会忽略并继续启动, 同时打印

如下警告日志:

# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 397856725 !!!
# AOF loaded anyway because aof-load-truncated is enabled
  • 配置参数
aof-load-truncated yes

4 、RDB-AOF混合持久化

redis4.0x之后混合持久化

开启了混合持久化,aof在重写时,不再是单纯将内存数据转换为RESP命令写入aof文件,而是将重写这一刻之前的内存做rdb快照处理,
并且将rdb快照内容和增量的aof修改内存数据的命令存在一起,都写入新的aof文件,新的aof文件一开始不叫appendonly.aof,
等到重写完成后,新的aof文件才会进行改名,原子的覆盖原有的aof文件,完成新旧两个aof文件的替换。
于是在redis重启的时候,可以先加载rdb文件,然后再重放增量的aof日志就可以完全替代之前的aof全量文件重放,因此重启效率大幅得到提高。

[原理](https://yq.aliyun.com/articles/193034)

  • 配置参数
aof-use-rdb-preamble yes --4.0版本的混合持久化默认关闭的,通过aof-use-rdb-preamble配置参数控制,yes则表示开启,no表示禁用,默认是禁用的,可通过config set修改。
混合持久化过程
127.0.0.1:6379> info server
# Server
redis_version:5.0.6
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:24cefa6406f92a1f
redis_mode:standalone
os:Linux 3.10.0-862.el7.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:8.3.0
process_id:1
run_id:d9c2b6c262f5dec88de6a869aed1c481111ba582
tcp_port:6379
uptime_in_seconds:264540
uptime_in_days:3
hz:10
configured_hz:10
lru_clock:14384904
executable:/data/redis-server
config_file:
127.0.0.1:6379> config get aof-use-rdb-preamble
1) "aof-use-rdb-preamble"
2) "yes"
127.0.0.1:6379> debug populate 10000
OK
127.0.0.1:6379> save
OK
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> 
  • 当我们开启了混合持久化时,启动redis依然优先加载aof文件,aof文件加载可能有两种情况如下:
    • aof文件开头是rdb的格式, 先加载 rdb内容再加载剩余的 aof。
    • aof文件开头不是rdb的格式,直接以aof格式加载整个文件。
# head -10 appendonly.aof
REDIS0009dis-ver5.0.6edis-bitsctime]ed-mem   -- 此时的aof文件已经和只开启AOF持久化文件不一样了,上半部分是RDB持久化的数据,下半部分是AOF格式数据。
value:4158key:949       value:94key:9963
value:9963key:585       value:585key:244        value:24key:1707
value:170key:6249
value:624key:8052
value:805key:1440
value:144key:4474
value:4474key:941       value:94key:6500

5 、三种持久化的优缺点

RDB

  • 优点:
    • RDB 是一个非常紧凑(compact)的文件,体积小,因此在传输速度上比较快,因此适合灾难恢复。
    • RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
    • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
  • 缺点:
    • RDB是一个快照过程,无法完整的保存所以数据,尤其在数据量比较大时候,一旦出现故障丢失的数据将更多。
    • 当redis中数据集比较大时候,RDB由于RDB方式需要对数据进行完成拷贝并生成快照文件,fork的子进程会耗CPU,并且数据越大,RDB快照生成会越耗时。
    • RDB文件是特定的格式,阅读性差,由于格式固定,可能存在不兼容情况。

AOF

  • 优点:
    • 数据更完整,秒级数据丢失(取决于设置fsync策略)。
    • 兼容性较高,由于是基于redis通讯协议而形成的命令追加方式,无论何种版本的redis都兼容,再者aof文件是明文的,可阅读性较好。
  • 缺点:
    • 数据文件体积较大,即使有重写机制,但是在相同的数据集情况下,AOF文件通常比RDB文件大。
    • 相对RDB方式,AOF速度慢于RDB,并且在数据量大时候,恢复速度AOF速度也是慢于RDB。
    • 由于频繁地将命令同步到文件中,AOF持久化对性能的影响相对RDB较大,但是对于我们来说是可以接受的。

混合持久化

  • 优点:
    • 混合持久化结合了RDB持久化 和 AOF 持久化的优点, 由于绝大部分都是RDB格式,加载速度快,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。
  • 缺点:
    • 兼容性差,一旦开启了混合持久化,在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,阅读性较差

6 、问题定位与优化

Redis持久化功能一直是影响Redis性能的高发地, 本节我们结合常见的持久化问题进行分析定位和优化。

6.1 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自动触发时机, 避免不必要的全量复制。

6.2 子进程开销监控和优化

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

1.CPU

  • CPU开销分析。

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

CPU消耗优化。

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

2.内存

  • 内存消耗分析。

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

  • 内存消耗监控。 RDB重写时, Redis日志输出容如下:
* Background saving started by pid 7692
* DB saved on disk
* RDB: 5 MB of memory used by copy-on-write
* Background saving terminated with success

如果重写过程中存在内存修改操作, 父进程负责创建所修改内存页的副本, 从日志中可以看出这部分内存消耗了5MB, 可以等价认为RDB重写消耗了5MB的内存

AOF重写时, Redis日志输出容如下:

* Background append only file rewriting started by pid 8937
* AOF rewrite child asks to stop sending diffs.
* Parent agreed to stop sending diffs. Finalizing AOF...
* Concatenating 0.00 MB of AOF diff received from parent.
* SYNC append only file rewrite performed
* AOF rewrite: 53 MB of memory used by copy-on-write
* Background AOF rewrite terminated with success
* Residual parent diff successfully flushed to the rewritten AOF (1.49 MB)
* Background AOF rewrite finished successfully

父进程维护页副本消耗同RDB重写过程类似, 不同之处在于AOF重写需要AOF重写缓冲区, 因此根据以上日志可以预估内存消耗为:
53MB 1.49MB, 也就是AOF重写时子进程消耗的内存量

编写shell脚本根据Redis日志可快速定位子进程重写期间内存过度消耗情况。

  • 内存消耗优化

    • 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。

3.硬盘(IO)

  • 硬盘(IO)开销分析

子进程主要职责是把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重写期间的数据, 需要根据数据安全性决定是否配置。

6.3 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 bufferwithout waiting for fsync to complete, this may slow down Redis

    • 2) 每当发生AOF追加阻塞事件发生时, 在info Persistence统计中,aof_delayed_fsync指标会累加, 查看这个指标方便定位AOF阻塞问题。
    • 3) AOF同步最多允许2秒的延迟, 当延迟发生时说明硬盘存在高负载问题, 可以通过监控工具如iotop, 定位消耗硬盘IO资源的进程。优化AOF追加阻塞问题主要是优化系统硬盘负载

7 、多实例部署问题

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重写结束。3485) 确认实例AOF重写完成后, 再检查其他实例并重复2) ~4) 步操作。从而保证机器内每个Redis实例AOF重写串行化执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值