Python--Redis实战:第四章:数据安全与性能保障:第2节:快照持久化
下一篇文章:Python--Redis实战:第四章:数据安全与性能保障:第3节:AOF持久化
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。在创建快照之后,用户可以对快照进行备份,可以将快照复制到其它服务器从而创建具有相同数据的服务器副本,还可以将快照留在原地以便重启服务器时使用。
根据配置,快照将被写入dbfilename选项指定的文件里面,并存储在dir选项指定的路径上面。如果在新的快照文件创建文笔之前,Redis、系统或者硬件上三者之中的任意一个崩溃了,那么Redis将丢失最近一次创建快照之后写入的所有数据。
举个例子,假设Redis目前在内存里面存储了10GB的数据,上一个快照是在下午2:35开始创建的,并且已经创建成果。下午3:06时,Redis又开始创建新的快照,并且在下午3:08快照文件创建完毕之前,有35个键进行了更新。如果在下午3:06至3:08期间,系统发生崩溃,导致Redis无法完成新快照的创建工作,那么Redis将丢失下午2:35之后写入的所有数据。
创建快照的办法有以下几种:
- 客户端可以通过向Redis发送bgsave命令创建一个快照。对于支持bgsave命令的平台来说(基本上所有的平台都支持,除了Windows平台),Redis会调用fork来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。
- 客户端还可以通过向Redis发singsave命令来创建一个快照,接到save命令的Redis服务器在快照创建完毕之前将不再响应任何其他命令。save命令并不常用,我们通常只会在没有足够内存去执行bgsave命令的情况下,又或者即使等待持久化操作执行完毕也无所谓的情况下,才会使用这个命令。
- 如果用户设置了save配置选项,比如save 60 10000,那么从Redis最近一次创建快照之后开始算起,当【60秒之内有10 000次写入】这个条件满足时,Redis就会自动触发bgsave命令。如果用户设置了多个save配置选项,那么当任意一个save的配置选项所设置的条件被满足时,Redis就会触发一次bgsave命令。
- 当Redis通过shutdown命令接收到关闭服务器的请求时,或者接受到标准term信号时,会执行一个save命令,阻塞所有客户端,不再执行客户端发送的任何命令,并在save命令执行完毕之后关闭服务器。
- 当一个Redis服务器连接另一个Redis服务器,并向对方发送sync命令来开始一次复制操作的时候,如果主服务器目前没有在执行bgsave操作,或者主服务器并非刚刚执行完bgsave操作,那么主服务器就会执行bgsave命令。
在只使用快照持久化来保存数据时,一定要记住:如果系统真的发生崩溃,用户将丢失最近一次生成快照之后更改的所有数据。因此,快照持久化只适用于那些丢失一部分数据也不会造成问题的应用程序,而不能接受这种数据损失的应用程序则可以考虑使用AOF持久化。接下来将展示几个使用快照持久化的场景,读者可以从中学习到如何通过修改配置来获得资金想要的快照持久化的行为。
个人开发
在个人开发服务器上面,我主要考虑的是尽可能地降低快照持久化带来的资源消耗。基于这个原因以及对自己硬件的信任,我只设置了save 900 1这一条规则。其中save 选项告知Redis,它应该根据这个选项提供的两个值来执行bgsave操作。在这个规则设置下,如果服务器距离上次成功生成快照超过了900秒(15分钟),并且在运行期间执行了至少一次写入操作,那么Redis久自动开始一次新的bgsave操作。
如果你打算在生产服务器中使用快照持久化并存储大量数据,那么你的开发服务器最好能够运行在与生产服务器相同或者相似的硬件上面,并在这两个服务器上使用相同的save选项、存储相似的数据集并处理相似的负载量。把开发环境设置得尽量贴近生产环境,有助于判断快照是否生成的过于频繁或者稀少。过于频繁会浪费资源,过于稀少则带有丢失大量数据的隐患。
对日志进行聚合计算
在对日志文件进行聚合计算或者对页面浏览量进行分析的时候,我们需要唯一考虑的就是:如果Redis因为崩溃而未能成功创建新的快照,那么我们能够承受丢失多长时间以内产生的新数据。如果丢失一个小时之内产生的数据是可以接受的,那么可以使用配置值save 3600 1(3600秒为1小时)。在决定好了持久化配置值之后,另一个需要解决的问题就是如何恢复因为故障而被终端的日志处理操作。
在进行数据恢复时,首先要做的就是弄清楚我们丢失了哪些数据。为了弄明白这一点,我们需要在处理日志的同时记录被处理日志的相关信息。
下面代码展示了一个用于处理新日志的函数,该函数有3个参数,他们分别是:一个Redis链接;一个存储日志文件的路径;待处理日志文件中各个行(line)的回调函数。这个函数可以在处理日志文件的同时,记录被处理的日志文件的名字以及偏移量。
import os import redis # 导入redis包包 #日志处理函数接收的其中以恶搞参数为回调函数 #这个回调函数接收一个Redis连接和一个日志行作为参数, #并通过调用流水线呢对象的方法来执行Redis命令 def process_log(conn,path,callback): #获取文件当前的处理进度 current_file,offset=conn.mget('progress.file','progress.position') pipe=conn.pipeline() #通过使用闭包(closur)来减少重复代码 def update_progress(): pipe.mset({'progress.file':fname,'progress:position':offset}) #这个语句负责执行实际的日志更细操作,并将日志文件的名字和目前的处理器进度记录到Redis里面 pipe.execute() #有序的遍历各个日志文件 for fname in sorted(os.listdir(path)): #略过所有已处理的日志文件 if fname <current_file: continue inp=open(os.path.join(path,fname),'rb') #在接着处理一个因为系统崩溃而未能完成处理的日志文件时,略过已处理的内容 if fname==current_file: inp.seek(int(offset,10)) else: offset=0 current_file=None #枚举函数遍历一个由文件行足哼的序列,并返回任意多个二元组 #每个二元祖包含了行号lno和行数据line,其中行号从0开始 for lno,line in enumerate(inp): #处理日志 callback(pipe,line) #更细已处理内容的偏移量 offset+=int(offset)+len(line) #每当处理完1000个日志行或者处理完 整个日志文件的时候,都更新一次文件的处理进度 if not (lno+1) %1000: update_progress() update_progress() inp.close()
通过将日志的处理进度记录到Redis里面,城西可以在系统崩溃之后,根据进度记录继续执行之前未完成的处理工作。而通过事务流水线,程序保证日志的处理结果和处理进度总是会同时被记录到快照文件里面。
大数据
在Redis存储的数据量只有几个GB的时候,使用快照来保存数据是没有问题的。Redis会创建子进程并将数据保存到硬盘里面,生成快照需要的时间比你读这句话的时间还要短。三随着Redis占用的内存越来越多,bgsave在创建子进程时耗费的时间越来越多。如果Redis的内存占用量达到数十个GB,并且剩余的空闲内存并不多,或者Redis运行在虚拟机上面,那么执行bgsave可能会导致系统长时间地停顿,也可能引发系统大量地使用虚拟内存,从而导致Redis的性能降低至无法使用的程度。
执行bgsave而导致的挺短时间有多长取决于Redis所在的系统:对于真实的硬件,vmware虚拟机或者KVM虚拟机来说,Redis进行每占用一个GB的内存,创建该进程的子进程所需的时间就要增加10~20毫秒,而对于Xen虚拟机来说,根究配置的不同,Redis进程每占用一个GB的内存,创建该进程的子进程所需的时间就要增加200~300毫秒。因此,如果我们的Redis进程占用了20GB的内存,那么在标准硬件上运行bgsave所创建的子进程将导致Reeis停顿200~400毫秒,如果我们使用的是Xen虚拟机(亚马逊EC2和其他几个云计算供应商都使用这种虚拟机),那么相同的创建子进程操作将导致Redis停顿4~6秒。用户必须考虑自己的应用程序能否接受这种停顿。
为了防止Redeis因为创建子进程而出现停顿,我们可以考虑关闭自动保存呢,转而通过手动发送bgsave或者save来进行持久化。手动发送bgsave一样会引起停顿,唯一不同的是用户可以控制停顿出现的时间。另一方面,虽然save会一直阻塞Redis知道快照生产完毕,但是因为它不需要创建子进程,所以就不会想bgsave一样因为创建子进程而导致Redis停顿;并且因为没有子进程在争抢资源,所以save创建快照的速度比bgsave创建快照的速度要快一些。
根据个人经验,在一台拥有68GB内存的Xen虚拟机上面,对一个占用50GB内存的Redis服务器执行bgsave命令的话,光是创建子进程就需要花费15秒以上,而生成快照则需要花费15~20分钟;但使用save值需要3~5分钟就可以完成快照的生成工作。因为的应用程序只需要每天生成一次快照,所以我写了一个脚本,让它在每天凌晨3点停止所有客户端对Redis的访问,调用save命令并等待该命令执行完毕,之后备份刚刚生成的快照文件,并通知客户端继续执行操作。
如果用户能够妥善地处理快照持久化可能会带来的大量数据丢失,那么快照持久化对用户来说将使一个不错的选择,但对于很多用于程序来说,丢失15分钟、1小时甚至更长时间的数据都是不可接受的,在这种情况情况下,我们可以使用AOF持久化来将存储在内存里面的数据尽快的保存到硬盘里面。
上一篇文章:Python--Redis实战:第四章:数据安全与性能保障:第1节:持久化选项下一篇文章:Python--Redis实战:第四章:数据安全与性能保障:第3节:AOF持久化