Redis的数据库功能
关于数据库的数据持久化
关于数据库首先要简单说明一下。
关于持久化
数据库与缓存数据的关键性区别就是,持久化;
数据库是可以持久化将数据保存到磁盘上,而缓存是为了提升效率而放弃保存数据的功能;而redis作为缓存数据库,并不是说redis无法保存数据到磁盘,redis也是可以持久化数据的,这就是RDB功能了。
关于数据的保存方式
对于数据库的保存,我们知道基于存储层面来说有两种方式:
- 快照 也是数据副本,就是数据直接保存到磁盘上;对于恢复来说直接将快照文件就是数据。
- 日志 就是写操作的所有命令;对于日志来说就是数据操作的过程,日志恢复是将所有的操作命令执行一遍来获取所有的数据。
关于保存的办法
对于保存要先了解一个概念
时点数据: 就是某个时间点的数据;比如 1日 0点的数据。等等都是有一个时间点的。
保存的数据,通常就是指保存的某个时间点的数据。相对的恢复数据也就是指恢复到了某个时间点的数据。
阻塞方式
数据库再保存过程中进入阻塞的状态,除了保存数据的动作再执行,其余所有对数据的写操作都停止【不执行或者进入等待的状态】;
这样在这个时间点的数据时没有进行写操作的,可以放心的进行保存,这时的时点数据是可靠的。
但是这样对于缓存数据库来说会十分影响他的功能,在保存数据的过程中,除了保存数据在执行其余所有的功能都阻塞,若数据很大则阻塞的时间就会很长,所以在实际过程中对于阻塞的操作来保存数据的办法,是需要进行慎重考虑的。
非阻塞方式
我们知道了阻塞方式的弊端后,为了不停止服务同时需要对某时点数据进行保存,就出现了非阻塞的方式。
先考虑一个问题,假设需要保存0点的数据,这时开始进行保存,保存执行了10分钟,那么0点 到0点10分的数据该如何处理?
对于我们写程序来说,最简单的办法就是保存0点数据时,将0点的数据复制一份出来,让内存中同时有两份一模一样的数据;一份进行继续使用,一份用来进行保存。这样即使数据被改动也不会影响保存的那份数据的内容。
但是这样又有一个问题,当内存数据随之增大,复制的过程也会增长,而且消耗资源也会随之翻倍。到这个时候就出现新问题。这时可以直接通过复制数据的引用【指针】,数据不变,只用将指针复制一份新的就好了,保存的时候直接通过复制后的引用获取到数据后进行保存。因为引用【指针】占用的空间很小所以效率会大大的提升。
所以对于非阻塞的数据保存方式就是对时点数据的的引用【指针】进行复制,然后对引用数据进行保存操作。
Redis的持久化操作
Linux 中的一些小知识
进程与子进程
在Linux中,一个进程是可以启动多个子进程的。而进程与进程之间的数据是不互通的;进程之间的数据是相互隔离,不互通的。但是可以通过export操作将变量或者数据让子进程看到,但是再不同的进程中对这个变量进行变更是不会影响任何的进程;子进程的操作不会影响到父进程,父进程中的修改也不会影响到子进程。
这个就是Linux中的fork()函数的操作;fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事
Copy On Write
对于Linux 中的fork 操作其实就是操作系统的内核机制Copy On Write 写时复制。其实在创建子进程的过程中并不会发生复制。前面也说了非阻塞方式的保存,大致原理也是类似Copy On Write通过对指针的复制来创建一个子进程,但是创建的过程会变的更快,占用的空间也会变的更小【比复制一份内存快很多,前面有介绍】。
RDB
前置的知识都介绍完了,现在才是真正的Redis DB 说明。
阻塞试的保存数据 命令:SAVE
在控制台可以通过 SAVE 命令来进行阻塞式的数据保存。
相比来说非阻塞的方式对于Redis的SAVE使用都很局限,通常是在维护状态进行SAVE,停机时,禁止了外面服务的使用。
非阻塞的数据保存 命令:BGSAVE
在控制台可以通过BGSAVE 命令来进行非阻塞的方式进行数据保存。
执行BGSAVE后,其实Redis就是再后台调用了fork()方法创建了一个子线程,并对当时数据进行了指针复制,在进行数据保存,即使保存时间再长也不会影响到Redis的其他服务功能
配置文件非阻塞的数据保存
详见配置文件中 SNAPSHOTTING 部分
save
-
开启
save <seconds> <changes>这里的save与命令中的save不是一回事这里的save代表的是命令中的bgsave。
大概命令意思是 :距离上次bgsave执行,当多少seconds过去并有changes个key进行了改变时执行bgsave操作。
配置文件中也有样例,与默认配置save 900 1 after 900 sec (15 min) if at least 1 key changed
save 300 10 after 300 sec (5 min) if at least 10 keys changed
save 60 10000 after 60 sec if at least 10000 keys changed可以看出来这个配置有着递进的关系,60秒10000次的话执行,若不满足5分钟之内有10次,若还不满足 15分钟内有1次改变执行。
-
关闭
save 后面为空或者 没有save的配置项,就关闭了bgsave的功能。
例如:save “”
stop-writes-on-bgsave-error
当bgsave出现错误,Redis将停止接受写入。默认yes 开启。
大概意思就是你再执行bgsave时出现了错误,如磁盘满了或者其他什么问题,反正就是失败了。这时redis会关闭所有对key的写操作。这里的writes是只对key的操作,不是只子进程对时点数据保存磁盘的写操作。
rdbcompression
保存数据 是否开启压缩, 默认"yes" 开启。
需要知道开启压缩是需要消耗CPU的,所以有时可以放弃CPU的消耗反而消耗磁盘存储来换取CPU的性能,这时配置 “no” 即可
rdbchecksum
数据检查,默认“yes” 开启
同样数据检查也是对cpu资源的消耗,可以选择性的进行开启或关闭
dbfilename
RDB dump文件的文件名称 默认是 dump.rdb ,要注意的是这里只是名字不包含路径等其他信息。
dir
RDB dump文件存放的目录 。 要注意的是这里是目录不包含文件名称。
RDB的优缺点
缺点
- 只有一个dump文件。无法有其他文件生成,若需要做不同时点的备份文件需要依赖系统或者其他的定时任务功能等来实现。
- 若丢失,丢失的数据会相对多,比如 8点一个dump 文件9点一个dump文件,若9点失败那么只有8点的dump文件了,但是8点到9点之间的数据就没有了。这个算是数据库定时全量dump的通病。
优点
- redis的dump类似于二进制数据的搬移,直接将二进制数据写到磁盘中,所以在恢复的速度是很快的。
AOF
Redis的 AOF
AOF : Append Only File
之前简要说明了一下关于通过日志方式保存数据的办法;是将写数据的过程【或者说命令】进行记录。而再Redis中这个类似日志方式保存数据的方式就是AOF。
相对于RDB的优缺点
优点
- 对于RDB方式来说AOF若丢失数据则会比RDB少的多。因为AOF是append方式的会将数据的所有写操作进行追加记录。
- redis的AOF与RDB是同时可以开启的,AOF会写AOF的文件,RDB会写RDB的文件,两者互不干扰,但是再恢复过程中若开启AOF则只会用AOF方式来进行恢复,因为AOF恢复的数据要比RDB方式恢复的数据全,
缺点
- 因为是操作命令的追加所以AOF文件的体积会无限制的变大。
- 因为文件大而且属于操作命令所以数据的恢复方面要比RDB方式慢
Redis AOF对于数据库日志的优化办法
- 想法:
因为日志方式的保存方式,会使文件无限变大,但是数据信息的重要性日志方式也是最完整的。所以有什么办法能包证这个数据完整性的优点,那就是数据日志文件足够小。只要日志文件够小那不就避免了数据恢复慢的缺点了吗? - Redis的实现:
Redis中都是对AOF文件进行重新来实现的:
- 4.0版本之前:
在4.0版本之前对AOF文件的重写都是把一些相关的命令进行抵消,或者把很多命令进行合并,通过这样使得日志文件变的小;但是需要注意的是即使是这样,这样重写完成之后的文件还是一个纯指令文件。
- 4.0版本之后:
当aof文件大小到一定程度的时候会先执行RDB操作,将RDB文件存放在AOF文件前,然后再将指令进行追加到AOF文件中。这样之后AOF就变成了一个混合文件有RDB的二进制数据也有AOF的增量指令。
AOF的配置
不要忘了初心,Redis还是一个缓存数据库。
主要看配置文件的 APPEND ONLY MODE 章节
appendonly
是否开启AOF模式 默认“no” 关闭 ;yes 开启
appendfilename
aof文件的文件名称 默认“appendonly.aof”
appendfsync
aof 执行的频率 默认“everysec” 每秒执行; 可选 “always”,“no”;
关于这个配置需要说明一下,虽然Redis 有这些RDB与AOF的功能,但是只要你有IO,就必然会拖慢性能;这个命令就是控制redis的io操作频率的配置。
首先要有一个概念程序对于IO的操作都是调用操作系统的IO来向着磁盘写写数据。
拿Redis 来举例,redis写数据并不会直接写到磁盘而是把数据交给操作系统内核,内核拿到数据以后会把数据放到对这个需要执行io操作的文件的buff中去。默认情况是当buff满了以后,操作系统会把这个buff的数据写向磁盘。但是我们也是可以通过很多办法直接让操作系统将buff写入磁盘,不必等待满了再写。【例举一下java中的io操作,通常我们io操作写完数据以后会执行一个flash的方法然后再去关闭io,这个flash就是通知操作系统内核把文件写入磁盘的指令,不需要等待buff满了再去写】。
而对此再来看一下关于这个配置的三个可选项的意思。
- always:一直写,只要有写指令我就触发操作系统的内核让他将buff写入磁盘;可想而知,这样来说只要有就写,对于数据来说是最完整的;但是IO操作也是最频繁的,当操作超级多的时候,那么性能开销会非常的大。
- no:不刷新,让操作系统内核来判断是否满了,满了就写,没满就存buff里面。写磁盘操作完全交由操作系统内核来实现。这样的速度是最快的。但是这里需要考虑的一个问题是,当出现问题的时候很可能会把这个buff大小的数据就丢失了。【比如断电了】数据完整性就没有办法保证了。
- eversec:每秒刷新一次。这个是基于always最频繁的与no交由系统刷新的直接的操作。属于一种折中办法。不会频繁的io,即使丢数据也没有多少。
no-appendfsync-on-rewrite
当有子进程进行磁盘操作时是否进行追加指令操作 默认“no”;可选“yes”
这个可以理解为当你的子线程在执行RDB操作时候或者其他IO操作的时候是否需要与这个子线程进行IO的资源争抢,因为redis默认理解为子线程已经再很频繁的使用io了。我这次就先不io了等子线程io完成我再进行追加。但是这也有个问题,你不进行追加指令出现问题就会遗失数据。这也取决于数据的重要与否来觉得是否打开这个配置。
aof-use-rdb-preamble
是否开启重写AOF文件的RDB功能,默认“yes” 开启
上面说过Redis对于AOF文件的优化就是重写,这个配置就是对于这个日志文件优化的办法,当开启的时候采用的是新的4.0以后的办法,不开启的时候采用的是4.0以前的办法。还需要注意的是,混合AOF文件的开头是一个“REDIS”的字符串,若没有这个“REDIS”字符串则认为这是一个纯指令的AOF文件。
auto-aof-rewrite-percentage 与 auto-aof-rewrite-min-size
这两个参数是用来控制aof自动刷新的 ,其实就是控制什么时候执行BGREWRITEAOF 操作。
auto-aof-rewrite-percentage 默认100 这里指的是半分比。默认100%
auto-aof-rewrite-min-size 默认 64mb 初始大小64MB ,
redis再自动执行aof是有记录的。 最小64mb,当aof记录的日志到64mb以后会触发一次BGREWRITEAOF 这时去掉多余的操作,aof肯定会小于64MB 。这时会记录一下这个大小。当aof的增大量增大100%时会再次触发一次BGREWRITEAOF 。然后再记录大小,到aof文件再次增大量到到这次记录大小的100%时再执行一次BGREWRITEAOF 如此循环。
RDB与AOF实际操作
实际操作前置设置
- 新安装一套服务。设置端口为6380. 然后关闭 redis-cli -p 6380 shutdown
- 查看配置文件 vi /etc/redis/6380.conf 配置文件,修改其中部分内容
- 将 daemonize 选项 由 yes 改为 no 让其不在后台运行,进行前台阻塞的运行模式,为了观察相对应的日志内容
- 将logfile 配置注释掉,日志将在conlose中显示。
- 将appendonly 设置为yes 开启aof
- 将aof-use-rdb-preamble 设置为no 先不使用混合模式
- :wq保存退出文件
- 删除原有的数据文件 rm -rf /var/lib/redis/6380/*
- 启动这个设置完成的redis redis-server /etc/redis/6380.conf
操作开始
-
新打开一个客户端,链接到6380的服务上去 redis-cli -p 6380
-
在打开另外一个客户端,查看目录
ll -rt /var/lib/redis/6380
total 0
-rw-r–r--. 1 root root 0 Aug 27 22:50 appendonly.aof可以看到启动服务后就创建了一个appendonly.aof的 aof文件但是大小为0
-
再redis client中 执行 set kk hi!
127.0.0.1:6380> set kk hi!
OK -
这时更换另外客户端查看6380文件目录,可以看到aof已经进行了修改,打开查看文件 vi appendonly.aof
ll -rt /var/lib/redis/6380
total 4
-rw-r–r--. 1 root root 53 Aug 27 22:58 appendonly.aof
vi appendonly.aof*2
$6
SELECT
$1
0
*3
$3
set
$2
kk
$3
hi!
~
~
~
~
~
~
AOF文件的阅读
对上面生成的AOF进行一下阅读:
其实也就是主要理解一下文件中的* 与$ 的含义。
- *代表这一个指令的开始,后面带一串数字,代表有哪些元素内容。
例如: *3 说明,开始了一条指令,指令后面有两个元素内容,那么在这行后会有3×2行的元素与内容。 - $ 代表一个元素 的开始,后面跟着一串数字,代表的是偏移量指这个元素的内容有多长,而下一行就是具体的内容了。
例如:$3 一个元素 长度是3 下一行是这个元素的内容 set
那么上面生成的appendonly.aof文件的意思就是
*2 一条指令 两个元素内容
$6 元素 6个长度
SELECT 内容是select
$1 下一个 1个长度
0 内容0
*3 又是一条指令 3个元素内容
$3 元素 长度为三
set 内容是set
$2 元素 长度2
kk 内容kk
$3 元素长度3
hi! 内容hi!
可以看出来。*其实就是一行内容 具体有哪些部分组成就是$。刚才我们从客户端登陆到执行了第一条set操作在aof文件中就体现成这两行【两条】指令
SELECT 0
set kk hi!
RDB BGSAVE的执行
-
再redis cli中接着执行set kkk nihao,并执行bgsave 看看rdb功能。
127.0.0.1:6380> set kkk nihao
OK
127.0.0.1:6380> BGSAVE
Background saving started -
切换窗口查看6380目录
ll -rt /var/lib/redis/6380
total 8
-rw-r–r--. 1 root root 86 Aug 27 23:13 appendonly.aof
-rw-r–r--. 1 root root 116 Aug 27 23:13 dump.rdb
这时rdb文件已经产生了
- 再redis server 客户端窗口可以看到新增了如下日志内容,解读一下
2597:M 27 Aug 2020 23:13:52.353 * Background saving started by pid 2704
后台保存启动,子进程pid为 2704 。再启动的时候可以看出来当前6380redis 启动的pid 为2597
2704:C 27 Aug 2020 23:13:52.355 * DB saved on disk
开始保存
2704:C 27 Aug 2020 23:13:52.357 * RDB: 6 MB of memory used by copy-on-write
用 copy-on-write 方式来保存。大小为6MB
2597:M 27 Aug 2020 23:13:52.452 * Background saving terminated with success
bgsave 完成,状态成功
这些都是rdb执行的日志
- 查看一下rdb文件 还是用vi dump.rdb 直接查看,内容如下
REDIS0009ú redis-ver^E5.0.5ú
redis-bitsÀ@ú^Ectime H_úHused-memÂèEM@úLaof-preambleÀ@þ@ûB@@BkkChi!@CkkkEnihaoÿ<88>2Æ<9f>KVT`
~
~
可以看到起始位置的几个字母REDIS,前面也介绍过,混合模式开启后起始部分会是rdb内容,首字母也是REDIS,通过这点是可以验证的。
后面内容为2进制内容,具体内容是redis将内存数据序列化后的结果了。但是可以通过命令来进行查看 redis-check-rdb dump.rdb
-
执行一下 redis-check-rdb dump.rdb 查看结果
redis-check-rdb dump.rdb
[offset 0] Checking RDB file dump.rdb
[offset 26] AUX FIELD redis-ver = ‘5.0.5’
[offset 40] AUX FIELD redis-bits = ‘64’
[offset 52] AUX FIELD ctime = ‘1598595232’
[offset 67] AUX FIELD used-mem = ‘853480’
[offset 83] AUX FIELD aof-preamble = ‘0’
[offset 85] Selecting DB ID 0
[offset 116] Checksum OK
[offset 116] \o/ RDB looks OK! \o/
[info] 2 keys read
[info] 0 expires
[info] 0 already expired
通过这个命令可以查看一下rdb文件内容。返回结果也很简单不是很复杂
AOF文件中重复过滤
-
再redis cli 中对kk进行不断的重复操作
127.0.0.1:6380> set kk 1
OK
127.0.0.1:6380> set kk 2
OK
127.0.0.1:6380> set kk 3
OK
127.0.0.1:6380> set kk a
OK -
观察一下 AOF文件
ll -rt /var/lib/redis/6380
total 8
-rw-r–r--. 1 root root 114 Aug 27 23:39 dump.rdb
-rw-r–r--. 1 root root 198 Aug 27 23:39 appendonly.aof
vi appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$2
kk
$3
hi!
*3
$3
*2
$6
SELECT
$1
0
*3
$3
set
$2
kk
$3
hi!
*3
$3
set
$3
kkk
$5
nihao
*3
$3
set
$2
kk
$1
1
*3
$3
set
$2
kk
$1
2
*3
$3
set
$2
kk
$1
3
*3
$3
set
$2
kk
$1
a
可以看到这时aof文件大小198 kb 但是实际AOF文件中有很多重复的操作,对kk一顿猛如虎的执行完后其实kk的值就只是a而已
-
再redis cli中执行一个命令 BGREWRITEAOF 后
BGREWRITEAOF
Background append only file rewriting started -
服务日志内容
2597:M 27 Aug 2020 23:39:11.269 * Background saving terminated with success
2597:M 27 Aug 2020 23:43:37.124 * Background append only file
rewriting started by pid 2770
2597:M 27 Aug 2020 23:43:37.176 * AOF rewrite child asks to stop sending diffs.
2770:C 27 Aug 2020 23:43:37.185 * Parent agreed to stop sending diffs. Finalizing AOF…
2770:C 27 Aug 2020 23:43:37.185 * Concatenating 0.00 MB of AOF diff received from parent.
2770:C 27 Aug 2020 23:43:37.185 * SYNC append only file rewrite performed
2770:C 27 Aug 2020 23:43:37.186 * AOF rewrite: 6 MB of memory used by copy-on-write
2597:M 27 Aug 2020 23:43:37.223 * Background AOF rewrite terminated with success
2597:M 27 Aug 2020 23:43:37.223 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
2597:M 27 Aug 2020 23:43:37.223 * Background AOF rewrite finished successfully -
文件的变化
ll -rt /var/lib/redis/6380
total 8
-rw-r–r--. 1 root root 84 Aug 27 23:43 appendonly.aof
-rw-r–r--. 1 root root 114 Aug 27 23:54 dump.rdb
vi appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
SET
$2
kk
$1
a
*3
$3
SET
$3
kkk
$5
nihao
~
大小变成了84k多余的指令也不存在了。
混合类型的操作
-
停掉服务ctrl+c
-
修改配置文件 将aof-use-rdb-preamble 设置为yes启用混合模式
-
清除/var/lib/redis/6380 目录下aof文件与 rdb文件
-
启动服务
-
客户端链接
-
这里开始还是重复的对一个key进行操作
127.0.0.1:6380> set kk 1
OK
127.0.0.1:6380> set kk 2
OK
127.0.0.1:6380> set kk 3
OK
127.0.0.1:6380> set kk 5
OK
127.0.0.1:6380> set kk 7
OK实际操作最后还是将kk 值设为了7
-
观察一下aof文件大小与文件内容。
[root@localhost 6380]# ll
total 4
-rw-r–r--. 1 root root 163 Aug 28 00:42 appendonly.aof
[root@localhost 6380]# vi appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$2
kk
$1
1
*3
$3
set
$2
kk
$1
2
*3
$3
set
$2
kk
$1
3
*3
$3
set
$2
kk
$1
5
“appendonly.aof” [dos] 40L, 163C这时aof依旧如此再没有执行 BGREWRITEAOF 还是如此。那么在没有混合之前【aof-use-rdb-preamble 为no时】 执行完成后aof文件应该就一个指令set kk 7,现在开启了设置,应该AOF应该是REDIS开头的文件才对啊,这里需要注意的是只有再进行重写之后才会变成REDIS开头的AOF文件。在没有重写之前依旧是一行一行的日志样式
-
执行一下 BGREWRITEAOF 查看一下最后结果
日志部分:2919:M 28 Aug 2020 00:38:08.426 * Ready to accept connections 2919:M 28 Aug 2020 00:47:49.435 * Background append only file rewriting started by pid 2955
2919:M 28 Aug 2020 00:47:49.477 * AOF rewrite child asks to stop sending diffs.
2955:C 28 Aug 2020 00:47:49.477 * Parent agreed to stop sending diffs. Finalizing AOF…
2955:C 28 Aug 2020 00:47:49.477 * Concatenating 0.00 MB of AOF diff received from parent.
2955:C 28 Aug 2020 00:47:49.477 * SYNC append only file rewrite performed
2955:C 28 Aug 2020 00:47:49.479 * AOF rewrite: 6 MB of memory used by copy-on-write
2919:M 28 Aug 2020 00:47:49.515 * Background AOF rewrite terminated with success
2919:M 28 Aug 2020 00:47:49.515 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
2919:M 28 Aug 2020 00:47:49.515 * Background AOF rewrite finished successfully还和原先差不多的。
文件部分:[root@localhost 6380]# ll
total 4
-rw-r–r--. 1 root root 103 Aug 28 00:47 appendonly.aof
[root@localhost 6380]# vi appendonly.aof
REDIS0009ú redis-ver^E5.0.5ú
redis-bitsÀ@úEctimeÂ¥¶H_úHused-mem EM@úLaof-preambleÀAþ@ûA@@BkkÀ^Gÿ¯ ^L{<84>ä<8c>Ü
~
~很明显的AOF文件开头变成了rdb文件格式,有着REDIS开头的首字母。
- 紧接着在redis cli中再进行一些操作
127.0.0.1:6380> set kk a
OK
127.0.0.1:6380> set kk b
OK
127.0.0.1:6380> set kk c
OK再对AOF进行观察
ll
total 8
-rw-r–r--. 1 root root 210 Aug 28 00:55 appendonly.aof
-rw-r–r--. 1 root root 103 Aug 28 00:53 dump.rdb
[root@localhost 6380]# vi appendonly.aof
REDIS0009ú redis-ver^E5.0.5ú
redis-bitsÀ@úEctimeÂ¥¶H_úHused-mem EM@úLaof-preambleÀAþ@ûA@@BkkÀ^Gÿ¯ L{<84>ä<8c>Ü*2M
$6^M
SELECT^M
$1^M
0^M
*3^M
$3^M
set^M
$2^M
kk^M
$1^M
a^M
*3^M
$3^M
set^M
$2^M
kk^M
$1^M
b^M
*3^M
$3^M
set^M
$2^M
kk^M
$1^M
c^M
~
~
~
~
~
~
“appendonly.aof” [converted] 27L, 231C可以看到后面的kk set操作被以明文的方式追加到了Aof文件的后面。这就是混合模式了。时点数据rdb模式加 增量AOF日志,这样是最好的回复速度快而且数据完整性最高。