Redisのデータ永続化戦略(RDB)

Redisのは、すべてのデータは、Redisのプロセスが予期せず終了するので、一度、メモリに直接格納、またはサーバー自体の異常なダウンタイム、我々はRedisの中でデータを保存するには、突然、どこにも見つからないことが、消えることですされ、インメモリデータベースです。

Redisのは、優れたデータミドルウェアとして、独自の永続的なデータバックアップメカニズムを持たなければならない場合は、そこディスクにメモリに格納されたデータをバックアップするための2つの主要な持続性戦略があり、そしてサーバーを再起動のRedisバックアップファイルのリロード。

RDBとAOFは二つの内部Redisのデータの永続化戦略が、これはメモリのスナップショットに基づいて2つの異なる永続化戦略、一つであり、一つは操作ログに基づいており、BenpianこのRDBについて何かを言うために最初に基づいて、持続性戦略のメモリのスナップショット。

まず、RDBの永続化戦略とは何か

RDB(Redisのデータベース)、スナップショットの永続戦略。RDBは、Redisのデフォルトの永続化戦略である、あなたはredis.confを開くことができ、次の3つのデフォルト設定が表示されます。

画像

save 900 1            900秒内执行一次set操作 则持久化1次  
save 300 10           300秒内执行10次set操作,则持久化1次
save 60 10000         60秒内执行10000次set操作,则持久化1次
复制代码

RDBは、2分割され、1は同期で、saveコマンドRDBファイル生成バックアップRedisのトリガすることができます呼び出すが、これは、バックアップが完了する前に、同期コマンドでサーバーがどのクライアントの要求に応答しないのRedis。他には、非同期、コールbgsaveコマンドで、子プロセスフォークサーバーをRedisのと同時に、メインプロセスはまだすることができ、クライアントの要求に応答し、RDBファイルのバックアップの生成を行いました。

明らかに、非同期RDB生成主流の戦略は、いくつかの特別な場合を除いて、私は誰もコマンドは、本番環境でRDBファイルを生成するために、Redisのサービスをブロックされて保存されませんと信じています。

私たちは手動でクライアントにトリガーに要求を送信するために、我々はアクティブなトリガーを呼び出す必要があるこれら二つのコマンド、保存してbgsave、上記の2つのコマンドを紹介します。

そして、私たちは急いでいくつかの受動的な構成をトリガし、我々は受動的なトリガーと呼ばれるトリガー設定、前に導入、のは、見てみましょう。

1、設定を保存します

設定は非常に重要な構成で保存、それが非同期RDBのバックアップファイルが生成されbgsave状況が自動的に起動するものの下でのRedisサーバーが装備されています。

基本的な構文:

save <seconds> <changes>
复制代码

秒以内のRedisデータベースは、データベースのキーは時間の変更を生じたときは、コールbgsaveコマンドをトリガします。

2、dbfilename設定

dbfilename設定項目は、生成されたRDBファイル名、dump.rdbのデフォルト設定を決定します。

dbfilename dump.rdb
复制代码

3、rdbcompression構成

rdbcompression構成は、RDBファイル圧縮有効に設定、基本的な構文は次のとおりです。

rdbcompression yes(|no)
复制代码

rdbcompressionがyesに設定されている場合、その文字列の値は、20以上のバイトを占める、請求それが文字列オブジェクトに遭遇した場合RDBファイルの生成のために表すRedisの次に列LZF圧縮アルゴリズムであろう。

4、ストップ書き込みオンbgsaveエラー配置

Redisのを書き込みを停止するかどうかをストップ書き込みオンbgsaveエラーコンフィギュレーション、およびRDBバックアップファイル生成プロセス場合は、エラーが発生しました、バックアップRDBの例外にユーザーに警告するためのサービスを提供するには、デフォルト状態で有効になっています。

stop-writes-on-bgsave-error yes(|no)
复制代码

5、DIR設定

DIRの設定は、RDBファイル格納ディレクトリで、デフォルトは現在のディレクトリです。

dir ./
复制代码

6、rdbchecksum構成

rdbchecksum設定では、選択的にオフにすることができ、パフォーマンスを向上させるために必要がある場合は、損傷していないCRC64チェックサムアルゴリズムチェックRDBファイル、上のデフォルトを使用するかどうかのRedis。

rdbchecksum yes(|no)
复制代码

二、saveparamsと汚いカウンター

我々は2つの分野で非常にredisServer構造を有しています。

画像

次のようにsaveparams構造が定義されています。

struct saveparam {
    time_t seconds;  //秒数
    int changes;    //变更次数
};
复制代码

私は2つのパラメータのプロファイルに対応し、上記の構成の保存、データベースの変更は、何秒でbgsaveをトリガーする回数を発生して、あなたが考えることができると信じています。

我々saveparamは、コードの構造、構成を保存構造saveparamに各行相当にマッピングされ、最終的にアレイ状にredisServer saveparamに読み込まれます。

PSは:このプレゼンテーションは、現在、私たちは、RDBが後でソースコードファイルの生成を分析達成するための方法の事前舗装を行っています。

また、redisServerデータ構造は、非常に多くの2つのフィールドがあります。

画像

保存とbgsaveコマンドを含む最後の正常なバックアップRDBファイルから汚れたフィールドのレコードは、全体のRedisデータベースの変更が行われ、多くの時間を要しました。最後bgsaveコマンドは、データベースの変更の総数をバックアップするときdirty_before_bgsaveフィールドを理解することができます。

持続フィールドに関連付けられているいくつかの時間、RDB時間の最後に成功したバックアップのポイント、コマンドの実行開始時刻bgsave前回、というように。

画像

ここで我々はまた、ソースコードを貼り付ける貼り付け分析RDBのバックアップファイルが生成されRedisの方法を参照してください。

int serverCron(....){
    .....
    //如果已经有子进程在执行 RDB 生成,或者 AOF 恢复,或者有子进程未返回
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {
        int statloc;
        pid_t pid;
        //查看这个进程是否返回信号
        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            int exitcode = WEXITSTATUS(statloc);
            int bysignal = 0;

            if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
            //持久化异常,打印日志
            if (pid == -1) {
                serverLog(LL_WARNING,"wait3() returned an error: %s. "
                    "rdb_child_pid = %d, aof_child_pid = %d",
                    strerror(errno),
                    (int) server.rdb_child_pid,
                    (int) server.aof_child_pid);
            } else if (pid == server.rdb_child_pid) {
                //成功持久化 RDB 文件,调用方法用心的RDB文件覆盖旧的RDB文件
                backgroundSaveDoneHandler(exitcode,bysignal);
                if (!bysignal && exitcode == 0) receiveChildInfo();
            } else if (pid == server.aof_child_pid) {
                //成功执行 AOF,替换现有的 AOF文件
                backgroundRewriteDoneHandler(exitcode,bysignal);
                if (!bysignal && exitcode == 0) receiveChildInfo();
            } else {
                //子进程成功,但返回的 pid 类型异常,无法匹配
                if (!ldbRemoveChild(pid)) {
                    serverLog(LL_WARNING,
                        "Warning, detected child with unmatched pid: %ld",
                        (long)pid);
                }
            }
            //如果子进程未结束,不允许字典进行 rehash
            updateDictResizePolicy();
            closeChildInfoPipe();
        }
    } else{.......}
}
复制代码

serverCronは(可能なフォローアップRedisのバージョンは、物品4.0に基づいて、異なる)、RDBやAOF子プロセスが成功した判断は初めてとなるごとに百ミリ秒後に実行され、成功した場合は、古いファイル操作を置き換えるカバーされます。私たちは、他のセクションを見続けています。

int serverCron(....){
    .....
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {
        ..........
    }
    else{
        //如果未有子进程做 RDB 文件生成
        //遍历 saveparams 数组,取出我们配置文件中的 save 配置项
        for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;
            //根据我们之前介绍的 dirty 计数器判断 save 配置条件是否满足
            if (server.dirty >= 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);
                //核心方法,进行 RDB 文件生成
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
         }
         //AOF 下篇我们在介绍,本篇看 RDB
         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();
            }
         }
    }
}
复制代码

ノー子プロセスRDBファイルが生成されている場合は、彼らは本物のrdbSaveBackground RDBファイル生成のためのコールを満たしていれば、その後、私たち保存トラバーサルサイクルの設定項目は、満たしています。私たちは、コアな方法を見ていき:

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
    pid_t childpid;
    long long start;

    if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;

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

    start = ustime();
    if ((childpid = fork()) == 0) {
        int retval;
        closeListeningSockets(0);
        redisSetProcTitle("redis-rdb-bgsave");
        retval = rdbSave(filename,rsi);
        if (retval == C_OK) {
            size_t private_dirty = zmalloc_get_private_dirty(-1);

            if (private_dirty) {
                serverLog(LL_NOTICE,
                    "RDB: %zu MB of memory used by copy-on-write",
                    private_dirty/(1024*1024));
            }

            server.child_info_data.cow_size = private_dirty;
            sendChildInfo(CHILD_INFO_TYPE_RDB);
        }
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        server.stat_fork_time = ustime()-start;
        server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
        latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
        if (childpid == -1) {
            closeChildInfoPipe();
            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 %d",childpid);
        server.rdb_save_time_start = time(NULL);
        server.rdb_child_pid = childpid;
        server.rdb_child_type = RDB_CHILD_TYPE_DISK;
        updateDictResizePolicy();
        return C_OK;
    }
    return C_OK; 
}
复制代码

rdbSaveBackgroundコア機能は、フォークとrdbSave関数を呼び出すことです。fork関数は、実際にシステムコールで、彼は、子プロセスを子プロセスをコピーして、親プロセスは、ほぼ正確に同じメモリのデータです。

フォーク関数は、子プロセスをコピーする際、プログラムの後続のコードセクションが言うことであること、同時に親と子で実行されますが、ブロックされている、フォークした後、次のコードは、同時に親と子が実行されますが、システムは、実行の順序を保証するものではありません。

親プロセス、フォーク関数戻り値は、子プロセスのプロセスIDに等しい、子プロセスフォーク関数戻り値はゼロに等しいです。

だから、コアロジック機能が成功フォーク後、また非常に明確rdbSaveBackground、子プロセスは、rdbSaveはRDBのファイル書き込みすることと、「temp-%のd.rdb」一時ファイル、およびログ情報の親レコード数を生産呼び出します子プロセスID、時間及びその他の情報。

rdbSave機能はRDBファイルを書かれているかについては、これはRDBファイルには、固定のプロトコル仕様である限り、契約に基づいてデータを書き込むことができますプログラムとして、この契約には、我々は次の細部それを待つ、また非常に簡単です。

これは定期的に読んで行われ、条件が満たされているかどうかを判断する、コンフィギュレーション・セーブ機能、設定ファイルserverCronをまとめるの条件が満たされた場合、rdbSaveBackground関数呼び出しは、RDBファイルの書き込みを完了するために子プロセスをforkし、一時ファイルを生成するにはそして一時ファイルが成功に書かれていることを保証するために、古いRDBファイル、最後の子プロセスが終了を交換してください。

PS:子供のうちfork関数のコピーは、終了することを覚えておく必要がありそうでない場合は、すべての主要なプロセスは、最終的にサービスOOMをリードし、子プロセスを複製します。

RDBファイルの構造解析

任意の形式のファイルは、独自のコーディングプロトコル、すべての合意されたプロトコルの独自のセットを持っているものをここでRDBファイルや、Javaでのバイトコードや、画像フォーマット、それぞれに固有のを持っています入れどのデータフィールドのバイト位置は、これはプロトコルに従ってコード化されたバイナリを書くときに読んだときに、また、契約に応じたバイトフィールドを読んで、慣例です。

RDB契約全体のファイルには、次のフィールドが含まれます。

画像

5バイトの第1の部分が固定され、それは「D」、「I」、「S」、「E」、「R」の5つの文字を、マジックナンバーと呼ばれる固定のRedis。

画像

私たちは、キーと値のペアで0 Redisのデータベースを追加し、[保存コマンドRDBファイルを実行して、バイナリファイルを開きます。

画像

私たちは、あなたが私たちの固定Redisの5つの文字の最初の5つのバイトを見つけるだろう、ASCIIおよびバイナリファイル出力オプションでは、ODコマンドを使用します。

RDBの現在のバージョンを記述した4バイトの次のフィールドREDIS_VERSION合計、上記の例は、Redisの-4.0バージョンRDB 0008に対応するファイルのバージョンです。

次のフィールドがAUXフィールドであり、当局は補助フィールドは、RDB 7は、主に以下の情報フィールドを含む、後で追加されます。

  1. Redisの-VER:バージョン番号
  2. Redisのビット:OSアーチ
  3. CTIME:RDBファイルの作成時間
  4. 使用-MEM:メモリを使用します
  5. REPLストリーム-DB:クライアントデータベースのserver.masterで選択
  6. REPL-ID:現在のインスタンスのレプリケーションID
  7. REPLオフセット:レプリケーションの現在のインスタンスのオフセット

次は、私たちの辞書のこの章では、実際のデータに格納され、データベース部である複数のデータベースにRedisの、唯一のRDBデータベースファイルを生成し、データが書き込まれたとき、およびフォーマットのこの部分は、以下の通りであります:

画像

我々は上記の例に対応し、この部分です。

画像

私たちのrdb.hファイルヘッダは、いくつかの定数ように定義されています。

#define RDB_OPCODE_AUX        250
#define RDB_OPCODE_RESIZEDB   251
#define RDB_OPCODE_EXPIRETIME_MS 252
#define RDB_OPCODE_EXPIRETIME 253
#define RDB_OPCODE_SELECTDB   254
#define RDB_OPCODE_EOF        255
复制代码

その後のフォローがデータベースの番号をオープンしようとしているので、六角FEは、そのRDB_OPCODE_SELECTDBに対応し、小数点以下254に変換され、アイデンティティは、データベースをオープンしようとしている、我々はゼロデータベースにここにあります。

251 FB Hexは10進数に変換され、対応するRDB_OPCODE_RESIZEDBがあり、ここでは1つのキーだけを持って、ある現在のデータベースの容量、キーの数を特定します。

このセクションの私達のキーと値のペアの形式をその続いておく次のとおりです。

画像

現在のキー、すなわち、オブジェクトの種類、以下の任意のタイプの種類を表すバイトタイプ識別子:

#define RDB_TYPE_STRING 0
#define RDB_TYPE_LIST   1
#define RDB_TYPE_SET    2
#define RDB_TYPE_ZSET   3
#define RDB_TYPE_HASH   4
#define RDB_TYPE_ZSET_2 5 
#define RDB_TYPE_MODULE 6
#define RDB_TYPE_MODULE_2 7 
/* Object types for encoded objects. */
#define RDB_TYPE_HASH_ZIPMAP    9
#define RDB_TYPE_LIST_ZIPLIST  10
#define RDB_TYPE_SET_INTSET    11
#define RDB_TYPE_ZSET_ZIPLIST  12
#define RDB_TYPE_HASH_ZIPLIST  13
#define RDB_TYPE_LIST_QUICKLIST 14
复制代码

それは、常にコンテンツの価値に続くキー文字列、文字列の長さに加えて、その内容プレフィックスを構成しています。

RDB EOFフィールド識別するファイルの終わりに、1バイト、固定値では、ファイルヘッダrdb.h.で発見された255個の六角FFに等しいです

CHECK_SUMチェックサムフィールドを格納するファイルやRDB、ファイルはRDBを損傷しているかどうかをチェックするために、表現する8バイト。

上記、我々は単にRDBファイルの構成を紹介し、実際には、エンコーディングの各タイプのオブジェクトが同じでないとき、ああを超えていなかったが、また、など、いくつかの圧縮技術、のオブジェクトなど、私たちはここにいますすべてが網羅することはできません。

全体的に、ライン上の基本的な理解を構成するRDBファイル、実際には、非常に少数の人々RDBの何もなどRDB-ツールも、手動分析として、ツールであって分析されている場合でも、ファイル内のデータを分析しないようにするにはあまりにも破裂していました。

まあ、RDBたちこれまでの簡単な紹介に、次のAOF本研究では、我々は永続化戦略、別れを調査しました!


国民の関心は、プログラマを共有するために、愛を失っていません。
学習探検する公共の「1024」の作者プラスマイクロチャネルへの返事はありません!
すべてのケースで材料を使用し、各記事のコードは、私の個人的なgithubのをアップロードされます
github.com/SingleYam/o...
トレッドへようこそ!

いいえ公共YangAMありません

おすすめ

転載: juejin.im/post/5e116cb1e51d45412862aeb7