redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到磁盘来保证持久化。可以不定期的通过异步方式保存到磁盘上(即“半持久化模式”);也可以把每一次数据变化都写入到一个Append Only File(AOF)里面(即“完全持久化模式”)。redis支持两种持久化方式,一种是默认方式的RDB(Snapshotting快照)持久化,另一种是AOF(Append-only file)持久化方式。这两种持久化方式都可以将内存中的数据库状态保存到磁盘上,redis对应的也有两种落地文件:数据文件(默认文件名dump.rdb,也即快照文件)、AOF持久化文件。接下来详细说明它们之间的用法:
2.AOF持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集,它可以实现每次操作都持久化。
AOF文件中的命令全部以redis协议的格式来保存,新命令会被追加到文件的末尾。
redis还可以在后台对AOF文件进行重写(rewrite),使得AOF文件的体积不会超出保存数据集状态所需的实际大小。redis还可以同时使用AOF持久化和RDB持久化。在这种情况下,当redis重启时,它会优先使用AOF文件来还原数据集,
因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。也可以关闭持久化功能,让数据只在服务器运行时存在。
RDB方法在redis异常死掉时,最近的数据会丢失(丢失数据的多少视你save策略的配置),所以这是它最大的缺点,当业务量很大时,丢失的数据是很多的。
Append-only方法可以做到全部数据不丢失,但redis的性能就要差些。AOF就可以做到全程持久化,只需要在配置文件中开启(默认是no),appendonly
yes开启AOF之后,redis每执行一个修改数据的命令,都会把它添加到aof文件中,当redis重启时,将会读取AOF文件进行“重放”以恢复到redis关闭前的最后时刻。
redis启动装载:
AOF优先于RDB
RDB性能优于AOF,因为里面没有重复
Redis一次性将数据加载到内存中,一次性预热
LOG Rewriting(重写)随着修改数据的执行AOF文件会越来越大,其中很多内容记录某一个key的变化情况。因此redis有了一种比较有意思的特性:在后台重建AOF文件,而不会影响client端操作。在任何时候执行bgrewriteaof命令,都会把当前内存中最短序列的命令写到磁盘,这些命令可以完全构建当前的数据情况,而不会存在多余的变化情况(比如状态变化,计数器变化等),缩小的AOF文件的大小。所以当使用AOF时,redis推荐同时使用bgrewriteaof。AOF 重写和 RDB 创建快照一样,都巧妙地利用了写时复制机制。
为了压缩AOF的持久化文件,Redis提供了bgrewriteaof命令。
收到此命令后Redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件,以此来实现控制AOF文件的增长。
由于是模拟快照的过程,因此在重写AOF文件时并没有读取旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的AOF文件。
AOF文件刷新的方式,有三种,参考配置参数appendfsync :
appendfsync always:每提交一个修改命令都调用fsync刷新到AOF文件,非常非常慢,但也非常安全;
appendfsync everysec:每秒钟都调用fsync刷新到AOF文件,很快,但可能会丢失一秒以内的数据;
appendfsync no:依靠OS进行刷新,redis不主动刷新AOF,这样最快,但安全性就差。默认并推荐每秒刷新,这样在速度和安全上都做到了兼顾。
redis默认的持久化方式是RDB,数据写入到dump文件中。如果要启用AOF持久化,就在redis.conf文件中配置如下:
appendonly yes #启用AOF持久化方式
appendfilename "appendonly.aof" #AOF文件的名称,默认为appendonly.aof
# appendfsync always #每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但速度也是最慢的,一般不推荐使用。
appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,这种fsync策略可以兼顾速度和安全性,是受推荐的方式。
# appendfsync no #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不被推荐。
可能由于系统原因导致了AOF损坏,redis无法再加载这个出错的AOF文件,可以按照下面步骤来修复:
1)首先做一个AOF文件的备份,复制到其他地方;
2)修复原始AOF文件,执行命令redis-check-aof –fix ;
3)可以通过diff –u命令来查看修复前后文件不一致的地方;
4)重启redis服务。
LOG Rewrite的工作原理:
同样用到了copy-on-write:首先redis会fork一个子进程;子进程将最新的AOF写入一个临时文件;父进程增量的把内存中的最新执行的修改写入(这时仍写入旧的AOF,rewrite如果失败也是安全的);当子进程完成rewrite临时文件后,父进程会收到一个信号,并把之前内存中增量的修改写入临时文件末尾;这时redis将旧AOF文件重命名,临时文件重命名,开始向新的AOF中写入。
为以防万一(机器坏掉或磁盘坏掉),最好定期把使用filesnapshotting 或 Append-only 生成的*rdb *.aof文件备份到远程机器上,然后可以用crontab定时(比如每半小时)scp一次。如果没有使用redis的主从功能 ,半小时备份一次应该是可以了;并且如果应用数据量不大的话,可以单机部署,做主从有点浪费。具体还是要根据应用而定。
接着具体解说下这两种持久化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
如果的内存中的数据量非常大的时候,rdb持久化的临时文件就会非常大,几乎是原文件的1倍,性能有所降低。
如果当写操作要立刻持久化的时候,可以执行命令:save
save是全阻塞的,bgsave是异步的。
snapshot快照首先将数据写入临时文件,当成功结束后,将临时文件重名为dump.rdb
使用RDB恢复数据:
自动的持久化数据存储到dump.rdb后。实际只要重启redis服务即可完成(启动redis的server时会从dump.rdb中先同步数据)
客户端使用命令进行持久化save存储:
# redis-cli -h ip -p port save
# redis-cli -h ip -p port bgsave
一个是在前台进行存储,一个是在后台进行存储。我的client就在server这台服务器上,所以不需要连其他机器,直接. /redis-cli bgsave。
由于redis是用一个主线程来处理所有 client的请求,这种方式会阻塞所有client请求。所以不推荐使用。另一点需要注意的是,每次快照持久
化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能
如果aof或rdb文件语法有误,可以使用下面两条命令来修复。
1)aof修复命令:
# redis-check-aof --fix appendonlly.aof
2)rdb修复命令:
# redis-check-rdb--fix dump.rdb
aof是采用文件追加方式,将所有的写操作保存在aof文件中,当文件越来越大时,有可能存在相同的写操作,这些相同的操作可以将他浓缩为一条操作,这样可以减少aof文件的容量。
redis对aof新增了一种重写机制,当aof文件大小超过所设定的阈值时,redis会启动aof文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof。
|
RDB持久化优点
RDB
是一个非常紧凑(compact)的文件,它保存了Redis在某个时间点上的数据集。
这种文件非常适合用于进行备份的,比如说,可以在最近的24小时内,每小时备份一次RDB文件,并且在每个月的每一天,也备份一个 RDB
文件。这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。RDB 非常适用于灾难恢复(disaster
recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心,或者亚马逊 S3 中。RDB可以最大化
Redis
的性能:父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O
操作。RDB在恢复大数据集时的速度比AOF的恢复速度要快。
AOF持久化缺点
对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB
。在一般情况下,每秒 fsync 的性能依然非常高,而关闭fsync可以让AOF的速度和 RDB 一样快, 即使在高负荷之下也是如此。
不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。AOF 在过去曾经发生过这样的 bug :
因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。 (举个例子,阻塞命令BRPOPLPUSH就曾经引起过这样的
bug 。)测试套件里为这种情况添加了测试:它们会自动生成随机的、复杂的数据集,并通过重新载入这些数据来确保一切正常。 虽然这种 bug 在
AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。
RDB持久化和AOF持久化,应该用哪一个?
一般来说,如果想达到足以媲美PostgreSQL的数据安全性,应该同时使用两种持久化功能。
如果非常关心数据,但仍然可以承受数分钟以内的数据丢失, 那么可以只使用RDB持久化。
不推荐只使用AOF持久化:因为定时生成RDB快照(snapshot)非常便于进行数据库备份,并且RDB恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用RDB还可以避免之前提到的AOF程序的bug。
快照
在默认情况下,Redis将数据库快照保存在名字为dump.rdb的二进制文件中。可以对Redis
进行设置, 让它在“ N 秒内数据集至少有M个改动”这一条件被满足时,自动保存一次数据集。也可以通过调用 SAVE 或者 BGSAVE ,
手动让 Redis 进行数据集保存操作。比如说, 以下设置会让 Redis 在满足“ 60
秒内有至少有1000个键被改动”这一条件时,自动保存一次数据集:
save 60 1000
这种持久化方式被称为快照(snapshot)。
快照的运作方式:
当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:
Redis 调用 fork() ,同时拥有父进程和子进程。
子进程将数据集写入到一个临时 RDB 文件中。
当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。
只进行追加操作的文件(append-only file,AOF)
快照功能并不是非常耐久(durable):
如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。尽管对于某些程序来说,
数据的耐久性并不是最重要的考虑因素, 但是对于那些追求完全耐久能力(full durability)的程序来说, 快照功能就不太适用了。
从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化。
你可以通过修改配置文件来打开 AOF 功能:
appendonly yes
从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。
这样的话, 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。
下面是redis默认的快照设置(要想关闭save快照功能,即注释掉这三个默认的save设置即可):
save 900 1 #当有一条Keys数据被改变时,900秒刷新到Disk一次
save 300 10 #当有10条Keys数据被改变时,300秒刷新到Disk一次
save 60 10000 #当有10000条Keys数据被改变时,60秒刷新到Disk一次
Redis的RDB文件不会坏掉,因为其写操作是在一个新进程中进行的。
当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件。
这样在任何时候出现故障,Redis的RDB文件都总是可用的。
同时,Redis的RDB文件也是Redis主从同步内部实现中的一环。
第一次Slave向Master同步的实现是:
Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。
第二次以及以后的同步实现是:
Master将变量的快照直接实时依次发送给各个Slave。
但不管什么原因导致Slave和Master断开重连都会重复以上两个步骤的过程。
Redis的主从复制是建立在内存快照的持久化基础上的,只要有Slave就一定会有内存快照发生。
可以很明显的看到,RDB有它的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的。
从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。
AOF重写:
因为 AOF
的运作方式是不断地将命令追加到文件的末尾, 所以随着写入命令的不断增加, AOF 文件的体积也会变得越来越大。举个例子,
如果你对一个计数器调用了 100 次 INCR , 那么仅仅是为了保存这个计数器的当前值, AOF 文件就需要使用 100
条记录(entry)。然而在实际上, 只使用一条 SET 命令已经足以保存计数器的当前值了, 其余 99
条记录实际上都是多余的。为了处理这种情况, Redis 支持一种有趣的特性: 可以在不打断服务客户端的情况下,
对AOF文件进行重建(rebuild)。执行 BGREWRITEAOF 命令,
Redis将生成一个新的AOF文件,这个文件包含重建当前数据集所需的最少命令。
RDB和AOF之间的相互作用:
在版本号大于等于 2.4 的 Redis 中, BGSAVE 执行的过程中, 不可以执行 BGREWRITEAOF 。 反过来说, 在 BGREWRITEAOF 执行的过程中, 也不可以执行 BGSAVE 。
这可以防止两个 Redis 后台进程同时对磁盘进行大量的 I/O 操作。
如果
BGSAVE 正在执行, 并且用户显示地调用 BGREWRITEAOF 命令, 那么服务器将向用户回复一个 OK 状态, 并告知用户,
BGREWRITEAOF 已经被预定执行: 一旦 BGSAVE 执行完毕, BGREWRITEAOF 就会正式开始。当 Redis 启动时,
如果 RDB 持久化和 AOF 持久化都被打开了, 那么程序会优先使用 AOF 文件来恢复数据集, 因为 AOF
文件所保存的数据通常是最完整的。
备份Redis 数据:
Redis对于数据备份是非常友好的,因为你可以在服务器运行的时候对
RDB 文件进行复制: RDB 文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的 RDB 文件时,
它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用 原子地用临时文件替换原来的 RDB 文件。这也就是说,
无论何时, 复制 RDB 文件都是绝对安全的。
1)主库不开启AOF持久化,并关闭save快照功能(即注释默认的三个save设置),只在每晚12点定时手动做一次bgsave快照,并将快照文件转移到异地。即主库上不产生appendonly.aof持久化文件,做的快照数据放在.rdb文件里(如dump.rdb,由于是压缩配置(rdbcompression
yes表示快照文件要压缩),所以快照文件要比aof文件小),然后将这个快照文件dump.rdb转移到其他的服务器上,防止主服务器宕机造成的损失!
2)从库开启AOF持久化并1秒落地,同样不做快照save。并在每晚12点做一次bgrewriteaof压缩appendonly.aof持久化文件,压缩前先对aof文件进行备份。
/data/logs/bgsave.log
从库服务器脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[root@cdn redis] # cat redis_backup.sh
#!/bin/bash
CURDATE=` date +%Y%m%d`
CURHOUR=` date +%Y%m%d_%H`
CURTIME=` date +%Y%m%d_%H%M%S`
LOGFILE= /data/logs/redisbak/redis_allbak_ ${CURDATE}.log
REDISNAME=appendonly.7000
DDIR= /data/backup/redis/ $CURHOUR
mkdir -p ${DDIR}
RDIR= /data/redis
cd ${RDIR}
tar -zcf $DDIR/${REDISNAME}_${CURTIME}. tar .gz appendonly.7000.aof
if [ $? != 0 ]; then
echo "tar error $REDISNAME.aof" >> $LOGFILE
fi
sleep 5
/usr/local/bin/redis-cli -h 127.0.0.1 -p 6379 bgrewriteaof
sleep 5
### delete old backup data dir ###
#/bin/rm -rf `date -d -30day + "%Y%m%d"`
find /data/backup/redis/ -mtime +30 | xargs rm -rf
echo "Backup $REDISNAME ok at $CURTIME !" >> $LOGFILE
|
[root@cdn redis]# crontab -e
59 23 * * * /bin/bash /data/redis/redis_backup.sh >/dev/null 2>&1
[root@cdn redis]# cd /data/backup/redis/20161207_13
[root@cdn 20161207_13]# ls
appendonly.7000_20161207_133131.tar.gz
/usr/local/redis/var/bgsave.log
从库每晚12点拷贝每个实例的AOF到其他目录并对其打包,压缩包要有异地备份,之后再压缩AOF(bgrewriteaof)。
从库备份AOF并bgrewriteaof脚本(redis_backup.sh :对单个实例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#!/bin/sh
## FD:File Dir
## RD:Runing Dir
## 第一个参数为redis实例名
if [ $ # -ne 1 ]; then
echo “Usage:$0 redis_name”
exit
fi
CURDATE=` date +%Y%m%d`
CURHOUR=` date +%Y%m%d_%H`
CURTIME=` date +%Y%m%d_%H%M%S`
REDISPASSWORD=My #redis
REDISNAME=$1
PORT=` echo $REDISNAME | cut -c6-9`
LOGFILE= /data/logs/redisbak/redis_allbak_ ${CURDATE}.log
if [ "${REDISNAME}" = "" ]; then
echo “redis name Error!”
exit 1
else
if [ ! -d "/data/redis-data/${REDISNAME}" ]; then
echo “redis name Error!”
exit 1
fi
fi
DDIR= /data/backup/redis/ $CURHOUR
mkdir -p ${DDIR}
RDIR= /data/redis-data/ $REDISNAME
cd ${RDIR}
tar -zcf $DDIR/${REDISNAME}_${CURTIME}. tar .gz $REDISNAME.aof
if [ $? != 0 ]; then
echo “ tar error $REDISNAME.aof” >> $LOGFILE
#exit 1
fi
sleep 5
/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p $PORT -a $REDISPASSWORD bgrewriteaof
sleep 5
### delete old backup data dir ###
#/bin/rm -rf `date -d -7day +”%Y%m%d”`
find /data/backup/redis/ -mtime +7 | xargs rm -rf
echo “Backup $REDISNAME ok at $CURTIME !” >> $LOGFILE
|
从库对所有实例备份(/data/sh/redis_allbak.sh)
1 2 3 4 5 6 7 |
#!/bin/sh
CURDATE=` date +%Y%m%d`
LOGFILE= /data/logs/redisbak/redis_allbak_ ${CURDATE}.log
for PORT in ` seq 6001 6010`
do
/data/sh/redis_backup .sh slave${PORT} && echo “slave${PORT} ok ` date +%Y%m%d_%H%M%S`” >> $LOGFILE 2>&1 || echo “slave${PORT} backup error” >> $LOGFILE 2>&1
done
|
操作注意事项
1)若主库挂了,不能直接开启主库程序。若直接开启主库程序将会冲掉从库的AOF文件,这样将导致只能恢复到前一天晚上12的备份。
2)程序在跑时,不能重启网络(程序是通过网络接口的端口进行读写的)。网络中断将导致中断期间数据丢失。
3)确定配置文件全部正确才启动(尤其不能有数据文件名相同),否则会冲掉原来的文件,可能造成无法恢复的损失。
灾难恢复
1)主库故障,快速恢复到最近状态描述:主库挂了(redis程序挂了/机器宕机了),从库正常,恢复到主库挂掉的时间点:去从库手动做一次快照,拷贝快照到主库相应目录,启动,OK。
在从库做一次快照,转移快照文件到其他目录,将快照文件目录拷贝到主库相应目录,启动主库,OK!
( /data/sh/redis_bgsave_cp.sh )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/bin/bash
REDISPASSWORD=My #redis
for PORT in ` seq 6001 6010`
do
/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p $PORT -a $REDISPASSWORD bgsave
sleep 5
done
sleep 15
for PORT in ` seq 6001 6010`
do
SDIR= /data/redis-data/slave ${PORT}/
DDIR= /data/redis_recovery/redis-data/
mkdir -p $DDIR /redis ${PORT}/
cd $SDIR
cp -rf slave${PORT}.rdb $DDIR /redis ${PORT} /redis ${PORT}.rdb
#sleep 1
done
|
在主库将原来数据目录重命名。
从从库拷贝快照文件到主库。
启动主库。
2)恢复到当天12点状态注意备份数据(当前状态AOF+正常退出快照)!
停止redis。
解压AOF(/data/sh/redis_untar.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/bin/bash
DAY=20111102
SDIR= /data/backup/redis/20161102_00/
cd $SDIR
for PORT in ` seq 6001 6010`
do
tar -zxf slave${PORT}_$DAY_*. tar .gz
sleep 2
done
切割AOF( /data/sh/redis_sed .sh)
#!/bin/bash
DDIR= /data/redis_recovery/
TAG=”TAG111101_1200″
for PORT in ` seq 6001 6010`
do
SDIR= /data/backup/redis/20161102_00/
SAOF=${SDIR} /slave ${PORT}.aof
line=` sed -n “/$TAG/=” $SAOF`
num=$[$line + 3]
mkdir -p ${DDIR} /slave ${PORT}/
sed “${num},\$d” $SAOF > ${DDIR} /slave ${PORT} /slave ${PORT}.aof
done
|
将原来数据目录重命名。
将切割出来的AOF目录重命名为配置文件的数据目录。(注释主从同步配置项)。
启动从库。
做快照,拷贝到主库,启动主库(同上面第1)步)。
3)恢复到两天或几天前12点状态从库每晚备份要备份AOF未bgrewriteaof之前的数据,可根据当天晚上12点备份,没有bfrewriteaof之前的AOF文件来进行恢复,方法同上面的第2)步。