在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
之前的文章介绍了Redis的简单数据结构的相关使用和底层原理,这篇文章我们就来聊一下Redis应该如何保证高可用。 数据持久化我们知道虽然单机的Redis虽然性能十分的出色, 单机能够扛住10w的QPS,这是得益于其基于内存的快速读写操作,那如果某个时间Redis突然挂了怎么办?我们需要一种持久化的机制,来保存内存中的数据,否则数据就会直接丢失。 Redis有两种方式来实现数据的持久化,分别是RDB(Redis Database)和AOF(Append Only File),你可以先简单的把RDB理解为某个时刻的Redis内存中的数据快照,而AOF则是所有记录了所有修改内存数据的指令的集合(也就是Redis指令的集合),而这两种方式都会生成相应的文件落地到磁盘上,实现数据的持久化,方便下次恢复使用。 接下来就分别来聊聊这两种持久化方案。 RDB在redis中生成RDB快照的方式有两种,一种是使用 生成方法savesave命令直接调用 void saveCommand(client *c) { if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); return; } rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); if (rdbSave(server.rdb_filename,rsiptr) == C_OK) { addReply(c,shared.ok); } else { addReply(c,shared.err); } } bgsavebgsave命令会fork出一个子进程,由fork出来的子进程调用 /* BGSAVE [SCHEDULE] */ void bgsaveCommand(client *c) { int schedule = 0; /* The SCHEDULE option changes the behavior of BGSAVE when an AOF rewrite * is in progress. Instead of returning an error a BGSAVE gets scheduled. */ if (c->argc > 1) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) { schedule = 1; } else { addReply(c,shared.syntaxerr); return; } } rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); } else if (hasActiveChildProcess()) { if (schedule) { server.rdb_bgsave_scheduled = 1; addReplyStatus(c,"Background saving scheduled"); } else { addReplyError(c, "Another child process is active (AOF?): can't BGSAVE right now. " "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever " "possible."); } } else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) { addReplyStatus(c,"Background saving started"); } else { addReply(c,shared.err); } } 这也就是为什么Redis是单线程的,但却能够在生成RDB文件的同时对外提供服务。 fork之后,操作系统内核会把父进程中的所有内存设置为只读,只有当发生写数据时,会发生页异常中断,内核会把对应的内存页拷贝一份,父子进程各持有一份,所以在生成RDB过程中,由于使用了COW,内存脏页会逐渐和子进程分开。 那么有没有可能在调用 实际上在调用 例如,在 if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); return; } 而在 if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); } else if (hasActiveChildProcess()) { ... } 可以看到都是对同一个变量的判断,如下: pid_t rdb_child_pid; /* PID of RDB saving child */ 换句话说,在调用save、bgsave命令的时候,会提前去判断 那我把RDB文件生成了之后怎么使用呢? Redis在启动服务器的时候会调用 你吹的这么好,RDB的优缺点分别是啥? 优点RDB策略可以灵活配置周期,取决于你想要什么样的备份策略。例如:
基于这个策略,可以快速的恢复之前某个时间段的数据。 其次,RDB非常的适合做冷备份,你可以把RDB文件存储后转移到其他的存储介质上。甚至可以做到跨云存储,例如放到OSS上的同时,又放到S3上,跨云存储让数据备份更加的健壮。 而且,基于RDB模式的恢复速度比AOF更快,因为AOF是一条一条的Redis指令,RDB则是数据最终的模样。数据量大的话所有AOF指令全部重放要比RDB更慢。 缺点RDB作为一个数据持久化的方案是可行的,但是如果要通过RDB做到Redis的高可用,RDB就不那么合适了。 因为如果Redis此时还没有来得及将内存中的数据生成RDB文件,就先挂了,那么距离上次成功生成RDB文件时新增的这部分数据就会全部丢失,而且无法找回。 而且,如果内存的数据量很大的话,RDB即使是通过fork子进程来做的,但是也需要占用到机器的CPU资源,也可能会发生很多的也异常中断,也可能造成整个Redis停止响应几百毫秒。 AOF上面提到过RDB不能满足Redis的高可用。因为在某些情况下,会永久性的丢失一段时间内的数据,所以我们来聊聊另一种解决方案AOF。首先我们得有个概念,那就是RDB是对当前Redis Server中的数据快照,而AOF是对变更指令的记录(所有的获取操作不会记录,因为对当前的Redis数据没有改变)。 但是也正因为如此,AOF文件要比RDB文件更大。下面聊一下一个Redis命令请求从客户端到AOF文件的过程。 AOF记录过程首先Redis的客户端和服务器之间需要通信,客户端发送的不是我们写入的字符串,而是专门的协议文本。如果你可以熟悉Thrift或者Protobuf的话应该就能理解这个协议。 例如执行命令 然后Redis服务器就会根据协议文本的内容,选择适当的handler进行处理。当客户端将指令发送到Redis服务器之后,只要命令成功执行,就会将这个命令传播到AOF程序中。 注意,传播到AOF程序中之后不会马上写入磁盘,因为频繁的IO操作会带来巨大的开销,会大大降低Redis的性能,协议文本会被写到Redis服务器中的aof_buf中去,也叫AOF的写入缓冲区。 你这全部都写到缓冲区去了,啥时候落地? 每当 这个命令会调用 但是如果在这期间机器宕了,那么数据仍然会丢失。所以如果想要真正的将AOF文件保存在磁盘上,必须要调用上面提到的两个函数才行。 ServerCron作用现在我们就来具体聊一下serverCron函数,它主要是用于处理Redis中的常规任务。 什么叫常规任务? 就比如上面提到的AOF写入缓冲区,每次serverCron执行的时候就会把缓冲区内的AOF写入文件(当然,OS会写入自己的buffer中)。其余的就像AOF和RDB的持久化操作,主从同步和集群的相关操作,清理失效的客户端、过期键等等。 那这个cron间隔多久执行一次? 很多博客是直接给出的结论, /* This is our timer interrupt, called server.hz times per second. * ............. */ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ... server.hz = server.config_hz; } 为了避免影响大家的思路,我省略了暂时对我们没用的代码和注释。可以看到注释中有 server.hz相信大家都知道HZ(赫兹)这个单位,它是频率的国际单位制单位,表示每一条周期性事件发生的次数。所以,我们知道这个配置项是用于控制周期性事件发生的频率的。 其赋值的地方在上面的函数中已经给出,可以看到其初始值是来源于 # Redis calls an internal function to perform many background tasks, like # closing connections of clients in timeout, purging expired keys that are # never requested, and so forth. # # Not all tasks are performed with the same frequency, but Redis checks for # tasks to perform according to the specified "hz" value. # # By default "hz" is set to 10. Raising the value will use more CPU when # Redis is idle, but at the same time will make Redis more responsive when # there are many keys expiring at the same time, and timeouts may be # handled with more precision. # # The range is between 1 and 500, however a value over 100 is usually not # a good idea. Most users should use the default of 10 and raise this up to # 100 only in environments where very low latency is required. hz 10 简单的提取一下有用的信息,Redis会在内部调用函数来执行很多后台的任务,而调用这些函数的频率就由这个 写入策略上面说到,如果Redis的AOF已经位于OS的缓冲中,如果此时宕机,那么AOF的数据同样会丢失。 你这不行啊,那你这个持久化有什么意义?怎么样数据才能不丢失? 这得聊一下AOF日志的写入策略,它有三种策略,分别如下:
很明显 而如果使用 而 666,那这AOF文件咋用的,怎么恢复? 上面提到过,AOF文件是记录了来自客户端的所有写命令,所以服务器只需要读入并重放一遍即可将Redis的状态恢复。 但是,Redis的命令只能在客户端中的上下文才能够执行,所以Redis搞了一个没有网络连接的伪客户端来执行命令,直到命令执行完毕。 老铁,你这不行啊,万一AOF日志数据量很大,你这岂不是要恢复很长时间,那服务岂不是不可用了? 的确,随着服务器的运行,AOF的数据量会越来越大,重放所需要的时间也会越来越多。所以Redis有一个重写(AOF Rewrite)机制,来实现对AOF文件的瘦身。 虽然名字叫对AOF文件的瘦身,但是实际上要做的操作跟之前已经生成的AOF文件没有一毛钱的关系。 所谓瘦身是通过读取Redis服务器当前的数据状态来实现的,当然,这里的当前是在服务器正常运行的时候。其实你也可以理解为快照,只不过不是实打实的二进制文件了,而是直接设置快照值的命令。 用人话举个例子,假设你Redis中有个键叫 而之前的AOF文件还是照常的写入,当新的AOF文件生成后替换即可。 你tm在逗我?你在rewrite的同时,服务器仍然在处理正常的请求,此时如果对服务器的状态做了更改,你这个瘦身之后的AOF文件数据不就不一致了? 这种情况的确会出现,但是Redis通过一个AOF重写缓冲区来解决了这个问题。 当rewrite开始后,Redis会fork一个子进程,让子进程来实现AOF的瘦身操作,父进程则可以正常处理请求。AOF重写缓冲区会在rewrite开始创建了子进程之后开始使用,此时Redis服务器会把写的指令同时发送到两个地方:
你可能会问,为啥要记录到两个地方?上面提到过,Redis执行瘦身操作时,常规的AOF文件仍然是正常生成的,所以新的Redis指令一定会发送到写入缓冲区。 而发送到AOF重写缓冲区是为了重放在瘦身操作进行当中对Redis状态进行的更改,这样瘦身之后的AOF文件状态才能保证与Redis的状态一致。总的来说,就是为了保证瘦身的AOF文件中的数据状态与Redis当时的内存状态保持数据上的一致性。 End关于Redis数据持久化的问题,就先聊这么多,下一期的计划的应该就是聊一聊Redis的高可用的相关机制了。 ©著作权归作者所有:来自51CTO博客作者S.H的原创作品,如需转载,请与作者联系,否则将追究法律责任 到此这篇关于Redis做数据持久化的解决方案及底层原理的文章就介绍到这了,更多相关Redis数据持久化内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论