参考文档:
https://blog.csdn.net/weixin_40074110/article/details/125519332
https://www.cnblogs.com/zyf98/p/15934058.html
RDB:
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename "dump_6379.rdb"
rename-command FLUSHDB ""
rename-command FLUSHALL ""
1.触发rdb持久化的四种条件:
1.执行save命令;save命令会导致主进程执行RDB,这个过程中其他所有命令都会被阻塞的。只有数据迁移时候,可能会用到这个命令。其他时候,慎用啊
2.执行bgsave命令;这个命令执行后会开启独立的进程完成RDB,主进程可以持续处理用户的请求,主进程不受影响。
3.Redis停机时候;Redis停机的时候(shutdown),会执行一次save命令,实现RDB持久化。
4.触发RDB条件时候。save 60 10000,save 300 10,save 900 1分别是触发RDB持久化的三个条件((新增、修改和删除,注意对同一Key的多次修改,修改多少次就算多少次),三个条件是独立的
,save 60 10000为例,是指的超过60S后开始,积累的dirty key大于等于10000时触发持久化。触发任何一个都会进行bgsave操作,执行bgsave命令时,Redis主进程会fork一个子进程来完成RDB的过程,会
先将数据写入到一个临时二进制文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件(可以理解为Copy On Write机制)。
2.save m n 配置的实现原理:
通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。Redis会每隔100ms执行serverCron函数,会检查所有save m n的配置,只要满足一个save m n则执行bgsave。
当前时间-lastsave > m
dirty >= n
3.RDB持久化原理
最近生产环境中某个Set的Redis集群经常出现短暂的内存降低现象,经过查看日志是因为在RDB持久化所造成的内存突降(日志中:RDB: 4929 MB of memory used by copy-on-write ),
其根本原理是RDB持久化的过程中,Redis借助操作系统提供的写时复制技术(Copy-On-Write,COW),在执行bgsave(snapshot)快照的同时,会间接消耗额外的内存。
RDB是一次的全量备份,即周期性的把Redis当前内存中的全量数据写入到一个快照文件中。Redis是单线程程序,这个线程要同时负责多个客户端的读写请求,
还要负责周期性的把当前内存中的数据写到快照文件中RDB中,数据写到RDB文件是IO操作,IO操作会严重影响Redis的性能,甚至在持久化的过程中,读写请求会阻塞,
为了解决这些问题,Redis需要同时进行读写请求和持久化操作,这样又会导致另外的问题:持久化的过程中,内存中的数据还在改变,假如Redis正在进行持久化一个大的数据结构,
在这个过程中客户端发送一个删除请求,把这个大的数据结构删掉了,这时候持久化的动作还没有完成,那么Redis该怎么办呢?
于是Redis使用操作系统的多进程写时复制(Copy On Write)机制来实现快照的持久化,在持久化过程中调用glibc(Linux下的C函数库)的函数fork()产生一个子进程,快照持久化完全交给子进程来处理,
父进程继续处理客户端的读写请求。子进程刚刚产生时,和父进程共享内存里面的代码段和数据段,也就是说,父子进程的虚拟空间不同,但其对应的物理空间(内存区)是同一个。
这是Linux操作系统的机制,为了节约内存资源,所以尽可能让父子进程共享内存,这样在进程分离的一瞬间,内存的增长几乎没有明显变化。
写时复制技术:
如果主线程收到的客户端的读写请求,需要修改某块数据,那么这块数据就会被复制一份到内存,生成该数据的副本,主进程在该副本上进行修改操作。
所以即使对某个数据进行了修改,Redis持久化到RDB中的数据也是未修改的数据,这也是把RDB文件称为"快照"文件的原因,
子进程所看到的数据在它被创建的一瞬间就固定下来了,父进程修改的某个数据只是该数据的复制品。这里再深入一点,Redis内存中的全量数据由一个个的"数据段页面"组成,
每个数据段页面的大小为4K,客户端要修改的数据在哪个页面中,就会复制一份这个页面到内存中,这个复制的过程称为"页面分离",在持久化过程中,随着分离出的页面越来越多,
内存就会持续增长,但是不会超过原内存的2倍,因为在一次持久化的过程中,几乎不会出现所有的页面都会分离的情况,读写请求针对的只是原数据中的小部分,
大部分Redis数据还是"冷数据"。正因为修改的部分数据会被额外的复制一份,所以会占用额外的内存,当在进行RDB持久化操作的过程中,与此同时如果持续往redis中写入的数据量越多,
就会导致占用的额外内存消耗越大。
那么在此期间写入的数据最终去哪了呢?
写入的数据还是存在了内存当中,并没有写入当前的持久化文件中,等到下次进行RDB持久化时才会把 ” 写入的数据 ” 落盘到RDB文件中。
bgsave:fork出的子进程开始根据父进程内存数据生成临时的快照文件,然后替换原文件。
如何查看redis持久化信息?
127.0.0.1:6379> info persistence
# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1697179568
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
rdb_last_cow_size:516096
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
aof_last_cow_size:0
module_fork_in_progress:0
module_fork_last_cow_size:0
这里解释一下几个跟 RDB 相关的参数:
rdb_changes_since_last_save:自上次 RDB 后,Redis 数据的改动条数
rdb_bgsave_in_progress:bgsave 是否在进行中,0 否,1 是
rdb_last_save_time:上次 bgsave 的时间戳
rdb_last_bgsave_status:上次 bgsave 的状态
rdb_last_bgsave_time_sec:上次 bgsave 的持续时间
rdb_current_bgsave_time_sec:正在执行的 bgsave 耗时,如果没有正在执行的,则为 -1
rdb_last_cow_size:上一次RDB保存操作期间写时复制分配的大小(以字节为单位)
4.flushall,flushdb,shutdown测试结果:
1、flushall 清空所有数据库缓存并立即执行持久化操作,也就是rdb文件会发生改变,变成92个字节大小(初始状态下为76字节),所以执行flushall之后数据库真正意义上清空了.
2、flushdb 清空当前数据库但并不立即执行持久化操作,会等待save 60 10000配置触发持久化操作,这个时候如果shutdown会立即执行持久化操作。
3.showdown不会清空数据库。
5. RDB的优点
RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复。
RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。
RDB的缺点
耗时、耗性能。RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,
AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。
不可控、丢失数据。如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你。虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,
你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据。
6.触发RDB持久化查看日志文件redis_6379.log
触发条件:save 60 10000
java程序:for (int i=0;i<100000;i++)
{
jedis.set("name"+i,"tomandjjjyyywhoismyname"+i);
}
11:09:12 启动redis
11:15:25 执行java程序
11:15:28.532 触发10000 changes in 60 seconds,然后开始持久化
11:15:28.632 持久化结束
11:16:29.062再次触发触发10000 changes in 60 seconds,然后开始持久化
11:16:29.163 持久化结束
注意:RDB: 0 MB of memory used by copy-on-write,是因为java程序没有写脏页
3965945:C 13 Oct 2023 11:09:12.109 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
3965945:C 13 Oct 2023 11:09:12.109 # Redis version=6.0.5, bits=64, commit=00000000, modified=0, pid=3965945, just started
3965945:C 13 Oct 2023 11:09:12.109 # Configuration loaded
3965946:M 13 Oct 2023 11:09:12.111 * Running mode=standalone, port=6379.
3965946:M 13 Oct 2023 11:09:12.111 # Server initialized
3965946:M 13 Oct 2023 11:09:12.111 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
3965946:M 13 Oct 2023 11:09:12.111 * Loading RDB produced by version 6.0.5
3965946:M 13 Oct 2023 11:09:12.112 * RDB age 158897 seconds
3965946:M 13 Oct 2023 11:09:12.112 * RDB memory usage when created 0.85 Mb
3965946:M 13 Oct 2023 11:09:12.112 * DB loaded from disk: 0.000 seconds
3965946:M 13 Oct 2023 11:09:12.112 * Ready to accept connections
3965946:M 13 Oct 2023 11:15:28.532 * 10000 changes in 60 seconds. Saving...
3965946:M 13 Oct 2023 11:15:28.532 * Background saving started by pid 3966668
3966668:C 13 Oct 2023 11:15:28.542 * DB saved on disk
3966668:C 13 Oct 2023 11:15:28.543 * RDB: 0 MB of memory used by copy-on-write
3965946:M 13 Oct 2023 11:15:28.632 * Background saving terminated with success
3965946:M 13 Oct 2023 11:16:29.062 * 10000 changes in 60 seconds. Saving...
3965946:M 13 Oct 2023 11:16:29.063 * Background saving started by pid 3966814
3966814:C 13 Oct 2023 11:16:29.143 * DB saved on disk
3966814:C 13 Oct 2023 11:16:29.144 * RDB: 0 MB of memory used by copy-on-write
3965946:M 13 Oct 2023 11:16:29.163 * Background saving terminated with success
AOF:
appendonly yes
appendfilename "appendonly_6379.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
rename-command FLUSHDB ""
rename-command FLUSHALL ""
1.appendfsync的三种选项:
appendfsync always 每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全。
appendfsync everysec 每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
appendfsync no 从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。
测试:通过java程序循环set 10W个KEY,发现everysec和no性能一样,而always确实慢一些
always: 16:51:22-16:52:10 48秒
everysec: 16:45:20-16:45:50 30秒
no: 16:46:43-16:47:13 30秒
2.AOF重写触发条件:
1).自动触发:
在redis.conf中有如下两个配置项来决定是否触发aof重写操作:
auto-aof-rewrite-percentage 100 指当前aof文件比上次重写的增长比例大小,达到这个大小就进行 aof 重写。
auto-aof-rewrite-min-size 64mb 最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了。
在 aof 文件小于64mb的时候不进行重写,当到达64mb的时候,就重写一次。重写后的 aof 文件可能是10mb。上面配置了auto-aof-rewrite-percentage 100,即 aof 文件到了20mb的时候,又可以开始重写。
2).手动触发:
也可以通过执行bgrewriteaof来手动触发。
3.AOF重写触发条件:
AOF文件重写过程与RDB快照bgsave工作过程类似似,都是通过fork子进程,由子进程完成相应的操作。
开始bgrewriteaof,判断当前有没有bgsave命令(RDB持久化)/bgrewriteaof在执行,倘若有,则这些命令执行完成以后在执行。
主进程fork出子进程来进行aof重写操作。rewrite会像replication一样,创建一个临时文件,遍历数据库,将每个key、value对输出到临时文件。输出格式就是Redis的命令
主进程fork完子进程后继续接受客户端请求。此时,客户端的写请求不仅仅写入aof_buf缓冲区,还写入aof_rewrite_buf重写缓冲区。一方面是写入aof_buf缓冲区并根据appendfsync策略同步到磁盘,
保证原有AOF文件完整和正确。另一方面写入aof_rewrite_buf重写缓冲区,保存fork之后的客户端的写请求,防止新AOF文件生成期间丢失这部分数据。
子进程写完新的AOF文件后,向主进程发信号,主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
flushall,flushdb测试结果:flushall会清空缓存并持久化至appendonly.aof