特立独行是对的,融入圈子也是对的。关键是要弄清楚自己想要什么样的生活,愿意为此付出什么样的代价。我们通常使用Redis作为缓存来提高读响应性能。Redis一旦宕机,内存中的所有数据都会丢失。如果直接访问数据库,大流量打MySQL,可能会导致更严重的问题。另外,从数据库中慢慢读取放到Redis中的性能势必比从Redis中获取要快,这也会导致响应变慢。为了实现不怕宕机的快速恢复,Redis设计了两大杀手,分别是AOF(AppendOnlyFile)日志和RDB快照。学习一门技术,通常只接触零散的技术点,没有在脑海中建立完整的知识框架和架构体系,没有系统观。这个会很吃力,而且会显得你乍一看好像知道,后来你就忘记了,一脸懵逼。跟着《兄弟代码》深入了解Redis,深层次掌握Redis的核心原理和实战技巧。建立一套完整的知识框架,学会用全局的眼光来组织整个知识体系。本文硬核,建议收藏点赞,静下心来阅读,相信会有不少收获。上一篇《Redis 核心篇:唯快不破的秘密》分析了Redis的核心数据结构、IO模型、线程模型,以及根据不同的数据使用合适的数据编码。深入掌握真正快的原因!本文将重点关注以下几点:宕机后如何快速恢复?宕机,Redis如何避免数据丢失?什么是RDB内存快照?AOF日志实现机制什么是copy-on-write技术?....涉及到的知识点如图所示:Redis全景全景可以围绕两个维度展开,即:应用维度:缓存使用、集群使用、数据结构的巧妙运用系统维度:可以分为三个高性能:线程模型、网络IO模型、数据结构、持久化机制;高可用:主从复制、哨兵集群、集群分片集群;高扩展:负载均衡Redis系列章节围绕以下思维导图展开,本次从《Redis 日志篇:无畏宕机与快速恢复的杀手锏》来探寻Redis高性能和持久化机制的秘密。对Redis的透彻理解,有一个全景图,一个系统视图的把握。系统观其实很重要。从某种程度上说,在解决问题的时候,有系统的眼光,就意味着能够有理有据、有条不紊地定位和解决问题。RDB内存快照,让宕机可以快速恢复65哥:Redis因为某种原因宕机,会导致流量全部打到后端MySQL。我立即重启Redis,但它的数据存储在内存中。没有任何数据,重启后如何防止数据丢失?65哥别着急,《码哥字节跳动》带你一步步了解Redis崩溃后如何快速恢复。Redis数据存储在内存中。是否可以考虑将内存中的数据写入磁盘?当Redis重启时,存储在磁盘上的数据会快速恢复到内存中,这样重启后就可以正常提供服务了。65哥:我想到了办法。每次执行一次“写”操作,同时操作内存和写入磁盘,这种方案有一个致命的问题:每次写命令不仅写入内存,还写入磁盘。与内存相比,磁盘的性能太慢了。会导致Redis的性能大大降低。内存快照65哥:那怎么避免同时写的问题呢?我们通常使用Redis作为缓存,所以即使Redis不保存所有数据,仍然可以通过数据库获取,所以Redis不会保存所有数据。Redis数据持久化采用“RDB数据快照”方式实现宕机快速恢复。65哥:那什么是RDB内存快照呢?当Redis执行“write”命令时,内存数据会一直变化。所谓内存快照是指某个时刻Redis内存中数据的状态数据。时间仿佛凝固在了某个时刻。我们在拍照的时候,可以通过照片完整的记录下某个瞬间的瞬间画面。Redis跟这个类似,就是把某个时刻的数据拍下来,以文件的形式写到磁盘上。这个快照文件称为RDB文件,RDB是RedisDataBase的缩写。Redis定时执行RDB内存快照,这样就不需要每次执行“write”命令时都写入磁盘,而只需要在执行内存快照时才写入磁盘。既保证了只快不坏,又做到了持久化,宕机后快速恢复。RDB内存快照用于数据恢复时,直接将RDB文件读入内存完成恢复。65哥:应该抓拍哪些数据?或者应该多久拍摄一次快照?这会影响快照的执行效率。65哥,还不错,开始考虑数据效率了。在《Redis 核心篇:唯快不破的秘密》中,我们知道他的单线程模型决定了我们要尽量避免会阻塞主线程的操作,在生成RDB文件的时候要避免阻塞主线程。生成RDB策略Redis提供了两条生成RDB文件的指令:save:主线程执行,会阻塞;bgsave:调用glibc的函数fork生成写入RDB文件的子进程,快照持久化完全交给子进程处理,父进程继续处理客户端请求,生成RDB文件的默认配置.65哥:对内存数据进行“快照”时,内存数据还能被修改吗?即写命令能否正常处理?首先要明确一点,RDB文件生成过程中避免阻塞和能够处理写操作不是一回事。虽然主线程没有阻塞,但是此时为了保证快照数据的一致性,只能处理读操作,不能修改正在执行的快照的数据。显然,Redis不允许为了生成RDB而暂停写操作。65哥:那么Redis是如何处理写请求的同时生成RDB文件的呢?Redis利用操作系统的多进程写时复制技术COW(CopyOnWrite)来实现快照持久化。这个机制很有趣,很少有人知道。多进程COW也是一个程序员知识面广度的重要标志。当Redis持久化时,会调用glibc函数fork生成子进程。快照持久化完全交给子进程,父进程继续处理客户端请求。子进程刚生成时,与父进程共享内存中的代码段和数据段。此时你可以把父子进程想象成一个连体双胞胎,共享一个身体。这就是Linux操作系统的机制。为了节省内存资源,尽可能让它们共享。进程分离的那一刻,内存增长几乎没有明显变化。bgsave子进程可以共享主线程的所有内存数据,读取主线程的数据并写入RDB文件。当执行SAVE命令或BGSAVE命令新建RDB文件时,程序会检查数据库中的key,过期的key不会保存到新建的RDB文件中。当主线程执行写命令修改数据时,会复制一份数据,bgsave子进程读取复制的数据写入RDB文件,这样主线程可以直接修改原始数据。copy-on-write技术保证了快照期间的数据修改,既保证了快照的完整性,又可以让主线程同时修改数据,避免对正常业务造成影响。Redis会使用bgsave对当前内存中的所有数据进行快照。这个操作是由子进程在后台完成的,可以让主线程同时修改数据。65哥:RDB文件能不能每秒执行一次,这样即使宕机,最多也就是丢失1秒的数据。过于频繁地执行全量快照有两个严重的性能开销:频繁生成RDB文件并写入磁盘,磁盘压力过大。会出现上一个RDB还没执行完,又开始生成下一个,陷入死循环。forkbgsave子进程会阻塞主线程,主线程内存越大,阻塞时间越长。优缺点快照的恢复速度快,但是生成RDB文件的频率不容易掌握。如果频率太低,则会因停机而丢失更多数据;如果太快,将消耗额外的开销。RDB采用二进制+数据压缩方式写入磁盘,文件体积小,数据恢复速度快。除了RDB全量快照,Redis还设计了AOF写后日志。接下来说说什么是AOF日志。AOFpost-writelog避免宕机数据丢失AOF日志存储的是Redis服务器的顺序指令序列,AOF日志只记录修改内存的指令记录。假设AOF日志记录了Redis实例创建以来所有修改的指令序列,那么可以通过在一个空的Redis实例上依次执行所有指令来恢复当前Redis实例的内存数据结构,即“replay””。状态。WriteAheadLogvs.WriteAheadLogWriteAheadLog(WAL):在真正写入数据之前,将修改后的数据写入日志文件,保证故障恢复。例如MySQLInnodb存储引擎中的重做日志(redolog)就是一种记录修改的数据日志。在真正修改数据之前,先记录修改日志,执行修改数据。Post-writelog:先执行“write”命令请求,将数据写入内存,然后记录日志。AOFpost-write命令日志格式当Redis收到“setkeyMageByte”命令向内存写入数据时,Redis会按照如下格式写入AOF文件。“*3”:表示当前命令分为三部分,每部分以“$+数字”开头,后面是该部分具体的“命令、键、值”。“Number”:表示这部分的command、key、value占用的字节大小。例如“$3”表示这部分包含3个字节,也就是“set”命令。AOF日志格式65师兄:Redis为什么要采用post-writelog方式?post-writelog避免了额外的检查开销,不需要对执行的命令进行语法检查。如果使用预写日志,需要检查语法是否正确,否则日志会记录错误的命令,使用日志恢复时会出错。另外,日志是写完后记录的,不会阻塞当前“write”命令的执行。65哥:那么有了AOF,是不是万无一失?傻孩子,没那么简单。如果Redis刚刚执行完命令,还没有记录日志就宕机了,那么这个命令相关的数据可能会丢失。另外AOF虽然避免了当前命令的阻塞,但是可能会造成下一条命令阻塞的风险。AOF日志由主线程执行。在将日志写入磁盘的过程中,如果磁盘压力大,写入磁盘会很慢,导致后续的“write”命令阻塞。你发现了吗?这两个问题都与磁盘回写有关。如果能合理控制“write”命令执行后AOF日志回写磁盘的时机,问题就迎刃而解了。回写策略为了提高文件写入的效率,当用户调用write函数向文件中写入一些数据时,操作系统通常会将写入的数据暂时保存在一个内存缓冲区中,直到缓冲区空间耗尽。填满或超过指定的时间限制后,缓冲区中的数据才真正写入磁盘。这种方式虽然提高了效率,但是也给写入的数据带来了安全问题,因为如果电脑关机,存储在内存缓冲区中的写入数据就会丢失。为此,系统提供了两个同步函数fsync和fdatasync,可以强制操作系统立即将缓冲区中的数据写入硬盘,从而保证写入数据的安全。Redis提供的AOF配置项appendfsync回写策略直接决定了AOF持久化功能的效率和安全性。always:同步回写,执行write命令后立即将aof_buf缓冲区中的内容刷新到AOF文件中。everysec:每秒回写一次。write命令执行后,日志只会写入AOF文件缓冲区,缓冲区的内容每秒都会同步到磁盘。no:受操作系统控制,写执行完成后,将日志写入AOF文件内存缓冲区,由操作系统决定何时刷新到磁盘。没有两全其美的策略,我们需要在性能和可靠性之间做出权衡。始终同步回写可以保证数据不丢失,但是每次“写”命令都需要写入磁盘,性能是最差的。Everysec每秒回写一次,避免了同步回写的性能开销。一旦发生宕机,写入磁盘的数据可能会丢失一秒,这是性能和可靠性之间的折衷。没有操作系统控制。执行完写命令后,写入AOF文件缓冲区,执行后续的“写”命令。性能最好,但可能会丢失大量数据。65哥:那我怎么选择策略呢?我们可以根据系统对高性能、高可靠性的要求来选择回写策略。总结一下:如果你想获得高性能,选择No策略;如果想获得高可靠性保证,选择Always策略;如果你允许有一点数据丢失,又希望性能不会受到太大影响,那就选择Everysec策略。优缺点优点:只有在执行成功后才记录日志,避免了命令语法检查的开销。同时,不会阻塞当前的“write”命令。缺点:由于AOF记录了每条命令的内容,具体格式请参考上面的日志格式。故障恢复时需要执行每条指令。如果日志文件太大,整个恢复过程会很慢。另外,文件系统对文件大小也有限制,不能保存太大的文件,文件会变大,追加的效率也会变低。日志过大:AOF重写机制65师兄:AOF日志文件过大怎么办?AOF预写日志记录了每一次“写”命令操作。不会像RDB全量快照那样造成性能损失,但是执行速度没有RDB快。同时,过大的日志文件会导致性能问题。对于只快不坏的Redis来说,肯定不能容忍日志过大带来的问题。因此Redis设计了一个杀手级的“AOF重写机制”,Redis提供了bgrewriteaof命令来瘦身AOF日志。原理是开辟一个子进程遍历内存,转换成一系列的Redis操作指令,序列化到一个新的AOF日志文件中。序列化完成后,将运行过程中产生的增量AOF日志追加到这个新的AOF日志文件中。添加后,旧的AOF日志文件会立即被替换,瘦身工作完成。65兄:为什么AOF重写机制可以收缩日志文件?改写机制具有“一改一”的功能,将旧日志中的多条指令改写后变为一条指令。如下图:AOF重写机制(纠错:重写前记录了3条指令)65哥:重写后AOF日志变小,最后将整个数据库最新数据的操作日志刷到磁盘。重写会阻塞主线程吗?“码哥”上面说了AOF日志是由主线程写回的。AOF改写的过程实际上是由后台子进程bgrewriteaof完成的,防止阻塞主线程。重写过程不同于主线程回写AOF日志。重写过程由后台子进程bgrewriteaof完成,也是为了避免阻塞主线程,导致数据库性能下降。一般情况下,一共有两份日志,一份内存数据副本,分别是旧的AOF日志和新的AOF重写日志以及Redis数据副本。Redis会将重写过程中收到的“write”命令操作同时记录到旧AOF缓冲区和AOF重写缓冲区中,这样重写日志也保存了最新的操作。复制数据的所有操作记录改写完成后,rewritebuffer中记录的最新操作也会写入新的AOF文件中。每次重写AOF,Redis都会先进行一次内存拷贝,遍历数据生成重写记录;使用两份日志,保证新写入的数据在重写过程中不会丢失,保持数据的一致性。AOF重写过程65哥:AOF重写也有重写日志,为什么不分享使用AOF本身的日志呢?竞争问题出现了,控制竞争意味着影响父进程的性能。如果AOF重写过程失败,那么原来的AOF文件就相当于被污染了,无法恢复。所以RedisAOF重写了一个新的文件。如果重写失败,直接删除文件即可,不会影响原来的AOF文件。改写完成后,直接替换旧文件即可。Redis4.0混合日志模型重启Redis后,我们很少使用rdb来恢复内存状态,因为会丢失大量数据。我们通常使用AOF日志重放,但是重放AOF日志的性能比rdb慢很多,所以在Redis实例很大的情况下需要很长时间才能启动。为了解决这个问题,Redis4.0带来了一个新的持久化选项——混合持久化。将rdb文件的内容和增量的AOF日志文件一起存储。这里的AOF日志不再是全量日志,而是从开始持久化到持久化结束这段时间发生的增量AOF日志。通常,这部分AOF日志很小。因此,当Redis重启时,可以先加载rdb的内容,然后重放增量AOF日志,完全替代之前的AOF全量文件重放,大大提高了重启效率。因此RDB内存快照的执行频率稍慢,使用AOF记录两次RDB快照期间发生的所有“写”操作。这样就不需要经常执行快照。同时,由于AOF只需要记录两次快照之间发生的“写”指令,不需要记录所有操作,避免了文件过大的情况。总结Redis设计了bgsave和copy-on-write,尽可能避免快照执行过程中对读写指令的影响。频繁的快照会对磁盘造成压力,fork会阻塞主线程。Redis设计了两个杀手级特性来实现在不丢失数据的情况下从停机时间快速恢复。为了避免日志过大,提供了AOF重写机制。根据数据库数据的最新状态,将产生数据的写入操作作为新的日志,通过后台完成时不会阻塞主线程。AOF和RDB的结合在Redis4.0中提供了一种新的持久化策略和混合日志模型。Redis重启时,可以先加载rdb的内容,然后重放增量AOF日志,完全替代之前的AOF全量文件重放,大大提高了重启效率。最后,关于AOF和RDB的选择,“码哥字节”给出了三点建议:当数据不能丢失时,内存快照和AOF混合使用是一个不错的选择;如果允许分钟级别的数据丢失,则只能使用RDB;如果只使用AOF,则首选配置选项everysec,因为它在可靠性和性能之间取得了平衡。经过两篇Redis系列文章,读者朋友们应该对Redis有了一个全局的了解。本文转载自微信公众号“码哥字节”,可通过以下二维码关注。转载本文请联系码哥字节公众号。
