什么是持久化
redis的数据时保存在内存中,持久化是将redis数据异步保存到磁盘上
持久化的方式
快照 rdb
日志 aof
RDB
rdb是快照文件(二进制文件),redis重启时会读取rdb文件载入之前保存的数据。
三种触发机制:
A.手动触发
a. save (同步)
b. bgsave (异步)
PS: save是同步的,意味着在保存快照的过程中会阻塞其他操作。直到save执行完才能执行其他命令。如果要保存的数据很多就会阻塞的非常久。
如存在老的rdb文件,新替换老
save的复杂度为n
bgsave是异步的,他会使用Linux的fork()函数,这个函数作用是生成子进程,让子进程完成RDB快照的生成保存。此时客户端执行的其他命令的执行不会被阻塞(阻塞发生在fork)。当rdb快照生成以后,子进程会发送成功信号给主进程,让主进程关掉子进程。
除了要知道bgsave要用到fork之外,还要知道用到了cow(copy-on-write,写时复制),这意味着redis在fork出一个子进程之后,不会马上给子进程分配新的内存空间,而是和父进程共享同一个物理空间,指针指向的是同一块物理空间。只有在bgsave过程中,父进程发生写操作修改内存数据时,才会真正去分配内存空间,并复制内存数据,而且也只是复制被修改的内存页中的数据,并不是全部内存数据;
举个例子:
主进程和子进程现在都指向同一块内存空间,这个内存空间是由一块块小的内存块组成的,假设一共有10000块。
当主进程在bgsave过程中写入新数据set k1 v1, 此时会开辟一个新的内存空间(现在就有10001块内存块了),新的key就存在这第10001个内存块中。主进程就一共指向了10001个内存块,而子进程还是只用到原来的10000个内存块。
如果k1原本就存在,我是修改数据而不是新增数据,假如k1在第5000个内存块上,系统会开辟一块新的空间,将第5000块内存块复制一份到这个新的空间里。此时也是有10001块内存块。只不过主进程指向的是旧的9999块内存块 和 新复制的那一块内存块,而子进程是指向旧的那10000块内存块。
bgsave复杂度为n
save和bgsave的区别:
同步|异步
阻塞|fork子进程会阻塞
不会额外消耗内存|需要fork,额外消耗内存
阻塞客户端命令|不阻塞客户端命令
B.自动触发
a.按照配置文件定时触发
redis的配置文件有默认设置
save 900 1
save 300 10
save 60 10000
表示多少秒内执行了多少条写命令就会自动触发bgsave异步保存rdb
但是,redis的默认配置在实际开发中是不合适的。
因为60秒写10000条在稍微繁忙一点的系统中是很容易实现的。所以会造成频繁写入rdb的操作,给磁盘造成压力。
redis服务器会保存一个dirty计数器, 以及一个lastsave属性。
dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令 之后,服务器对数据库(服务器中的所有数据库)进行了多少次修 改。
lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行 SAVE命令或者BGSAVE命令的时间。
根据配置文件的rdb配置,以及redis服务器中的dirty 和 lastsave 决定当前是否进行bgsave操作。
Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执 行一次,它的其中一项工作 就是检查save选项所设置的保存条件是否已经满足,如果满足的话,就 执行BGSAVE命令。
其他配置:
dbfilename dump.rdb # rdb文件名
dir ./ # rdb文件存放位置
stop-writes-on-bgsave-error yes # bgsave发生错误时是否停止
rdbcompression yes # 是否对rdb压缩
rdbchecksum yes # 重启时是否检查rdb文件是否正确
系统默认配置有很多是不合理的。
最佳配置:
注释掉save,表示不开启自动生成rdb,因为子进程生成rdb快照很消耗CPU和内存
# redis是单线程,但系统一般是多核,为了充分利用多核的资源,一台机器上一般会布置多个redis,此时需要以端口命名rdb文件一个redis对应一个rdb,以免多个redis共用一个rdb,造成数据覆盖
dbfilename dump-${port}.rdb
# rdb文件放在大磁盘的目录
dir /bigdiskpath
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
b.全量复制时触发
进行主从复制时,主会自动生成rdb
c.执行debug reload时触发
这是一个重启命令,使用该命令重启时,内存的数据不会清空,并且会生成rdb
d.执行shutdown save关闭服务器时触发
rdb注意事项
服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作 完成为止。
rdb的问题
1 耗时耗性能
其时间复杂度为 n
fork生成子进程会消耗额外内存和CPU
2 部分数据易丢失
如果使用配置默认的save,那么如果在一段时间用户写入了数据但是还没达到触发条件,此时宕机,这段时间的数据没有存入rdb,会丢失
此时可以使用AOF
AOF
原理:将客户端执行的写命令写入aof日志文件,当重启redis时会读取日志文件的命令并执行一遍完成数据恢复
AOF的三种策略
always
每执行一条命令会立刻写入到aof
优点:不丢失数据
缺点:IO开销大
everysec
每秒执行一次写入aof
优点:IO开销没那么大
缺点:会丢1s的数据
no
由系统决定
优点:由操作系统自己决定,不用开发者操心
缺点:丢失的数据不可控
aof持久化原理和过程
每次执行客户端写命令的时候,redis不会直接将命令写入到aof日志文件中,而是先将命令写入到aof_buf缓冲区(每执行一条命令,就会将命令写入缓冲区),再按照上面三种策略从缓冲区fsync到磁盘文件中(fsync就是缓冲区把数据写入磁盘,fsync()是由内核执行的,不是由redis执行的,因此过程不会阻塞到主进程执行读写命令的,而且这是一个异步的系统调用,所以刷盘的过程也不会占用cpu和阻塞内核进程)。整个aof的过程是redis主进程完成的,没有产生子进程,而且redis进程做的也只是日志写入到缓冲区而已。
默认 everysec
AOF的问题
随着时间的推移,并发,执行的命令累积,aof文件会变的很大,造成的问题是使用aof恢复数据会很慢
解决方法:AOF重写
把过期的,没有用的,重复的命令化简,使得aof文件变小。
作用有二:减小磁盘占用,加快恢复数据速度
例如:
incr num # 执行一亿次,会有一亿条命令
aof会简化为 set num 100000000
AOF重写的两种方式:
a.手动执行bgrewriteaof
原理和bgsave相似:client执行bgrewrite,redis会fork一个子进程进行aof重写,这里的重写不是读取aof文件再简化里面的命令,而是对内存的一个回溯,根据内存数据生成一个aof新文件。在这个过程中redis主进程会正常执行客户端其他写操作,并将这些操作写入aof_rewrite_buf这个缓冲,再从这个缓冲写入到新aof中。最后新aof替代旧的aof文件。
aof appendfsync(过程1) 和 aof rewrite(过程2)的流程图
b.根据配置文件自动触发
auto-aof-rewrite-min-size # aof文件达到指定大小就会重写
auto-aof-rewrite-percentage # aof文件增长率达到指定值就会重写
必须两个条件同时触发才会重写。
当进行aof持久化(aof appendfsync)时会检测这两个条件,两个条件满足就会自动执行bgrewriteaof命令触发aof重写。
不要搞混了aof持久化(aof appendfsync)和aof重写,这是两个不同的独立的过程。但是他们一般会同时进行(子进程在执行aof重写的时候,父进程在产生新命令的aof日志)
AOF重写的注意事项
BGREWRITEAOF和BGSAVE两个命令不能同时执行,不能同时执行它们只是一个性能方面的考虑——并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写入操作,磁盘开销大了还在其次,子进程占用内存可能发生内存不足的情况。
aof的所有配置
appendonly yes # 开启aof持久化
appendfilename "appendonly-${port}.aof"
appendfsync everysec
dir /bigdiskpath
no-appendfsync-on-rewrite yes # 表示在进行aof重写时,停止命令写入(旧的)aof文件。可以节省性能。但是如果这段时间aof重写失败,旧的aof也没有更新命令,可能会丢失数据。需要作出权衡
auto-aof-rewrite-min-size 100
auto-aof-rewrite-percentage 64m
如果redis已经启动,可以使用 config set 动态配置。
AOF和RDB对比:
命令 RDB AOF
启动优先级 低 高
体积 小 大
恢复速度 块 慢
数据安全 丢数据 根据策略
轻重 重 轻
RDB最佳策略
关闭自动rdb (但不代表不做rdb持久化,可以隔一段时间手动执行,隔天隔天的在夜深人静的时候手动执行bgsave,保存了数据同时避开了高峰期开销)
集中管理(意思是如果你有多台节点进行同步,只让其中一台节点进行rdb持久化,其他节点共用这个rdb文件)
主从复制时,从节点可以开
AOF最佳策略
开
AOF重写集中管理 ( 多节点同步时,只让一台节点进行重写,其他节点共用这个aof文件)
everysec
最佳策略:
小分片
缓存或存储 ( 将数据存到数据库中,这部分数据在redis中设置过期时间,这样redis进行持久化的时候就可以不将这部分数据写入rdb或aof)
监控
保持足够的内存给其他程序,如fork子进程
===============================
持久化的常见运维问题
fork操作
之前说做bgsave的时候,redis会fork出一个子进程,子进程进行rdb备份,主进程可以继续执行其他写操作。
但是这里又说fork是同步操作,如果子进程进行rdb持久化操作的时间太长还是会阻塞主进程,这里不是很理解。
子进程开销和优化
CPU
开销:RDB和AOF文件的生成属于CPU密集型,就是很耗CPU
优化:不要做CPU绑定,即不要将redis进程绑定到一个CPU上,不然redis的子进程和主进程都消耗这个CPU,相当于是争CPU,对主进程的操作不利。
不要和CPU密集型程序部署在一起。
尽量少做一些aof重写和bgsave操作,节省资源
内存
开销:fork内存开销,copy-on-write
优化:no-appendfsync-on-rewrite=yes
硬盘
开销:AOF和RDB文件的写入