Redis による永続的なソース コード分析

この記事では、 redis 5.0.7 のソース コードに基づいてaof 永続性を分析します。

Redis の aof 永続化メソッドには 2 つの重要なコンポーネントがあります

  1. インクリメンタル書き込みコマンドをディスクに同期する
  2. aofファイルの完全な書き換え

1. 増分同期

1. インクリメンタル書き込みコマンドがバッファに追加される

Redis にはバッファがあり、ディスクに書き込まれていないコマンドはバッファに格納され、条件が満たされたときにディスクに書き込まれます。

struct redisServer {
	// sds 是redis定义的char数组
	sds aof_buf;      /* AOF buffer, written before entering the event loop */
}

Redis は各書き込み操作を実行した後、伝播関数を呼び出して書き込み操作を aof_buf バッファーに追加します。

void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags)
{
    if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
    	// aof功能打开的前提下,把新的追加aof_buffer
        feedAppendOnlyFile(cmd,dbid,argv,argc);
    if (flags & PROPAGATE_REPL)
        replicationFeedSlaves(server.slaves,dbid,argv,argc);
}

2. バッファデータがディスクにフラッシュされます

ディスクへの aof バッファ同期には 3 つの戦略があります

appendfsync always // 書き込みコマンドごとに 1 回同期します
appendfsync Everysec // 1 秒ごとに 1 回同期します
appendfsync no // 手動同期は行わず、同期のタイミングはオペレーティング システムによって決定されます

注: appendfsync no 戦略は、バッファ内のデータをオペレーティング システムのカーネル バッファに書き込むための write 関数のみを呼び出します。カーネル バッファ内のデータがいつディスクに書き込まれるかについては、オペレーティング システムによって決定されます。

毎秒同期するのは妥協策です。同期には、ユーザー状態とオペレーティング システムのコア状態の間の切り替えを伴うシステム関数 fsync を呼び出す必要がありますが、同時に実際のディスク IO がここで発生し、パフォーマンスがより多く消費されます。

flashAppendOnlyFile を呼び出して、Redis のservercron サイクルでデータを同期します。

void flushAppendOnlyFile(int force) {
    ssize_t nwritten;
    int sync_in_progress = 0;
    mstime_t latency;
	... ... 
    latencyStartMonitor(latency);
    // 调用write函数将aof_buf的数据写入文件的内核缓冲区
    nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
    latencyEndMonitor(latency);
   
   ... ...

try_fsync:
    /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
     * children doing I/O in the background. */
    if (server.aof_no_fsync_on_rewrite &&
        (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
            return;

    /* Perform the fsync if needed. */
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
        /* redis_fsync is defined as fdatasync() for Linux in order to avoid
         * flushing metadata. */
        latencyStartMonitor(latency);
        // 直接调用fsync函数将内核缓冲区的数据写入磁盘
        redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
        latencyEndMonitor(latency);
        latencyAddSampleIfNeeded("aof-fsync-always",latency);
        server.aof_fsync_offset = server.aof_current_size;
        server.aof_last_fsync = server.unixtime;
    } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
                server.unixtime > server.aof_last_fsync)) {
        if (!sync_in_progress) {
        	// 创建后台线程,在后台线程里调用sync函数将内核缓冲区的数据写入磁盘
            aof_background_fsync(server.aof_fd);
            server.aof_fsync_offset = server.aof_current_size;
        }
        server.aof_last_fsync = server.unixtime;
    }
}

1 秒に 1 回同期すると、スレッドが作成され、スレッド内で fsync が呼び出されてデータが同期されます。

/* Starts a background task that performs fsync() against the specified
 * file descriptor (the one of the AOF file) in another thread. */
void aof_background_fsync(int fd) {
    bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
}

バックグラウンド スケジューリング スレッドはタスク キューからタスクを取得して実行します。

void *bioProcessBackgroundJobs(void *arg) {
    struct bio_job *job;
    unsigned long type = (unsigned long) arg;
    sigset_t sigset;

    while(1) {
        listNode *ln;
		
		// 从任务队列中取出位于队首的任务
        ln = listFirst(bio_jobs[type]);
        job = ln->value;
        /* It is now possible to unlock the background system as we know have
         * a stand alone job structure to process.*/
        pthread_mutex_unlock(&bio_mutex[type]);

       // 根据任务类型执行任务
        if (type == BIO_CLOSE_FILE) {
        	// 关闭文件
            close((long)job->arg1);
        } else if (type == BIO_AOF_FSYNC) {
        	// 执行fsync同步数据
            redis_fsync((long)job->arg1);
        } else if (type == BIO_LAZY_FREE) {
            if (job->arg1)
                lazyfreeFreeObjectFromBioThread(job->arg1);
            else if (job->arg2 && job->arg3)
                lazyfreeFreeDatabaseFromBioThread(job->arg2,job->arg3);
            else if (job->arg3)
                lazyfreeFreeSlotsMapFromBioThread(job->arg3);
        } else {
            serverPanic("Wrong job type in bioProcessBackgroundJobs().");
        }
        // 释放存放任务的对象
        zfree(job);

        pthread_mutex_lock(&bio_mutex[type]);
        // 删除任务队列队首元素
        listDelNode(bio_jobs[type],ln);
        bio_pending[type]--;

        /* Unblock threads blocked on bioWaitStepOfType() if any. */
        pthread_cond_broadcast(&bio_step_cond[type]);
    }
}

多くの投稿では、redis はシングルスレッドであると書かれていますが、実際はそうではありません。Redis はマルチプロセスおよびマルチスレッドであり、単一スレッドで読み取りおよび書き込みコマンドのみを実行します。
ここに画像の説明を挿入

write システムコールと fsync の違い

write システム コールでは、データが最終的にディスクに書き込まれることは保証されません。マシンの電源がオフで、カーネル バッファ内のデータがディスクに書き込まれなかった場合、データは失われます。したがって、write を呼び出すだけでは十分ではありません。
ここに画像の説明を挿入
Redis の aof メカニズムと同様に、MySQL も書き込みコマンドを binlog に追加します。mysql のマスターに対してスレーブを構成する場合、スレーブはマスターのデータと一貫性を保つためにマスターの binlog を使用してデータを復元します。

2. aof ファイルを完全に書き換えます

プロセスを完全に書き直す

ここに画像の説明を挿入

完全同期が必要な理由

書き込み操作が進むにつれて、aof ファイルはますます大きくなり、同時に大量の冗長データが存在します。このような一連の操作があるとします

set key value1
set key value2
set key value3 
... ...
set key valueN

その後、aof ファイルを保存するだけで済み、set key valueNこのコマンドはデータベースの復元に使用されます。

オーバーライドされたトリガー

auto-aof-rewrite-percentage 100  // 当前文件超过上次同步后文件百分比
auto-aof-rewrite-min-size 64mb  // 重写的文件最小大小

serverCronビッグイベントループ内で、書き換え条件を満たすかどうかを判定

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
		... ...
		
        /* Trigger an AOF rewrite if needed. */
        if (server.aof_state == AOF_ON &&
            server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            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();
            }
        }
        ... ...
        
}

子プロセスでは、redis はデータベース内のすべてのキーと値のペアを対応する形式のコマンドに変換し、それらを新しい aof ファイルに書き込みます。ソースコードには補助金が支払われており、基本的にはこのプロセスです。

おすすめ

転載: blog.csdn.net/bruce128/article/details/104579288
おすすめ