原文链接: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. Java中的四种权限修饰符及六种非访问修饰符(简识)

    一.是哪四种访问权限修饰符呢? public > protected > [default] > private (公共的 ) (受保护的) (默认的) (私有的) 二.简单认识四种 ...

  2. 安装JDK9,jemter无法正常启动,怎么退回到JDK8

    安装JDK8,配置环境变量 java -version显示的是8.1 然后安装JDK9之后,java -version显示的是9+8.1 这个时候,无法正常启动jemter 在环境变量中把path的C ...

  3. Linux tar 使用笔记

    常用语法 打包和压缩包 仅打包不压缩:tar -cvf usr.tar /usr 将 /usr 目录打包为 usr.tar 打包并以 gz 格式压缩:tar -czvf usr.tar.gz /usr ...

  4. 实验: survivor放不下的对象进入老年代

    实验一: 存活对象包含 小于survivor大小的对象 + 大于survivor的对象 private static final Integer _1MB = 1024 * 1024; /** * - ...

  5. ch1_6_5求解旋转词问题

    import java.util.Scanner; public class ch1_6_5求解旋转词问题 { public static void main(String[] args) { // ...

  6. 攻防世界 reverse 进阶 16-zorropub

    16.zorropub  nullcon-hackim-2016 (linux平台以后整理) https://github.com/ctfs/write-ups-2016/tree/master/nu ...

  7. vps-java环境配置

    昨天准备用vps打一台反序列化的机子要java环境,java环境安装了但是javac一直用不了,鼓捣了一段时间把配置文件给搞没了,只好重装vps,找了很多帖子才搞定,为了防止下次继续出现这种情况又要重 ...

  8. [题解] [NOI Online 2021 入门组 T3] 重力球

    题目大意 在一个 \(n\times n\) 的矩形中,题目会给出 \(m\) 个障碍物.有两个小球,你可以选定四个方向(上下左右)的其中一个,小球会朝着这四个方向一直滚动,直到遇到障碍物或是矩形的边 ...

  9. Java学习之数组的简单用法

    •概念 其实所谓的数组指的就是一组相关类型的变量集合,并且这些变量可以按照统一的方式进行操作. 数组本身属于引用数据类型,那么既然是引用数据类型,这里面实际又会牵扯到内存分配: 而数组的定义语法有两种 ...

  10. OO第一单元感悟与体会

    第一单元的三次编程作业结束了,现在分享一些我对自己作业的分析和感想 1.程序结构的分析 第一次作业: 本次作业我的主要思路是,为每一项写一个正则表达式,在输入的字符串中匹配每一项,多项式类中保存着一个 ...