数据持久化

    Redis提供了两种不同的持久化方法来讲数据存储到硬盘里面。一种方法叫快照(snapshotting),它可以将存在于某一时刻的所有数据都写入硬盘中。另一种方法叫只追加文件(append-only file, AOF),它会在执行写命令时,将被执行的写命令复制到硬盘中。将内存中的数据存储到硬盘中的一个主要原因是为了在之后宠用数据或为了防止系统故障而将数据备份到一个远程位置。

  快照持久化选项

    save 60 1000   // 明明硬盘上的快照文件

    stop-writes-on-bgsave-error no  //多次执行一次自动快照操作

    rdbcompression yes  //是否对快照文件进行压缩

    dbfilename dump.rdb  //在创建快照失败后是否仍然执行写命令

  AOF持久化选项

    appendonly no  //是否使用AOF持久化

    appendsync everysec  //多久将写入的内容同步到磁盘

    no-appendifsync-in-rewrite no  //对AOF进行压缩时是否执行同步操作

    auto-aof-rewrite-percentage 100  //多久执行一次AOF压缩

    auto-aof-rewrite-min-size 64mb

  共享选项,快照文件或AOF文件的保存位置

    dir./ 

    

  快照持久化

    Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。在创建快照之后,用户可以对快照进行备份,可以将快照复制到其他服务器而创建具有相同数据的服务器,还可以将快照留在原地以便重启服务器时使用。若爱新的快照文件创建完毕之前,Redis,系统或硬件这三者之中任意的一个崩溃了,那么Redis将丢失最近一次创建快照之后写入的所有数据。因此持久化快照只适用于及时丢失一部分数据也不会造成问题的应用程序。

    创建快照的几种方式:

      客户端通过向Redis发送BGSAVE命令来创建一个快照。Redis会调用fork来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。

      客户端通过向Redis发送SAVE命令创建一个快照。SAVE命令不常用。通常只会在没有足够内存去执行BGSAVE,或即使等待持久化操作执行完毕也无所谓的情况下才会使用这个命令。

      若客户设置了save配置选项,比如save 60 10000。那么Redis最近一次创建快照之后开始算起,当60s内有10000次写入这个条件满足时,Redis会自动触发BGSAVE命令、若用户设置了多了save配置选项,那么当任意一个save配置选项所设置的条件被满足时,Redis就会触发一个BGSAVE命令。

      当Redis通过SHUTDOWN命令接收到关闭服务器的请求时,或接收到标准TERM信号时,会执行一个SAVE命令,阻塞所有客户端,不再执行客户端发送的任何命令,并在SAVE命令执行完毕后关闭服务器

      当一个Redis服务器连接另一个Redis服务器时,并向对方发送SYNC命令来开始一次复制操作的时候,若主服务器目前没有执行BGSAVE操作,或主服务器并非刚刚执行完BGSAVE操作,那么主服务器就会执行BGSAVE命令。

    若打算在生产服务器中使用快照持久化并存储大量数据,那么开发服务器最好能运行在与生产服务器相同或相似的硬件上,并在这两个服务器上使用相同的save选项,存储相似的数据集并处理相近的负载量。把开发环境设置得尽量贴近生产环境,有助于判断快照是否生成的过于频繁或稀少。

    在对日志文件进行聚合计算或对页面浏览量进行分析的时候,需要考虑若Redis因为崩溃而未能成功创建新的快照,那么我们能够承受丢失多长时间以内产生的新数据。在决定好了持久化配置值之后,另一个需要解决的为就是如何恢复因为故障而被中断的日志处理操作。再进行数据修复时,首先要做的是弄清丢失了那些数据,因为需要在处理日志的同时记录日志的相关信息。

    当Redis占用的内存越来越多,BGSAVE在创建子进程时耗费的时间越来越多。当Redis的内存占用量达到数十个GB并且剩余的空闲内存并不多或Redis运行在VM上,此时执行BGSAVE会导致系统长时间的卡顿,也可能引发系统大量的使用虚拟内存,从而导致Redis的性能降低至无法使用的程度。执行BGSAVE而导致IDE停顿时间取决于Redis所在的系统,对于真实的硬件,VMWare虚拟机或KVM虚拟机来说,Redis进程每占用一个GB 的内存,创建该进程的子进程所需的事件就要增加1-20ms;对于Xen虚拟机来说,根据配置的不同,Redis进程没占用一个GB的内存,创建该进程的子进程所需的事件就腰增加200-300ms。为了防止Redis因创建子进程而导致的停顿,我们可以通过手动发送BGSAVE或SAVE来进行持久化。手动发送BGSAVE可以控制停顿出现的时间。虽然SAVE会一直阻塞Redis知道快照生成完毕,但是它不需要创建子进程,从而避免了子进程在争抢资源,所以SAVE创建快照的速度会比BGSAVE创建快照的速度快一些。

 

  AOF持久优化

    AOF持久化会将被执行的写命令写到AOF文件的末尾,一次来记录数据发生的变化。因此Redis只需要从头到尾重新执行一次AOF文件包含的所有写命令,就可以恢复AOF文件所记录的数据集。

    在向硬盘写入文件时,至少会发生3件事。当调用file.write()方法对文件进行写入时,写入的内容首先会被存储到缓冲区,然后操作系统会在将来某个时候将缓冲区存储的内容写入硬盘,而数据只有在被写入硬盘后,才算是真正保存到了硬盘里面。用户可以通过调用file.flush()方法来请求操作系统尽快地将缓冲区存储的数据写入到硬盘里,但具体何时写入操作仍然由操作系统决定。用户还可以命令操作系统将文件同步到硬盘,同步操作会一直阻塞知道指定的文件被写入硬盘为止。当同步操作执行完毕后,即使系统出现故障也不会对被同步的文件造成任何影响。

选项 同步频率
always 每个Redis写命令都要同步写入硬盘,但会严重降低Redis的速度
everysec 每秒执行一次同步,显式的将多个命令同步到硬盘
no 让操作系统来决定应该何时进行同步

 

 

 

    若用户使用appendsync always选项,那么每个Redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减少到最少。因为这种同步策略需要对硬盘进行大量写入,所以Redis处理命令的速度会受到硬盘性能的限制:转盘式硬盘在always下每秒只能处理大约200个写命令。而固态硬盘每秒大概只能处理几万个写命令。使用固态硬盘的用户谨慎使用appendfsync always选项,Redis每次只会写入一个命令,而不是像其他appendfsync选项一次写入N个命令。这种不断的写入少量数据可能会引发严重的写入放大问题。为了兼顾数据安全和写入性能,用户可以考虑使用appendfsync everysec选项,让Redis每秒一次的频率对AOF文件进行同步。Redis每秒同步一次AOF文件时的性能和不适用过任何持久化特性的性能相差无几,而通过每秒同步一次AOF文件,Redis可以保证,即使出现系统崩溃,用户最多也只会丢失一秒内产生的数据。当硬盘忙于执行写入操作时,Redis还会放慢自己的速度以适应硬盘的最大写入速度。若用户使用appendfsync no选项,则Redis则不对AOF文件执行任何显式的同步操作,而是由操作系统来决定应该在何时对AOF文件进行同步。但在系统崩溃将导致使用这种选项的Redis服务器丢失不定数量的数据。另外,若用户的硬盘处理写入操作的速度不够快,那么当缓冲区被等待写入硬盘的数据填满时,Redis的写入操作将被阻塞,并导致Redis处理命令请求的速度变慢,因此一般不推荐使用appendfsync no选项。

    向Redis发送BGREWRITEAOF命令会移除AOF文件中的冗余命令来重写AOF文件,使AOF文件的体积变得尽可能的小。Redis会创建一个子进程,然后由这个子进程负责对AOF文件进行重写。在进行AOF重写并删除旧AOF文件时,删除一个体积达到数十GB大小的旧的AOF文件可能会导致操作系统挂起数秒。

    AOF可以通过设置auto-aof-rewrite-percentage选项和auto-aof-rewrite-min-size选项来自动执行BGREWRITEAOF。若用户设置了auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb并且启用了AOF持久化,那么当AOF文件的体积大于64MB并且AOF文件的体积比上一次重写之后的体积大了至少一倍时,Redis将执行BGREWRITEAOF命令。

 

  复制

    Redis使用一个主服务器向多个从服务器发送更新,并使用从服务器来处理所有读请求。

    设置主从服务器时,从服务器必须有slaveof选项。若用户在启动Redis服务器时,指定了一个包含slaveof host port选项的配置文件,那么Redis服务器将根据该选项给定的IP地址和端口号来连接主服务器。对于正在运行的Redis服务器,可以通过发送SLAVEOF no one命令来让服务器终止复制,不再接受主服务器的数据更新,可以通过发送slaveof host port命令来让服务器开始复制一个新的主服务器。

    从服务器连着主服务器时的步骤

步骤 主服务器操作 从服务器操作
1 等待命令进入 连接(重连接)主服务器,发送SYNC命令
2 开始执行BGSAVE,并使用缓冲区记录BGSAVE之后执行的所有写命令 根据配置选项来决定是继续使用现有的数据来处理客户端的请求,还是想发送请求的客户端返回错误
3 BGSAVE执行完毕,向从服务器发送快照文件,并在发送期间继续使用缓冲区记录被执行的写命令 丢弃所有旧数据,开始载入主服务器发来的快照文件
4 快照文件发送完毕,开始向从服务器发送存储在缓冲区里的写命令 完成对快照文件的解释操作,像往常一样开始接受命令请求
5 缓冲区存储的写命令发送完毕,从现在开始,每执行一个写命令,就像从服务器发送相同的写命令 执行主服务器发来的所有存储在缓冲区里面的写命令,并从现在开始,接收并执行主服务器传来的每个写命令

 

 

 

 

 

    在实际开发中最好让主服务器只是用50%-65%的内存,留下30%-45%的内存用于执行BGSAVE命令和创建写命令的缓冲区。

    若使用SLAVEOF选项,Redis在启动时载入当前可用的任何快照文件或AOF文件,然后连接主服务器进行同步;若使用SLAVEOF命令,则Redis会立即尝试连接主服务器,并在连接成功后开始同步。从服务器在进行同步时会清空自己所有的数据。Redis不支持主主复制。被互相设置为主服务器的两个Redis会持续地占用大量处理器资源并且连接不断的尝试与对方通信。

  当多个从服务器尝试连接同一个主服务器时,若尚未向从服务器发送快照文件,则所有从服务器都会收到相同的快照文件和相同的缓冲区写命令;若正在发送快照文件或已发送完快照文件,此时当主服务器与已连接完的从服务器同步后,主服务器会与新连接的从服务器重新执行一次。

 

  主从链

    从服务器也可以拥有自己的从服务器构成主从链(master/slave chaining)。若从服务器X拥有从服务器Y,那么当从服务器X对主服务器进行复制,开始向从服务器Y发送存储在缓冲区里面的写命令时,服务器X将会断开从服务器Y的连接,从服务器Y需要重新连接并重新同步。

    当读请求的重要性明显高于写请求的重要性,并且读的请求数量远远超出一台Redis服务器可以处理的范围时,用户需要添加新的从服务器来处理读请求。随着负载不断上升,主服务器可能会无法快速更新所有的从服务器,或因重新连接和重新同步从服务器而导致系统超载。此时可以创建一个主从节点组成的中间层来分担主服务器的复制工作。

 

  检验硬盘写入

    为了验证主服务器是否已经将写入数据发送至服务器,用户需要在向主服务器写入真正的数据之后,再向主服务器写入一个唯一的虚构值(unique dummy value),然后通过检查虚构值是否存在于从服务器来判断写数据是否已经到达从服务器。对于每秒同步一次AOF文件的Redis服务器来说,检查INFO命令的输出结果中aof_pending_bio_fsync的属性值是否为0。

  

  验证快照文件和AOF文件

    Redis提供了两个命令行程序redis-check-aof和redis-check-dump,它们可以在系统故障发生之后,检查AOF文件和快照文件的状态,并在需要的情况下对文件进行修复。若用户在运行redis-check-aof程序时指定了--fix参数,那么程序将对AOF文件进行修复。Redis扫描给定的AOF文件,寻找不正确或不完整的命令,当发现第一个出错命令时,程序会删除出错命令以及位于出错命令之后的所有命令,只保留那些位于出错命令之前的正确命令。目前并没有什么办法可以修复出错的快照文件。因为快照文件本身经过了压缩,而出现在快照文件中间的错误部分有可能会导致快照文件的剩余部分无法读取。因此用户最好为快照文件保留多个备份,并在进行数据修复时,通过计算快照文件的SHA1散列值和SHA256散列值来对内容进行验证。从2.6版本开始,Redis会在快照文件中包含快照文件自身的CRC64校验和。CRC校验和对于发现典型的网络传输错误和硬盘损坏非常有帮助,而SHA加密散列值则更擅长于发现文件中的任意错误。

 

  更换故障主服务器

    若主服务器挂掉,可以另开一台服务器座位主服务器,让当前的从服务器重新成为新主服务器的从服务器。当主服务器挂掉时,先向从服务器发送一个SAVE命令,让它创建一个新的快照文件,接着将这个快照文件发送给新服务器,并在新机器上启动Redis,最后让从服务器成为新服务器的从服务器。

#machine-b
redis-cli
SAVE  //执行save命令
QUIT
scp /var/local/reids/dump.rdb machine-c vpn:/var/local/redis/dump.rdb  //将快照文件发送至新的主服务器
ssh machine-c.vpn
#machine-c
sudo /etc/redis-server start //开启redis
exit
#machine-b
redis-cli
SLAVEOF machine-c.vpn 6379 //让它成为machine-c的从服务器
QUIT
exit

    另一种创建新的主服务器的方法是将从服务器升级为主服务器,并未升级后的主服务器创建从服务器。

 

  事务

    Redis的事务以特殊命令MULTI为开始,之后跟着用户传入的多个命令,最后以EXEC为结束。Redis在执行事务的过程中,会延迟已入队的命令直到客户端发送EXEC命令为止。这种“一次性发送多个命令,然后等待所有回复出现”的做法通常称为流水线(pipelining),它可以通过减少客户端与Redis服务器之间的网络通信次数来提升Redis在执行多个命令时的性能。 Redis不会在执行WATCH命令时加锁。

 

  非事务流水线

    若用户在执行pipeline()时传入True作为参数,或不传入任何参数,那么客户端将使用MULTI和EXEC包裹起用户要执行的所有命令。若用户在执行pipeline()时传入False,那么Redis将不会用MULTI和EXEC包裹命令。若一个命令执行的结果并不会影响另一个命令的输入,此时可以使用pipeline(false)来提升Redis的性能。

 

  Redis的性能

    可以通过调用Redis-benchmark来得知Redis的性能。一般情况下,对于只使用单个客户端的redis-benchmark来说,根据被调用命令的复杂度,一个不使用流水线的Python客户端的性能大概只有redis-benchmark所示性能的50%-60%。

    若发现客户端的性能只有redis-benchmark的25%-30%,并且客户端返回了Cannot assign requested address错误,那么可能的原因似乎在每次发送命令时都创建了新的连接。

性能或错误原因 可能的原因 解决方案
单个客户端的性能达到redis-benchmark的50%-60% 不使用流水线时的预期性能  
单个客户端的性能达到redis-benchmark的25%-30% 对于每个命令或每组命令都创建了新的连接 重用已有的Redis连接
客户端返回错误:Cannot assign requested address 对于每个命令或每组命令都创建了新的连接 重用已有的Redis连接