Redis的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证Redis的数据不会因为故障而丢失,这种机制就是Redis的持久化机制。
Redis的持久化有两种,第一种是快照,第二种是AOF日志。快照是一次性全量备份,AOF日志是连续的增量备份。
第一种快照是内存数据的二进制序列化形式,在存储上非常紧凑,而第二种AOF日志记录的是内存数据修改的指令记录文本。
AOF日志在长期的运行过程中会变得无比庞大,数据库重启时需要加载AOF日志进行指令重放,这个时间就会很漫长,所以需要定期进行AOF重写,给AOF日志瘦身。
快照(RDB)的原理:
Redis是单线程程序,这个线程要同时负责:
1、多个客户端socket的并发读写操作
2、内存数据结构的逻辑重写
在服务线上请求的同时,Redis还需要进行内存快照,内存快照要求Redis必须进行文件io操作,可是文件io操作不能使用多路复用的API。
这就意味着单线程在服务器上请求的同时,还要进行文件io操作,而文件io操作会严重拖累服务器的性能。
另外,为了不阻塞线上的业务,Redis需要一边持久化,一边相应客户端的请求。持久化的同时,内存数据结构还在变化,比如一个大型的hash字典正在持久化的时候,这时一个请求过来把它给删掉了,或者修改了,可是还没持久化完毕,这怎么办?Redis使用操作系统的多进程COW(Copy on Write)机制来实现快照持久化
Fork多进程
Redis在持久化的时候会调用glicb的函数fork产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端业务请求。子进程刚刚产生的时候和父进程共享内存里的代码段和数据段。
子进程做数据持久化的时候,不会修改现有内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘上。
但是父进程不一样,必须持续服务客户端的请求,然后对内存数据结构进行不断的修改。这个时候就会使用OS的COW机制来进行数据段页面的分离。
![e3ed3485d7b8f9e82b6c5a8968062725.png](https://i-blog.csdnimg.cn/blog_migrate/dc1f3b6f39e9a5311a583cfc151fd082.png)
如上图所示,数据段是由很多操作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对复制的这个页面进行修改。这时子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据。
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长,但是也不会超过原有数据的2倍大小。另外Redis实例里的冷数据占的比例往往是比较高的,所以很少会出现所有的页面都被分离的情况,被分离的往往只有其中一部分页面。每个页面的大小只有4KB,一个Redis实例里面一般都会有成千上万个页面。
子进程因为数据没有变化,它能看到的内存里的数据在进程产生的那一瞬间就凝固了,再也不会发生改变。这也是为何Redis的持久化叫做“快照”的原因了。接下来子进程就可以安心的遍历数据,进行序列化写到磁盘中了。
那现在就有一个问题了,父进程如果占了10G,那么子进程也占10G,redis的空间够不够呢?持久化的时候速度快不快呢?其实父进程和子进程只保存了物理机的内存的地址,两份地址而已,指向的都是内存。所就不存在两个都占10G了。而父进程和子进程是通过fork()出来的,还有就是子进程也不是一下就创建出来,其中的COW是copy on write,意思是写的时候才复制地址。创建子进程并不发生复制,玩的是指针。
例如看一下fork :
输入 man 2 fork 命令
![31f709c04e45538a06c682281a3e70ed.png](https://i-blog.csdnimg.cn/blog_migrate/6289a2d6e8756b0c1f6c5df0c575e921.jpeg)
然后输入/copy
![3372926ec6c8497e294c6d23def24662.png](https://i-blog.csdnimg.cn/blog_migrate/189aa2b3969058dd6ec877cd1b7f6df6.png)
![34ee9f7f227fced8ad3fd3e13d23c293.png](https://i-blog.csdnimg.cn/blog_migrate/384a5f7baab77fef3f9f696c6a838a96.png)
NOTES
Under Linux, fork() is implemented using copy-on-write pages, so the only
penalty that it incurs is the time and memory required to duplicate the parent’s
page tables, and to create a unique task structure for the child.
Since version 2.3.3, rather than invoking the kernel’s fork() system call, the
glibc fork() wrapper that is provided as part of the NPTL threading implementa-
tion invokes clone(2) with flags that provide the same effect as the traditional
system call. The glibc wrapper invokes any fork handlers that have been estab-
lished using pthread_atfork(3).
在Linux下,fork()是使用写时复制的页面实现的,因此它带来的唯一损失是复制父页面表和为子页面创建唯一的任务结构所需的时间和内存。自从2.3.3版本以来,作为NPTL线程实现一部分提供的glibc fork()包装器没有调用内核的fork()系统调用,而是使用提供与传统系统调用相同效果的标志调用clone(2)。glibc包装器调用使用pthread_atfork(3)建立的任何fork处理程序。
![22aa4b6d8f1bb8f42e1b5aee67dbeebc.png](https://i-blog.csdnimg.cn/blog_migrate/4d8ec00ec3a83e892cd534b80da29eea.jpeg)
就是父进程进行修改的时候,会新创建一个,然后指向,而子进程还是指向原来的。
fork是系统调用,copy on write是内核机制。
redis调用fork()优点是速度快,占用空间小。
![3a52b14723fa12e71105c848f975d27b.png](https://i-blog.csdnimg.cn/blog_migrate/5a1408428533c77987aace4f35ed080b.jpeg)
![d13f8c1427f3751a075eb891c51c1014.png](https://i-blog.csdnimg.cn/blog_migrate/700d0a56afd28fece2a5ad9f4e9097a1.png)
![4fe2648208a26f7360584117acd17bc2.png](https://i-blog.csdnimg.cn/blog_migrate/d450b6d3595d843c89fda65d6e5e0ad6.png)
达到60秒了 操作数达到10000,满足其中一个,就会写rdb。依次类推,如果到900秒了,操作数达到1笔的时候,满足其中一个,就会写RDB。
Redis默认是开启RDB的,如果想关闭,那么就把下图中这个删除,或者 save ""
![3cd6b21c01531f7df96e1bd745c8f627.png](https://i-blog.csdnimg.cn/blog_migrate/5c0f8a820218eb1f2ca2dd13051782d5.png)
那么持久化的RDB文件存储在哪呢?继续往下翻:
![73fff4f63c2d114ab6e4a6a2fbaec806.png](https://i-blog.csdnimg.cn/blog_migrate/bd13eb5d8864ffa04176d3799ff6156b.png)
![15ad86db9eb20843a3483023cf508ed2.png](https://i-blog.csdnimg.cn/blog_migrate/48c50ec82f00e66730deff64a6ad1a91.png)
RDB的优缺点:
优点:1,压缩后的二进制文件,适用于备份、全量复制,用于灾难恢复
2,加载RDB恢复数据远快于AOF方式
缺点:1,无法做到实时持久化,每次都要创建子进程,频繁操作成本过高
2,保存后的二进制文件,存在老版本不兼容新版本rdb文件的问题
3,不支持拉链,只有一个dump.rdb
4,丢失数据相对较多,窗口数据容易丢失,8点做了一个rdb,假设9点又要做一个rdb,此时断电了,那么就丢了一个小时的数据。这是全量备份技术的通用的缺点。
以上是RDB。
针对RDB不适合实时持久化,redis提供了AOF持久化方式来解决
AOF(append only file )(只会向文件追加)AOF日志存储的是Redis服务器的顺序指令序列,AOF日志只记录对内存进行修改的指令记录,假设AOF日志记录了自Redis实例创建以来所有的修改性指令序列,那么就可以通过对一个空的Redis实例顺序执行所有的指令--也就是“重放”,来恢复Redis当前实例的内存数据结构的状态。
Redis会在收到客户端修改指令后,进行参数校验、逻辑处理,如果没问题,就立即将该指令文本存储到AOF日志中,也就是说,先执行指令才将日志存盘。这点不同于hbase等存储引擎,他们都是先存储日志再做逻辑处理的。
Redis在长期运行的过程中,AOF日志会越来越长,如果实例宕机重启,重放整个AOF日志会非常耗时,导致Redis长时间无法对外提供服务,所以需要对AOF日志瘦身。
AOF重写:
Redis 提供了 bgrewriteaof 指令用于对AOF日志进行瘦身,原理是开辟一个子进程对内存进行遍历,转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替代旧的AOF日志文件了,瘦身工作就完成了。而重写的意义是以后加载速度变快,减小AOF体积,比如set k1 a set k1 b set k1 c,就会在aof文件中出现这些记录,而此时k1对应的value是C,a和b是没用的,经过bgrewriteaof命令后,aof文件中将不再有a和b的影子,就只保留了k1 c。
fsync:
AOF日志是以文件的形式存在的,当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符(File Descriptors ,简称fd ,很多fd就是 fds)分配到一个内存缓冲中,然后内核会异步将脏数据刷回到磁盘。这就意味着如果突然宕机,AOF日志内容可能还没来得及完全刷到磁盘中,这个时候就会出现日志丢失,这怎么办?不急,Linux的glibc提供了 fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。只要Redis进程实时调用fsync函数就可以保证AOF日志不丢失。但是fsync是磁盘io操作,很慢,如果Redis执行一条指令就要 fsync 一次,那么Redis高性能的地位就不保了。所以在生产环境的服务器中,Redis通常是每隔1s左右执行一次 fsync 操作,这个1s周期是可以配置的。这是在数据安全性和性能之间做的一个折中,在保持高性能的同时,尽可能是数据少丢失。
在Redis的主节点不会进行持久化操作,因为无论是AOF还是RDB都比较耗资源,持久化操作主要在从节点进行。从节点是备份节点,没有来自客户端请求的压力,它的操作系统资源往往比较充沛。但是如果出现网络分区,从节点长期连不上主节点,就会出现数据不一致的问题,特别是在网络分区出现的情况下,主节点一旦不小心宕机了,那么数据就会丢失,所以在生产环境下要做好实时监控工作,保证网络畅通或者能快速修复。另外还应该再增加一个从节点以降低网络分区的概率,只要有一个从节点数据同步正常,数据也就不会轻易丢失。
假设AOF文件是10T,那么恢复的时候不会内存溢出,因为只要写写写直到AOF文件成了10T,但凡能写到10个T,那肯定没有内存溢出。所以恢复的时候也不会内存溢出。如果要溢出的话,那么早就溢出了,还能把AOF文件写到10个T么?
下来说AOF的配置:
![0bfd5dfd54432efedd092f15410b30d8.png](https://i-blog.csdnimg.cn/blog_migrate/4614f6b793eb476ec8d653d63ba01ab4.png)
vi 6379.conf, 不是vim 用 vi 打开 上面写错了。
![76d859f7f0c85dda3ee36864954a48eb.png](https://i-blog.csdnimg.cn/blog_migrate/218ab4dd33cbd26e7b72f059de15e044.jpeg)
![e4017e716029bd97058b06e8e7a402b1.png](https://i-blog.csdnimg.cn/blog_migrate/40f07050d8fcd8b30f2f740f654e3977.jpeg)
Redis4.0混合持久化:
重启Redis时,很少使用rdb的方式恢复内存状态,因为rdb会丢失大量数据。通常使用AOF日志重放,但是重放AOF日志相对于使用rdb来说慢很多,这样在Redis实例很大的情况下,启动需要花很长的时间。
Redis4.0带来了一个新的持久化选项--混合持久化。将rdb的内容和增量的AOF日志文件存在一起。这里的AOF不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量AOF日志,通常这部分AOF日志很小。一定是重写之后才开始混合,比如开启了混合持久化后,set k1 v1 set k1 v2 等等 一定是输入 bgrewriteaof 命令后,前面的一系列操作就变成了 RDB形式,保存在aof文件中,后续继续set k3 v3 的时候,是以明文的形式追加到aof文件中
于是,在Redis重启的时候,可以先加载rdb的内容,然后再重放增量AOF日志,就可以完全替代之前的AOF全量文件重放,重启效率得到大幅提升。
下来演示一下redis持久化
![244bece80ef39ccbf7d71ac6fc470e19.png](https://i-blog.csdnimg.cn/blog_migrate/b81a219a68a8c71bd1684e20ed12bd4b.png)
![f015b3b3669aa558476b091c5c2f2c4a.png](https://i-blog.csdnimg.cn/blog_migrate/22e21e26fff5ba37ed940bd0458051a8.png)
下来改配置文件:来验证rdb和aof是什么概念
vi /etc/redis/6379.conf
![b148b69060741d3d581b4ed7dbacb95b.png](https://i-blog.csdnimg.cn/blog_migrate/90ca041e56993320c2082650f3841bcb.png)
![2fd6e9988f557e0163ba9fb7ef441b72.png](https://i-blog.csdnimg.cn/blog_migrate/3c770c32079b371b9390d3aeaf8fd4cd.png)
把daemonize yes改成daemonize no ,表示不让redis变成后台服务进程运行,成为前台阻塞的运行。
![04fb9b7509f85f8dde3a9c3fcb1f0473.png](https://i-blog.csdnimg.cn/blog_migrate/547e01f8a7cdb0304699fe52a5620f84.png)
打开AOF持久化,因为RDB是默认开启的不用管
![f773a928d9069b3867cf0796564a2bf7.png](https://i-blog.csdnimg.cn/blog_migrate/0adecd93f6b38c461fbe5ad014c600a5.png)
然后把混合持久化关闭
![aa4a129fbd2204af9e9c2bad6bd15eba.png](https://i-blog.csdnimg.cn/blog_migrate/625c238e4b000bdde9b963ddf9d816be.png)
现在准备工作已经做好了,打开了aof,打开了rdb(默认开启),关闭了混合持久化。
我现在是在root根目录:
![2b008110562f38c06081107d8aee619a.png](https://i-blog.csdnimg.cn/blog_migrate/4b839313a7d387622e55ce596f24aee8.png)
然后把1号shell窗口切换到etc下的redis目录:
![7e98597bc2ca7573648a981c81ea9c4b.png](https://i-blog.csdnimg.cn/blog_migrate/cda0362fd87f49946ca8dcca21d49b79.png)
然后切换到2号shell窗口:
![b15e4a2daa72ac854d212d88ff884cbf.png](https://i-blog.csdnimg.cn/blog_migrate/35571ce05776563d35977d5dfd11c4be.jpeg)
![b8d969376b34ac3b9c1850b2f0744467.png](https://i-blog.csdnimg.cn/blog_migrate/c4c4a61426aaf7df9e055db6bf3ed4d9.png)
然后切回到1号shell窗口,启动redis
![b2838a9c012013f56d5ba6ab12e25df1.png](https://i-blog.csdnimg.cn/blog_migrate/498c3b999a3897ccd7b77e7d75b12583.jpeg)
然后切回到2号shell窗口,LL看一下
![2ccc21170a5c37822a00e0923a3ab0b1.png](https://i-blog.csdnimg.cn/blog_migrate/0bb4e89148a08c004ba726f52e1fa3ff.png)
然后 vi appendonly.aof,发现什么都没有记录
![d2e2af8e23175c6ddade987fd0ed7a62.png](https://i-blog.csdnimg.cn/blog_migrate/2c4206ccce9057a62afdc4f27bebc3a1.png)
然后在3号shell打开 redis 6379 客户端
![bbc01185a766bd1780269e34923dd3e6.png](https://i-blog.csdnimg.cn/blog_migrate/cbcda21d2a9ca4a38057b9a461d727aa.png)
然后设置一个kv。set k1 hello
![62dc44e8aaa3b0c710d800bfa56cb895.png](https://i-blog.csdnimg.cn/blog_migrate/953171583ed3346510793d883adf30b0.png)
然后切回到2号shell。LL看一下,因为不满足rdb的那三个条件
![3cd6b21c01531f7df96e1bd745c8f627.png](https://i-blog.csdnimg.cn/blog_migrate/5c0f8a820218eb1f2ca2dd13051782d5.png)
所以可以看到还是只落了一个aof文件
![4182f03c1c25e7745f660d011d0b5395.png](https://i-blog.csdnimg.cn/blog_migrate/2b9799636dfed585af87b303813549a8.png)
打开这个aof文件看一下
![536a961ebbc6cada5f25b82f5f4376c9.png](https://i-blog.csdnimg.cn/blog_migrate/444a2394248d15265b44a2b60ef8ac58.png)
*2代表:两个元素组成 一个是 select 另一个是 0号库
![943bc1df5ac9f26c5b76a8cba2c89eb3.png](https://i-blog.csdnimg.cn/blog_migrate/d45797ecf4a5e724dc5fb9bdbd132c0e.png)
![326b791894d2eb1819083d522fa7b1ff.png](https://i-blog.csdnimg.cn/blog_migrate/4c6657309da7097bd19897111a74f940.png)
然后在3号shell 继续输入 set k2 xxoo
![9ae1b2fe764a4d8fabda1a21fc60d9a9.png](https://i-blog.csdnimg.cn/blog_migrate/e7a7f369d178990061653c3516de6fef.png)
然后切换到2号shell 查看 aof文件:
![6e82009f8547b9bdbc69c1b6fa45e9ed.png](https://i-blog.csdnimg.cn/blog_migrate/4b64e43b994636b7b553286cc8d7a85c.jpeg)
然后想一下 把这两笔数据 弄到rdb里面去。在3号shell 输入 bgsave
![e557977d008c89108833575e0fe5c73b.png](https://i-blog.csdnimg.cn/blog_migrate/3580238c1323363ca60b17dec6ccc484.png)
然后在2号shell查看:
![1b5419a39cc524a9c8a05d8612375835.png](https://i-blog.csdnimg.cn/blog_migrate/05ca7a7959cb5d20da3a71bfe9f5b324.png)
然后1号shell窗口 会发生
![34416c111519cd15fb12222693c038e8.png](https://i-blog.csdnimg.cn/blog_migrate/e1a452ed1376401b93532784b3c07b24.jpeg)
然后切换到2号shell,打开这个dump.rdb
![151d2cb87282e1dd5b7f8f8507bb2fc5.png](https://i-blog.csdnimg.cn/blog_migrate/ef5c9f10ce8460162f46b562bb690b11.png)
![f6cd68495069ce0b569cfe5a7dc985c9.png](https://i-blog.csdnimg.cn/blog_migrate/6ad1ad8133acf40bbac107f9cdf6a10f.jpeg)
下来看一下 aof 的重写功能:
![97285109ab708c940959d8f5709990ee.png](https://i-blog.csdnimg.cn/blog_migrate/ae2f254b43e3a0bb0402f07a0ea921c0.png)
那么aof文件会相应的增长很多对k1的操作:
![6ec0afbd23ab09c95ff9fc0b5599ac42.png](https://i-blog.csdnimg.cn/blog_migrate/29c7ccf63c410dcd32ec339a65b5ea99.png)
那现在k1的最终value是c,所以以上k1之前的value值都是垃圾。所以给aof文件瘦身一下:瘦身前是170字节
![3c56bf25a5cd0f23737a26a364e5961f.png](https://i-blog.csdnimg.cn/blog_migrate/92232316c43b9b741cbe198b3f359e5b.png)
然后在3号shell 窗口 :输入bgrewriteaof
![7f084c6c24f8766fc128cc9d885ac28a.png](https://i-blog.csdnimg.cn/blog_migrate/d095679e2de248fbbce8436edb8a5733.png)
然后在2号shell 看一下 目前 aof的大小 :
![4670f523a2a0e53f16fc47d5953b411e.png](https://i-blog.csdnimg.cn/blog_migrate/8d903b112aec489b72937b65f9ac1781.png)
然后打开aof文件:
![cae39616eeacc87ec72f2c81cef00a1b.png](https://i-blog.csdnimg.cn/blog_migrate/0fab7ed53c83446d4ad4417d0e097c0e.png)
然后在1号shell 中 ctrl +c 结束:
![4d6c6b7271328f41ebb8a19dbb2a308c.png](https://i-blog.csdnimg.cn/blog_migrate/b990213f9517ee34bf1b9533de81f2c9.png)
把3号shell的客户端 也退出去 exit,把2号的aof和rdb文件也全部删除 rm -fr ./*
然后切到1号shell,改配置:
![471a9ba95f7689b1b88b9a3f4b3303f3.png](https://i-blog.csdnimg.cn/blog_migrate/ae1dc0566a8846d12ff8e032b35fd9e6.png)
开启混合持久化:
![6fe291db13bb82a6d6db06d405f92eec.png](https://i-blog.csdnimg.cn/blog_migrate/485c8646ff5ad899ac5677aca7eb53e6.png)
保存后,启动redis:
![09318a2c6f100497a9ee60e718fa9946.png](https://i-blog.csdnimg.cn/blog_migrate/3d56fc9a7401d06292be285bf70bc841.jpeg)
然后2号shell 里还是一个空的 aof,没毛病
![ac8d8d36c0abb36729764c892241390a.png](https://i-blog.csdnimg.cn/blog_migrate/1551a1708f24ec0900924d0a22e7671e.png)
然后3号shell开启redis客户端 :set 两次
![eaaec760e64c2fcc8ab5a7da9cb259e7.png](https://i-blog.csdnimg.cn/blog_migrate/03d2463f0c0d057e73eded285b288670.png)
然后在2号shell 看一下:然并卵,不是混合持久化。
![c9b903039e0ff47095fbc8864a58d979.png](https://i-blog.csdnimg.cn/blog_migrate/0c8836e271585706a7c5e73082b86178.png)
然后继续3号shell 写,写完后bgrewriteaof
![8e4a1f349f01b81677544013d258b985.png](https://i-blog.csdnimg.cn/blog_migrate/07440ee86435ec984fc10eb4f9eb840e.png)
现在是混合持久化:所以查看2号shell:没有增量前是103字节
![5c548a657ca46f681a8967807782e1c2.png](https://i-blog.csdnimg.cn/blog_migrate/ef5cbb2df5a8f8a2912dd63aaecb3f80.png)
打开,可以看到变成了二进制的rdb了。和以前的aof文件不一样
![24b04e424d010c683826eb8cd2e47658.png](https://i-blog.csdnimg.cn/blog_migrate/34e387639446987e04ea411612e566d6.png)
然后继续set两条记录 3号shell:
![12afc9b68525bc417c76334acf10355c.png](https://i-blog.csdnimg.cn/blog_migrate/874bec4fc89a66e8fd0a30db15a2cc88.png)
然后转到2号shell:查看aof文件:变成104字节了。
![2b55e5ae1214cbaf266cad7f75bbbd9f.png](https://i-blog.csdnimg.cn/blog_migrate/c789a97bb3d399c37d09f8b4c5e82446.png)
我不知道为什么,我打开看不到增量的内容,可能是io的延迟吧,不知道出什么bug了。
![1770b02c6833c0b6af93cc53df85359a.png](https://i-blog.csdnimg.cn/blog_migrate/da09381165f0fa4bf2f99f909395a020.png)
这下能看了:
![8be262629e2421a3a40ecae9b5152ad7.png](https://i-blog.csdnimg.cn/blog_migrate/6611da471a629d2f1a957f68bdac67b4.png)
![5fe1d16399582b814dae608d6bf4e048.png](https://i-blog.csdnimg.cn/blog_migrate/54852720f5634257ea24d4f7e7ea5827.png)
![31f9a16f577aeb3a899cef547d84247f.png](https://i-blog.csdnimg.cn/blog_migrate/e66340728d045f9a491647c14e37b5e1.png)
dump.rdb
![3c2a768f56cd133ba730c4067c682147.png](https://i-blog.csdnimg.cn/blog_migrate/c63edf9089ee14d7e65aff1def209495.png)
appendonly.aof
![f617bd3e30026dab5f27f0d90559c4aa.png](https://i-blog.csdnimg.cn/blog_migrate/f27311994f30d110eb429902240491e8.png)
appendonly yes (默认不开启,为no)