前言我们都知道,redis是基于内存的K-V数据库 。由于内存是断电易失的,所以redis提供了相应的持久化机制 。
本篇主要讲解redis提供的RDB和AOF两种持久化方式,以及他们的实现原理 。
RDBRDB(Redis DataBase)是指把某个时刻内存中的数据生成快照(snapshot),以dump.rdb文件的形式存在磁盘上 。RDB每次生成的快照(snapshot)都是redis中的全量数据 。
生成快照可以由两个命令完成,分别是save和bgsave,先看下这两个命令的描述
127.0.0.1:6379> help saveSAVE -summary: Synchronously save the dataset to disksince: 1.0.0group: server
127.0.0.1:6379> help bgsaveBGSAVE -summary: Asynchronously save the dataset to disksince: 1.0.0group: server
从描述上来看,这两个命令实现的功能一模一样,只是save是以同步的方式写入磁盘,而bgsave是以异步的方式,bg就是Background的意思 。
事实上调用save命令后,redis进程会被阻塞,直到快照生成完成,期间redis不能对外提供服务 。而bgsave会调用linux的fork()函数来创建一个子进程,让子进程来生成快照,期间redis依然可以对外提供服务 。
了解了RDB的相关命令,再来思考下这个问题:假设redis中有6G数据,要给这6G数据生成一个快照,不可能在一瞬间完成,肯定会持续一段时间 。那么从快照开始生成(t1),到快照生成成功(t2)的这段时间内,redis中被修改的数据应该怎么处理?持久化的数据应该是t1时刻的数据,还是t2时刻的数据呢?
对于save的方式来说,生成快照期间,redis不能对外提供服务,所以在t1到t2期间不会有数据被修改 。但是对于bgsave方式来说,生成快照期间,redis依然可以对外提供服务,所以极有可能有些数据被修改 。这时子进程是根据t1时刻的数据来生成快照的 。t1到t2期间被修改的数据只能在下一次生成快照时处理 。但是在t1到t2期间被修改的值,对外部调用方来说是可以实时访问的 。也就是说redis不仅要存储快照生成点(t1)时刻的所有值,还要存储变量的最新值 。这样的话,redis中6G的数据,在生成快照的时候,会瞬间变成12G 。
但是事实并非如此,以性能著称的redis肯定不允许这样的事发生 。那这个问题是如果解决的呢?这样就不得不说copy on write机制了
copy on write(COW,写时复制)是一种计算机程序设计领域的优化策略 。
其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变 。这过程对其他的调用者都是透明的(transparently) 。前文提到调用bgsave时,会调用linux系统的fork()函数来创建子进程,让子进程去生成快照 。fork()函数实现了copy on write机制 。
如下图所示,redis调用bgsave之后,bgsave调用fork 。也就是在t1时刻,内存中的数据并不会为了两个进程而复制成两份,而是两个进程中的指针都指向同一个内存地址 。
文章插图
此时子进程开始生成快照,如果在生成快照期间,redis中的数据被修改了,k3的值由c变成了d 。操作系统仅仅会把k3复制一份,而没有变化的k1和k2不会被复制 。这就是写时复制(copy on write)机制 。可以看到此时子进程取到的数据还是t1时刻的数据,而redis对外提供的服务也能获取最新数据 。
文章插图
此处用copy on write优化的前提是生成快照的过程持续的时间较短,期间只有少量的数据发生了变化 。如果期间所有的数据都发生了变化,也就相当于真的把6G数据变成了12G 。
写时复制是一种优化思想,在JDK中也能看它的实现
配置前文说RDB模式生成快照的命令是save和bgsave,但是在实际使用redis的时候,也没见我们定期手动执行这两个命令 。所以快照的生成还有一种自动的触发方式,在配置文件中可以找到相关的配置
################################ SNAPSHOTTING################################## Save the DB on disk:##save <seconds> <changes>##Will save the DB if both the given number of seconds and the given#number of write operations against the DB occurred.##In the example below the behaviour will be to save:#after 900 sec (15 min) if at least 1 key changed#after 300 sec (5 min) if at least 10 keys changed#after 60 sec if at least 10000 keys changed##Note: you can disable saving completely by commenting out all "save" lines.##It is also possible to remove all the previously configured save#points by adding a save directive with a single empty string argument#like in the following example:##save ""save 900 1save 300 10save 60 10000
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- redis集群搭建
- Redis命令大全,满足你的日常工作,看这一篇就够了
- Redis这么讲你还不能入门么
- 在SpingBoot中使用Redis对接口进行限流
- 实战基于Redis实现阻塞队列
- redis5.0.7 版本集群liunx部署简易流程
- redis内存碎片
- 一文读懂Redis的dict字典数据结构
- Go语言 连接池相关总结:HTTP、RPC、Redis 和数据库等
- 谈谈 Redis 的过期策略