redis是内存数据库,进程退出之后为保证数据能恢复就有了持久化。redis持久化分为下面两种:
(1) 手动执行命令触发
save: save命令会阻塞redis主进程,知道RDB完毕,线上应该杜绝。
bgsave: 会fork一个子进程来执行,只有fork子进程时会阻塞redis主进程,其余都是不影响redis主进程的。
SHUTDOWN命令也会触发。
(2) 自动触发 save m n
在配置文件中配置 save m n 指定在m秒内数据发生n次变化就执行rdb持久化。
上图表示:三种情况任意一种满足就执行bgsave。
redis通过serverCron函数、dirty计数器、和lastsave时间戳来实现save m n的。
serverCron函数
是redis的一个定时器,每隔100ms执行一次,redis停止,则停止,用来维护redis服务的状态,其中有一项就是:检查save m n 配置条件是否满足,满足就执行bgsave。
dirty计数器
用来标记上一次save/bgsave执行后,服务器进行了多少次修改,增加,删除操作 。当执行save/bgsave后就置为0。
lastsave时间戳
记录的是上一次成功执行save/bgsave的时间。
save m n的原理如下
每隔100ms,执行serverCron函数;在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足,就进行bgsave。对于每一个save m n条件,只有下面两条同时满足时才算满足:
(1)当前时间-lastsave > m
(2)dirty >= n
(1) fork时的注意?
fork 会消耗一定时间,并且父子进程所占据的内存是相同的,当 Redis 存储的数据较大时,fork 的时间会很长,这段时间内 Redis 是无法响应其他命令的。除此之外,Redis 占据的内存空间也会翻倍。
(2) 为什么redis不启动线程来执行rdb操作?
如果在主进程内启动一个线程,这样会造成对数据的竞争条件。所以为了避免使用锁降低性能,Redis选择启动新的子进程,独立拥有一份父进程的内存拷贝,以此为基础执行RDB持久化。
(1) 存储路径
redis.conf存储路径设置
## 指定目录
dir "/usr/local/yunji/redis/data"
# 指定rdb文件
dbfilename "dump-14159.rdb"
(2) RDB文件格式
----------------------------# RDB文件是二进制的,所以并不存在回车换行来分隔一行一行.
52 45 44 49 53 # 以字符串 "REDIS" 开头
30 30 30 33 # RDB 的版本号,大端存储,比如左边这个表示版本号为0003
----------------------------
FE 00 # FE = FE表示数据库编号,Redis支持多个库,以数字编号,这里00表示第0个数据库
----------------------------# Key-Value 对存储开始了
FD $length-encoding # FD 表示过期时间,过期时间是用 length encoding 编码存储的
$value-type # 1 个字节用于表示value的类型,比如set,hash,list,zset等
$string-encoded-key # Key 值,通过string encoding 编码
$encoded-value # Value值,根据不同的Value类型采用不同的编码方式
----------------------------
FC $length-encoding # FC 表示毫秒级的过期时间
$value-type # 同上,也是一个字节的value类型
$string-encoded-key # 同样是以 string encoding 编码的 Key值
$encoded-value # 同样是以对应的数据类型编码的 Value 值
----------------------------
$value-type # 下面是没有过期时间设置的 Key-Value对,为防止冲突,数据类型不会以 FD, FC, FE, FF 开头
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding # 下一个库开始,库的编号用 length encoding 编码
----------------------------
... # 继续存储这个数据库的 Key-Value 对
FF ## FF:RDB文件结束的标志
(3) RDB常用配置
(4) 线上建议
RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据备份。非常适合备份,全量复制等场景。比如每6小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中,用于灾难恢复。
开启aof
##开启aof
appendonly yes
## aof文件名称
appendfilename appendonly.aof
## 目录
dir /usr/local/redis/data
文件格式
*2 # 2个参数
$6 # 第一个参数长度为 6
SELECT # 第一个参数
$1 # 第二参数长度为 1
8 # 第二参数
*3 # 3个参数
$3 # 第一个参数长度为 4
SET # 第一个参数
$4 # 第二参数长度为 4
name # 第二个参数
$4 # 第三个参数长度为 4
Jhon # 第二参数长度为 4
上面aof内容就对应 redis 命令:SELECT 8;SET name Jhon.
执行流程
命令追加(append)
redis先将命令放到缓冲区,然后在写入磁盘,避免了频繁写磁盘。
文件写入(write)和文件同步(sync)
为了提高写入效率,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。
AOF缓存区的同步文件策略由参数appendfsync控制,各个值的含义如下:
文件重写(rewrite)
时间越长Redis写命令越来越多,AOF会越来越大。于是Redis在指定时间内,会重写AOF文件,需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作!
# 假设服务器对键list执行了以下命令s;
127.0.0.1:6379> RPUSH list "A" "B"
(integer) 2
127.0.0.1:6379> RPUSH list "C"
(integer) 3
127.0.0.1:6379> RPUSH list "D" "E"
(integer) 5
127.0.0.1:6379> LPOP list
"A"
127.0.0.1:6379> LPOP list
"B"
127.0.0.1:6379> RPUSH list "F" "G"
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "C"
2) "D"
3) "E"
4) "F"
5) "G"
当前列表键list在数据库中的值就为[“C”, “D”, “E”, “F”, “G”]。要使用尽量少的命令来记录list键的状态,最简单的方式不是去读取和分析现有AOF文件的内容,而是直接读取list键在数据库中的当前值,然后用一条RPUSH list “C” “D” “E” “F” "G"代替前面的6条命令。
为了避免执行命令时造成客户端输入缓冲区溢出(个数过多),Redis会指定一个常量最大值64,即每条命令设置的元素的个数最多64。
文件重写触发
线上配置
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 40
文件重写的流程
AOF常用配置
关于 no-appendfsync-on-rewrite 配置
AOF其实分为两个步骤:
no-appendfsync-on-rewrite no 表示 rewrite时 ,主进程fsync继续写磁盘。如果内存数据过大,会导致子进程rewrite占用大量的磁盘IO,这样会影响主进程fsync从而阻塞主进程。
线上应该配置no-appendfsync-on-rewrite yes