1、为什么需要Redis的持久化
我们都知道,Redis的数据都是保存在内存里面的,导致了Redis有如下特点
1.由于内存的容量较小,并且速度很快,所以redis相对于mysql、orcle等数据库相比,存储的容量较小,但是io速度特别快。
2.由于内存的临时存储性,在机器断电之后内存里的内容将会丢失,若redis没有持久化,则数据也将全部丢失。
简而言之,redis持久化是保证redis数据不会因为故障而丢失的一种机制。
如图,redis的持久化机制一共有两种:一种是快照的形式,另一种是AOF日志。下面将分别介绍这两种机制
2、快照
快照就是redis把内存中的数据通过磁盘io持久化到硬盘当中,默认生成为dump.db为文件名的文件,里面信息是已经被序列化的文本。
2.1 原理
redis的快照是全量备份,所谓全量备份就是对某一时间点上的所有数据进行一个完全拷贝。快照保存的数据是一种二进制序列化的形式,在存储上是非常紧凑的
Redis是单线程的程序,意味着在这个线程当中需要负责多个客户端套接字的并发操作和内存逻辑的读写操作。在服务器请求的同时,redis还要进行内存的快照操作,该操作需要进行文件的io,可是文件io操作不能使用多路复用api,这就意味这服务器在处理请求的同时还要对io进行操作,严重影响到了服务器的性能。
还有另一个问题,为了不堵塞线上的业务,redis需要进行一边持久化一边处理请求,在持久化未完成的同时,很可能数据结构已经被请求所改变,比如一个数据正在进行持久化(未完成),结果一个请求来了,把数据给删了,这将会出现很严重的问题。
redis的解决策略是使用操作系统的多进程COW机制来实现快照持久化。下面将做出一些解释
2.2 fork多进程
在持久化的时候,redis会调用glibc函数的fork产生一个子进程进行快照的处理,而父进程则单独处理请求操作。子进程和父进程共享内存块里面的数据,所以在分离进程的时候,内存基本上不会进行增长。
1
2
3
4
5
6
7
8
9 1pid = os.fork()
2if pid > 0: #当前为父进程,返回为子进程pid
3 handle_client_requests() #父进程继续处理客户端请求
4if pid == 0: #当前为子进程
5 handle_snapshot_write() #子进程处理快照写磁盘
6if pid < 0 : #内存资源不足
7 #fork error
8
9
子进程进行持久化的时候,并不会修改到内存的数据结构,仅仅是对内存进行遍历读取,然后序列化到磁盘上。修改数据结构的操作在于父进程的请求处理中
由前文所述,redis是使用多进程的COW机制来实现快照的
当数据被父进程修改的时候,数据段将会拷贝出一份需要修改的代码段的副本出来,然而并不影响子进程对原有的数据段的遍历,然后父进程进行对副本数据的修改。当父进程不断对数据进行改写的时候,越来越多的数据段进行复制,这样会不断的产生新的内存,但是值得注意的一点就是redis内存中的冷数据往往比较多,所以被分离的数据总体来说还是比较少的,假设在最差的情况下,内存的增长量也是原数据的两倍(全部数据被修改)。
当创建好快照之后,快照会跟着数据的变化,一旦监控到原数据被改写后,则把原数据拷贝到一个新的数据块上,然后将最新的数据覆盖到原数据当中完成更新操作,这样下来,所有的原数据块成为原数据卷,所有新数据块成为了快照卷,由此看来快照操作有一个很大的缺点那就是降低了写的性能,因为每次更新数据都进行了两次的写操作。
子进程的数据是不会由于新请求而变化,相当于在某一时刻用相机对数据进行快速的拍了下来,这大概是叫快照的原因了,是数据对时间的定格。
3、AOF
在redis配置文件中设置 appendonly yes
aof日志是连续的增量备份,它的内容记录着对内存数据修改的指令文本,随着日志的长期运行,数据文本会变得非常庞大,数据库在重启的时候要对日志进行加载,也是对指令的重放,如果不进行日志瘦身的话该操作会耗时很长,所以要定期对aof进行重写。
3.1原理
在用指令对数据结构进行修改的时候,进行一系列的参数校验、逻辑处理过后,该指令会被记录到aof日志当中,所谓的重放,也就是对aof日志中指令的顺序执行,来恢复redis实例中的内存数据结构的状态。该处理的特点是“先执行,后存储”,像leveldb、hbase等属于“先存储,后执行“
3.2 AOF瘦身
redis可以通过bgrewriteaof指令对aof日志进行瘦身,其原理是开辟一个内存对redis的数据结构进行遍历,从而转换成一系列的指令,序列化到一个新的日志文件中,序列好之后,如果再对数据结构进行修改则指令直接追加到新的日志文件中,追加完之后则代替旧的日志文件,完成了瘦身。
3.3 fsync
当程序对AOF文件进行写操作的时候,是将内容先写到了一个内核缓存当中,再从内核缓存中把数据刷新到日志文件磁盘中。
如果内容在写到内核缓存中还没来得及刷新到磁盘中,机器突然宕机了,文件将会被丢失。redis同样考虑到了这点。Linux提供了一个fsync函数可以将指定的内容强制从内存刷新到磁盘当中。只要redis进程实时的调用fsnyc可以保证aof不会被丢失,但是!fsnyc是一个磁盘io操作,所以会特别的慢(比如使用来一个指令就调用一次fsync的策略),如果调用的特别频繁的话会严重影响到redis的性能。
在生产环境中,redis通常是每隔一秒钟执行一次fsync操作,这样在数据安全性和性能之间做了一个折中。在保证性能的同时,尽可能的减少数据丢失。
参考资料:
《Redis深度历险-核心原理与应用实践》