Redis篇(5)——持久化

news2024/11/20 16:35:38

持久化

RDB

1、save会阻塞所有命令。而bgsave则不能与其他持久化命令同时执行
2、自动rdb的发起:servercorn默认每100ms执行一次,根据redisserver里面记录的dirty(上次save后修改的次数)和lastsave(上次save的时间)变量以及配置的saveparams(保存条件)来判断是否符合保存条件,若符合则执行save
3、默认的自动保存条件
save 900 1 (900秒内1次修改)
save 300 10
save 60 10000

数据结构

  struct redisServer {
  		// ...
  		long long dirty;    //距离上次保存后修改的键数
        time_t lastsave;    //上次保存时间
        struct saveparam *saveparams;   //保存条件
  		// ...
  }

源码

serverCron里根据根据redisserver里面记录的dirty和lastsave变量以及配置的*saveparams参数来判断当前是否需要触发bgsave
在这里插入图片描述
在这里插入图片描述

save和bgSave伪代码
在这里插入图片描述

AOF

数据结构

  struct redisServer {
  		// ...
  		sds 	aof_buf; //aof命令缓存区
  		// ...
  }

源码

在每次命令的call函数最终会执行到feedAppendOnlyFile方法,这个方法会将写命令存入aof_buf缓存区
在这里插入图片描述
而在后续serverCron都会执行flushAppendOnlyFile考虑是否将aof缓存区的内容写入同步到aof文件,以下是伪代码
在这里插入图片描述
不同的appendfsync决定了不同的刷盘策略
在这里插入图片描述
flushAppendOnlyFile源码

void flushAppendOnlyFile(int force) {
    ssize_t nwritten;
    int sync_in_progress = 0;
    mstime_t latency;

    if (sdslen(server.aof_buf) == 0) {
        //如果是AOF_FSYNC_EVERYSEC且超过一秒且当前无刷盘任务则刷盘
        if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
            server.aof_fsync_offset != server.aof_current_size &&
            server.unixtime > server.aof_last_fsync &&
            !(sync_in_progress = aofFsyncInProgress())) {
            goto try_fsync;
        } else {
            return;
        }
    }

    if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
        sync_in_progress = aofFsyncInProgress();

    if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
        //如果当前有刷盘任务且本次又不强制刷盘则判断是否需要推迟等待
        if (sync_in_progress) {
            if (server.aof_flush_postponed_start == 0) {
                //如果是第一次则推迟并记录推迟开始时间
                server.aof_flush_postponed_start = server.unixtime;
                return;
            } else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                //最多推迟等待两秒
                return;
            }
            //记录下延迟个数
            server.aof_delayed_fsync++;
            serverLog(LL_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
        }
    }

    latencyStartMonitor(latency);
    nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf)); //写入系统缓存
    latencyEndMonitor(latency);

    if (sync_in_progress) {
        latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
    } else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
        latencyAddSampleIfNeeded("aof-write-active-child",latency);
    } else {
        latencyAddSampleIfNeeded("aof-write-alone",latency);
    }
    latencyAddSampleIfNeeded("aof-write",latency);

    //已经写入系统缓存,重置延迟刷盘时间
    server.aof_flush_postponed_start = 0;

    //当实际写入与buf长度不一致时处理错误及打印些日志
    if (nwritten != (ssize_t)sdslen(server.aof_buf)) {
        static time_t last_write_error_log = 0;
        int can_log = 0;

        /* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */
        if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
            can_log = 1;
            last_write_error_log = server.unixtime;
        }

        /* Log the AOF write error and record the error code. */
        if (nwritten == -1) {
            if (can_log) {
                serverLog(LL_WARNING,"Error writing to the AOF file: %s",
                    strerror(errno));
                server.aof_last_write_errno = errno;
            }
        } else {
            if (can_log) {
                serverLog(LL_WARNING,"Short write while writing to "
                                       "the AOF file: (nwritten=%lld, "
                                       "expected=%lld)",
                                       (long long)nwritten,
                                       (long long)sdslen(server.aof_buf));
            }

            if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
                if (can_log) {
                    serverLog(LL_WARNING, "Could not remove short write "
                             "from the append-only file.  Redis may refuse "
                             "to load the AOF the next time it starts.  "
                             "ftruncate: %s", strerror(errno));
                }
            } else {
                /* If the ftruncate() succeeded we can set nwritten to
                 * -1 since there is no longer partial data into the AOF. */
                nwritten = -1;
            }
            server.aof_last_write_errno = ENOSPC;
        }

        /* Handle the AOF write error. */
        if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
            /* We can't recover when the fsync policy is ALWAYS since the
             * reply for the client is already in the output buffers, and we
             * have the contract with the user that on acknowledged write data
             * is synced on disk. */
            serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
            exit(1);
        } else {
            /* Recover from failed write leaving data into the buffer. However
             * set an error to stop accepting writes as long as the error
             * condition is not cleared. */
            server.aof_last_write_status = C_ERR;

            /* Trim the sds buffer if there was a partial write, and there
             * was no way to undo it with ftruncate(2). */
            if (nwritten > 0) {
                server.aof_current_size += nwritten;
                sdsrange(server.aof_buf,nwritten,-1);
            }
            return; /* We'll try again on the next call... */
        }
    } else {
        if (server.aof_last_write_status == C_ERR) {
            serverLog(LL_WARNING,
                "AOF write error looks solved, Redis can write again.");
            server.aof_last_write_status = C_OK;
        }
    }
    server.aof_current_size += nwritten;

   //aof较小时则清空重用,比较大则释放内存并重新申请
    if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
        sdsclear(server.aof_buf);
    } else {
        sdsfree(server.aof_buf);
        server.aof_buf = sdsempty();
    }

//实际刷盘
try_fsync:
    //如果配置了aof rewrite时不要刷盘,且当前有子进程在执行aof重写或rdb保存,就先不要fsync
    if (server.aof_no_fsync_on_rewrite &&
        (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
            return;

    //AOF_FSYNC_ALWAYS 同步刷盘
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
        latencyStartMonitor(latency);
        redis_fsync(server.aof_fd);
        latencyEndMonitor(latency);
        latencyAddSampleIfNeeded("aof-fsync-always",latency);
        server.aof_fsync_offset = server.aof_current_size;
        server.aof_last_fsync = server.unixtime;
    }
    //AOF_FSYNC_EVERYSEC 提交job异步刷盘 
    else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
                server.unixtime > server.aof_last_fsync)) {
        if (!sync_in_progress) {
            aof_background_fsync(server.aof_fd);
            server.aof_fsync_offset = server.aof_current_size;
        }
        server.aof_last_fsync = server.unixtime;
    }
}

其实不止serverCron会执行flushAppendOnlyFile,像beforesleepprepareForShutDownstopAppendOnly时也会执行
在这里插入图片描述
总结:aof核心方法是 feedAppendOnlyFile、flushAppendOnlyFile

混合持久化(aof重写)

混合持久化本质是通过 AOF 后台重写(bgrewriteaof 命令)完成的,不同的是当开启混合持久化时,fork 出的子进程先将当前全量数据以 RDB 方式写入新的 AOF 文件,然后再将 AOF 重写缓冲区(aof_rewrite_buf_blocks)的增量命令以 AOF 方式写入到文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

数据结构

  struct redisServer {
  		// ...
  		int aof_rewrite_perc;         //重写条件之增长百分比
   		off_t aof_rewrite_min_size;     //重写条件之aof文件大小
  		list *aof_rewrite_buf_blocks; //aof 重写缓存区
  		int aof_pipe_write_data_to_child; //管道传递发送端
    	int aof_pipe_read_data_from_parent; //管道传递接收端
  		// ...
  }

源码

除主动调用 rewriteaof/bgrewriteaof 外,在serverCron会根据配置的参数判断是否需要重新。具体判断逻辑如下
在这里插入图片描述
serverCron里的判断是否需要aof重写的源码:
在这里插入图片描述
rewriteAppendOnlyFileBackground 重写aof
在这里插入图片描述

思考

aof重写会阻塞主线程吗?如果不会,那如何保证重写过程中的数据一致性?
不会阻塞,也是fork一个子线程。并且会有一个aof_rewrite_buf用于存储重写过程中的命令。所以当正在进行重写时一条命令的执行会同时写入aof_buf和aof_rewrite_buf_blocks缓存区,再通过aof_pipe_write_data_to_child管道传给重写子进程,子进程重写的过程中会尽可能的从管道中获取数据(因为aof重写缓存区写到aof文件是主线程执行了,为了防止长时间阻塞,每次等待可读取事件1ms,如果一直能读取到数据,则这个过程最多执行1000次,也就是1秒。如果连续20次没有读取到数据,则结束这个过程。),当重写进程结束了,会在主进程(阻塞)将剩余的新命令写入aof文件。

在这里插入图片描述
其实难点就在于aof重写是子进程异步的,主进程新加入的写命令如何传递给重写子进程。具体原理用到了管道传递(共有3个管道:2个控制管道,1个数据管道),具体实现后面有机会再详细独立一篇吧,这里只大概说一下数据发送端和接收端。

数据发送端:
写命令写入aof_buf的同时会判断是否正在aof重写,如果是则同时写入aof重写缓存区并创建管道读事件
在这里插入图片描述
数据接收端:
rewriteAppendOnlyFileBackground方法fork的子进程中rewriteAppendOnlyFile方法里的第1405行-1414行
在这里插入图片描述

过期键对rdb和aof的影响

rdb:过期键不会写入rdb文件,载入rdb文件时,主服务器也不会载入,只有从服务器会载入过期键。不过因为主服务器会同步至从,所以最终从服务器也没有过期键。(在复制模式下从服务器不会删除过期键,只能以主服务器同步del,在主服务器统一的删除,以确保数据一致性)
aof:在真正删除时才会追加删除命令到aof,同时重写aof文件时也会去掉过期键。

过期删除策略
1、定时:aeMain(时间事件)
2、定期:servercron
3、惰性:expireIfNeeded

持久化文件的加载

在redis启动的main函数里第4373行的loadDataFromDisk加载持久化文件,而aeMain的监听在main函数的最后。所以先加载完持久化文件后才会提供redis服务。
在这里插入图片描述

参考文献:《Redis设计与实现》黄健宏著、redis-5.0.14源码

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

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

相关文章

sklearn基础篇(七)-- 随机森林(Random forest)

上一节我们提到决策树的一个主要缺点在于经常对训练数据过拟合。随机森林是解决这个问题的一种方法。随机森林是属于集成学习&#xff0c;其核心思想就是集成多个弱分类器以达到一个强分类器的效果。 1 bagging的原理 随机森林采用Bagging的思想&#xff0c;所谓的Bagging可以…

HDFS的高级功能

3.6 HDFS的高级功能 3.6.1 安全模式 安全模式&#xff08;Safemode&#xff09;是HDFS所处的一种特殊状态。处于这种状态时&#xff0c;HDFS只接受读数据请求&#xff0c;不能对文件进行写、删除等操作。安全模式是保证一个系统保密性、完整性及可使用性的一种机制&#xff0…

精彩回顾:CACTER邮件数据防泄露EDLP亮相2022世界互联网大会

2022年世界互联网大会乌镇峰会于11月11日胜利闭幕。 本届峰会是世界互联网大会国际组织成立后的首届年会&#xff0c;以“共建网络世界 共创数字未来—携手构建网络空间命运共同体”为主题&#xff0c;共设置1场全体会议和20场分论坛&#xff0c;围绕全球网络空间热点问题展开讨…

【猿创征文】Vue3 企业级优雅实战 - 组件库框架 - 6 搭建example环境

本系列已更新文章&#xff1a; 分享一个实用的 vite vue3 组件库脚手架工具&#xff0c;提升开发效率 开箱即用 yyg-cli 脚手架&#xff1a;快速创建 vue3 组件库和vue3 全家桶项目 Vue3 企业级优雅实战 - 组件库框架 - 1 搭建 pnpm monorepo Vue3 企业级优雅实战 - 组件库框架…

线程池相关总结

多线程之线程池总结 1. 概述&#xff1a; 线程池&#xff08;Thread Pool&#xff09;&#xff1a;把一个或多个线程通过统一的方式进行调度和重复使用的技术&#xff0c;统一管理&#xff0c;避免了因线程过多而带来使用上的开销和不可控。 作用&#xff1a; 降低资源消耗…

SpringBoot进阶学习(二)---配置高级

第三方bean属性绑定 在要绑定的类上添加ConfigurationProperties(prefix “”)&#xff0c;prefix为对应的配置类中的内容&#xff0c;在添加注解ConfigurationProperties时候会产生错误&#xff0c;如&#xff1a; 这时候添加依赖&#xff1a; <dependency><groupI…

计算机网络 5 - 链路层

第6章 链路层和局域网(Link Layer and LANs)6.2 差错检测 和 纠正奇偶校验校验和CRC 循环冗余校验6.3 多路访问协议信道划分 MAC协议随机存取MAC协议6.4 LAN 局域网MAC地址 和 ARP无效的MAC帧格式Ethernet 以太网交换机第6章 链路层和局域网(Link Layer and LANs) 6.2 差错检测…

H5基本开发1——(H5简单概述)

html概述 HTML是用来描述网页的一种语言 HTML指的是超文本标记语言Hyper Text Markup Language&#xff0c;是一种用于创建网页的标准标记语言 标记语言是一套标记标签markup tag HTML使用标记标签来描述网页 HTML文档的后缀名&#xff1a;.html或者.htm&#xff0c;两种后缀名…

Deep Leakage from Gradients

Summary 对于分布式学习&#xff0c;特别是相关之前共享梯度的学习&#xff0c;提出了一种攻击方式&#xff08;DLG&#xff09;。通过窃取client之间传递的梯度反推出&#xff08;也是使用机器学习迭代的方式&#xff09;原始的输入。并在图像分类、Masked Language Model方面…

代谢ADMET在线网页预测工具SwissADME 、SOMP 、BioTransformer

药物代谢(Drug Metabolism)指药物在体内多种药物代谢酶&#xff08;尤其肝药酶&#xff09;的作用下&#xff0c;化学结构发生改变的过程&#xff1b;包括分解代谢和合成代谢 1、概念 药物的代谢反应大致可以分为氧化(oxidation)、还原(reduction)、水解(hydrolysis)和结合(co…

Android 增加布局圆角功能,支持背景裁切圆角

前言 我们Android开发同学最常见、频繁画UI时会遇到有角度的布局。例如: 通常,我们都会在drawble文件夹下创建Shape.xml去实现对吧?当然这样的代码实现方式没毛病。但是,项目大了业务繁杂,工程中会出现很多此类文件,显得非常臃肿,而且不方便复用,不利于研发效率…

22、7大参数自定义线程池(核心线程数,最大核心线程数。。。拒绝策略(4种))

7大参数自定义线程池&#xff08;核心线程数&#xff0c;最大核心线程数。。。拒绝策略&#xff08;4种&#xff09;&#xff09; 第一步&#xff1a;我们首先看单例线程池的源码 第二步&#xff1a;多个固定线程的线程池源码 第三步&#xff1a;可变的线程数的线程池源码 开启…

相控阵天线(三):直线阵列低副瓣综合(切比雪夫、泰勒分布、SinZ-Z和Villeneuve分布)

目录阵列天线综合方法概述切比雪夫阵列综合泰勒阵列综合高斯分布、二项式分布、SinZ-Z和Villeneuve分布切比雪夫、泰勒和Villeneuve综合比较切比雪夫、泰勒和Villeneuve分布的口径效率比较切比雪夫综合python代码示例阵列天线综合方法概述 直线阵列天线的综合是在预先给定辐射…

C++15 ---继承2:重载与覆盖、隐藏、拷贝构造函数、赋值运算符重载、静态数据成员

一、重载与覆盖的特征 1、重载 成员函数被重载的特征: (1&#xff09;相同的范围&#xff08;在同一个类中)&#xff1b; (2&#xff09;函数名字相同&#xff1b; (3&#xff09;参数不同&#xff1b; (4&#xff09; virtual关键字可有可无。 2、覆盖 覆盖是指派生类函数…

STM32CubeMX环境安装(保姆级)

目录 JAVA环境安装 安装包 文件夹设置 运行exe STM32CubeMX下载 第一步 第二步 第三步 第四步 第五步 第六步 第七步 第八步 注意&#xff0c;我们使用STM32CubeMX需要安装JAVA环境&#xff01;&#xff01;&#xff01; JAVA环境安装 安装包 JAVA下载链接&…

《FFmpeg Basics》中文版-02-显示帮助和功能

正文 关于FFmpeg程序的帮助和其他信息都显示在空格和连字符之后输入的各种选项&#xff0c;示例显示了FFmpeg工具的用法&#xff0c;但是相同的选项对于ffplay、ffprobe和ffserver是有效的。参数是区分大小写的。FFmpeg组件的开发速度很快&#xff0c;从2012年11月开始&#x…

MicroPython——有点东西,但是不多

引言 之前做过一个树莓派驱动墨水屏的项目&#xff0c;本来想整理出来与大家分享的&#xff0c;但是由于树莓派已经成了理财产品&#xff0c;所以为了让这个项目更加具有实践意义&#xff0c;最近我打算把这个项目移植到ESP32上。在树莓派上我使用的是Python编写的代码&#x…

C++简单工厂模式详解

C简单工厂模式详解1.问题引入2.编写代码思路的迭代2.1 main函数主体内编写全部代码2.2 修改上述问题后的main函数代码2.3 引入面向对象后的代码2.4 加上继承和多态后的代码3.C简单工厂代码4.总结4.1 简单工厂模式适用场景4.2收获1.问题引入 编写一个计算器程序代码&#xff0c…

论文笔记:Region Representation Learning via Mobility Flow

2017 CIKM 1 摘要和介绍 使用出租车出行数据学习区域向量表征 同时考虑时间动态和多跳位置转换——>通过flow graph和spatial graph学习表征出租车交通流可以作为区域相似度的一种 A区域和B区域之间流量大 ——>A和B的特征更相关——>用一个/很相似的vector来表征他…

如何实现基于场景的接口自动化测试用例?来看看大佬的方案

自动化本身是为了提高工作效率&#xff0c;不论选择何种框架&#xff0c;何种开发语言&#xff0c;我们最终想实现的效果&#xff0c;就是让大家用最少的代码&#xff0c;最小的投入&#xff0c;完成自动化测试的工作。 基于这个想法&#xff0c;我们的接口自动化测试思路如下…