原文链接:https://www.changxuan.top/?p=1386


Redis 是一个非关系型的内存数据库,使用内存存储数据是它能够进行快速存取数据的原因之一。

在实际应用中,常有人提倡把 Redis 只作为一种能够提高用户体验的组件来使用, 也就是说即使 Redis 服务挂掉之后也要保证系统正常使用。不过,在很多系统中还是希望既能发挥 Redis 基于内存快速存取的特性,又希望机器断电或 Redis服务停止后数据不丢失。所以,才引出了 Redis 的持久化功能。

在许多技术文章中,提到 Redis 的持久化时往往都会直接抛出两个名词 RDB 和 AOF。然后接下来就是分别介绍这两个名词。当然,如果要谈 Redis 的持久化肯定避免不了讲 RDB 和 AOF,但这是介绍持久化最恰当的方式吗?这样的文章是不是显得有些生硬呢?所以,在尝试弄明白一个事物的原理时一定要从头到尾的思考它存在的意义?为了解决什么问题?采用了什么方式?达到了什么目的?自己有没有其它的方案?这样从问题的源头切入,才能对这个事物理解的更加深刻,从而能够更好的帮助自己进行举一反三。而不是人云亦云,对于一些知识仅仅是背诵下来,这种死记硬背下来的知识在脑海里的保质期也是短的可怜。

在前面,我们已经提到为什么需要引入持久化?简单的来说持久化就是把内存中的数据存储到外存上,这样服务停止后,当再启动的时候就可以把外存的数据读取到内存中从而达到了不丢失数据的目的。

RDB

如果让你设计一个持久化的方案,你会怎么做呢?(假装绞尽脑汁… …)首先,我们可以使用一种简单的策略,将 Redis 中所有的数据按照一定格式全部写到磁盘上,即创建数据的快照文件。然后,你为了尽量保证不丢数据需要考虑使用实时写还是定时写,又或者用其它策略。其实,现在的你已经在尝试着去实现 RDB (Redis Database)持久化的机制了。所以,你看它其实并不难。万丈高楼从地起,先从一个简单的 idea 开始,逐渐去完善它,丰富它的过程便是解决问题的过程。例如用这种思路去学习计算机网络也是同样适用的,你可以给自己出一个问题“如何让两台电脑进行通信?”,自己想办法解决这个问题的过程肯定会比在计算机网络课堂上收获的知识更多,也更牢固。

尽管不需要我们写代码来实现 RDB 持久化,但是并不妨碍我们来思考一下假如让我们来实现的话大概会遇到哪些问题?例如:什么时候生成数据快照?文件数据格式的定义?如果在主进程中进行持久化,阻塞客户端的请求后会不会有影响?接下来,我们就看一下 RDB 是如何做的吧。

基本命令

在 Redis 中,提供了两个 RDB 持久化的命令: SAVE 和 BGSAVE 。执行 SAVE 时,Redis 服务会停止处理任何客户端的命令请求;执行 BGSAVE 时,Redis 服务则会创建一个子进程,由子进程来负责数据的持久化,而此时 Redis 服务就可以正常处理客户端的请求。

BGSAVE 解决了我们对于持久化时是否会影响 Redis 服务处理客户端的请求的担心。

自动间隔性保存

自动间隔性保存,则解决了“什么时候生成数据快照?”的问题。在 Redis 的配置文件中我们可以写入以下配置:

save 600 1
save 300 10
save 60 100
save 30 1000

上面的配置表示,如果在 600 秒内对数据库进行了 1 次修改,就执行执行一次 BGSAVE 命令;如果在 300 秒内对数据库进行了 10 次修改,就执行一次 BGSAVE 命令;以此类推。你可以根据你的业务场景,配置 save 的参数,也不仅仅局限于 4 条配置。

实现原理

在 Redis 启动时,会把上述配置存储到 Redis 服务器的状态中,具体的结构体则是 redisServer,存储 save 参数的结构体为 saveparam。

 1 // Redis 服务器状态信息结构体
2 struct redisServer {
3 // ... ...
4
5 // 记录多个 save 配置参数
6 struct saveparam *saveparams;
7 // 修改次数计数器
8 long long dirty;
9 // 上次执行保存的时间
10 time_t lastsave;
11
12 // ... ...
13 }
14 // Save 参数结构体 saveparam
15 struct saveparam {
16 // 秒数
17 time_t seconds;
18 // 修改数
19 int changes;
20 }

看到上面 redisServer 结构体的属性信息,你心里应该有答案了吧?dirty 表示的是自从上次执行 SAVE 或者 BGSAVE 命令完成之后对数据库进行修改的次数;lastsave 表示的是上次成功执行SAVE 或者 BGSAVE 命令的时间。这个时候,如果再有个机制能够定时检查是否有满足条件的配置参数就可以了。

Redis 提供了一个周期性操作函数 serverCron,每 100 ms 会执行一次。它其中的一项工作就是来检查是否有符合条件的 save 参数,如果存在符合条件的参数则执行 BGSAVE 命令,执行完毕之后将 dirty 和 lastsave 的值重置。相信只要有基础的编程知识,根据这些变量就能实现这个检查的过程吧。

文件结构

在上图中,大写字母的单词表示的常量,小写字母单词则是变量和数据。RDB 文件开头的“REDIS”是我们习惯称为的魔数,类似于 class 文件的 COFFEE,用来识别文件类型;紧接着长度为四个字节的 db_version 记录的是 RDB 文件的版本号;database 表示的是所存储的数据;EOF 则表明数据内容结束了;check_sum 的值是整个文件的校验和,用来检查文件是否损坏。

AOF

其实持久化数据除了 RDB 这种方式,肯定会有同学能想到另一种方式,就是把服务端执行的所有客户端请求增加、修改和删除等会改变数据的命令全都存储起来。通过存储这些命令数据,在遇到机器宕机和服务进程异常中断的情况下重启服务时只要执行一遍这些持久化的命令即可恢复之前的数据了。(也是一个相当好的办法呀!)

原理就是如此,那么问题来了,假如同样让你来实现这个过程,你会考虑到哪些问题呢?

一是性能问题,执行完命令之后是否直接将此命令持久化到磁盘上还是由操作系统控制文件同步?在这个问题上如何做取舍?二是文件大小问题,随着 Redis 服务运行越来越久,数据文件势必会越来越大?应该使用什么办法解决?… …

我们来看下 Redis 的 AOF 的过程吧!

持久化过程

首先,通过在配置文件中增加一行配置 appendonly yes 来开启 AOF 持久化。

像 RDB 机制所依赖 redisServer 结构体中的 saveparams、dirty、lastsave 参数一样,AOF 的实现依赖 redisServer 结构体中的 aof_buf 参数。

1 struct redisServer{
2 // ... ...
3
4 // AOF 缓冲区
5 sds aof_buf;
6
7 // ... ...
8 }

aof_buf 参数用来以协议格式缓存会对数据进行变更的命令。

在 Redis 服务器执行完命令,并将命令以协议的格式追加到 aof_buf 缓冲区之后,在当前这个事件循环结束之前,Redis 还会调用一个函数 flushAppendOnlyFile,这个函数会根据配置文件中 appendfsync 的值来决定接下来的持久化行为。appendfsync 有三个可选值,分别是 always、everysec、no

  • always: 将 aof_buf 缓冲区中的内容写入并同步到 AOF 文件。(性能最低,安全最高)
  • everysec: 将 aof_buf 缓冲区中的内容写入到 AOF 文件,如果上次同步 AOF 文件的时间距离现在超过一秒钟,那么再次对 AOF 文件同步,并且这个同步是由一个线程专门负责的。(同时兼顾性能与安全,推荐)
  • no: 将 aof_buf 缓冲区中的内容写入到 AOF 文件,但并不负责对 AOF 文件的同步,把同步的控制权交由操作系统控制。(性能最高,安全最低)

以上就是 AOF 持久化的基本过程。

数据载入

由于命令数据是以协议格式存储至文件中的,所以在启动 Redis 服务时检测到 AOF 文件的存在后会启动载入程序。(如果 RDB 和 AOF 持久化的文件同时存在则会优先载入 AOF 文件数据)

启动载入程序后,其载入过程如下图所示:

AOF 重写

在前面,我们提到 AOF 的这种机制会造成 AOF 数据文件越来越大,并且可能会存在许多无意义的命令。例如,先执行了一个命令 set chang xuan ,随后又执行了命令 del chang 。其实这两条语句都会被持久化到 AOF 文件中,但实际上除了能证明曾经执行过这两条命令之外对于我们要持久化数据的目的而言并没有什么作用。

对此,Redis 提供了 AOF 重写的机制。

Redis 的 AOF 重写其实是根据当前存储的数据,生成命令的过程。并且会采用一些策略尽量减小 AOF 文件的大小,例如对于 List 中的数据会尽量使用较少的命令操作较多的数据。当然,如果在当前进程中进行重写处理并且数据量特别大的情况下肯定会阻塞客户端的请求,所以和 RDB 一样,Redis 提供了 AOF 后台重写的机制。

后台重写(BGREWRITEAOF)

AOF 通过 fork 子进程的方式进行后台重写有两个优点:

  1. 重写期间服务器进程可以继续处理请求。
  2. 子进程带有服务器进程的数据副本,能充分利用操作系统提供的写时复制机制从而提升效率,还可以在避免使用锁的情况下保证数据的安全性。

天下没有免费的午餐,这种方式还带来一个问题。就是在使用子进程重写期间,如果父进程还在处理着客户端请求,如何保证重写后 AOF 文件数据的一致性呢?

对于这个问题,Redis 设置了一个 AOF 重写缓冲区。在子进程被创建后,Redis 服务器就会启用这个重写缓冲区。在将命令以协议格式追加到 AOF 缓冲区之后,同时也会追加到 AOF 重写缓冲区。

当子进程完成重写工作后会向父进程发送一个信号。父进程接收到信后之后会进行调用相关函数,进行以下工作:

  1. 将 AOF 重写缓冲区中的内容写入到新的 AOF 文件中。
  2. 对新的 AOF 文件进行改名,原子地覆盖现有的 AOF 文件,完成新旧文件的替换。

这时,就完成了一次 AOF 后台重写。

总结

通过前文内容,我们可以大致清楚 Redis 所提供的 RDB 和 AOF 两种持久化机制的过程以及基本原理。它们各有特点,也各有适合使用的场景所以并不能说谁一定比谁好。通过搭配使用,能够确保线上环境数据的安全性就是最好的。

Redis 的持久化的更多相关文章

  1. Redis总结(四)Redis 的持久化

    前面已经总结了Redis 的安装和使用今天讲下Redis 的持久化. redis跟memcached类似,都是内存数据库,不过redis支持数据持久化,也就是说redis可以将内存中的数据同步到磁盘来 ...

  2. Redis的持久化的两种方式drbd以及aof日志方式

    redis的持久化配置: 主要包括两种方式:1.快照  2 日志 来看一下redis的rdb的配置选项和它的工作原理: save 900 1 // 表示的是900s内,有1条写入,则产生快照 save ...

  3. redis启用持久化

    redis的持久化有rdb和aof两种. rdb是记录一段时间内的操作,一盘的配置是一段时间内操作超过多少次就持久化. aof可以实现每次操作都持久化. 这里我们使用aof. 配置方式,打开redis ...

  4. redis + 主从 + 持久化 + 分片 + 集群 + spring集成

    Redis是一个基于内存的数据库,其不仅读写速度快,每秒可以执行大约110000的写操作,81000的读取操作,而且其支持存储字符串,哈希结构,链表,集合丰富的数据类型.所以得到很多开发者的青睐.加之 ...

  5. Redis笔记(八)Redis的持久化

    Redis相比Memcached的很大一个优势是支持数据的持久化, 通常持久化的场景一个是做数据库使用,另一个是Redis在做缓存服务器时,防止缓存失效. Redis的持久化主要有快照Snapshot ...

  6. 深入剖析 redis AOF 持久化策略

    本篇主要讲的是 AOF 持久化,了解 AOF 的数据组织方式和运作机制.redis 主要在 aof.c 中实现 AOF 的操作. 数据结构 rio redis AOF 持久化同样借助了 struct ...

  7. 深入剖析 redis RDB 持久化策略

    简介 redis 持久化 RDB.AOF redis 提供两种持久化方式:RDB 和 AOF.redis 允许两者结合,也允许两者同时关闭. RDB 可以定时备份内存中的数据集.服务器启动的时候,可以 ...

  8. redis 数据持久化

    1.快照(snapshots) 缺省情况情况下,Redis把数据快照存放在磁盘上的二进制文件中,文件名为dump.rdb.你可以配置Redis的持久化策略,例如数据集中每N秒钟有超过M次更新,就将数据 ...

  9. redis的持久化之AOF

    AOF Redis 分别提供了 RDB 和 AOF 两种持久化机制: RDB 将数据库的快照(snapshot)以二进制的方式保存到磁盘中. AOF 则以协议文本的方式,将所有对数据库进行过写入的命令 ...

  10. redis的持久化方式RDB和AOF的区别

    1.前言 最近在项目中使用到Redis做缓存,方便多个业务进程之间共享数据.由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能, ...

随机推荐

  1. Java8 BiFunction 简单用用

    最近来了新公司,主要用到了ElasitcSearch,大家都知道在底层查询代码中往往需要判断传入某个参数是否为空来判断设置查询,例如下方代码: BoolQueryBuilder query = Que ...

  2. 解决springMVC https环境 jstlview redirect时变为http请求的问题

    <property name="redirectHttp10Compatible" value="false" />

  3. 新的颜色对比度算法-感知对比度算法APCA

    目录 对比度 在控制台查看 插件或网站 感知对比度算法(APCA) APCA Math 原理 js 实现的 SAPC 最后 灵感的源泉来源于不断的接受新鲜事物. Chrome 89 新功能一览,性能提 ...

  4. 走进docker-初识

    什么是Docker容器? 容器是打包代码及其所有依赖项的软件的标准单元,因此应用程序可以从一个计算环境快速可靠地运行到另一个计算环境.Docker容器映像是一个轻量级的,独立的,可执行的软件软件包,其 ...

  5. 自动QQ邮箱发送邮件

    语言:python 参考:https://www.runoob.com/python/python-email.html 前提: 1.QQ邮箱开启了SMTP服务 2.生成了授权码,这个授权码将作为自己 ...

  6. 计算机体系结构——CH2 指令系统

    CH2 指令系统 右键点击查看图像,查看清晰图像 X-mind CH2 指令系统 数据表示 定义 指计算机硬件能够直接识别,可以被指令系统直接调用的那些数据类型 确定哪些数据类型用哪些数据表示实现,是 ...

  7. 使用Drone构建Docker映像

    使用Drone构建Docker映像 实践所用软件: Git Gogs Drone Docker 私有镜像仓库 实践链接:https://www.katacoda.com/courses/cicd/bu ...

  8. [矩阵乘法] PKU3233 Matrix Power Series

    [ 矩 阵 乘 法 ] M a t r i x P o w e r S e r i e s [矩阵乘法]Matrix Power Series [矩阵乘法]MatrixPowerSeries Desc ...

  9. OO第三单元——基于JML的社交网络总结

    OO第三单元--基于JML的社交网络总结 一.JML知识梳理 1)JML的语言基础以及基本语法 JML是用于java程序进行规格化设计的一种表示语言,是一种行为接口规格语言.其为严格的程序设计提供了一 ...

  10. pandas(5):数学统计——描述性统计

    Pandas 可以对 Series 与 DataFrame 进行快速的描述性统计,方便快速了解数据的集中趋势和分布差异.源Excel文件descriptive_statistics.xlsx: 一.描 ...