前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis的持久化存储RDB的原理分析

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

作者头像
袁新栋-jeff.yuan
发布2020-08-26 14:26:15
6450
发布2020-08-26 14:26:15
举报

背景

想到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)
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-06-07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • redis持久化机制
    • 1. RDB
      • RDB底层实现
  • 总结
  • 参考文章:
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档