Redis: 持久化之RDB和AOF

概述

  • Redis 有一个高质量的课题:数据安全性与数据可靠性
  • Redis 是一个内存型数据库,数据大部分都是存在内存里面
  • 当信息在内存中流通时,Redis 节点突然就故障挂掉
  • 当重新启动的时候,内存中的数据肯定是全部丢失了
  • 如果在这种情况下,Redis 提供了对应的持久化方案,它有两种持久化方案
    • RDB和AOF可以把内存中的数据保存到磁盘,避免数据的流失
  • 关于RDB,我们需要了解到
    • 什么是持久化
    • 什么是快照
    • RDB的工作原理,包括它的优点和缺点
  • 关于AOF,需要知道
    • 为什么需要有AOF
    • 存储上线后重写,为什么要重写,以及其触发条件
    • 文件如何写入,文件损坏了怎么办
    • 它是一个备选方案,或者说一种增强方案
  • Redis 既然有了RDB又提供了AOF, 肯定两者之间是会有一个互补的关系
    • 这里的话, 我们要去讲他们两者该如何选择, 如何互补?
    • 该开启RDB还是开启AOF, 还是说同时都开启
    • 同时都开启的情况下,数据冗移是需要考虑
    • 从 RDB 动态切换到 AOF 在 Redis 不重启的情况下并保证数据不丢失,如何实现
  • 关于 Redis 的备份容灾
    • 比如说我们写一个脚本
    • 再来一个定时任务,定时的去执行脚本
    • 把我们的数据给它备份起来
    • 备份了之后,怎么去恢复呢?如何更好的去恢复
  • 关于Redis 的优化方案
    • 更好的提升性能,从硬盘角度,从fork进程角度
    • 以及从主从的角度

Redis 持久化方式

1 )通常数据库存在三种用于持久操作以防止数据损坏的常见策略:

  • 是数据库不关心故障,而是在数据文件损坏后从数据备份或快照中恢复,RDB就是这种情况
  • 该数据库使用操作日志记录每个操作的操作行为,以在失败后通过日志恢复一致性。由于操作日志是按顺序追加写入的,因此不会出现无法恢复操作日志的情况。类似于Mysql的重做(redolog)和撤消日志(undolog)
  • 数据库不修改旧数据,而仅通过追加进行写入,因此数据本身就是日志,因此永远不会出现数据无法恢复的情况。CouchDB是一个很好的例子,AOF类似这种情况

2 )严格上讲Redis为持久化提供了三种方式:

  • RDB:在指定的时间间隔能对数据进行快照存储,类似于MySQL的dump备份文件
  • AOF:记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据(MySQL的binlog)
  • RDB与AOF混合使用,这是Redis4.0开始的新特性,在混合使用中AOF读取RDB数据重建原始数据集,集二者优势为一体

RDB持久化


1 )初始化环境

1.1 创建配置/数据/日志目录

# 创建配置目录
mkdir -p /usr/local/redis/conf
# 创建数据目录
mkdir -p /usr/local/redis/data
# 创建日志目录
mkdir -p /usr/local/redis/log

1.2 配置文件

  • 创建一份配置文件至 conf 目录

    vim /usr/local/redis/conf/redis.conf
    
  • 文件内容如下

    # 放行访问IP限制
    bind 0.0.0.0
    
    # 后台启动
    daemonize yes
    
    # 日志存储目录及日志文件名
    logfile "/usr/local/redis/log/redis.log"
    
    # rdb数据文件名 持久化后生成的文件
    dbfilename dump.rdb
    
    # rdb数据文件和aof数据文件的存储目录
    dir /usr/local/redis/data
    
    # 设置密码
    requirepass 123456
    
  • 通过这个配置文件,启动: bin/redis-server conf/redis.conf

  • 验证:ps -ef | grep redis

  • 客户端连接 bin/redis-cli -a 123456

    • 这里密码明文不推荐,仅供参考

1.3 数据准备

  • 查看数据库数据大小:$ DBSIZE 目前新环境没有任何数据显示 0
  • 插入一些数据来演示RDB中一些阻塞的命令,若数据集较小则看不到任何效果
  • 我们通过一个python文件,循环插入 500 w 数据,使用 redis 管道的方式来插入大概30s-1min
    • 不用管道,则可能要 30min-1h, 非常慢
  • $ cd bin/ && vim initdata.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

class Token(object):
	def __init__(self, value):
		if isinstance(value, Token):
			value =value.value
		self.value =value

	def _repr_(self):
		return self.value
		
	def __str__(self):
		return self.value
	
def b(x):
	return x


SYM_STAR = b('*')
SYM_DOLLAR = b('$')
SYM_CRLF = b('\r\n')
SYM_EMPTY = b('')

class RedisProto(object):

def __init__(self, encoding='utf-8', encoding_errors='strict'):
	self.encoding = encoding
	self.encoding_errors = encoding_errors

def pack_command(self, *args):
	"""将redis命令安装redis的协议编码,返回编码后的数组,如果命令很大,返回的是编码后chunk的数组"""
	output =[]
	command = args[0
	if '' in command:
		args = tuple([Token(s) for s in command. split('')])+ args[1:]
	else:
		args =(Token(command),)+ args[1:]

	buff = SYM_EMPTY.join((SYM_STAR, b(str(len(args))), SYM_CRLF))

	for arg in map(self.encode, args):
		"""数据量特别大的时候,分成部分小的chunk"""
		if len(buff) > 6000 or len(arg) > 6000:
			buff = SYM_EMPTY.join((buff, SYM_DOLLAR, b(str(len(arg))
			output.append(buff)
			return output


def encode(self, value):
	if isinstance(value, Token):
		return b(value.value)
	elif isinstance(value, bytes):
		return value
elif isinstance(value, int):
value = b(str(value))
elif not isinstance(value, str):
value = str(value)
if isinstance(value, str):
value= value.encode(self.encoding, self.encoding_errors)
return value

if _name_ == '_main_':
for i in range(5000000):
commands_args = [('SET', 'key_'+ str(i), 'value_'+ str(i))]
commands = ''.join([RedisProto().pack_command(*args)[0] for args in commands_args])
print commands

  • 执行 python initdata.py | ./redis-cli -a 123456 --pipe
  • 过个几十秒,查看 $ DBSIZE
  • 之后,查看内存使用情况:$ info memory 可看到,大约 438M 的内存占用
  • 进入 data 目录查看 dump.rdb 是否生成,发现并没有,说明RDB持久化并没有去做
  • 如果强制被 kill 掉 redis 模拟故障,内存数据将会全部丢失,所以需要对内存数据做持久化处理
  • 在一些业务场景下,内存数据是需要被保存下来的,就需要设置 RDB
  • 现在修改配置文件 $ vim conf/redis.conf, 添加如下,并重启 redis
    # 5秒有一个key的改动,就执行快照生成
    save 5 1
    
  • 这么这个配置用来模拟我们的故障,实际上缺省配置如下
    # 900秒内如果超过1个key改动,则发起快照保存
    save 900 1
    
    # 300秒内如果超过10个key改动,则发起快照保存
    save 300 10
    
    # 60秒内如果超过1W个key改动,则发起快照保存
    save 60 10000
    
  • 还是通过模拟故障的配置来看
  • 启动后,重新执行py脚本插入数据
  • 数据插入完成后,进入 data 目录,发现已经有 dump.rdb 文件了
    • 而且还多了 temp-1428.rdb 类似的文件
    • 这个 temp 文件是其内部的生成工作原理
  • 再次模拟故障,可发现数据仍存在
  • 因为我们配置中写的是 5s, 那如果配置成 15min 的时间,那没有生成快照数据还会丢失
  • AOF的出现,弥补了这一个缺陷

Redis 快照

  • 快照,顾名思义可以理解为拍照一样,把整个内存数据映射到硬盘中,保存一份到硬盘,因此恢复数据起来比较快,把数据映射回去即可,不像AOF,一条条的执行操作命令
  • 快照是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb,可以通过配置设置自动做快照持久化的方式
  • 产生快照的情况有以下几种:
    • 手动 bgsave 执行
    • 手动 save 执行
    • 注意:
      • BGSAVE 命令是非阻塞的
        • 本质上是folk出一个子进程在后台做快照生成, 仍然能对外提供服务
        • 对外提供的是 get ,但是 save 这种写操作就不一样了
      • SAVE 命令是阻塞的
        • 主进程来做这件事,你来访问我的时候
        • 我在忙的时候肯定提供不了服务给你
      • 同时,SHUTDOWN 命令也是阻塞的
        • 要先保存好数据
    • 根据配置文件自动执行
    • 客户端发送 shutdown, 系统会先执行 save 命令阻塞客户端,然后关闭服务器
    • 当有主从架构时,从服务器向主服务器发送sync 命令来执行复制操作时,主服务器会执行 bgsave操作

RDB 工作原理

  • Redis默认会将快照文件存储在Redis当前进程的工作目录中的dump.rdb文件中

  • 可以通过配置dir和dbfilename两个参数分别指定快照文件的存储路径和文件名

  • 流程过程如下(rdb.c中)

  • 这个图,也是通过源码 rdb.c 来总结的

  • serverCron 是一个循环时间事件函数

    • 这个函数它的作用就是来监控配置文件的
    • 它就监控配置文件里边的save配置项
    • 当这个时间满足,就开始触发,触发之后,最终也会执行 bgsave 的底层流程
  • bgsave 内部调用 bgsaveCommand, 它fork子进程做持久化,是非阻塞的

  • save 也是调用 saveCommand, 它是主进程的,它会阻塞,不对外提供读的服务,全心全意持久化

  • 也就是说:serverCron, bgsave, save 三者都在做持久化的事情,都会有 rdbSave的流程

    • 先写一个 temp 的文件
    • 然后这个临时文件开启之后,会把之前的数据先全部通通的加载过来
    • 加载过来之后,把新的内存里边的数据给它写进去
    • 写进去持久化之后,然后刷新,关闭
    • 关闭以后,把这个临时文件内改一个名字,再改成原始的你的文件名把它覆盖一下
    • 这个就对应上面 temp-1428.rdb 的文件
  • 这就是 Redis RDB的一个工作原理

RBD的优缺点

1 )优点

  • 紧凑压缩的二进制文件
    • $ vim dump.rdb 可看到
    • 备份非常简单
  • fork子进程性能最大化
    • 非阻塞,可对外继续提供服务
  • 启动效率高
    • 二进制文件,读取数据快

2 )缺点

  • 生成快照的时机问题
    • save 5 1 这种 5s 触发一次只是我们的模拟
    • 一般生产会配置 15min 和 30min, 在较长的时间窗口期宕机,数据也会丢失
  • fork子进程的开销问题
    • 数据集比较大,频繁做这件事,会导致性能开销

AOF持久化

  • 它也是Redis持久化的重要手段之一,AOF(Append Only File)只追加文件
  • 也就是每次处理完请求命令后都会将此命令追加到aof文件的末尾
  • 而RDB是压缩成二进制等时机开子进程去干这件事
  • 弥补了save配置时间内宕机风险的问题

1 ) 开启AOF

  • 通过配置进行启动,默认是关闭的

    # 默认 appendonly 为 no
    appendonly yes
    appendfilename "appendonly. aof"
    
    # RDB文件和AOF文件所在目录
    dir /usr/local/redis/data
    
  • 基于此,我们可以开启两个窗口验证下

    • 窗口1,追踪文件变动,执行 $ tail -f data/appendonly.aof
      • 这个文件中会记录写命令,不会记录读命令
    • 窗口2,进行读写操作,执行 $ set username zhagnsan 这是写命令
  • 在窗口1 中,输出

    *2
    $6
    SELECT
    $1
    0
    *3
    $3
    SET
    $8
    username
    $8
    zhangsan
    
    • 这里, *2 代表下面2行是命令,*3表示下面三行是命令
    • $6 不在行计算范围内,$6 表示命令的字节长度 SELECT 长度是6
    • 可忽略 $ 相关来看, 就是:
      • SELECT 0
      • set username zhangsan
  • 所以,可见,aof 文件的优点是易读,缺点是容易让人读不懂

  • 这样,即使 dump 文件被删除,也可以通过 aof 来恢复了

2 )同步策略

  • Redis中提供了3种AOF同步策略:
    • 每秒同步(默认,每秒调用一次fsync,这种模式性能并不是很糟糕)
    • 每修改同步(会极大消弱Redis的性能,因为这种模式下每次 write后都会调用 fsync)
    • 不主动同步(由操作系统自动调度刷磁盘,性能是最好的, 注意:不主动不代表不写入)
  • 参考
    #每秒钟同步一次,该策略为AOF的缺省策略
    appendfsync everysec
    #每次有数据修改发生时都会写入AOF文件
    appendfsync always
    #从不同步。高效但是数据不会主动被持久化
    appendfsync no
    

3 )AOF 工作原理

  • client ----> Redis Server ----> 执行命令 ----> 命令写入AOF缓冲区 ----> 刷新磁盘写入AOF文件

  • 解释一下

    • AOF的频率高的话肯定会对Redis带来性能影响,因为每次都是刷盘操作
    • 跟mysql一样了,Redis每次都是先将命令放到缓冲区
    • 然后根据具体策略(每秒/每条指令/缓冲区满)进行刷盘操作
    • 如果配置的always,那么就是典型阻塞,如果是everysec
    • 每秒的话,那么会开一个同步线程去每秒进行刷盘操作,对主线程影响稍小

4 )写入文件与恢复

  • AOF文件是一个只进行append操作的日志文件,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。假如一次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过 redis-check-aof 工具来帮助我们修复问题。

  • AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松

  • 导出(export)AOF文件也非常简单:举个例子,如果你不小心执行了 FLUSHALL 命令,但只要AOF文件未被重写,那么只要停止服务器,移除AOF文件末尾的 FLUSHALL 命令,并重启Redis, 就可以将数据集恢复到 FLUSHALL 执行之前的状态

5 )重写

  • Redis 可以在AOF 文件体积变得过大时,自动地在后台对AOF 进行rewrite。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行

  • 因为Redis在创建新AOF文件的过程中,会继续将命令追加到现有的AOF文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢失

  • 而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作

  • 为什么要重写

    • 比如我有业务很简单,就来回 delete set 同一个key
    • 就这个业务运行了10年,那么aof文件将记录无数个delete k1,set k1 xxx
    • 其实都是重复的,但是我aof每次都追加,文件变成了1T大小
    • 这时候Redis宕机了,要恢复,你想想1TB大小的aof文件去恢复,累死了
    • 最主要的是1TB大小只记录了两个命令,所以压缩其实就是来处理这件事的
  • rewrite触发条件

    • 当 aof 文件越来越大,恢复的时候会降低性能
    • 客户端执行 bgrewriteaof 命令,内部有些配置什么时候重写
      • auto-aof-rewrite-min-size 64mb 这里文件超过 64m的时候会重写,生产环境要配置大些
      • auto-aof-rewrite-percentage 100 这种是按百分比来配置的

7 )常用的配置

# fsync 持久化策略
appendfsync everysec

# AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘)
# 但是可能会丢失AOF重写期间的数据
# 需要在负载和安全性之间进行平衡
# 默认是 no , aof 在重写期间是可能有新的命令写入的,no则表示会记录新写入的命令,yes 则不记录
no-appendfsync-on-rewrite no

# 当前aof文件大于多少字节后才触发重写
auto-aof-rewrite-min-size 64mb

# 当前写入日志文件的大小超过上一次rewrite之后的文件大小的百分之100时,也就是2倍时触发Rewrite
auto-aof-rewrite-percentage 100

# 如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件
aof-load-truncated yes

8 )AOF 优点

  • 数据不易丢失
  • 自动重写机制 (保证aof文件不会无休止膨胀)
  • 易懂易恢复

9 )AOF缺点

  • AOF文件恢复数据慢
  • AOF持久化效率低
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值