redis——持久化

因为redis是内存数据库,他把数据都存在内存里,所以要想办法实现持久化功能。

RDB

RDB持久化可以手动执行,也可以配置定期执行,可以把某个时间的数据状态保存到RDB文件中,反之,我们可以用RDB文件还原数据库状态。

    生成

有两个命令可以生成RDB文件:

  • SAVE 命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器,服务器不能接受其他指令。
  • BGSAVE 命令由子进程执行保存操作,所以该命令不会阻塞服务器,服务器可以接受其他指令。。

禁止BGSAVE和SAVE同时执行,也就是说执行其中一个就会拒绝另一个,这是为了避免父进程和子进程同时执行两个rdbsave,防止产生竞争条件。

    载入

    RDB载入工作是服务器启动时自动执行的。

    自动保存

用户可以通过save选项设置多个保存条件,服务器状态中会保存所有用 save 选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行 BGSAVE 命令。

比如

save 900 1

save 300 10

满足:服务器在900秒之内被修改至少一次或者300秒内修改至少十次。就会执行BGSAVE。

 

当服务器启动时,用户可以通过指定配置文件或者传入启动参数来设置save选项,服务器会把条件放到一个结构体里,结构体有一个数组,保存了所有条件。

 

serverCron函数默认100毫秒检查一次,他会遍历数组依次检查,符合条件就会执行BGSAVE。

    RDB文件结构

一个完整 RDB 文件所包含的各个部分:

REDIS,长度5字节, 保存着 "REDIS" 五个字符。 通过这五个字符, 可以在载入文件时, 快速检查载入文件是否 RDB 文件。

db_version ,长度 4 字节, 它的值是一个字符串表示的整数, 这个整数记录了 RDB 文件的版本号

databases 部分包含着零个或任意多个数据库, 以及各个数据库中的键值对数据

EOF 常量的长度为 1 字节, 这个常量标志着 RDB 文件正文内容的结束

check_sum 是一个 8 字节长的无符号整数, 保存着一个校验和,以此来检查 RDB 文件是否出错或损坏

我并不想深入探究databases的组成。就是知道

  • RDB 文件是一个经过压缩的二进制文件,由多个部分组成。
  • 对于不同类型的键值对, RDB 文件会使用不同的方式来保存它们。

即可。

 

AOF

AOF持久化是通过保存服务器执行的命令来记录状态的。还原的时候再执行一遍即可。

功能的实现可以分为命令追加、文件写入、文件同步三个步骤。

 

当 AOF 持久化功能处于打开状态时, 服务器在执行完一个写命令之后, 会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾:

struct redisServer {// ...// AOF 缓冲区sds aof_buf;// ...
};

Redis 服务器进程就是一个事件循环

循环中的文件事件负责接收客户端的命令请求, 以及向客户端发送命令回复,

而时间事件则负责执行像 serverCron 函数这样需要定时运行的函数。

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

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

flushAppendOnlyFile 函数的行为由服务器配置的 appendfsync 选项的值来决定

值为 always 时, 服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件并且同步 AOF 文件, 所以 always 的效率最慢的一个, 但从安全性来说, always 是最安全的, 因为即使出现故障停机, AOF 持久化也只会丢失一个事件循环中所产生的命令数据。

值为 everysec 时, 服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件, 每隔超过一秒就要在子线程中对 AOF 文件进行一次同步: 从效率上来讲, everysec 模式足够快, 并且就算出现故障停机, 数据库也只丢失一秒钟的命令数据。

值为 no 时, 服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件, 至于何时对 AOF 文件进行同步, 则由操作系统控制。

因为处于 no 模式下的 flushAppendOnlyFile 调用无须执行同步操作, 所以该模式下的 AOF 文件写入速度总是最快的, 不过因为这种模式会在系统缓存中积累一段时间的写入数据, 所以该模式的单次同步时长通常是三种模式中时间最长的: 从平摊操作的角度来看,no 模式和 everysec 模式的效率类似, 当出现故障停机时, 使用 no 模式的服务器将丢失上次同步 AOF 文件之后的所有写命令数据。

    重写

AOF持久化是保存了一堆命令来恢复数据库,随着时间流逝,存的会越来越多,如果不加以控制,文件过大可能影响服务器甚至计算机。而且文件过大,恢复时需要时间也太长。

所以redis提供了重写功能,写出的新文件不会包含任何浪费时间的冗余命令。

接下来,我们就介绍重写的原理。

其实重写不会对现有的AOF文件进行读取分析等操作,而是通过当前服务器的状态来实现。

 # 假设服务器对键list执行了以下命令s;
127.0.0.1:6379> RPUSH list "A" "B"
(integer) 2
127.0.0.1:6379> RPUSH list "C"
(integer) 3
127.0.0.1:6379> RPUSH list "D" "E"
(integer) 5
127.0.0.1:6379> LPOP list
"A"
127.0.0.1:6379> LPOP list
"B"
127.0.0.1:6379> RPUSH list "F" "G"
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "C"
2) "D"
3) "E"
4) "F"
5) "G"
127.0.0.1:6379> 

当前列表键list在数据库中的值就为["C", "D", "E", "F", "G"]。要使用尽量少的命令来记录list键的状态,最简单的方式不是去读取和分析现有AOF文件的内容,,而是直接读取list键在数据库中的当前值,然后用一条RPUSH list "C" "D" "E" "F" "G"代替前面的6条命令。

  • 伪代码表示如下
def AOF_REWRITE(tmp_tile_name):f = create(tmp_tile_name)# 遍历所有数据库for db in redisServer.db:# 如果数据库为空,那么跳过这个数据库if db.is_empty(): continue# 写入 SELECT 命令,用于切换数据库f.write_command("SELECT " + db.number)# 遍历所有键for key in db:# 如果键带有过期时间,并且已经过期,那么跳过这个键if key.have_expire_time() and key.is_expired(): continueif key.type == String:# 用 SET key value 命令来保存字符串键value = get_value_from_string(key)f.write_command("SET " + key + value)elif key.type == List:# 用 RPUSH key item1 item2 ... itemN 命令来保存列表键item1, item2, ..., itemN = get_item_from_list(key)f.write_command("RPUSH " + key + item1 + item2 + ... + itemN)elif key.type == Set:# 用 SADD key member1 member2 ... memberN 命令来保存集合键member1, member2, ..., memberN = get_member_from_set(key)f.write_command("SADD " + key + member1 + member2 + ... + memberN)elif key.type == Hash:# 用 HMSET key field1 value1 field2 value2 ... fieldN valueN 命令来保存哈希键field1, value1, field2, value2, ..., fieldN, valueN =\get_field_and_value_from_hash(key)f.write_command("HMSET " + key + field1 + value1 + field2 + value2 +\... + fieldN + valueN)elif key.type == SortedSet:# 用 ZADD key score1 member1 score2 member2 ... scoreN memberN# 命令来保存有序集键score1, member1, score2, member2, ..., scoreN, memberN = \get_score_and_member_from_sorted_set(key)f.write_command("ZADD " + key + score1 + member1 + score2 + member2 +\... + scoreN + memberN)else:raise_type_error()# 如果键带有过期时间,那么用 EXPIREAT key time 命令来保存键的过期时间if key.have_expire_time():f.write_command("EXPIREAT " + key + key.expire_time_in_unix_timestamp())# 关闭文件f.close()

    AOF后台重写


aof_rewrite函数可以创建新的AOF文件,但是这个函数会进行大量的写入操作,所以调用这个函数的线程被长时间的阻塞,因为服务器使用单线程来处理命令请求;所以如果直接是服务器进程调用AOF_REWRITE函数的话,那么重写AOF期间,服务器将无法处理客户端发送来的命令请求;


Redis不希望AOF重写会造成服务器无法处理请求,所以将AOF重写程序放到子进程(后台)里执行。这样处理的好处是: 
1)子进程进行AOF重写期间,主进程可以继续处理命令请求;
2)子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性。

还有一个问题,可能重写的时候又有新的命令过来,造成信息不对等,所以redis设置了一个缓冲区,重写期间把命令放到重写缓冲区。
 

  总结


AOF重写的目的是为了解决AOF文件体积膨胀的问题,使用更小的体积来保存数据库状态,整个重写过程基本上不影响Redis主进程处理命令请求;
AOF重写其实是一个有歧义的名字,实际上重写工作是针对数据库的当前状态来进行的,重写过程中不会读写、也不适用原来的AOF文件;
AOF可以由用户手动触发,也可以由服务器自动触发。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/445402.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis原理总结

数据结构(字典、链表、字符串) 数据结构(整数集合,压缩列表) 数据结构(跳表介绍和手撕) LRU介绍和实现 对象(字符串对象、列表对象、哈希对象、集合对象、有序集合总结&#xff…

剑指offer(刷题51-60)--c++,Python版本

文章目录目录第51题:解题思路:代码实现:cpython第52题:解题思路:代码实现:cpython第53题:解题思路:代码实现:cpython第54题:解题思路:代码实现&am…

2017第一届河北省大学生程序设计竞赛题解

超级密码 小明今年9岁了,最近迷上了设计密码!今天,他又设计了一套他认为很复杂的密码,并且称之为“超级密码”. 说实话,这套所谓的“超级密码”其实并不难:对于一个给定的字符串,你只要提取其中…

剑指offer(刷题61-65)--c++,Python版本

文章目录目录第61题:解题思路:代码实现:cpython第62题:解题思路:代码实现:cpython第63题:解题思路:代码实现:cpython第64题:解题思路:代码实现&am…

2018第二届河北省大学生程序设计竞赛题解

icebound的账单 题目描述 icebound从小就有记账的习惯。又到了月末icebound统计资金状况的时候。icebound每个月除了不停的挥霍以外,有时他会良心发现,勤工俭学,因此会有一些微薄的收入。然而icebound数学不好,需要你来帮助他统计…

大数的四则运算(加法、减法、乘法、除法)

大数的四则运算(加法、减法、乘法、除法) 前言: 在计算机中数字表示的范围是有限制的,比如我们熟知的 int、float、double 等数据类型所能表示的范围都是有限的,如果我们要对位数达到几十位、几百位、上千位的大整数进…

数组基操三连(1)

题目: 给定一个数组arr,求出需要排序的最短子数组长度 要求: 时间o(n),空间o(1) 思路: 有序的数组中,任意一个数字,一定小于左边的数大于右边的数。 我们找到的需要排序的子数组,显然是比右边…

IT互联网公司的笔试的输入输出- c++ python

文章目录目录c方式1&#xff1a;方式2&#xff1a;Python方式1&#xff1a;方式2&#xff1a;方式3&#xff1a;目录 c 方式1&#xff1a; 第一种情况&#xff1a;输入n个数&#xff0c;存放在数组中 #include <iostream> #include <vector> using namespace st…

随机过程1

随机过程1概述1.参考书目2.主要内容3.概率论--基本概念回顾3.1对“不确定性”的认识3.2 应对“不确定性”应该怎么做3.3随机变量&#xff08;Random Variable&#xff09;3.4分布函数&#xff08;Distribution Function&#xff09;3.5概率密度&#xff08;Density&#xff09;…

数组基操三连(4)

题目一 给定一个长度为N的整型数组arr&#xff0c;其中有N个互不相等的自然数1~N 请实现arr的排序 但是不要把下标0~N-1位置上的数值通过直接赋值的方式替换成1~N。 要求&#xff1a;时间复杂度为O(N)&#xff0c;额外空间复杂度为O(1)。 思路&#xff1a;从左向右检查&…

Linux(1)-touch,mkdir,rm,mv,cp,ls,cd,cat

Linux1-实用终端命令1. touch, mkdir2. rm, mv, cp3. ls(通配符),cd(绝对/相对路径)4. cat, more/less文件内容浏览文件/目录-增删查改, 文件内容查看.1. touch, mkdir touch新文件 &#xff1a;在当前文件夹下&#xff0c;创建文件。文件不存在则创建新文件&#xff1b;文件存…

java常用类介绍及源码阅读(ArrayList)

java.util 类 ArrayList<E> 继承关系&#xff1a; java.lang.Objectjava.util.AbstractCollection<E>java.util.AbstractList<E>java.util.ArrayList<E>List 接口的动态数组的实现。 实现了所有可选列表操作&#xff0c;并允许包括 null 在内的所有…

tests1

ls,cd,tardone

数组精选题目三连(5)

子数组的最大累加和问题 输入一个整形数组&#xff0c;求数组中连续的子数组使其和最大。比如&#xff0c;数组x 应该返回 x[2..6]的和187. 这四个代码完成的功能都是求最大子数组&#xff08;注意用词准确&#xff0c;子数组连续&#xff0c;子序列可以不连续&#xff09;。…

大数据学习(1)-大数据概述

文章目录目录大数据产生背景大数据概念大数据影响大数据应用大数据关键技术大数据产业大数据&#xff0c;云计算&#xff0c;物联网关系云计算物联网大数据&#xff0c;物联网&#xff0c;云计算三者之间联系目录 大数据产生背景 三次信息化浪潮 根据IBM前首席执行官郭士纳福…

java常用类介绍及源码阅读(LinkedList)

java.util 类 LinkedList<E> java.lang.Objectjava.util.AbstractCollection<E>java.util.AbstractList<E>java.util.AbstractSequentialList<E>java.util.LinkedList<E> List 接口的链接列表实现。实现所有可选的列表操作&#xff0c;并且允…