REDIS持久化的两种方式
RDB
将数据生成快照保存在磁盘
命令
bgsave:fork操作创建子进程,阻塞时间短
触发方式:
- redis配置文件/etc/redis.conf中设置自动持久化
save 900 1 表示每900秒有一次修改就自动bgsave
3. 从节点执行全量复制时,主节点自动bgsave生成RDB文件发送给从节点
4. 执行debug reload时也会触发save操作
5. 默认情况下,执行shutdown时,如果没有开启AOF持久化就会自动执行bgsave进RDB持久化
bgsave流程说明:
- 父进程判断是否存在子进程在进行持久化,如果存在就退出
- fork操作,产生子进程后返回:background saving started。父进程已经释放。latest_fork_usec是最近一次fork操作的耗时,单位(微妙)
- 子进程开始创建RDB文件:lastsave 可以看到最后一次save的时间
- 结束后子进程向父进程发送信号,父进程统计信息更新info persistence
save:阻塞redis,已经废弃
RDB文件
- 存储位置:config get dir; config get dbfilename
- 压缩存储建议开启:config set rdbcompression yes
- 校验文件是否损坏:工具redis-check-dump
RDB优缺点
紧凑压缩的二进制文件,加载快,适用于灾备。但是使用子进程是一个重量级操作不适合实时持久化,而且老版本无法兼容。故有了AOF(append only file)持久化方式
AOF
以独立日志的方式记录下每一个写命令,重启时在重新执行。解决了redis数据持久化的实时性问题。
开启使用
开启:config set appendonly yes
文件名:config get dir ;config get appendfilename; 默认为appendonly.aof
流程:
- 所有的写入命令会追加到aof_buf缓冲区中
- AOF缓冲区根据相应策略向硬盘做出同步操作
- 需要对aof文件定期重写压缩空间
- redis服务器重启时加载用于数据恢复
命令写入AOF文件格式
文本协议格式
例如:set hello world
写入AOF文件内:*3\r\n$3\r\nset\r\n$5\r\n\hello\r\n$5\r\nworld\r\n
优点:可读,便于修改;避免二次处理;兼容性好
文件同步策略
由参数appendfsync控制
- always:命令写入buf后调用系统fsync操作同步到AOF文件,完成后线程返回。(不建议)
- everysec:写入aof_buf后调用write操作写入缓冲区后即返回。每秒钟调用一次fsync操作。(默认,最多丢失2s的数据)
- no:写入aof_buf后调用write操作写入缓冲区后即返回。同步到硬盘的工作由操作系统进行,通常是30s。(安全性无法保证,不建议)
fsync操作:强制同步到硬盘,写入硬盘后线程返回,保证了持久化。
write操作:写入文件缓冲区页后即返回,缓冲区写满或者达到特定条件后进行持久化。如果中间发生宕机,缓冲区页内的数据可能会丢失。
重写机制
将进程内的数据转化为写命令重新写入AOF文件,压缩AOF文件大小,能够更快的加载。
触发方式:
手动触发:bgrewriteaof
自动触发:根据参数:auto-aof-rewrite-min-size(运行AOF重写时的aof文件最小体积)和auto-aof-rewrite-percentage(当前aof文件体积aof_current_size和上一次文件重写后的体积aof_base_size的比值)参数确定触发时机
当aof-current-size>auto-aof-rewrite-min-size
且(aof_current_size - aof_base_size)/aof_base_size>=auto-aof-rewrite-percentage
ps: aof_current_size 和 aof_base_size通过info persistence查看
重写流程:
- 父进程查看是否由fork操作,如无则创建子进程
- 父进程返回响应其他命令,所有操作记录进入AOF缓冲区,并根据appendfsync策略同步到硬盘;子进程的新数据写入AOF重写缓冲区
- 子进程根据内存快照将命令合并规则写入到新的AOF文件,每次批量写入硬盘,根据参数aof-rewrite-incremental-fsync控制,默认32M。
- 新AOF文件写入完成后,父进程更新info统计信息
- 父进程把AOF文件重写缓冲区中的数据写入到新的AOF文件中
- 替换新旧文件,完成重写。
重启加载机制
优先加载AOF文件,没有的话再看RDB文件
文件校验:
对于错误的文件无法加载时可以备份后进行修复:redis-check-aof --fix
掉电等故障可能会导致aof文件尾部写入不全。参数aof-load-truncated用来兼容此情况,默认开启,如果遇到此问题忽略并继续加载,打印警告日志。
问题优化和定位
fork操作
fork操作会复制父进程的空间内存页表。例如10G的redis进程需要复制约20M的内存页表。故fork操作耗时跟进程的总内存量息息相关。
fork耗时问题定位
正常情况下fork操作每GB耗时20ms左右。
查看上一次fork操作耗时:info stats 中latest_fork_usec(单位:微秒)
如何改善fork操作耗时:
- 使用物理机,避免使用Xen
- 控制redis实例最大可用内存,建议10G以内。
- 降低fork频率,适度放宽AOF自动触发时机,避免不必要的全量复制。
子进程开销
cpu
- redis 是cpu密集型服务,子进程可以消耗掉单核cpu的90%,所以不要做cpu的单核绑定。
- 保证只有一个子进程执行重写
内存
子进程通过fork操作产生,占用内存大小等同于父进程,理论上需要2倍内存来完成持久化操作。但是linux有写时复制机制(copy-on-write),
父子进程共享相同的物理内存页,父进程处理写请求时会把修改的内存页创建副本,子进程在fork过程中共享父进程整内存快照。
重写内存消耗情况:
监控redis日志
RDB重写:copy-on-write父进程创建的副本内存
AOF重写:copy-on-write父进程创建的副本内存+AOF缓冲区所占内存
内存消耗优化:
- 尽量保证只有一个子进程在工作
- 避免大量写入时做子进程重写工作,会导致父进程创建的内存页副本过多,内存消耗大
- linux有TransparentHugePage(默认开启),大页2M,当开启时复制页会从4k变到2M,会大幅增加重写期间的内存消耗。
关闭方式:sudo echo never > /sys/kernel/mm/transaparent_hugepage/enabled
硬盘
持久化到硬盘,硬盘写入压力,命令:sar/iostat/iotop 分析当前的硬盘负载情况。
优化方式:
- 不要和其他高硬盘负载的服务部署在一起,如存储服务、消息队列
- 开启配置:no-appendfsync-on-rewrite,表示重写期间不开启fsycnc操作
- 当开启AOF功能的redis并且应对高流量写入场景时,普通机械磁盘的吞吐在100MB/s。瓶颈是AOF同步硬盘上
- 如果是多redis实例,可以将AOF文件存储在不同的盘上,分摊写入压力
AOF追加阻塞
AOF持久化常用的硬盘同步策略是everysec,如果系统硬盘资源繁忙,会造成redis主线程的阻塞。
主线程每隔1秒进行一次对比,如果距离上次同步成功时间>2s,堵塞主线程
所以
- everysec可能会丢失2s的数据
- 硬盘资源的繁忙可能会导致redis主线程的堵塞
问题定位:
- 发生AOF阻塞时,redis日志中会输出相关内容
- 发生阻塞时,info persistence的统计中aof_delayed_fsync指标会累加
- iotop 可以看出硬盘的高负载问题
参考:《redis开发与运维》付磊、张益军著