>  기사  >  데이터 베이스  >  Redis 심층 학습의 지속성 원리에 대한 자세한 설명

Redis 심층 학습의 지속성 원리에 대한 자세한 설명

青灯夜游
青灯夜游앞으로
2022-03-10 10:54:512173검색

이 기사에서는 Redis에 대해 자세히 알아보고 지속성을 자세히 분석하고 작동 원리, 지속성 프로세스 등을 소개합니다. 도움이 되길 바랍니다!

이 글에서는 다음과 같은 측면에서 Redis 지속성 메커니즘을 소개합니다.

## 이전에 작성됨

이 글에서는 작동 원리, 지속성 프로세스 및 실행 전략을 포함하여 Redis의 두 가지 지속성 방법을 전체적으로 자세히 소개합니다. , 그리고 그 뒤에 숨겨진 몇 가지 이론적 지식. 이전 글에서는 RDB Persistence만 소개하였지만 Redis Persistence는 전체이므로 따로 소개할 수 없으므로 재구성하였습니다. [관련 권장 사항: Redis 동영상 튜토리얼]

Redis는 인메모리 데이터베이스이며 모든 데이터가 메모리에 저장됩니다. MySQL, Oracle, SqlServer와 같은 기존 관계형 데이터베이스는 데이터를 하드에 직접 저장합니다. 디스크, Redis 읽기 및 쓰기 효율성이 매우 높습니다. 하지만 메모리에 저장하는 것에도 큰 결함이 있습니다. 전원이 꺼지거나 컴퓨터가 다운되면 메모리 데이터베이스에 있는 모든 내용이 손실됩니다. 이러한 단점을 보완하기 위해 Redis는 메모리 데이터를 하드디스크 파일에 유지하고, 백업 파일을 통해 데이터를 복원하는 기능을 제공하는데, 이것이 Redis Persistence 메커니즘입니다.

Redis는 RDB 스냅샷과 AOF라는 두 가지 지속성 방법을 지원합니다.

RDB 지속성

RDB 스냅샷 공식적으로 말하면: RDB 지속성 솔루션은 지정된 시간 간격으로 데이터 세트에서 생성된 특정 시점 스냅샷입니다. Redis 데이터 백업, 전송 및 복구에 사용할 수 있는 압축된 바이너리 파일로 특정 시간에 Redis 데이터베이스에 있는 모든 데이터 개체의 메모리 스냅샷을 저장합니다. 지금까지는 여전히 공식적인 기본 지원 솔루션입니다.

RDB 작동 원리

RDB는 Redis에 있는 데이터 세트의 특정 시점 스냅샷이므로 먼저 Redis의 데이터 개체가 메모리에 어떻게 저장되고 구성되는지 간략하게 살펴보겠습니다.

기본적으로 Redis에는 0부터 15까지 번호가 매겨진 16개의 데이터베이스가 있으며, 각 Redis 데이터베이스는 하나를 사용합니다. redisDb对象来表示,redisDb해시테이블을 사용하여 K-V 개체를 저장합니다. 이해를 돕기 위해 DB 중 하나를 예로 들어 Redis 내부 데이터의 저장 구조에 대한 개략도를 그려보았습니다.

Redis 심층 학습의 지속성 원리에 대한 자세한 설명
특정 시점 스냅샷은 Redis의 각 DB에 있는 각 데이터 객체의 상태입니다. 이 순간 모든 데이터 객체가 변경되지 않는다고 가정, 위 그림을 따를 수 있습니다. 데이터 구조 관계에 따라 Redis 지속성을 달성하기 위해 이러한 데이터 객체를 읽고 파일에 기록합니다. 그런 다음 Redis가 다시 시작되면 규칙에 따라 이 파일의 내용을 읽은 다음 Redis 메모리에 기록하여 지속성 상태로 복원합니다.

물론 위의 가정이 참일 때 이 전제도 참입니다. 그렇지 않으면 항상 변하는 데이터 세트 앞에서 시작할 방법이 없을 것입니다. Redis에서 클라이언트 명령 처리는 단일 스레드 모델이라는 것을 알고 있습니다. 지속성이 명령으로 처리되면 데이터 세트는 확실히 정적 상태가 됩니다. 또한 운영 체제에서 제공하는 fork() 함수로 생성된 하위 프로세스는 상위 프로세스와 동일한 메모리 데이터를 얻을 수 있습니다. 이는 포크가 완료된 후 메모리 데이터의 복사본을 얻는 것과 동일합니다. 부모 프로세스가 수행하고 상태를 유지하는 작업은 자식 프로세스에 넘겨집니다.

분명히 첫 번째 상황은 바람직하지 않습니다. 지속적인 백업으로 인해 Redis 서비스가 짧은 시간 내에 중단될 수 있으며 이는 HA가 높은 시스템에서는 용납되지 않습니다. 따라서 두 번째 방법이 RDB 지속성의 주요 실제 방법입니다. 하위 프로세스를 포크한 후에도 상위 프로세스의 데이터가 계속 변경되고 하위 프로세스가 상위 프로세스와 동기화되지 않기 때문에 RDB 지속성은 실시간 성능을 보장할 수 없습니다. RDB 지속성이 완료된 후 정전이나 다운타임이 발생합니다. 일부 데이터가 손실됩니다. 백업 빈도에 따라 손실된 데이터의 양이 결정됩니다. 백업 빈도를 높이면 포크 프로세스가 더 많은 CPU 리소스를 소비하고 디스크 I/O도 커집니다.

지속성 프로세스

Redis에서 RDB 지속성을 완료하는 두 가지 방법이 있습니다: rdbSave 및 rdbSaveBackground(소스 코드 파일 rdb.c에 있음) 두 가지의 차이점에 대해 간략하게 이야기해 보겠습니다.

  • rdbSave: 동기적으로 실행되며, 메소드 호출 직후 지속성 프로세스가 시작됩니다. Redis는 단일 스레드 모델이므로 지속성 프로세스 중에 차단되며 Redis는 외부 서비스를 제공할 수 없습니다.
  • rdbSaveBackground: 이 메서드는 백그라운드에서(비동기적으로) 실행됩니다. 프로세스가 하위 프로세스에 있습니다. 실행(rdbSave 호출) 후에도 기본 프로세스는 계속해서 서비스를 제공합니다.

RDB 지속성 트리거는 위의 두 가지 방법과 분리될 수 없으며 트리거 방법은 수동 및 자동으로 구분됩니다. . 수동 트리거는 이해하기 쉽습니다. 이는 Redis 클라이언트를 통해 Redis 서버에 대한 지속성 백업 지침을 수동으로 시작한 다음 Redis 서버가 지속성 프로세스를 실행하기 시작한다는 의미입니다. 여기에는 save 및 bgsave가 포함됩니다. 자동 트리거는 자체 ​​운영 요구 사항에 따라 미리 설정된 조건이 충족될 때 Redis가 자동으로 트리거하는 지속성 프로세스입니다. 자동으로 트리거되는 시나리오는 다음과 같습니다(이 문서에서 추출):

  • save m nin serverCron > 구성 규칙이 자동으로 트리거됩니다. save m n配置规则自动触发;
  • 从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会出发bgsave;
  • 执行debug reload命令重新加载redis时;
  • 默认情况下(未开启AOF)执行shutdown命令时,自动执行bgsave;

结合源码及参考文章,我整理了RDB持久化流程来帮助大家有个整体的了解,然后再从一些细节进行说明。
Redis 심층 학습의 지속성 원리에 대한 자세한 설명
从上图可以知道:

  • 自动触发的RDB持久化是通过rdbSaveBackground以子进程方式执行的持久化策略;
  • 手动触发是以客户端命令方式触发的,包含save和bgsave两个命令,其中save命令是在Redis的命令处理线程以阻塞的方式调用rdbSave方法完成的。

自动触发流程是一个完整的链路,涵盖了rdbSaveBackground、rdbSave等,接下来我以serverCron为例分析一下整个流程。

save规则及检查

serverCron是Redis内的一个周期性函数,每隔100毫秒执行一次,它的其中一项工作就是:根据配置文件中save规则来判断当前需要进行自动持久化流程,如果满足条件则尝试开始持久化。了解一下这部分的实现。

redisServer中有几个与RDB持久化有关的字段,我从代码中摘出来,中英文对照着看下:

struct redisServer {
    /* 省略其他字段 */ 
    /* RDB persistence */
    long long dirty;                /* Changes to DB from the last save
                                     * 上次持久化后修改key的次数 */
    struct saveparam *saveparams;   /* Save points array for RDB,
                                     * 对应配置文件多个save参数 */
    int saveparamslen;              /* Number of saving points,
                                     * save参数的数量 */
    time_t lastsave;                /* Unix time of last successful save 
                                     * 上次持久化时间*/
    /* 省略其他字段 */
}

/* 对应redis.conf中的save参数 */
struct saveparam {
    time_t seconds;                    /* 统计时间范围 */   
    int changes;                    /* 数据修改次数 */
};

saveparams对应redis.conf下的save规则,save参数是Redis触发自动备份的触发策略,seconds为统计时间(单位:秒), changes为在统计时间内发生写入的次数。save m n的意思是:m秒内有n条写入就触发一次快照,即备份一次。save参数可以配置多组,满足在不同条件的备份要求。如果需要关闭RDB的自动备份策略,可以使用save ""。以下为几种配置的说明:

# 表示900秒(15分钟)内至少有1个key的值发生变化,则执行
save 900 1
# 表示300秒(5分钟)内至少有1个key的值发生变化,则执行
save 300 10
# 表示60秒(1分钟)内至少有10000个key的值发生变化,则执行
save 60 10000
# 该配置将会关闭RDB方式的持久化
save ""

serverCron

슬레이브 노드가 완전히 복사되면 마스터 노드는 복사 작업을 완료하기 위해 rdb 파일을 보내고 마스터 노드는

디버그 다시 로드를 시작합니다. 명령은 redis를 다시 로드하기 위해 실행됩니다.

기본적으로(AOF는 활성화되지 않음) shutdown 명령이 실행되면 bgsave가 자동으로 실행됩니다.

    소스 코드와 참조 기사를 결합하여 RDB 지속성 프로세스를 구성했습니다. 모든 사람이 전반적인 이해를 돕도록 한 다음 몇 가지 세부 사항을 설명합니다.
    Redis 심층 학습의 지속성 원리에 대한 자세한 설명
    위 그림에서 알 수 있듯이:
  • 자동으로 트리거되는 RDB 지속성은 다음을 통해 이루어집니다. rdbSaveBackground. 하위 프로세스 모드에서 실행되는 지속성 전략
수동 트리거는 save 및 bgsave 명령을 포함한 클라이언트 명령에 의해 트리거됩니다. 여기서 save 명령은 차단 방식으로 Redis 명령 처리 스레드에서 호출됩니다. > 방법이 완료되었습니다.

자동 트리거 프로세스는 rdbSaveBackground, rdbSave 등을 포함하는 완전한 링크입니다. 다음으로 serverCron을 예로 사용하여 전체 프로세스를 분석하겠습니다.

🎜저장 규칙 및 확인🎜🎜serverCron은 100밀리초마다 실행되는 Redis의 주기적 기능입니다. 해당 작업 중 하나는 조건이 충족되는 경우 구성 파일의 저장 규칙을 기반으로 현재 자동 지속성이 필요한지 결정하는 것입니다. , 지속성을 시작하려고 시도합니다. 이 부분의 구현에 대해 알아보세요. 🎜🎜 redisServer에는 RDB 지속성과 관련된 여러 필드가 있습니다. 코드에서 추출하여 중국어와 영어로 살펴보았습니다. 🎜
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    /* 省略其他逻辑 */
    
    /* 如果用户请求进行AOF文件重写时,Redis正在执行RDB持久化,Redis会安排在RDB持久化完成后执行AOF文件重写,
     * 如果aof_rewrite_scheduled为true,说明需要执行用户的请求 */
    /* Check if a background saving or AOF rewrite in progress terminated. */
    if (hasActiveChildProcess() || ldbPendingChildren())
    {
        run_with_period(1000) receiveChildInfo();
        checkChildrenDone();
    } else {
        /* 后台无 saving/rewrite 子进程才会进行,逐个检查每个save规则*/
        for (j = 0; j = sp->changes 
                && server.unixtime-server.lastsave > sp->seconds 
                &&(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK))
            {
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...", sp->changes, (int)sp->seconds);
                rdbSaveInfo rsi, *rsiptr;
                rsiptr = rdbPopulateSaveInfo(&rsi);
                /* 执行bgsave过程 */
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

        /* 省略:Trigger an AOF rewrite if needed. */
    }
    /* 省略其他逻辑 */
}
🎜saveparams는 redis.conf 아래의 저장 규칙, 저장 매개변수는 Redis가 자동 백업을 트리거하는 트리거 전략이며, 는 통계 시간(단위: 초), 변경사항 통계 시간 내에 발생한 쓰기 횟수입니다. <code>save m n은 다음을 의미합니다. m초 내에 n번 쓰기하면 스냅샷, 즉 백업이 트리거됩니다. 다양한 조건에서 백업 요구 사항을 충족하도록 여러 그룹의 저장 매개변수를 구성할 수 있습니다. RDB의 자동 백업 정책을 꺼야 하는 경우 save ""를 사용하면 됩니다. 다음은 여러 구성에 대한 설명입니다. 🎜
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
    pid_t childpid;

    if (hasActiveChildProcess()) return C_ERR;

    server.dirty_before_bgsave = server.dirty;
    server.lastbgsave_try = time(NULL);

    // fork子进程
    if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
        int retval;

        /* Child 子进程:修改进程标题 */
        redisSetProcTitle("redis-rdb-bgsave");
        redisSetCpuAffinity(server.bgsave_cpulist);
        // 执行rdb持久化
        retval = rdbSave(filename,rsi);
        if (retval == C_OK) {
            sendChildCOWInfo(CHILD_TYPE_RDB, 1, "RDB");
        }
        // 持久化完成后,退出子进程
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        /* Parent 父进程:记录fork子进程的时间等信息*/
        if (childpid == -1) {
            server.lastbgsave_status = C_ERR;
            serverLog(LL_WARNING,"Can't save in background: fork: %s",
                strerror(errno));
            return C_ERR;
        }
        serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);
        // 记录子进程开始的时间、类型等。
        server.rdb_save_time_start = time(NULL);
        server.rdb_child_type = RDB_CHILD_TYPE_DISK;
        return C_OK;
    }
    return C_OK; /* unreached */
}
🎜serverCron RDB 저장 규칙에 대한 감지 코드는 다음과 같습니다. 🎜
# no-关闭,yes-开启,默认no
appendonly yes
appendfilename appendonly.aof
🎜백그라운드 RDB 지속성 또는 AOF 재작성 프로세스가 없는 경우 serverCron은 다음을 기반으로 합니다. 위의 구성 및 상태는 지속성 작업을 수행해야 하는지 여부를 결정하는 기준은 lastsave 및 dirty가 saveparams 배열의 조건 중 하나를 충족하는지 여부입니다. 조건이 일치하면 rdbSaveBackground 메서드가 호출되어 비동기 지속성 프로세스를 실행합니다. 🎜🎜rdbSaveBackground🎜🎜rdbSaveBackground는 RDB 지속성을 위한 보조 방법입니다. 주요 작업은 하위 프로세스를 분기하는 것이며 호출자(상위 프로세스 또는 하위 프로세스)에 따라 두 가지 다른 실행 논리가 있습니다. 🎜🎜🎜호출자가 상위 프로세스인 경우 하위 프로세스를 분기하고 하위 프로세스 정보를 저장한 후 직접 반환합니다. 🎜🎜호출자가 하위 프로세스인 경우 rdbSave를 호출하여 RDB 지속성 로직을 실행하고 지속성이 완료된 후 하위 프로세스를 종료합니다. 🎜🎜
set number 0
incr number
incr number
incr number
incr number
incr number
🎜rdbSave는 실행 중에 많은 수의 I/O 및 계산 작업이 필요하므로 Redis의 단일 스레드 모델에서는 시간이 많이 걸리고 CPU를 많이 차지합니다. 지속성 프로세스는 계속해서 스레드 리소스를 점유하게 되며, 이로 인해 Redis는 다른 서비스를 제공할 수 없게 됩니다. 이 문제를 해결하기 위해 Redis는 rdbSaveBackground에서 하위 프로세스를 분기하고 하위 프로세스는 상위 프로세스의 너무 많은 리소스를 차지하지 않고 지속성 작업을 완료합니다. 🎜🎜부모 프로세스가 차지하는 메모리가 너무 크면 포크 프로세스에 시간이 많이 걸리고 부모 프로세스가 외부 세계에 서비스를 제공할 수 없게 된다는 점에 유의해야 합니다. 또한 컴퓨터 메모리 사용량도 늘어납니다. 자식 프로세스를 포크한 후에는 메모리 리소스를 두 배로 차지하게 되므로 메모리가 충분한지 확인해야 합니다. info stats 명령어를 통해 late_fork_usec 옵션을 확인하면 최근 포크 작업에 걸린 시간을 확인할 수 있습니다. 🎜

rdbSave

Redis的rdbSave函数是真正进行RDB持久化的函数,流程、细节贼多,整体流程可以总结为:创建并打开临时文件、Redis内存数据写入临时文件、临时文件写入磁盘、临时文件重命名为正式RDB文件、更新持久化状态信息(dirty、lastsave)。其中“Redis内存数据写入临时文件”最为核心和复杂,写入过程直接体现了RDB文件的文件格式,本着一图胜千言的理念,我按照源码流程绘制了下图。
Redis 심층 학습의 지속성 원리에 대한 자세한 설명
补充说明一下,上图右下角“遍历当前数据库的键值对并写入”这个环节会根据不同类型的Redis数据类型及底层数据结构采用不同的格式写入到RDB文件中,不再展开了。我觉得大家对整个过程有个直观的理解就好,这对于我们理解Redis内部的运作机制大有裨益。

AOF持久化

上一节我们知道RDB是一种时间点(point-to-time)快照,适合数据备份及灾难恢复,由于工作原理的“先天性缺陷”无法保证实时性持久化,这对于缓存丢失零容忍的系统来说是个硬伤,于是就有了AOF。

AOF工作原理

AOF是Append Only File的缩写,它是Redis的完全持久化策略,从1.1版本开始支持;这里的file存储的是引起Redis数据修改的命令集合(比如:set/hset/del等),这些集合按照Redis Server的处理顺序追加到文件中。当重启Redis时,Redis就可以从头读取AOF中的指令并重放,进而恢复关闭前的数据状态。

AOF持久化默认是关闭的,修改redis.conf以下信息并重启,即可开启AOF持久化功能。

# no-关闭,yes-开启,默认no
appendonly yes
appendfilename appendonly.aof

AOF本质是为了持久化,持久化对象是Redis内每一个key的状态,持久化的目的是为了在Reids发生故障重启后能够恢复至重启前或故障前的状态。相比于RDB,AOF采取的策略是按照执行顺序持久化每一条能够引起Redis中对象状态变更的命令,命令是有序的、有选择的。把aof文件转移至任何一台Redis Server,从头到尾按序重放这些命令即可恢复如初。举个例子:

首先执行指令set number 0,然后随机调用incr numberget number 各5次,最后再执行一次get number ,我们得到的结果肯定是5。

因为在这个过程中,能够引起number状态变更的只有set/incr类型的指令,并且它们执行的先后顺序是已知的,无论执行多少次get都不会影响number的状态。所以,保留所有set/incr命令并持久化至aof文件即可。按照aof的设计原理,aof文件中的内容应该是这样的(这里是假设,实际为RESP协议):

set number 0
incr number
incr number
incr number
incr number
incr number

最本质的原理用“命令重放”四个字就可以概括。但是,考虑实际生产环境的复杂性及操作系统等方面的限制,Redis所要考虑的工作要比这个例子复杂的多:

  • Redis Server启动后,aof文件一直在追加命令,文件会越来越大。文件越大,Redis重启后恢复耗时越久;文件太大,转移工作就越难;不加管理,可能撑爆硬盘。很显然,需要在合适的时机对文件进行精简。例子中的5条incr指令很明显的可以替换为为一条set命令,存在很大的压缩空间。
  • 众所周知,文件I/O是操作系统性能的短板,为了提高效率,文件系统设计了一套复杂的缓存机制,Redis操作命令的追加操作只是把数据写入了缓冲区(aof_buf),从缓冲区到写入物理文件在性能与安全之间权衡会有不同的选择。
  • 文件压缩即意味着重写,重写时即可依据已有的aof文件做命令整合,也可以先根据当前Redis内数据的状态做快照,再把存储快照过程中的新增的命令做追加。
  • aof备份后的文件是为了恢复数据,结合aof文件的格式、完整性等因素,Redis也要设计一套完整的方案做支持。

持久化流程

从流程上来看,AOF的工作原理可以概括为几个步骤:命令追加(append)、文件写入与同步(fsync)、文件重写(rewrite)、重启加载(load),接下来依次了解每个步骤的细节及背后的设计哲学。
Redis 심층 학습의 지속성 원리에 대한 자세한 설명

Commandappend

AOF 지속성 기능이 켜져 있으면 Redis가 쓰기 명령을 실행한 후 실행된 쓰기 명령을 프로토콜 형식(즉, RESP, Redis 클라이언트와 서버 간의 상호 작용을 위한 통신 프로토콜)으로 추가합니다. Redis 서버에서 유지 관리하는 AOF 버퍼의 끝에 추가됩니다. AOF 파일은 단일 스레드 추가 작업만 수행되며, 정전이나 다운타임이 발생하더라도 파일이 손상될 위험이 없습니다. 또한 텍스트 프로토콜을 사용하면 많은 이점이 있습니다.

  • 텍스트 프로토콜은 호환성이 좋습니다.
  • 텍스트 프로토콜은 클라이언트 요청 명령이며 보조 처리가 필요하지 않아 저장 및 로드 중 처리 오버헤드가 절약됩니다. 보기, 수정 및 기타 처리에 읽기 쉽고 편리합니다.
  • AOF 버퍼 유형은 Redis가 독립적으로 설계한 데이터 구조 sds입니다. Redis는 명령 유형(catAppendOnlyGenericCommand, catAppendOnlyExpireAtCommand etc.) 명령 내용을 처리하고 마지막으로 버퍼에 씁니다.

명령이 추가될 때 AOF 재작성이 진행 중이면 이러한 명령도 재작성 버퍼(aof_rewrite_buffer)에 추가된다는 점에 유의해야 합니다. sds,Redis会根据命令的类型采用不同的方法(catAppendOnlyGenericCommandcatAppendOnlyExpireAtCommand等)对命令内容进行处理,最后写入缓冲区。

需要注意的是:如果命令追加时正在进行AOF重写,这些命令还会追加到重写缓冲区(aof_rewrite_buffer)。

文件写入与同步

AOF文件的写入与同步离不开操作系统的支持,开始介绍之前,我们需要补充一下Linux I/O缓冲区相关知识。硬盘I/O性能较差,文件读写速度远远比不上CPU的处理速度,如果每次文件写入都等待数据写入硬盘,会整体拉低操作系统的性能。为了解决这个问题,操作系统提供了延迟写(delayed write)机制来提高硬盘的I/O性能。
Redis 심층 학습의 지속성 원리에 대한 자세한 설명

传统的UNIX实现在内核中设有缓冲区高速缓存或页面高速缓存,大多数磁盘I/O都通过缓冲进行。 当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时, 再将该缓冲排入到输出队列,然后待其到达队首时,才进行实际的I/O操作。这种输出方式就被称为延迟写。

延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写到磁盘上。当系统发生故障时,这种延迟可能造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数为强制写入硬盘提供支持。

Redis每次事件轮训结束前(beforeSleep)都会调用函数flushAppendOnlyFileflushAppendOnlyFile会把AOF缓冲区(aof_buf)中的数据写入内核缓冲区,并且根据appendfsync配置来决定采用何种策略把内核缓冲区中的数据写入磁盘,即调用fsync()。该配置有三个可选项alwaysnoeverysec,具体说明如下:

  • always:每次都调用fsync(),是安全性最高、性能最差的一种策略。
  • no:不会调用fsync()。性能最好,安全性最差。
  • everysec:仅在满足同步条件时调用fsync()。这是官方建议的同步策略,也是默认配置,做到兼顾性能和数据安全性,理论上只有在系统突然宕机的情况下丢失1秒的数据。

注意:上面介绍的策略受配置项no-appendfsync-on-rewrite的影响,它的作用是告知Redis:AOF文件重写期间是否禁止调用fsync(),默认是no。

如果appendfsync设置为alwayseverysec,后台正在进行的BGSAVE或者BGREWRITEAOF消耗过多的磁盘I/O,在某些Linux系统配置下,Redis对fsync()的调用可能阻塞很长时间。然而这个问题还没有修复,因为即使是在不同的线程中执行fsync(),同步写入操作也会被阻塞。

为了缓解此问题,可以使用该选项,以防止在进行BGSAVEBGREWRITEAOF

파일 쓰기 및 동기화🎜🎜 AOF 파일의 쓰기 및 동기화는 운영 체제의 지원과 분리될 수 없습니다. 소개를 시작하기 전에 Linux I/O 버퍼에 대한 몇 가지 지식을 추가해야 합니다. 하드디스크의 I/O 성능이 좋지 않고, 파일을 쓸 때마다 하드디스크에 데이터가 쓰일 때까지 기다리면 파일 읽기/쓰기 속도가 CPU의 처리 속도에 비해 많이 떨어진다. 운영 체제의 전반적인 성능이 저하됩니다. 이 문제를 해결하기 위해 운영 체제에서는 지연된 쓰기 메커니즘을 제공하여 하드 디스크의 I/O 성능을 향상시킵니다.
Redis 심층 학습의 지속성 원리에 대한 자세한 설명🎜
기존 UNIX 구현에는 커널에 버퍼 캐시 또는 페이지 캐시가 있습니다. 대부분의 디스크 I/ O는 버퍼링을 통해 수행됩니다. 파일에 데이터를 쓸 때 커널은 일반적으로 버퍼 중 하나에 먼저 데이터를 복사합니다. 버퍼가 아직 가득 차지 않으면 출력 큐에 넣지 않고 채워질 때까지 기다립니다. 다른 디스크 블록 데이터를 저장하기 위해 버퍼를 재사용하는 경우 버퍼는 출력 큐에 대기하다가 큐의 선두에 도달한 경우에만 실제 I/O 작업이 수행됩니다. 이 출력 방법을 지연 쓰기라고 합니다.
🎜지연된 쓰기는 디스크 읽기 및 쓰기 횟수를 감소시키지만, 파일 내용의 업데이트 속도도 감소시키기 때문에 파일에 기록될 데이터가 일정 시간 동안 디스크에 기록되지 않습니다. 시스템 오류가 발생하면 이러한 지연으로 인해 파일 업데이트가 손실될 수 있습니다. 디스크의 실제 파일 시스템과 버퍼 캐시의 내용의 일관성을 보장하기 위해 UNIX 시스템은 하드 디스크에 강제 쓰기를 지원하는 sync, fsync 및 fdatasync의 세 가지 기능을 제공합니다. 🎜🎜Redis는 각 이벤트 회전이 끝나기 전에(beforeSleep) flushAppendOnlyFile 함수를 호출하고 flushAppendOnlyFile은 AOF 버퍼(aof_buf)를 호출합니다. )는 커널 버퍼에 기록되고, 커널 버퍼의 데이터를 디스크에 쓰는 데 사용되는 전략은 appendfsync 구성, 즉 fsync( ). 이 구성에는 <code>always, noeverysec의 세 가지 옵션이 있습니다. 구체적인 설명은 다음과 같습니다. 🎜🎜🎜always: 호출 fsync Every time()은 보안은 가장 높고 성능은 가장 나쁜 전략입니다. 🎜🎜no: fsync()가 호출되지 않습니다. 최고의 성능, 최악의 보안. 🎜🎜everysec: 동기화 조건이 충족되는 경우에만 fsync()를 호출합니다. 이는 공식적으로 권장되는 동기화 전략이자 기본 구성이기도 합니다. 이론적으로는 갑작스러운 시스템 종료 시 1초의 데이터만 손실됩니다. 🎜🎜🎜참고: 위에 소개된 전략은 no-appendfsync-on-rewrite 구성 항목의 영향을 받습니다. 해당 기능은 AOF 파일 재작성 중에 fsync() 호출을 금지할지 여부를 Redis에 알려주는 것입니다. . , 기본값은 아니오입니다. 🎜🎜appendfsyncalways 또는 everysec로 설정된 경우 BGSAVE 또는 가 진행 중입니다. 백그라운드에서 >BGREWRITEAOF는 너무 많은 디스크 I/O를 소비합니다. 특정 Linux 시스템 구성에서는 Redis의 fsync() 호출이 오랫동안 차단될 수 있습니다. 하지만 이 문제는 아직 수정되지 않았습니다. 다른 스레드에서 fsync()를 실행하더라도 동기 쓰기 작업이 차단되기 때문입니다. 🎜🎜이 문제를 완화하려면 이 옵션을 사용하여 BGSAVE 또는 BGREWRITEAOF를 수행할 때 기본 프로세스에서 fsync()가 호출되는 것을 방지할 수 있습니다. 🎜
  • 设置为yes意味着,如果子进程正在进行BGSAVEBGREWRITEAOF,AOF的持久化能力就与appendfsync设置为no有着相同的效果。最糟糕的情况下,这可能会导致30秒的缓存数据丢失。
  • 如果你的系统有上面描述的延迟问题,就把这个选项设置为yes,否则保持为no

文件重写

如前面提到的,Redis长时间运行,命令不断写入AOF,文件会越来越大,不加控制可能影响宿主机的安全。

为了解决AOF文件体积问题,Redis引入了AOF文件重写功能,它会根据Redis内数据对象的最新状态生成新的AOF文件,新旧文件对应的数据状态一致,但是新文件会具有较小的体积。重写既减少了AOF文件对磁盘空间的占用,又可以提高Redis重启时数据恢复的速度。还是下面这个例子,旧文件中的6条命令等同于新文件中的1条命令,压缩效果显而易见。
Redis 심층 학습의 지속성 원리에 대한 자세한 설명
我们说,AOF文件太大时会触发AOF文件重写,那到底是多大呢?有哪些情况会触发重写操作呢?
**
与RDB方式一样,AOF文件重写既可以手动触发,也会自动触发。手动触发直接调用bgrewriteaof命令,如果当时无子进程执行会立刻执行,否则安排在子进程结束后执行。自动触发由Redis的周期性方法serverCron检查在满足一定条件时触发。先了解两个配置项:

  • auto-aof-rewrite-percentage:代表当前AOF文件大小(aof_current_size)和上一次重写后AOF文件大小(aof_base_size)相比,增长的比例。
  • auto-aof-rewrite-min-size:表示运行BGREWRITEAOF时AOF文件占用空间最小值,默认为64MB;

Redis启动时把aof_base_size初始化为当时aof文件的大小,Redis运行过程中,当AOF文件重写操作完成时,会对其进行更新;aof_current_sizeserverCron执行时AOF文件的实时大小。当满足以下两个条件时,AOF文件重写就会触发:

增长比例:(aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage
文件大小:aof_current_size > auto-aof-rewrite-min-size

手动触发与自动触发的代码如下,同样在周期性方法serverCron中:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    /* 省略其他逻辑 */
    
    /* 如果用户请求进行AOF文件重写时,Redis正在执行RDB持久化,Redis会安排在RDB持久化完成后执行AOF文件重写,
     * 如果aof_rewrite_scheduled为true,说明需要执行用户的请求 */
    if (!hasActiveChildProcess() &&
        server.aof_rewrite_scheduled)
    {
        rewriteAppendOnlyFileBackground();
    }

    /* Check if a background saving or AOF rewrite in progress terminated. */
    if (hasActiveChildProcess() || ldbPendingChildren())
    {
        run_with_period(1000) receiveChildInfo();
        checkChildrenDone();
    } else {
        /* 省略rdb持久化条件检查 */

        /* AOF重写条件检查:aof开启、无子进程运行、增长百分比已设置、当前文件大小超过阈值 */
        if (server.aof_state == AOF_ON &&
            !hasActiveChildProcess() &&
            server.aof_rewrite_perc &&
            server.aof_current_size > server.aof_rewrite_min_size)
        {
            long long base = server.aof_rewrite_base_size ?
                server.aof_rewrite_base_size : 1;
            /* 计算增长百分比 */
            long long growth = (server.aof_current_size*100/base) - 100;
            if (growth >= server.aof_rewrite_perc) {
                serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
        }
    }
    /**/
}

AOF文件重写的流程是什么?听说Redis支持混合持久化,对AOF文件重写有什么影响?

从4.0版本开始,Redis在AOF模式中引入了混合持久化方案,即:纯AOF方式、RDB+AOF方式,这一策略由配置参数aof-use-rdb-preamble(使用RDB作为AOF文件的前半段)控制,默认关闭(no),设置为yes可开启。所以,在AOF重写过程中文件的写入会有两种不同的方式。当aof-use-rdb-preamble的值是:

  • no:按照AOF格式写入命令,与4.0前版本无差别;
  • yes:先按照RDB格式写入数据状态,然后把重写期间AOF缓冲区的内容以AOF格式写入,文件前半部分为RDB格式,后半部分为AOF格式。

结合源码(6.0版本,源码太多这里不贴出,可参考aof.c)及参考资料,绘制AOF重写(BGREWRITEAOF)流程图:
Redis 심층 학습의 지속성 원리에 대한 자세한 설명
结合上图,总结一下AOF文件重写的流程:

  • rewriteAppendOnlyFileBackground开始执行,检查是否有正在进行的AOF重写或RDB持久化子进程:如果有,则退出该流程;如果没有,则继续创建接下来父子进程间数据传输的通信管道。执行fork()操作,成功后父子进程分别执行不同的流程。
  • 父进程:

    • 记录子进程信息(pid)、时间戳等;
    • 继续响应其他客户端请求;
    • 收集AOF重写期间的命令,追加至aof_rewrite_buffer;
    • 等待并向子进程同步aof_rewrite_buffer的内容;
  • 子进程:

    • 현재 프로세스 이름을 수정하고, 다시 쓰기에 필요한 임시 파일을 생성하고, rewriteAppendOnlyFile 함수를 호출합니다.
    • aof-use-rdb-preamble 구성에 따라 전반부를 RDB로 작성하거나 AOF 모드 및 하드 디스크에 동기화 aof-use-rdb-preamble配置,以RDB或AOF方式写入前半部分,并同步至硬盘;
    • 从父进程接收增量AOF命令,以AOF方式写入后半部分,并同步至硬盘;
    • 重命名AOF文件,子进程退出。

数据加载

Redis启动后通过loadDataFromDisk函数执行数据加载工作。这里需要注意,虽然持久化方式可以选择AOF、RDB或者两者兼用,但是数据加载时必须做出选择,两种方式各自加载一遍就乱套了。

理论上,AOF持久化比RDB具有更好的实时性,当开启了AOF持久化方式,Redis在数据加载时优先考虑AOF方式。而且,Redis 4.0版本后AOF支持了混合持久化,加载AOF文件需要考虑版本兼容性。Redis数据加载流程如下图所示:
Redis 심층 학습의 지속성 원리에 대한 자세한 설명
在AOF方式下,开启混合持久化机制生成的文件是“RDB头+AOF尾”,未开启时生成的文件全部为AOF格式。考虑两种文件格式的兼容性,如果Redis发现AOF文件为RDB头,会使用RDB数据加载的方法读取并恢复前半部分;然后再使用AOF方式读取并恢复后半部分。由于AOF格式存储的数据为RESP协议命令,Redis采用伪客户端执行命令的方式来恢复数据。

如果在AOF命令追加过程中发生宕机,由于延迟写的技术特点,AOF的RESP命令可能不完整(被截断)。遇到这种情况时,Redis会按照配置项aof-load-truncated

상위 프로세스에서 증분 AOF 명령을 수신하고 나머지 절반을 AOF 모드에 쓴 다음 하드 디스크에 동기화합니다.
  • AOF 파일 이름을 바꾸면 하위 프로세스가 종료됩니다.

Data loading

Redis가 시작된 후 loadDataFromDisk 함수를 통해 데이터 로딩 작업이 수행됩니다. 지속성 방법은 AOF, RDB 또는 둘 다일 수 있지만 데이터를 로드할 때 두 가지 방법을 별도로 로드하면 혼란이 발생한다는 점에 유의해야 합니다.

이론적으로 AOF 지속성은 RDB보다 실시간 성능이 더 좋습니다. AOF 지속성을 활성화하면 Redis는 데이터를 로드할 때 AOF에 우선순위를 부여합니다. 또한 Redis 4.0 버전 이후에는 AOF가 하이브리드 지속성을 지원하므로 AOF 파일을 로드할 때 버전 호환성을 고려해야 합니다. Redis 데이터 로딩 프로세스는 아래 그림에 나와 있습니다.
Redis 심층 학습의 지속성 원리에 대한 자세한 설명
AOF 모드에서 하이브리드를 활성화합니다. 지속성 메커니즘 생성된 파일은 "RDB 헤더 + AOF 테일"입니다. 활성화되지 않은 경우 생성된 모든 파일은 AOF 형식입니다. 두 파일 형식의 호환성을 고려하여 Redis가 AOF 파일이 RDB 헤더임을 발견하면 RDB 데이터 로딩 방법을 사용하여 전반부를 읽고 복원한 다음 AOF 방법을 사용하여 후반부를 읽고 복원합니다. . AOF 형식으로 저장된 데이터는 RESP 프로토콜 명령이므로 Redis는 의사 클라이언트를 사용하여 명령을 실행하여 데이터를 복구합니다.

🎜AOF 명령을 추가하는 과정에서 다운타임이 발생하면 쓰기 지연의 기술적 특성으로 인해 AOF의 RESP 명령이 불완전(잘림)될 수 있습니다. 이러한 상황이 발생하면 Redis는 aof-load-truncated 구성 항목에 따라 다양한 처리 전략을 실행합니다. 이 구성은 Redis가 시작할 때 aof 파일을 읽도록 지시하고, 파일이 잘린(불완전) 것으로 발견되면 어떻게 해야 하는지 알려줍니다. 🎜🎜🎜yes: 가능한 한 많은 데이터를 로드하고 로그 형식으로 사용자에게 알립니다. 🎜🎜no : 그러면 시스템 오류로 인해 충돌이 발생하여 시작할 수 없습니다. 사용자는 파일을 복구한 후 다시 시작해야 합니다. 🎜🎜🎜Summary🎜🎜Redis는 두 가지 지속성 옵션을 제공합니다. RDB는 특정 실제 간격으로 데이터 세트에 대한 특정 시점 스냅샷 생성을 지원합니다. AOF는 Redis 서버에서 수신한 각 쓰기 명령을 로그에 유지합니다. 재부팅. 로그 형식은 RESP 프로토콜이며 로그 파일에 추가 작업만 수행되므로 손상될 위험이 없습니다. AOF 파일이 너무 크면 압축 파일을 자동으로 다시 작성할 수 있습니다. 🎜🎜물론 데이터를 유지할 필요가 없다면 Redis의 지속성 기능을 비활성화할 수도 있지만 대부분의 경우 그렇지 않습니다. 실제로 RDB와 AOF를 동시에 사용하는 경우가 있는데, 가장 중요한 것은 RDB와 AOF를 합리적으로 사용하기 위해서는 둘의 차이점을 이해하는 것입니다. 🎜

RDB 대 AOF

RDB 장점

  • RDB는 특정 시점의 Redis 데이터 스냅샷을 나타내는 압축된 압축 바이너리 파일로 백업, 전체 복제 및 기타 시나리오에 매우 적합합니다.
  • RDB는 재해 복구 및 데이터 마이그레이션에 매우 친숙합니다. RDB 파일은 필요한 곳으로 이동하고 다시 로드할 수 있습니다.
  • RDB는 Redis 데이터의 메모리 스냅샷으로, AOF 명령 재생보다 더 빠른 데이터 복구와 더 높은 성능을 제공합니다.

RDB 단점

  • RDB 방식은 실시간 또는 2단계 지속성을 달성할 수 없습니다. 자식 프로세스를 포크한 후 자식 프로세스에 의해 지속성 프로세스가 완료되기 때문에 자식 프로세스의 메모리는 포크 작업 이후 부모 프로세스의 데이터에 대한 스냅샷일 뿐입니다. 외부 세계에 서비스를 제공하고 내부 데이터는 항상 변경되며 데이터는 더 이상 업데이트되지 않으며 둘 사이에는 항상 차이가 있으므로 실시간 성능을 달성할 수 없습니다.
  • RDB 지속성 프로세스 중 포크 작업은 메모리 사용량을 두 배로 늘리고 상위 프로세스에 데이터가 많을수록 포크 프로세스가 길어집니다.
  • Redis 요청의 높은 동시성은 자주 저장 규칙에 도달하여 포크 작업 및 영구 백업의 빈도를 제어할 수 없게 만들 수 있습니다.
  • RDB 파일에는 파일 형식 요구 사항이 있으며 다양한 버전의 Redis가 파일 형식을 조정하며 일부 이전 버전도 있습니다. 버전이 호환되지 않습니다. 새 버전에 문제가 있습니다.

AOF 장점

  • AOF 지속성은 더 나은 실시간 성능을 제공하며 세 가지 다른 방법(appendfsync)을 선택할 수 있습니다. 아니요, 매초, 항상, 매초 기본 전략이 최고의 성능을 가지므로 극단적인 경우 1초의 데이터가 손실될 수 있습니다.
  • AOF 파일에는 추가 작업만 있고 복잡한 검색 및 기타 파일 작업이 없으며 손상 위험이 없습니다. 마지막으로 작성된 데이터가 잘려도 redis-check-aof 도구를 사용하여 쉽게 복구할 수 있습니다. AOF 파일이 커지면 Redis가 백그라운드에서 자동으로 다시 쓸 수 있습니다. 다시 쓰는 동안 이전 파일은 계속해서 쓰여집니다. 다시 쓰기가 완료된 후에는 새 파일의 크기가 작아지고 다시 쓰는 동안의 증분 명령도 새 파일에 추가됩니다.
  • AOF 파일에는 Redis의 데이터에 대한 모든 작업 명령이 이해하기 쉽고 구문 분석하기 쉬운 방식으로 포함되어 있습니다. 실수로 모든 데이터를 삭제하더라도 AOF 파일을 다시 쓰지 않는 한 마지막 명령을 제거하면 모든 데이터를 복구할 수 있습니다.
  • AOF는 이미 하이브리드 지속성을 지원하고 파일 크기를 효과적으로 제어할 수 있으며 데이터 로딩 효율성이 향상됩니다.
  • AOF 단점

동일한 데이터 수집의 경우 AOF 파일은 일반적으로 RDB 파일보다 큽니다.
  • 특정 fsync 전략에서 AOF는 RDB보다 약간 느립니다. 일반적으로 fsync_every_second의 성능은 여전히 ​​매우 높으며 fsync_no의 성능은 RDB와 비슷합니다. 그러나 엄청난 쓰기 압력 하에서 RDB는 가장 낮은 지연 시간을 보장할 수 있습니다.
  • AOF에서 Redis는 RDB에서는 거의 만날 수 없는 희귀한 버그를 발견한 적이 있습니다. 일부 특수 명령(예: BRPOPLPUSH)으로 인해 다시 로드된 데이터가 지속성 이전의 데이터와 일치하지 않게 되었습니다. Redis 관계자는 동일한 조건에서 테스트했지만 문제를 재현할 수 없었습니다.
  • 사용 제안

RDB와 AOF의 두 가지 지속성 방법의 작동 원리, 실행 프로세스, 장단점을 이해한 후 실제 시나리오에서 장단점을 가늠하고 두 가지 지속성 방법을 합리적으로 사용하는 방법에 대해 생각해 보겠습니다. Redis를 캐싱 도구로만 사용하는 경우 영구 데이터베이스를 기반으로 모든 데이터를 재구성할 수 있으며, 지속성 기능을 끄고 예열, 캐시 침투, 고장, 눈사태 등의 보호 작업을 수행할 수 있습니다.

일반적인 상황에서 Redis는 분산 잠금, 순위, 등록 센터 등과 같은 더 많은 작업을 수행합니다. 지속성 기능은 재해 복구 및 데이터 마이그레이션에서 더 큰 역할을 합니다. 다음과 같은 몇 가지 원칙을 따르는 것이 좋습니다.

Redis를 데이터베이스로 사용하지 마십시오. 모든 데이터는 가능한 한 애플리케이션 서비스에 의해 자동으로 재구성됩니다.
  • Redis 버전 4.0 이상을 사용하고 AOF+RDB 하이브리드 지속성 기능을 사용하세요.
  • AOF 재작성 또는 저장 중에 리소스 부족을 방지하기 위해 Redis가 차지하는 최대 메모리를 적절하게 계획하세요.
  • 단일 머신에 여러 인스턴스를 배포하지 마세요.
  • 대부분의 프로덕션 환경은 클러스터에 배포됩니다. 슬레이브에서 지속성 기능을 활성화하여 마스터가 외부 쓰기 서비스를 더 잘 제공할 수 있습니다.
  • 재난 백업에 대비해 백업 파일은 자동으로 외부 전산실이나 클라우드 스토리지에 업로드해야 합니다.
  • fork()에 대하여

위의 분석을 통해 우리 모두는 RDB 스냅샷과 AOF 재작성에는 포크가 필요하다는 것을 알고 있습니다. 이는 매우 중요한 작업이며 Redis를 차단하게 됩니다. 따라서 Redis 메인 프로세스의 응답에 영향을 주지 않기 위해서는 블로킹을 최대한 줄여야 합니다.

포크 빈도를 줄입니다. 예를 들어 RDB를 수동으로 실행하여 스냅샷 및 AOF 재작성을 생성할 수 있습니다.
  • 포크가 너무 오래 걸리지 않도록 Redis의 최대 메모리 사용량을 제어합니다.
  • 고성능 하드웨어를 사용합니다.
  • 제대로 구성된 Linux의 메모리 할당 전략은 물리적 메모리 부족으로 인한 포크 실패를 방지합니다.
  • 더 많은 프로그래밍 관련 지식을 보려면
  • 프로그래밍 소개
를 방문하세요! !

위 내용은 Redis 심층 학습의 지속성 원리에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제