【Redis 十】Redis的持久化原理和性能分析

一、Redis的持久化

     我們都知道內存就是暫時存儲程序以及數據的地方,存取速率快,那基於內存的Redis當然不會想在停機/故障的時候丟失數據,這個時候就得想辦法將暫時數據存到一個“永久”的地方(磁盤文件中、XML數據文件中),則爲持久化。上一節我們的Redis事務中有提到持久性,就讓我們來探索一下Redis的持久化是怎麼實現的。

二、RDB持久化方式 【快照方式(snapshotting)】

        在上圖我們可以看見RDB文件創建的兩個命令分別是SAVE和BGSAVE,實際上這兩個方法都是調用的rdbSave函數完成的。

#以不同的方式來調用rdbSave()
def SAVE():
    #創建RDB文件
    rdbSave()

def BGSAVE():
    #創建子進程
    pid = fork()
    if pid == 0:
        #子進程負責創建RDB文件
        rdbSave()
        #完成之後向父進程發送信號
        signal_parent()
    elif pid > 0:
        #父進程繼續處理命令請求,並通過輪詢等待子進程的信號
        handle_request_and_wait_signal()
    else:
        #處理出錯情況
        handle_fork_error()

     據我們所知Redis是可以配置文件或者傳入啓動參數的方式啓動的,所以可以配置save參數來設置自動間隔性保存的屬性。沒有主動設置save選項,那麼服務器會爲save選項設置默認條件:

#服務器在900秒之內,
#對數據庫進行了至少1次修改,即執行BGSAVE命令
save 900 1
#同上
save 300 10
save 60 10000

 服務器程序會根據save選項所設置的保存條件,設置服務器狀態redisServer結構saveparams屬性,saveparams屬性是一個數組,數組中的每個元素都是一個saveparam結構,每個saveparam結構都保存了一個save選項設置的保存條件。

struct redisServer {
    // ...
    //記錄了保存條件的數組
    struct saveparam *saveparams;
    // ...
    //修改計數器
    long long dirty;
    //上一次執行保存的時間
    time_t lastsave;
};

struct saveparam {
    //秒數
    time_t seconds;
    //修改數
    int changes;
};

 既然提到了服務器狀態redisServer結構,對於其中的dirty計數器lastsave的UNIX時間戳得了解一下。

  • dirty計數器記錄距離上一次成功執行SAVE命令或者BGSAVE命令之後,服務器對數據庫狀態(服務器中的所有數據庫)進行了多少次修改(包括寫入、刪除、更新等操作)。當服務器成功執行一個數據庫修改命令之後,程序就會對dirty計數器進行更新:命令修改了多少次數據庫,dirty計數器的值就增加多少。
  • lastsave屬性是一個UNIX時間戳,記錄了服務器上一次成功執行SAVE命令或者BGSAVE命令的時間。

既然提到了設置保存條件,那問題來了,怎麼知道的符合條件呢?

       原來Redis服務器有一個默認100毫秒就執一次的函數serverCron(),該函數用於對正在運行的服務器進行維護,它的其中一項工作就是檢查save選項所設置的保存條件是否已經滿足,如果滿足的話,就執行BGSAVE命令。

def serverCron():
    # ...
    #遍歷所有保存條件
    for saveparam in server.saveparams:
        #計算距離上次執行保存操作有多少秒
        save_interval = unixtime_now()-server.lastsave
        #如果數據庫狀態的修改次數超過條件所設置的次數
        #並且距離上次保存的時間超過條件所設置的時間
        #那麼執行保存操作
        if server.dirty >= saveparam.changes and \
           save_interval > saveparam.seconds:
            BGSAVE()
    # ...

  OK,那既然已經知道了RDB文件是怎麼樣創建的,接下來讓我們看看RDB文件是怎麼樣的?

關於key_value_pairs部分 我們在前面探索Redis的數據類型的時候已經一一的描述了,這裏如果有需要再補充。

三、AOF持久化方式

      3.1:通過保持Redis的寫命令來記錄數據庫狀態的AOF持久化例如通過SET、SADD、RPUSH等寫命令保存到AOF文件中。

我們在本文 的第一張圖中有提到AOF持久化的三個步驟:命令追加、文件寫入、文件同步

#在Redis的服務器中,命令追加是當AOF持久化功能處於打開狀態時,
#服務器在執行完一個寫命令之後,會以協議格式將被執行的寫命令
#追加到服務器狀態的aof_buf緩衝區的末尾
struct redisServer {
    // ...
    // AOF緩衝區
    sds aof_buf;
    // ...
};

文件的寫入與同步主要是依靠Redis的文件事件來進行

#上圖寫入和同步緩衝思路
def eventLoop():
    while True:
        #處理文件事件,接收命令請求以及發送命令回覆
        #處理命令請求時可能會有新內容被追加到 aof_buf緩衝區中
        processFileEvents()
        #處理時間事件
        processTimeEvents()
        #考慮是否要將 aof_buf中的內容寫入和保存到 AOF文件裏面
        flushAppendOnlyFile()
flushAppendOnlyFile函數的行爲由服務器配置的appendfsync選項的值來決定

       3.2:而AOF文件的載入和數據還原息息相關,AOF文件保存了所有寫操作命令,只要將AOF文件讀入系統並重新執行一遍對應的操作,可恢復服務器關閉之前的數據庫狀態

       AOF持久化方式的確好用,但將服務器所有的寫命令存起來會面臨 一個問題,就是隨着操作的不斷增加,AOF文件體積不斷的擴大爲了解決AOF文件存儲過大的問題,Redis提供了rewrite(AOF文件重寫功能)

def aof_rewrite(new_aof_file_name):
    #創建新 AOF文件
    f = create_file(new_aof_file_name)
    #遍歷數據庫
    for db in redisServer.db:
        #忽略空數據庫
        if db.is_empty(): continue
        #寫入SELECT命令,指定數據庫號碼
        f.write_command("SELECT" + db.id)
        #遍歷數據庫中的所有鍵
        for key in db:
            #忽略已過期的鍵
            if key.is_expired(): continue
            #根據鍵的類型對鍵進行重寫
            if key.type == String:
                rewrite_string(key)
            elif key.type == List:
                rewrite_list(key)
            elif key.type == Hash:
                rewrite_hash(key)
            elif key.type == Set:
                rewrite_set(key)
            elif key.type == SortedSet:
                rewrite_sorted_set(key)
            #如果鍵帶有過期時間,那麼過期時間也要被重寫
            if key.have_expire_time():
                rewrite_expire_time(key)
        #寫入完畢,關閉文件
        f.close()
    def rewrite_string(key):
        #使用GET命令獲取字符串鍵的值
        value = GET(key)
        #使用SET命令重寫字符串鍵
        f.write_command(SET, key, value)
    def rewrite_list(key):
        #使用LRANGE命令獲取列表鍵包含的所有元素
        item1, item2, ..., itemN = LRANGE(key, 0, -1)
        #使用RPUSH命令重寫列表鍵
        f.write_command(RPUSH, key, item1, item2, ..., itemN)
    def rewrite_hash(key):
        #使用HGETALL命令獲取哈希鍵包含的所有鍵值對
        field1, value1, field2, value2, ..., fieldN, valueN = HGETALL(key)
        #使用HMSET命令重寫哈希鍵
        f.write_command(HMSET, key, field1, value1, field2, value2, ..., fieldN, valueN)
    def rewrite_set(key);
        #使用SMEMBERS命令獲取集合鍵包含的所有元素
        elem1, elem2, ..., elemN = SMEMBERS(key)
        #使用SADD命令重寫集合鍵
        f.write_command(SADD, key, elem1, elem2, ..., elemN)
    def rewrite_sorted_set(key):
        #使用ZRANGE命令獲取有序集合鍵包含的所有元素
        member1, score1, member2, score2, ..., memberN, scoreN = ZRANGE(key, 0, -1, "WITHSCORES")
        #使用ZADD命令重寫有序集合鍵
        f.write_command(ZADD, key, score1, member1, score2, member2, ..., scoreN, memberN)
        def rewrite_expire_time(key):
        #獲取毫秒精度的鍵過期時間戳
        timestamp = get_expire_time_in_unixstamp(key)
        #使用PEXPIREAT命令重寫鍵的過期時間
        f.write_command(PEXPIREAT, key, timestamp)

問題

 

 

 

 

 

 

3.服務器停機
如果Redis服務器在執行事務的過程中停機,那麼根據服務器所使用的持久化模式,可能有以下情況出現:
❑如果服務器運行在無持久化的內存模式下,那麼重啓之後的數據庫將是空白的,因此數據總是一致的。
❑如果服務器運行在RDB模式下,那麼在事務中途停機不會導致不一致性,因爲服務器可以根據現有的RDB文件來恢復數據,從而將數據庫還原到一個一致的狀態。如果找不到可供使用的RDB文件,那麼重啓之後的數據庫將是空白的,而空白數據庫總是一致的。
❑如果服務器運行在AOF模式下,那麼在事務中途停機不會導致不一致性,因爲服務器可以根據現有的AOF文件來恢復數據,從而將數據庫還原到一個一致的狀態。如果找不到可供使用的AOF文件,那麼重啓之後的數據庫將是空白的,而空白數據庫總是一致的。
綜上所述,無論Redis服務器運行在哪種持久化模式下,事務執行中途發生的停機都不會影響數據庫的一致性。

事務的耐久性指的是,當一個事務執行完畢時,執行這個事務所得的結果已經被保存到永久性存儲介質(比如硬盤)裏面了,即使服務器在事務執行完畢之後停機,執行事務所得的結果也不會丟失。
因爲Redis的事務不過是簡單地用隊列包裹起了一組Redis命令,Redis並沒有爲事務提供任何額外的持久化功能,所以Redis事務的耐久性由Redis所使用的持久化模式決定:
❑當服務器在無持久化的內存模式下運作時,事務不具有耐久性:一旦服務器停機,包括事務數據在內的所有服務器數據都將丟失。
❑當服務器在RDB持久化模式下運作時,服務器只會在特定的保存條件被滿足時,纔會執行BGSAVE命令,對數據庫進行保存操作,並且異步執行的BGSAVE不能保證事務數據被第一時間保存到硬盤裏面,因此RDB持久化模式下的事務也不具有耐久性。
❑當服務器運行在AOF持久化模式下,並且appendfsync選項的值爲always時,程序總會在執行命令之後調用同步(sync)函數,將命令數據真正地保存到硬盤裏面,因此這種配置下的事務是具有耐久性的。
❑當服務器運行在AOF持久化模式下,並且appendfsync選項的值爲everysec時,程序會每秒同步一次命令數據到硬盤。因爲停機可能會恰好發生在等待同步的那一秒鐘之內,這可能會造成事務數據丟失,所以這種配置下的事務不具有耐久性。
❑當服務器運行在AOF持久化模式下,並且appendfsync選項的值爲no時,程序會交由操作系統來決定何時將命令數據同步到硬盤。因爲事務數據可能在等待同步的過程中丟失,所以這種配置下的事務不具有耐久性。
no-appendfsync-on-rewrite配置選項對耐久性的影響
配置選項no-appendfsync-on-rewrite可以配合appendfsync選項爲always或者everysec的AOF持久化模式使用。當no-appendfsync-on-rewrite選項處於打開狀態時,在執行BGSAVE命令或者BGREWRITEAOF命令期間,服務器會暫時停止對AOF文件進行同步,從而儘可能地減少I/O阻塞。但是這樣一來,關於“always模式的AOF持久化可以保證事務的耐久性”這一結論將不再成立,因爲在服務器停止對AOF文件進行同步期間,事務結果可能會因爲停機而丟失。因此,如果服務器打開了no-appendfsync-on-

 

 

sudo find / -name '*.aof'   # 此命令用於查找系統上所有以aof爲後綴的文件
  1. redis的主從複製
  2. redis的哨兵機制
  3. redis的Cluster集羣
  4. redis的消息模式
  5. 分佈式鎖

面試題目:

https://baijiahao.baidu.com/s?id=1625681293986572996&wfr=spider&for=pc

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章