专栏首页用户4352451的专栏redis的持久化存储RDB的原理分析

redis的持久化存储RDB的原理分析

背景

想到redis,你的第一反应是什么呢?redis很快,我们一般一用它做缓存,再想想他为什么快呢?也许你的第一反应和我的第一反应是一样的,因为他是基于内存存储的,IO多路复用等。那么既然是基于内存存储的,那要是redis当宕机了那岂不是内存的数据都无法恢复了(在一些特殊情况下数据比较重要的情况)。那redis是如何解决这一问题?那就是redis的持久化机制。

redis持久化机制

redis 有两种持久化方式,RDB和AOF,今天我们主要先聊聊RDB持久化数据

1. RDB

  • 权威指南(redis官方文档) “RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.”
  • 我们看看RDB的使用:RDB 功能最核心的是 rdbSave 和 rdbLoad 两个函数, 前者用于生成 RDB 文件到磁盘, 而后者则用于将 RDB 文件中的数据重新载入到内存中:
  • 根就上面的描述,首先是他是主动的,在一定的时间区间就会主动触发程序同步,可想而知,在出发同步的时候会影响redis的业务操作吗? 会出现类似垃圾回收机制中的Stop The word (数据)? 所以我们就得看看他的底层实现。
RDB底层实现

从RDB的两个命令说起:SAVE 和 BGSAVE

  • SAVE 命令直接调用rdbsave函数,会阻塞Redis主线程,直到同步保存数据完成,在这个过程中客户端的任务一个也不能执行。我们在这里同时也得到了另一个信息,那就是redis是单线程的。所以这个线程杯哦
  • BGSAVE 会fork出一个子进程(这里注意了是子进程不是线程),子进程负责调用rdbSave,并在保存完毕完成后向主线程发送信号通知主进程保存完毕。这个时候主进程程也是会处理客户端的数据的。所以我们在此也会留下疑问那就是为什么要通知主进程呢?

在同步到磁盘和持续写入这个过程是如何处理数据不一致的情况呢?这几句是他redis为什么用子进程而不用子线程的原因之一,一个进程他可以包含多个线程,这多个线程是使用的这个进程分配下来的内存地址进行工作的。所以说多个进程之间的内存地址是相互独立的了,所以这也就是说不会出现在线程内加锁的情况,加锁必然会造成延时(除偏向锁等特殊锁类型)降低性能。

既然是两个进程那如何同步另一个进程中的数据到另一个进程呢?同步的这个过程内存中的数据是不断的在变化的,且两个进程也操作的同一个数据啊,线程安全的概念:线程安全是程式设计中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。(来自维基百科) 这岂不是进程安全问题?(自造)

我们先看redis的具体是怎么做的,下面是伪代码, 也就是调用了fork()方法。*在计算机编程领域,尤其是 Unix 和类 Unix 系统中,fork 都是一个进程用于创建自己拷贝的操作,它往往都是被操作系统内核实现的系统调用,也是操作系统在 nix 系统中创建新进程的主要方法。当程序调用了 fork 方法之后,我们就可以通过 fork 的返回值确定父子进程,以此来执行不同的操作: fork 函数返回 0 时,意味着当前进程是子进程; fork 函数返回非 0 时,意味着当前进程是父进程,返回值是子进程的 pid;在 fork 的 手册 中,我们会发现调用 fork 后的父子进程会运行在不同的内存空间中,当 fork 发生时两者的内存空间有着完全相同的内容,对内存的写入和修改、文件的映射都是独立的,两个进程不会相互影响。,可想而知,由于RDB存储的是某一时间点之前的快照,所以父进程写入新的数据并不会影响子进程。

    def BGSAVE():
pid = fork()

    if pid == 0:

        # 子进程保存 RDB
        rdbSave()

    elif pid > 0:

        # 父进程继续处理请求,并等待子进程的完成信号
        handle_request()

    else:

        # pid == -1
        # 处理 fork 错误
        handle_fork_error()

通过上面的分析和理解,但是还会有一个问题,也就是内存中数据量很大的时候,这个子进程在拷贝父进程的内存数据的时候会耗费大量的CPU时间片导致客户端的线程一直处于就绪状态。那这个问题应该如何解决呢?

问题总是会被解决的,写时拷贝(COPY—ON-WRITE)意思就是在刚开始的时候调用fork()方法,并不会将主进程的物理空间复制一份出来给子进程,子进程在此时会和主进程共享同一块物理内存空间,但是拥有不同的虚拟空间。所以在这个时候并不会出现大量数据的复制操作而是给子进程搞一个引用就ok了。所以我们上面所担心的问题也就解决了。

总结

  • 我们通过上面的分析,RDB是redis定时持久化的一个业务逻辑,可以通过命令SAVA 和 BGSAVE 进行同步持久化,使用BGSAVA不会影响到客户端的使用。而使用SAVA会影响客户端的使用。BGSAVE的实现是通过调用fork()和 rdbsave实现的,其中fork()的意思就是创建一个子进程,且采用的是写时拷贝。
  • 为什么通过子进程来解决这个问题呢?
    1. 通过 fork 创建的子进程能够获得和父进程完全相同的内存空间,父进程对内存的修改对于子进程是不可见的,两者不会相互影响;
    2. 通过 fork 创建子进程时不会立刻触发大量内存的拷贝,内存在被修改时会以页为单位进行拷贝,这也就避免了大量拷贝内存而带来的性能问题;

参考文章:

  1. https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html(linux 写时拷贝)
  2. https://redisbook.readthedocs.io/en/latest/internal/rdb.html(RDB)
  3. http://www.redis.cn/topics/persistence.html(redis持久化)
  4. https://draveness.me/whys-the-design-redis-bgsave-fork/(RDB)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 程序員必須知道的面向对象设计六大原则

    在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期 引入的记忆术首字母缩略字,指代了面向对象编...

    居士
  • 使用Vavr进行函数式编程(二)

    居士
  • 深入浅出TCP/IP协议

    TCP/IP 是一类协议系统,它是用于网络通信的一套协议集合.传统上来说 TCP/IP 被认为是一个四层协议

    居士
  • CSDI2018广州关于《Nginx》的分享(附文字速录与PPT)

    应百林哲笑含的邀请,于2018.6.9号至7.1号前往广州白云国际会议中心参加《CSDI Summit 中国软件研发管理行业技术峰会》。会上认识了很多互联网一线...

    陶辉
  • python3爬取墨迹天气并发送给微信好

    先随便观察一个城市的墨迹天气,例如石家庄市的url为“https://tianqi.moji.com/weather/china/hebei/shijiazhu...

    py3study
  • 伙伴系统和slab机制

    Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表。四级页表分别...

    233333
  • 由RGB到HSV颜色空间的理解

    在图像处理中,最常用的颜色空间是RGB模型,常用于颜色显示和图像处理,三维坐标的模型形式,非常容易被理解。

    种花家的奋斗兔
  • Mybaits-plus实战(三)

    老梁
  • Discuz! Q-罩哥【答记者问】

    从2020.3.15发布dzq第一个内测版到现在,时间差不多一个月了。这一个月时间大家提了很多非常有意义的建议,我们也都有一一去看,去记录。但大家还是有很多关...

    腾讯云DNSPod团队
  • 文件操作API

        最近遇到了一个困难。下的一部视频,有100来集,但每一集都放在单独的文件夹里。我现在想把他们移到一起,莫非要一个一个手工移?

    phith0n

扫码关注云+社区

领取腾讯云代金券