前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初学乍练redis:持久化

初学乍练redis:持久化

作者头像
用户1148526
发布2019-05-25 19:42:25
3730
发布2019-05-25 19:42:25
举报
文章被收录于专栏:Hadoop数据仓库Hadoop数据仓库

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1433162

目录

一、RDB

1. 触发条件

2. 快照原理

二、AOF

1. 开启AOF

2. AOF的实现

3. 同步硬盘数据

三、RDB迁移到AOF


代码语言:txt
复制
    大部分摘自Redis入门指南(第2版)。
代码语言:txt
复制
    redis是一个内存数据库,如果没有持久化功能,当redis重启、宕机、掉电等情况发生时,所有存储在内存中的数据就会丢失,这种情况在某些应用场景下是不允许发生的,例如:(1)将redis作为数据库使用时;(2)将redis作为缓存服务器,但缓存被穿透后会对性能造成较大影响,所有缓存同时失效会导致缓存雪崩,从而使服务无法响应。
代码语言:txt
复制
    这时我们希望redis能将数据从内存以某种形式同步到磁盘中,使得重启后可以根据硬盘中的记录恢复数据,这一过程就是持久化。redis支持RDB和AOF两种方式的持久化。前者会根据指定的规则定时将内存中的数据存储到磁盘上,后者在每次执行命令后将写数据的命令本身记录下来。两种持久化方式可以单独使用,也可同时使用。

一、RDB

代码语言:txt
复制
    RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时redis会自动将内存中的所有数据生成一份副本并存储在硬盘上,这个过程即为“快照”。快照文件存储在redis工作目录下,即dir配置参数指定的目录,文件名由dbfilename参数指定,缺省为dump.rdb。

1. 触发条件

代码语言:txt
复制
    redis会在以下四种情况下对数据进行快照:
  • 执行save或bgsave命令。
  • 执行flushall命令。
  • 执行shutdown或shutdown save命令。
  • 执行复制初始化时。
  • 根据配置规则进行自动快照。

(1)执行save或bgsave命令

代码语言:txt
复制
     当执行save命令时,redis同步进行快照操作,在快照执行过程中会阻塞所有来自客户端的请求。当数据量较大时,快照过程会导致redis长时间不响应,因此要尽量避免在生产环境中使用save命令。
代码语言:javascript
复制
session1:
127.0.0.1:20003> save
OK
(7.38s)

session2:
127.0.0.1:20003> get f_friend_all_98980
(nil)
(4.98s)
代码语言:txt
复制
    当session1执行期间,session2的查询将被阻塞。
代码语言:txt
复制
    需要手动执行快照时推荐使用bgsave命令。该命令在后台异步进行快照操作,快照的同时服务器还可以继续响应请求。执行bgsave后redis会立即返回表示开始执行快照操作。通过lastsave命令获取最近一次成功执行快照的时间,返回结果是一个UNIX时间戳,可用来判断当前快照是否完成。
代码语言:javascript
复制
127.0.0.1:20003> bgsave
Background saving started
127.0.0.1:20003> lastsave
(integer) 1536200399

(2)执行flushall命令

代码语言:txt
复制
     flushall命令会清除所有数据库中的所有数据。不论清空数据库的过程是否触发了自动快照条件,只要自动快照条件不为空,redis就会执行一次快照操作。
代码语言:txt
复制
    在两个db分别设置一个key并保存:
代码语言:javascript
复制
127.0.0.1:6379[2]> select 1
OK
127.0.0.1:6379[1]> set key 1
OK
127.0.0.1:6379[1]> select 2
OK
127.0.0.1:6379[2]> set foo bar
OK
127.0.0.1:6379[2]> save
OK
127.0.0.1:6379[2]>
代码语言:txt
复制
    查看dump.rdb文件:
代码语言:javascript
复制
[root@hdp1/var/redis/6379]#ll /var/redis/6379/dump.rdb 
-rw-r--r-- 1 root root 205 Sep  6 10:45 /var/redis/6379/dump.rdb
[root@hdp1/var/redis/6379]#
代码语言:txt
复制
    执行flushall命令:
代码语言:javascript
复制
127.0.0.1:6379[2]> config get save
1) "save"
2) "900 1 300 10 60 10000"
127.0.0.1:6379[2]> flushall
OK
127.0.0.1:6379[2]> select 1
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
127.0.0.1:6379[1]> select 2
OK
127.0.0.1:6379[2]> dbsize
(integer) 0
127.0.0.1:6379[2]>
代码语言:txt
复制
    查看dump.rdb文件:
代码语言:javascript
复制
[root@hdp1/var/redis/6379]#ll /var/redis/6379/dump.rdb 
-rw-r--r-- 1 root root 179 Sep  6 10:48 /var/redis/6379/dump.rdb
代码语言:txt
复制
    可以看到,flushall触发生成了新的dump.rdb文件,但此dump.rdb文件中没有数据,实际上无意义。

(3)执行shutdown或shutdown save命令。

代码语言:txt
复制
     shutdown或shutdown save命令会正常关闭redis服务器。此时不论是否触发了自动快照条件,只要自动快照条件不为空,redis就会执行一次快照操作,将当前内存中的所有数据保存到磁盘上的快照文件中。shutdown nosave命令则不会触发RDB快照。如果自动快照条件为空,则shutdown不会触发快照。
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#redis-cli -p 20009 config get save
1) "save"
2) "900 100"
[root@hdp4/var/redis/20009]#redis-cli -p 20009 keys "*"
(empty list or set)
[root@hdp4/var/redis/20009]#redis-cli -p 20009 set key 1
OK
[root@hdp4/var/redis/20009]#redis-cli -p 20009 set foo bar
OK
[root@hdp4/var/redis/20009]#redis-cli -p 20009 keys "*"
1) "foo"
2) "key"
[root@hdp4/var/redis/20009]#redis-cli -p 20009 shutdown
[root@hdp4/var/redis/20009]#redis-server /var/redis/20009/redis.conf 
[root@hdp4/var/redis/20009]#redis-cli -p 20009 keys "*"
1) "foo"
2) "key"
[root@hdp4/var/redis/20009]#redis-cli -p 20009 del key
(integer) 1
[root@hdp4/var/redis/20009]#redis-cli -p 20009 del foo
(integer) 1
[root@hdp4/var/redis/20009]#redis-cli -p 20009 shutdown nosave
[root@hdp4/var/redis/20009]#redis-server /var/redis/20009/redis.conf 
[root@hdp4/var/redis/20009]#redis-cli -p 20009 keys "*"
1) "key"
2) "foo"
[root@hdp4/var/redis/20009]#

(4)执行复制初始化时

代码语言:txt
复制
     redis会在主从复制初始化时进行进行自动快照。即使没有定义自动快照条件,并且没有手动执行过快照操作,也会生成RDB快照文件。

(5)根据配置规则进行自动快照

代码语言:txt
复制
     redis允许用户自定义快照条件,当符合快照条件时redis自动执行快照操作。自动快照采用异步方式,不会阻塞redis的对外服务。进行快照的条件可以在配置文件中自定义,如:
代码语言:javascript
复制
save 900 1
save 300 10
save 60 10000
代码语言:txt
复制
    每条快照条件占一行,并且以save参数开头。save后面由两个参数构成:时间窗口M和改动的键的个数N。每当时间M内被更改的键的个数大于等于N时,即符合自动快照条件。可以同时存在多个条件,条件之间是“或”的关系。就上例而言,第一行的意思是在900秒内有一个或多个键被更改则进行快照。同理第二行表示300秒内至少有10个键被修改则进行快照,第三行则表示60秒内更改了10000及以上个键触发快照。redis缺省预设的3个条件为:
代码语言:javascript
复制
127.0.0.1:6379> config get save
1) "save"
2) "900 1 300 10 60 10000"
127.0.0.1:6379>

2. 快照原理

代码语言:txt
复制
    redis rdb自动快照过程如下:

(1)redis使用fork函数复制一份当前进程(父进程)的副本(子进程);

(2)父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘的临时文件;

(3)当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,一次快照操作完成。

代码语言:txt
复制
    执行fork时类UNIX操作系统会使用写时复制(copy-on-write)策略,即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改某片数据时(如执行一个写命令),操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的是执行fork一刻的内存数据。
代码语言:txt
复制
    写时复制策略也保证了在fork时虽然看上去生成了两份内存副本,但实际内存的占用量并不会增加一倍。这就意味着当系统内存只有2GB,而redis数据库的内存有1.5G时,执行fork后内存使用量并不会增加到3GB(超出物理内存)。为此需要确保Linux系统允许应用程序申请超过可用内存(物理内存和交换空间)的空间,方法是设置vm.overcommit\_memory=1并使其生效。
代码语言:txt
复制
    另外需要特别注意的是,进行快照过程中,如果写入操作较多,造成fork前后数据差异较大,会使得内存使用量显著超过实际数据的大小,因为内存中不仅保存了当前的数据库数据,而且还保存了fork时刻的内存数据。进行内存用量估算时很容易忽略这一问题,造成内存用量超限。
代码语言:txt
复制
    通过上述过程可以发现redis在进行快照的过程中不会修改RDB文件,只有快照结束后才将旧文件替换成新的,也就是说任何时候RDB文件都是完整的。这使得我们可以通过定时备份RDB文件来实现redis数据库备份。RDB文件是经过压缩(可以配置rdbcompression参数禁用压缩节省CPU占用)的二进制格式,所以占用空间会小于内存中的数据大小,更加利于传输。
代码语言:txt
复制
    redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将一个记录1000万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20-30秒。
代码语言:txt
复制
    通过RDB方式实现持久化,一旦redis异常退出,就会丢失最后一次快照以后更改的所有数据,结果类似于使用MySQL的冷备份或mysqldump导出文件来恢复数据。这就需要开发者根据具体的应用场景,通过组合设置自动快照条件的方式将可能发生的数据损失控制在能够接受的范围内。如果数据相对重要,希望将损失降到最小,可以使用AOF方式进行持久化。

二、AOF

代码语言:txt
复制
    当使用redis存储非临时数据时,一般需要打开AOF持久化俩减少进程终止导致的数据丢失。AOF可以将redis执行的每一条写命令追加到硬盘文件中(类似于MySQL的binlog\_format=statement),这一过程显然会降低redis的性能。

1. 开启AOF

代码语言:txt
复制
    默认情况下redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数启用:
代码语言:javascript
复制
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "no"
127.0.0.1:6379> config set appendonly yes
OK
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "yes"
127.0.0.1:6379> config rewrite
OK
127.0.0.1:6379>
代码语言:txt
复制
    config set appendonly yes密令会开启了AOF功能。redis会阻塞直到初始AOF文件创建完成为止,之后redis会继续处理命令请求, 并开始将写入命令追加到AOF文件末尾。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改。appendfilename参数不能通过config set动态修改,只能通过配置文件设置 appendfilename appendonly.aof。

2. AOF的实现

代码语言:txt
复制
    AOF文件以纯文本的形式记录了redis执行的写命令,例如在开启AOF持久化的情况下执行了如下命令:
代码语言:javascript
复制
127.0.0.1:6379> set foo 1
OK
127.0.0.1:6379> set foo 2
OK
127.0.0.1:6379> set foo 3
OK
127.0.0.1:6379> get foo
"3"
127.0.0.1:6379>
代码语言:txt
复制
    redis会将前三条命令写入AOF文件中:
代码语言:javascript
复制
[root@hdp2/var/redis/6379]#more /var/redis/6379/appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
set
$3
foo
$1
1
*3
$3
set
$3
foo
$1
2
*3
$3
set
$3
foo
$1
3
[root@hdp2/var/redis/6379]#
代码语言:txt
复制
    可见AOF文件的内容正是redis客户端向redis发送的统一请求通信协议的内容。\* 开头表示多行字符串,并在后面跟上字符串的组数,并以\r\n分隔。$ 开头表示单行字符串,并在后面跟上字符串的长度,并以\r\n分隔,接着是字符串的内容和\r\n。下面用telnet进行演示:
代码语言:javascript
复制
[root@hdp2/var/redis/6379]#telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
*3
$3
set
$4
foo1
$3
bar
+OK
代码语言:txt
复制
    查询foo键值:
代码语言:javascript
复制
[root@hdp2~]#redis-cli get foo1
(nil)
... 执行上面的telnet指令后再次查询 ...
[root@hdp2~]#redis-cli get foo1
"bar"
代码语言:txt
复制
    redis的AOF文件和主从复制时主数据库向从数据库发送的内容都使用了统一请求协议。在开启AOF后,首次写AOF文件时先记录一个SELECT 0命令,后面就是客户端执行的写命令。可见redis确实只记录了前3条命令。然而这时有一个问题是前2条命令其实都是冗余的,因为这两条的执行结果会被第三条命令覆盖。随着执行的命令越来越多,AOF文件的大小也会也来越大。redis可以自动优化AOF文件,每当达到一定条件时redis会自动重写AOF文件,这个条件可以通过参数auto-aof-rewrite-percentage和auto-aof-rewrite-min-size进行设置:
代码语言:javascript
复制
127.0.0.1:6379> config get auto*
1) "auto-aof-rewrite-percentage"
2) "100"
3) "auto-aof-rewrite-min-size"
4) "67108864"
127.0.0.1:6379>
代码语言:txt
复制
    auto-aof-rewrite-percentage参数的意义是当目前的AOF文件大小超过上一次重写时AOF文件大小的百分之多少时在此进行重写,如果之前没有重写过,则以启动时的AOF文件大小为依据。auto-aof-rewrite-min-size参数限制了允许重写的最小AOF文件大小。我们还可以使用bgrewriteaof命令手动执行AOF重写。
代码语言:javascript
复制
127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
127.0.0.1:6379>
代码语言:txt
复制
    查看AOF文件内容:
代码语言:javascript
复制
[root@hdp2/var/redis/6379]#more /var/redis/6379/appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
SET
$3
foo
$1
3
*3
$3
SET
$4
foo1
$3
bar
[root@hdp2/var/redis/6379]#
代码语言:txt
复制
    可见冗余的命令已经被删除了。重写的过程只和内存中的数据有关,和之前的AOF文件无关,这和RDB很相似,只不过二者的文件格式不同。在启动是redis会逐个执行AOF文件中的命令来将硬盘中的数据载入到内存中,在数据量大时,载入速度比RDB慢的多。

3. 同步硬盘数据

代码语言:txt
复制
    虽然每次执行更改数据库内容的操作时,AOF都会将命令记录在AOF文件中,但是事实上,由于操作系统的缓存机制,数据并没有真正地写入硬盘,而是进入了系统的硬盘缓存。
代码语言:javascript
复制
[root@hdp2/proc/sys/vm]#more /proc/sys/vm/dirty_expire_centisecs
3000
代码语言:txt
复制
    vm.dirty\_expire\_centisecs指定脏数据能存活的时间,单位为百分之一秒。这里设置为30秒,在操作系统进行写回操作时,如果脏数据在内存中超过30秒时,就会被写回磁盘。
代码语言:txt
复制
    默认情况下系统每30秒会执行一次同步操作,以便将硬盘缓存中的内容真正地写入硬盘,在这30秒的过程中如果系统异常退出则会导致硬盘缓存中的数据丢失。一般来讲启用AOF持久化的应用都无法容忍这样的损失,这就需要redis在写入AOF后主动要求系统将缓存内容同步到硬盘中。可以通过appendfsync参数设置同步的时机:
代码语言:javascript
复制
127.0.0.1:6379> config get appendfsync
1) "appendfsync"
2) "everysec"
127.0.0.1:6379>
代码语言:txt
复制
    默认情况下redis采用everysec规则,即每秒执行一次同步操作。always表示每次执行写入都会执行同步,这是最安全也是最慢的方式。no表示不主动进行同步操作,而是完全交由操作系统来做,即缺省每30秒一次,这是最快但最不安全的方式。一般情况下使用默认值everysec就足够了。appendfsync参数和MySQL的innodb\_flush\_log\_at\_trx\_commit参数作用类似,everysec对应innodb\_flush\_log\_at\_trx\_commit=0,always对应innodb\_flush\_log\_at\_trx\_commit=1。
代码语言:txt
复制
    如果同时开启了RDB和AOF,重启redis会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少。

三、RDB迁移到AOF

代码语言:txt
复制
    RDB是redis默认的持久化方式。假设redis运行在RDB方式,并且有一定的数据,现在需要切换到AOF方式。
代码语言:txt
复制
    初始运行在RDB方式的redis配置:
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#more redis.conf 
daemonize yes
port 20009
dir "/var/redis/20009"
pidfile "/var/redis/20009/redis.pid"
logfile "/var/redis/20009/redis.log"
protected-mode no
save 900 1
save 300 10
save 60 10000
代码语言:txt
复制
    添加数据并保存:
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#redis-cli -p 20009 set key 1
OK
[root@hdp4/var/redis/20009]#redis-cli -p 20009 set foo bar
OK
[root@hdp4/var/redis/20009]#redis-cli -p 20009 bgsave
Background saving started
[root@hdp4/var/redis/20009]#ll
total 16
-rw-r--r-- 1 root root  114 Sep  6 16:42 dump.rdb
-rw-r--r-- 1 root root  177 Sep  6 16:39 redis.conf
-rw-r--r-- 1 root root 1233 Sep  6 16:42 redis.log
-rw-r--r-- 1 root root    6 Sep  6 16:41 redis.pid
代码语言:txt
复制
    登录redis,开启AOF并保存配置:
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#redis-cli -p 20009 
127.0.0.1:20009> config set appendonly yes
OK
127.0.0.1:20009> config get appendonly
1) "appendonly"
2) "yes"
127.0.0.1:20009> config rewrite
OK
127.0.0.1:20009>
代码语言:txt
复制
    这样就开启了AOF功能,redis会阻塞直到初始AOF文件创建完成为止,之后redis会继续处理命令请求,并开始将写入命令追加到AOF文件末尾。
代码语言:txt
复制
    查看配置文件,确认已经添加appendonly参数:
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#more redis.conf 
daemonize yes
port 20009
dir "/var/redis/20009"
pidfile "/var/redis/20009/redis.pid"
logfile "/var/redis/20009/redis.log"
protected-mode no
save 900 1
save 300 10
save 60 10000
# Generated by CONFIG REWRITE
appendonly yes
[root@hdp4/var/redis/20009]#
代码语言:txt
复制
    查看AOF文件,确认文件内容包含两个key和foo两个键的设置命令:
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#more appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
SET
$3
foo
$3
bar
*3
$3
SET
$3
key
$1
1
代码语言:txt
复制
    现在已经启动了AOF方式,并且保存了配置,就可以关闭RDB方式了。关闭的方法是将save的配置清空,并删除配置文件中的配置。
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#redis-cli -p 20009 config set save ""
OK
[root@hdp4/var/redis/20009]#redis-cli -p 20009 config rewrite
OK
[root@hdp4/var/redis/20009]#more redis.conf 
daemonize yes
port 20009
dir "/var/redis/20009"
pidfile "/var/redis/20009/redis.pid"
logfile "/var/redis/20009/redis.log"
protected-mode no

# Generated by CONFIG REWRITE
appendonly yes
[root@hdp4/var/redis/20009]#
代码语言:txt
复制
    重启redis,验证数据完整性:
代码语言:javascript
复制
[root@hdp4/var/redis/20009]#redis-cli -p 20009 shutdown
[root@hdp4/var/redis/20009]#redis-server /var/redis/20009/redis.conf 
[root@hdp4/var/redis/20009]#redis-cli -p 20009 keys "*"
1) "key"
2) "foo"
[root@hdp4/var/redis/20009]#
代码语言:txt
复制
    将RDB迁移到AOF千万不能采用如下的方式,这样会丢失所有的数据。
  1. 在配置文件中配置AOF参数
  2. 正常停止redis
  3. 启动redis
代码语言:txt
复制
    如前所述,此时redis会执行AOF文件,而此时AOF的文件中并没有数据,所以此时查看所有数据库都是空的。这个时候一定要备份RDB文件,mv dump.rdb到备份位置。如果再次正常关机,RDB文件也会置空,要是没有备份数据就彻底丢失,无法恢复了。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年09月06日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、RDB
    • 1. 触发条件
      • 2. 快照原理
      • 二、AOF
        • 1. 开启AOF
          • 2. AOF的实现
            • 3. 同步硬盘数据
            • 三、RDB迁移到AOF
            相关产品与服务
            云数据库 Redis
            腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档