Redis 持久化

1. 持久化的意义

        Redis 是内存数据库,其将自己的数据存储在内存中,如果 Redis 发生宕机,且没有进行持久化的,那么Redis 重启后将没有之前的数据。而通过持久化,Redis可以在重启后,快速找回之前的数据,防止大量请求打入数据库。

       Redis持久化的方式有两种:RDB持久化、AOF持久化。

2. RDB 持久化

       RDB 持久化便是生成一个 RDB 文件,该文件是一个经过压缩的二进制文件。

在这里插入图片描述

2.1 RDB 文件的创建与载入

       Redis 有两个命令用于生成 RDB 文件,一个是 SAVE,另一个是 BGSAVE

       SAVE命令会阻塞 Redis服务器进程,直到 RDB文件创建完毕,期间不能处理任何客户端的命令。

       BGSAVE 命令则会创建一个子进程,由子进程负责创建 RDB文件,服务器进程依然可以继续处理命令请求。

       RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件

       因为AOF文件的更新频率通常比RDB文件的更新频率高,所以:

  • 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态
  • 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。

在这里插入图片描述
       服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

2.2 自动间隔性保存

       Redis允许用户通过设置服务器配置的 save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。

       用户可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行BGSAVE命令。

在这里插入图片描述

       服务器程序会根据 save 选项所设置的保存条件,设置服务器状态 redisServer 结构的 saveparams 属性:

struct redisServer{
    
    // ...
    
    // 记录了保存条件的数组
    struct saveparam *saveparam;
    
    // ...
};
struct saveparam{
    
    // 秒数
    time_t seconds;
    
    // 修改数
    int changes;
}

       如果 save 选项的值同上,那么服务器状态中的 saveparams 数组如下图所示:

在这里插入图片描述

       服务器状态还维持了一个 dirty 计数器,以及一个lastsave属性:

  • dirty 计数器记录了距离上一次成功执行 SAVE 命令或者BGSAVE 命令之后,服务器对数据库状态(所有的数据)进行了多少次修改。
  • lastsave 属性记录了上一次成功执行 SAVE 命令或 BGSAVE 命令的时间。
struct redisServer{
    
    // ...
    
    // 修改计数器
    long long dirth;
    
    // 上一次执行保存的时间
    time_t lastsave;
}

       Redis的服务器周期性操作函数 serverCron 默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足的话,就执行BGSAVE命令。

def serverCron () :

	# ...
	
	# 遍历所有保存条件
	for saveparam in server.saveparams :
	
		# 计算距离上次执行保存操作有多少秒
		save_interval = unixtime_now() - server.lastsave
		
		# 如果数据库状态的修改次数超过条件所设置的次数
		# 并且距离上次保存的时间超过条件所设置的时间
		# 那么执行保存操作
		if server.dirty >= saveparam.changes and save_interval > saveparam.seconds :

			BGSAVE ()
	# ...

3. AOF 持久化

       除了RDB持久化功能之外,Redis 还提供了AOF ( Append Only File)持久化功能。

       RDB持久化通过保存数据库中的所有键值对,生成当前时刻的快照文件来记录数据库状态不同

       AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的

在这里插入图片描述

3.1 AOF 持久化的实现

       AOF 持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。

3.1.1 命令追加

       服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。(注意,这里并没有直接写入到AOF文件中)

       每当有新的写入命令时,都会将命令先写入到aof_buf缓冲区内。

3.1.2 AOF 文件的写入和同步

       服务器每次结束一个文件事件循环之前,都可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以每次结束一个事件循环之前, 它都会调用 flushAppend0n1yFile函数,考虑是否需要将aof_ buf缓冲区中的内容写人和保存到AOF文件里面,这个过程可以用以下伪代码表示:

def eventLoop():

	while True:
		
		# 处理文件事件,接收命令请求以及发送命令回复
		# 处理命令请求时可能会有新内容被追加到 aof_buf 缓冲区中
		processFileEvents()
		
		# 处理时间事件
		processTimeEvents()
		
		# 考虑是否将 aof_buf 中的内容写入和保存的 AOF 文件里面
		flushAppendOblyFile()

       flushAppendOblyFile 函数的是否进行写入AOF文件则是由服务器配置的 appendfsync 选项的值来决定。

在这里插入图片描述
       即使调用flushAppendOblyFile()函数,也不意味aof_buf缓冲区中的内容一定被写入到文件中,因为Redis和磁盘之间还有一层OS操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,当缓冲区的空间被填满,或者超过了指定的时间后,才真正将缓冲区中的数据写入到磁盘里面

       这样便可能导致数据被操作系统写入到了内存缓冲区,但是OS还未来的及落地到磁盘,造成数据丢失。

       always则代表每执行一条写命令就落地到磁盘,但是Redis的效率会大幅降低。

       everysec则代表每隔一秒就落地到磁盘,该情况通常只会丢失掉一秒的数据。

       no则是由操作系统决定何时落地到磁盘,效率最高,如果宕机,则可能会丢失掉上次的同步到宕机时间内的数据。

3.2 AOF 文件的载入与数据还原

在这里插入图片描述

3.3 AOF 重写

       随着服务器运行时间的流逝,AOF文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的AOF文件很可能对Redis服务器、甚至整个宿主计算机造成影响,并且AOF文件的体积越大,使用AOF文件来进行数据还原所需的时间就越多。

       为了解决AOF文件体积膨胀的问题,Redis 提供了AOF文件重写( rewrite)功能。

       通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。

3.3.1 AOF 重写的实现

       新的AOF文件并不是基于之前的AOF文件进行分析和重写的,因为这样的效率并没有直接扫描Redis中的键值对的速度快。

       首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前这个键值对的多条命令,这就是AOF重写功能的实现原理。

       整个重写过程可以用以下伪代码表示:


def aof_rewrite (new_aof_file_name) :

	# 创建新AOF文件
	f = create_file (new_aof_file_name)

    # 遍历数据库
	for db in redisServer.db: 

		# 忽略空数据库
		if db.is_empty() : continue
		
        # 写入SELECT 命令,指定数据库号码
		f.write command ("SELECT”+ db. id)
                         
		# 遍历数据库中的所有键
		for key in db:
			
        	# 忽略巳过期的键
			if key.is_expired() : continue
                         
			# 根据键的类型对键进行重写
			if key.type == String:
				rewrite_ string (key)
			elif key.type == List:
				rewrite list (key)
			elif key.type == Hash:
				rewrite hash (key)
			elif key.type == Set:
				rewrite_set (key)
			elif key.type == SortedSet :
				rewrite_sorted_set (key)
                         
			# 如果键带有过期时间,那么过期时间也要被重写
			if key.have_expire_time() :
				rewrite_expire_time (key)
                         
	# 写入完毕,关闭文件
	f.close ()
def rewrite_string (key) :

	# 使用GET命令获取字符串键的值
	value = GET (key)
        
	# 使用SET命令重写字符串键
	f.write_command (SET, key, value)
        
def rewrite_list (key) :

	# 使用LRANGE命令获取列表键包含的所有元素
	iteml,item2, .... itemN = LRANGE (key, 0, -1)
        
	# 使用RPUSH命令重写列表键
	f.write_command (RPUSH, key, iteml, item2, .... itemN)
        
def rewrite_hash (key) :

	# 使用HGETALL命令获取哈希键包含的所有键值对
	fieldl, valuel, field2, value2, .... fieldN, valueN = HGETALL (key)

    # 使用HMSET命令重写哈希键
	f.write_command (HMSET,key, field1, value1, field2, value2, ....,fieldN,valueN)
        
def rewrite_set (key) :

	# 使用SMEMBERS命令获取集合键包含的所有元素
	elem1, elem2,elemN = SMEMBERS (key)
        
	# 使用SADD命令重写集合键
	f.write_command (SADD, key, elem1, elem2, .... elemN)

def rewrite_sorted_set (key) :
	
	# 使用ZRANGE命令获取有序集合键包含的所有元素
	member1,scorel, member2 ,score2,...,memberN, scoreN 
					= ZRANGE (key, 0,-1,"WITHSCORES")
        
	# 使用ZADD命令重写有序集合键
	f. write_ command (ZADD, key, scorel, member1, score2, member2,scoreN,memberN)
        
def rewrite_expire_time (key) :
	# 获取毫秒精度的键过期时间戳
	timestamp = get_ expire_ time_ in_ unixstamp (key)
        
	# 使用PEXPIREAT命令重写键的过期时间
	f.write_command (PEXPIREAT, key, timestamp)

3.3.2 AOF 后台重写

       AOF重写为了避免造成阻塞,其是通过一个子进程来完成的。

       同时,为了防止AOF子进程重写的过程中,客户端又有新的写命令,造成数据不一致的情况。Redis服务器设置了一个AOF重写缓冲区,该缓冲区在服务器创建子进程后开始使用。当Redis服务器执行完一个写命令之后, 它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。

在这里插入图片描述

       AOF重写完成后,子进程会给父进程发送一个信号,父进程便会执行如下操作:

  1. AOF重写缓冲区中所有内容写入到新AOF文件中,此时文件所保存的内容便和服务器数据一致了。
  2. 对新AOF文件进行改名,原子地覆盖现有AOF文件,完成新旧两个文件的替换。

4. 两种持久化方式的优点和缺点

4.1 RDB 持久化机制的优点和缺点

       优点

  • RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中Redis的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,以预定好的备份策略来定期备份Redis中的数据。

  • RDBRedis对外提供的读写服务,影响非常小,可以让Redis保持高性能,因为Redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可。

  • 相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复Redis进程,更加快速。

       缺点

  • 如果想要在Redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦Redis进程宕机,那么会丢失最近5分钟的数据。
  • RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

4.2 AOF 持久化机制的优点和缺点

       优点

  • AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。
  • AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
  • AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite的时候,通过子进程创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。
  • AOF日志文件的命令通过可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据。

       缺点

  • 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大。
  • AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的。

5. RDB 和 AOF 到底该如何选择

  • 不要仅仅使用RDB,因为那样会导致你丢失很多数据

  • 也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug

  • 综合使用AOFRDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值