数据库

11. Redis 持久化

2021-03-05  本文已影响0人  随便写写咯

5. Redis持久化

RDB:

系统默认每隔一段时间, 做一次快照, 保存到相同路径, 相同文件名, 新的快照文件, 会替换旧的, 因此, 默认系统只能保存一份最新的快照文件. 服务重启时, 会把这个文件, 加载到内存. 系统默认的快照和载入.rdb文件(二进制)都是redis自动完成, 无需人工操作.

优点: 定期得到数据库的完全备份, 重启服务后自动载入
缺点: 可能丢失从上次快照到当前时间点之间未做快照的数据

RDB bgsave 实现快照的具体过程:

Redis从master主进程fork出一个子进程, 使用写实复制机制, 子进程将内存的数据保存为一个临时文件, 比如dump.rdb.temp
当数据保存完成之后, 再将上一次保存的RDB文件替换掉, 之后关闭子进程, 这样可以确保每一次做RDB快照保存的数据都是完整的
因为直接替换RDB文件的时候, 可能会出现突然断电等问题, 而导致RDB文件还没有保存完整就因为突然关机停止保存, 而导致数据丢失的情况.
后续可以手动将每次生成的RDB文件进行备份, 这样可以最大化保存历史数据
RDB bgsave 实现快照的具体过程

RDB快照实现方式:

  1. save命令(手动): 同步, 会前台执行RDB快照操作, 阻塞其他用户的访问和对数据库的修改
  2. bgsave命令(手动): 异步, 后台执行RDB快照操作, 不阻塞其他用户请求, 生产环境推荐使用. bgsave会单独开启一个子进程, 进行备份, 因此不会影响其他的用户访问.
  3. 定义计划任务(自动执行): 生产建议禁用系统自动的备份规则, 而是使用计划任务周期备份,配合inotify, 监控备份时产生的临时文件, 来判断bgsave命令是否执行完毕, 当执行完毕后, 再把rdb文件移动到备份目录
  4. 配置文件中定义的自动save策略, 让RDB快照(自动执行): 根据定义的时间, 和数据更改次数来自动生成RDB快照文件

RDB相关配置, 在redis配置文件定义:

以CentOS8, yum安装的redis为例

################################ SNAPSHOTTING ################################
#
# Save the DB on disk:
#
#   save <seconds> <changes> # 自动进行RDB备份, 规定的秒内, 发生了规定次数的改变, 就会自动执行save快照.

#   save ""
#可以自动添加,修改
save 900 1  # 900秒. 15分钟内, 发生了一次记录的变化
save 300 10 
save 60 10000

快照文件存放路径

# The filename where to dump the DB
dbfilename dump.rdb  #一定要确保,redis账号对该目录有权限.

# The working directory.
dir /var/lib/redis #RDB文件, 默认的存放目录

[13:48:01 root@redis ~]#ll -d /var/lib/redis/
drwxr-x--- 2 redis redis 6 May  7  2020 /var/lib/redis/

[13:48:48 root@redis ~]#ll /var/lib/redis/
total 4
-rw-r--r-- 1 redis redis 92 Mar  5 13:48 dump.rdb

RDB文件在关闭redis服务时, 会自动生成, 从内存中把数据保存到.rdb文件里.
因此, 如果想彻底删除.rdb文件, 需要先停止redis服务, 这样内存中的数据会保存到rdb文件里, 然后再删除rdb文件, 这样系统再次启动redis服务时, 就没有数据了.

1554:M 17 Oct 2020 21:06:12.550 * DB loaded from disk: 0.000 seconds #redis启动时, 从磁盘加载rdb文件
1554:M 17 Oct 2020 21:06:12.550 * Ready to accept connections

范例: 定义redis的RDB快照, 每1秒内发生一次记录变化, 就做一次快照. (仅做测试)

先修改rdb文件的名字为6379, 只是为了方便观察文件

dbfilename dump_6379.rdb

停止服务, 删除.rdb文件, 确保重启后内存中的数据为空, 也可以不清空rdb文件, 修改配置文件后直接测试也行, 这里只是为了方便观察而已. 但是, 实际工作中清空rdb文件前, 一定要确保有做备份.

[23:12:08 root@redis ~]#systemctl stop redis
[23:13:11 root@redis ~]#rm -rf /var/lib/redis/dump.rdb 
[23:13:23 root@redis ~]#ll /var/lib/redis/dump.rdb 
ls: cannot access '/var/lib/redis/dump.rdb': No such file or directory

修改配置文件, 指定每一秒, 产生一次改变,就写入到rdb文件

save 1 1

开启redis服务, 确保没有数据加载到内存

 is disabled.
1948:M 17 Oct 2020 23:17:32.513 * Ready to accept connections #rdb文件并没有被加载到内存

连接到redis, 追踪日志, 执行命令, 观察结果

[23:17:32 root@redis /var/lib/redis]#redis-cli
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> set name wang
OK
#日志
1948:M 17 Oct 2020 23:19:21.764 * 1 changes in 1 seconds. Saving...
1948:M 17 Oct 2020 23:19:21.765 * Background saving started by pid 1953
1953:C 17 Oct 2020 23:19:21.804 * DB saved on disk
1953:C 17 Oct 2020 23:19:21.804 * RDB: 4 MB of memory used by copy-on-write
1948:M 17 Oct 2020 23:19:21.866 * Background saving terminated with success
#rdb文件自动生成
[23:19:51 root@redis /var/lib/redis]#ll /var/lib/redis/
total 4
-rw-r--r-- 1 redis redis 108 Oct 17 23:19 dump_6379.rdb

如果不想使用RDB快照, 也可以禁用, 修改配置文件即可. 不过一旦禁用了save, 那么关闭redis, 是不会把内存中的数据保存到rdb文件的, 需要注意.

如果禁用了save自动保存, redis服务关闭是不会自动把内存中的数据保存到rdb文件中, 因此, 一定要有额外的手动保存机制, 才不会丢失内存中的数据

save ""                                                                                                                                                                             

#save 900 1
#save 300 10
#save 60 10000

注意: 即使Redis是保存缓存数据也不建议禁用RDB快照功能, 因为快照保存了内存中的数据, 下次重启服务需要从快照中恢复数据, 如果没有了快照, 那么下次重启内存中是没有缓存数据的, 那么客户端就要去MySQL拿数据, 会给MySQL造成很大压力.

save命名, 手动执行RDB快照生成

工作中不会使用save命令, 因为save是前台执行, 会阻塞redis, 造成其他用户无法写入数据
[14:49:22 root@redis ~]#redis-cli
127.0.0.1:6379> save
OK

bgsave命名, 手动执行RDB快照生成

工作中建议使用bgsave, 配合计划任务, 进行定期的数据保存, bgsave在后台执行, 不会阻塞redis
127.0.0.1:6379> bgsave
Background saving started

stop-writes-on-bgsave-error yes

默认为yes时, 因为磁盘空间满等原因, 导致快照无法保存出错时, 会禁止redis写入操作, 生产建议为no. 不能因为无法备份,导致redis无法写入

rdbcompression yes

持久化到RDB文件时, 是否压缩, 默认yes为压缩. 这也意味着, rdb文件在磁盘上的大小, 和实际加载到内存中后占内存的大小是不一样的.

如何导入rdb备份文件

如果需要把备份的rdb文件导入到redis , 需要先停止redis服务, 让redis把内存中现有的数据保存到rbd文件里, 再把这个rdb文件保存到其他位置, 然后把需要导入的rdb文件放到dir目录下, 之后开启服务, 这样就可以实现导入备份的rdb文件. 需要注意的是, rdb文件需要给予redis属主和属组权限, 并且名字和配置文件中指定的rdb文件名字要一致

AOF:

图片.png
AOF: AppendOnlyFile, 按照操作的顺序依次将操作追加到指定的日志文件末尾
实时备份, 但无法单独用来还原, 因为aof默认是不开启的, 因此, 只能保存开启aof后产生的增量数据, 要结合RDB实现备份和还原
AOF和RDB一样使用了写实复制机制, AOF默认为每秒fsync一次, 即将执行的命令保存到AOF文件中
这样即使redis服务器发生故障, 最多也只丢失一秒钟内的数据, 也可以设置不同的fsync策略, 如always, 即设置每次执行命令的时候, 都执行fsync, fsync会在后台执行
所以主线程可以继续处理用户的正常请求而不受到写入AOF文件的I/O影响

AOF使用注意事项:

类似于MySQL的二进制日志, AOF只记录数据库发生的增量性操作, 如果redis服务器搭建好的同时没有启动AOF, 而是之后某个时间点启用的AOF, 那么AOF文件是没有数据的, 如果启动redis之前, 没有rdb数据备份, 那么这时用会优先用AOF恢复数据, 就会造成数据丢失

RDB只存放数据最新的状态, 而AOF是追加式记录数据的操作.
比如, x从1一直增加到了10, 那么RDB只会记录x=10, 而AOF会记录x从1到10发生的每一步操作. AOF文件会越来越大, 因为要把历史所有操作的记录下来.

但是AOF也不会完全的不丢数据, 这个要看数据保存策略, 如果数据发生修改就立即写磁盘到aof文件里, 那么数据不会丢失, 如果先放在内存缓冲区, 之后再由内核在空闲时间写入磁盘到aof文件, 那么这种情况就会丢数据. 如果不能丢数据, 那么要设置每次执行写操作, 就立即保存到磁盘.

RDB和AOF同时开启, 进行恢复时, 默认AOF会优先生效, 优先级高. 并且重启服务后, 会因为AOF的优先级高于RDB, 而AOF文件默认没有数据, 为空, 从而导致所有的数据丢失

注意: 很重要

AOF模式默认是关闭的, 第一次开启AOF, 并且重启服务生效后, 会因为AOF的优先级高于RDB, 而造成redis只会加载AOF中的数据, 即使有RDB文件, 也不会加载, 而AOF第一次启用, AOF文件默认没有数据, 为空, 需要执行命令后才会有数据保存到AOF文件中, 这样就会造成redis从AOF中把空数据加载到了内存中, 并且把当前内存中的数据覆盖了. 如果当前内存中的数据没有做备份, 那就丢失了. 另外, 如果在内存没有数据的时候, 执行了save/bgsave命令, 那么本地的rdb文件也会被清空.

AOF默认1秒钟同步一次磁盘, 这种情况有可能最多丢失一秒钟内的数据; 如果不想丢数据, 也可以设定为always, 只要有写操作, 就立即同步磁盘

# appendfsync always
appendfsync everysec
# appendfsync no

RDB和AOF最好在搭建完redis服务后就同时开启

启用AOF

appendonly no  --> appendonly yes
# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"  #启用后会在dir目录下生成aof文件
dir /var/lib/redis

重启服务器后会在dir目录下自动生成AOF文件

[14:15:34 root@redis ~]#ll /var/lib/redis
total 4
-rw-r--r-- 1 redis redis 108 Oct 17 23:19 dump_6379.rdb
[14:15:44 root@redis~]#systemctl restart redis
[14:15:50 root@redis~]#ll /var/lib/redis
total 4
-rw-r--r-- 1 redis redis   0 Oct 19 14:15 appendonly.aof
-rw-r--r-- 1 redis redis 108 Oct 19 14:15 dump_6379.rdb

这时, 由于我们是后启用的AOF, 导致了重启服务后, redis只会加载AOF里面的数据, 导致数据内存中的原有数据被清空

127.0.0.1:6379> keys *
(empty list or set)

一但发现内存中的数据被清空了, 要马上查看RDB备份文件, 千万别在执行命令然后备份, 这时候备份, 会把原有的RDB文件也清空, 就彻底丢数据了

解决方案:

  1. redis刚配好的时候, 就同时启用RDB和AOF
  2. 如果AOF是后启用的, 一定要确保提前有rdb文件备份, 开启AOF后, 借助导入工具, 把备份的rdb文件导入到Redis里; 由于AOF只记录着开启AOF后, redis的操作, 因此导入过程的操作也是可以记录到AOF里的, 这样就可以达到AOF和RDB是相同的数据了.
  3. 或者, 进入redis, 临时启用AOF, 这时会立即生成AOF文件, 并且把内存中现有的数据保存到AOF中, 之后新增的数据也会保存到AOF.

测试: 临时启动AOF, 进行数据的保存

查看dir目录下的文件, 如果有之前的appendaof文件, 删除掉

确保AOF处于no的状态, 重启服务

appendonly no
systemctl restart redis

重启服务后进入redis, 设置几个变量, 并且执行bgsave备份

[14:39:01 root@redis~]#redis-cli
127.0.0.1:6379> set name1 wang
OK
127.0.0.1:6379> set name2 bai
OK
127.0.0.1:6379> set name3 li
OK
127.0.0.1:6379> bgsave
Background saving started

然后启用AOF功能

127.0.0.1:6379> config set appendonly yes
OK

查看/var/lib/redis下生成了AOF文件, 并且文件大小和bgsave生成的rdb文件大小一样
这时, 只要不重启服务, AOF就是一直开启的, 并且默认会每一秒将发生的数据更改存到AOF文件里

[14:37:11 root@redis ~]#ll /var/lib/redis
total 8
-rw-r--r-- 1 redis redis 130 Oct 19 14:39 appendonly.aof
-rw-r--r-- 1 redis redis 130 Oct 19 14:39 dump_6379.rdb

不丢数据的宗旨就是需要重启服务时, 确保AOF中的数据和RDB文件中的数据一致

补充: AOF文件就是个文本文件, 下图是tail追踪的格式. 如果进行了错误操作, 可以直接修改AOF文件. 修改后重启服务即可. 但是要注意格式问题. 格式有问题, AOF文件有可能无法加载, 会导致Redis无法启动, 可以尝试用redis-check修复, 但是redis-check只能修复AOF尾部的错误, 中间和前面的错误无法修复, 很有可能因为无法修复就把AOF文件清空了

tail -c 100 /var/lib/redis/appendonly.aof
image.png

aof-load-truncated yes

是否加载由于某些原因导致的末尾异常的AOF文件, 如进程被kill, 断电, 建议yes

AOF rewrite重写

将一些重复的, 可以合并的, 过期的数据, 删除, 从而节约AOF文件占用的硬盘空间, 也能加速恢复过程.
整理过程中, 会生成一个temp临时的aof文件, 将有效的数据存入到这个临时的aof文件中, 等待整理完毕再把旧的aof文件替换掉
整理要挑系统空闲时间, 因为会消耗磁盘I/O
可以手动执行bgrewriteaof触发AOF重写, 或定义自定的rewrite策略

相关配置

appendonly yes
appendfilename "appendonly-${port}.aof"
appendfsync everysec  
# AOF持久化策略, no表示由操作系统保证数据同步到磁盘, always表示每次写入都执行fsync, 以保证数据同步到磁盘, 性能较差, 但是安全性高
# everysec表示每秒执行一次fsync, 可能会导致丢失这1s的数据, 此为默认值, 生成建议为一秒执行一次fsync
no-appendfsync-on-rewrite yes
# 同时在执行bgrewriteaof操作和主进程写AOF文件的操作时, 两者都会操作磁盘, 而bgwriteaof往往会涉及大量磁盘操作, 这样就会造成主进程在写AOF文件的时候出现阻塞的情形, 可通过no-appendfsync-on-rewrite参数进行控制
# 默认为no, 表示"不暂缓", 新的AOF记录仍然会被立即同步到磁盘, 是最安全的方式, 不会丢失数据, 但是要忍受阻塞的问题
# yes, 相当于将appendfsync设置为no, 这说明并没有执行磁盘操作, 只是写入了缓冲区, 因此这样并不会造成阻塞, 因为没有磁盘竞争
# 但是如果这个时候redis挂掉, 就会丢失数据, Linux默认fsync的策略是30s, 因此最多会丢失30s的数据, 但由于yes性能较好而且会避免出现阻塞, 因此比较推荐
auto-aof-rewrite-percentage 100 # 当aof文件增长超过指定的百分比时, 执行aof重写, 设置为0表示不自动重写aof文件, 重写是为了使aof体积保持最小, 但是还可以确保保存完整的数据
auto-aof-rewrite-min-size 64mb # 当AOF文件达到64m时, 就触发aof重写
aof-load-truncated yes

重写过程

重写AOF的时候,创建一个重写子进程(bgrewriteof),然后读取旧的AOF文件,执行重写并写入到一个临时AOF。

在此期间,主进程一边将接收到的指令累计到一个aof重写缓冲区中,一边将这些新的指令存到到AOF buffer, 之后写到旧的AOF文件。

(这样的好处,保证AOF文件的可用性,避免写过程时出意外)

子进程完成重写后,向主进程发送一个信号量,主进程就将aof_rewrite_buffer缓冲区中的指令追加到新AOF。

之后用新的AOF替换旧的AOF,之后的新指令就追加到新的AOF

由于子进程在将数据从旧的AOF压缩到新的AOF过程中, 新的数据会被主进程分别写入到aof缓冲和旧的aof文件
因此即使中间AOF重写出错, 旧的AOF中还是保存了所有的数据, 同时新的数据因为也同时被写入到了aof缓冲
那么一旦完成了旧的aof压缩, 这些缓冲数据就会追加到新的aof文件里, 并且把旧的aof替换
这样即使覆盖了旧的aof, 也不会丢失在执行压缩期间, 那一部分新写入的数据
image.png

手动执行AOF重写, 利用bgrewriteaof命令

bgrewriteaof
时间复杂度: O(N), N为要追加到AOF文件中的数据数量
执行一个AOF文件重写操作, 重写会创建一个当前AOF文件的体积优化版本

即使berewriteaof执行失败, 也不会有任何数据的丢失, 因为旧的aof文件在bgrewriteaof成功之前不会被修改

重写操作只会在没有其他持久化工作在后台执行时被触发, 也就是说:
如果redis的子进程正在执行快照的保存工作, 那么aof重写的操作会被预定(scheduled), 等到保存工作完成之后在执行aof重写. 
在这种情况下, bgwriteaof的返回值仍然是ok, 但还会加上一条额外的信息, 说明bgrewriteaof要等到保存操作完成之后才能执行. 
在redis 2.6版本或以上, 可以使用info [section] 命令来查看berewriteaof是否被预定

如果已经有别的aof文件重写在执行, 那么berewriteaof会返回一个错误, 并且这个新的重写请求不会被预定到下次执行

从redis2.4开始, aof重写由redis自行触发, bgrewriteaof仅仅用于手动触发重写操作

Python插入数据脚本

#! /bin/python3                                                                                                                                                                     
import redis
pool = redis.ConnectionPool(host="127.0.0.1",port=6379,password="")
r = redis.Redis(connection_pool=pool)
for i in range(100):
    r.set("k%d" %i, "v%d" %i)
    data=r.get("k%d" % i)
    print(data)
上一篇下一篇

猜你喜欢

热点阅读