[Redis源碼閱讀]redis持久化

来源:https://www.cnblogs.com/hoohack/archive/2018/04/04/8716910.html
-Advertisement-
Play Games

作為web開發的一員,相信大家的面試經歷里少不了會遇到這個問題:redis是怎麼做持久化的? ...


作為web開發的一員,相信大家的面試經歷里少不了會遇到這個問題:redis是怎麼做持久化的?

不急著給出答案,先停下來思考一下,然後再看看下麵的介紹。希望看了這邊文章後,你能夠回答這個問題。

為什麼需要持久化?

由於Redis是一種記憶體型資料庫,即伺服器在運行時,系統為其分配了一部分記憶體存儲數據,一旦伺服器掛了,或者突然宕機了,那麼資料庫裡面的數據將會丟失,為了使伺服器即使突然關機也能保存數據,必須通過持久化的方式將數據從記憶體保存到磁碟中。

對於進行持久化的程式來說,數據從程式寫到電腦的磁碟的流程如下:

1、客戶端發送一個寫指令給資料庫(此時數據在客戶端的記憶體)

2、資料庫接收到寫的指令以及數據(數據此時在服務端的記憶體)

3、資料庫發起一個系統調用,把數據寫到磁碟(此時數據在內核的記憶體)

4、操作系統把數據傳輸到磁碟控制器(數據此時在磁碟緩存中)

5、磁碟控制器執行真正寫入數據到物理媒介的操作(如磁碟)

如果只是考慮資料庫層面,數據在第三階段之後就安全了,在這個時候,系統調用已經發起了,即使資料庫進程奔潰了,系統調用會繼續進行,也能順利將數據寫入到磁碟中。
在這一步之後,在第4步內核會將數據從內核緩存保存到磁碟緩存中,但為了系統的效率問題,預設情況下不會太頻繁地執行這個動作,大概會在30s執行一次,這就意味著如果這一步失敗了或者就在進行這一步的時候伺服器突然關機了,那麼就可能會有30s的數據丟失了,這種比較普通的災難性問題也是需要考慮的。

POSIX API也提供了一個系統調用讓內核強制將緩存數據寫入到磁碟中,比較常見的就是fsync系統調用。

int fsync(int fd);

fsync函數只對由文件描述符fd指定的一個文件起作用,並且等待寫磁碟操作結束後才返回。每次調用fsync時,會初始化一個寫操作,然後把緩衝區的數據寫入到磁碟中。fsync()函數在完成寫操作的時候會阻塞進程,如果其他線程也在寫同一個文件,它也會阻塞其他線程,直到完成寫操作。

持久化

持久化是將程式數據在持久狀態和瞬時狀態間轉換的機制。對於程式來說,程式運行中數據是在記憶體的,如果沒有及時同步寫入到磁碟,那麼一旦斷電或者程式突然奔潰,數據就會丟失了,只有把數據及時同步到磁碟,數據才能永久保存,不會因為宕機影像數據的有效性。而持久化就是將數據從程式同步到磁碟的一個動作過程。

持久化

Redis的持久化

redis有RDB和AOF兩種持久化方式。RDB是快照文件的方式,redis通過執行SAVE/BGSAVE命令,執行數據的備份,將redis當前的數據保存到*.rdb文件中,文件保存了所有的數據集合。AOF是伺服器通過讀取配置,在指定的時間里,追加redis寫操作的命令到*.aof文件中,是一種增量的持久化方式。

RDB

RDB文件通過SAVE或BGSAVE命令實現。
SAVE命令會阻塞Redis服務進程,直到RDB文件創建完成為止。
BGSAVE命令通過fork子進程,有子進程來進行創建RDB文件,父進程和子進程共用數據段,父進程繼續提供讀寫服務,子進程實現備份功能。BGSAVE階段只有在需要修改共用數據段的時候才進行拷貝,也就是COW(Copy On Write)。SAVE創建RDB文件可以通過設置多個保存條件,只要其中一個條件滿足,就可以在後臺執行SAVE操作。

SAVE和BGSAVE命令的實現代碼如下:

void saveCommand(client *c) {
    // BGSAVE執行時不能執行SAVE
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);
    // 調用rdbSave函數執行備份(阻塞當前客戶端)
    if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

/*
* BGSAVE 命令實現 [可選參數"schedule"]
*/
void bgsaveCommand(client *c) {
    int schedule = 0;

    /* 當AOF正在執行時,SCHEDULE參數修改BGSAVE的效果
    * BGSAVE會在之後執行,而不是報錯
    * 可以理解為:BGSAVE被提上日程
    */
    if (c->argc > 1) {
        // 參數只能是"schedule"
        if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
            schedule = 1;
        } else {
            addReply(c,shared.syntaxerr);
            return;
        }
    }

    // BGSAVE正在執行,不操作
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
    } else if (server.aof_child_pid != -1) {
        // aof正在執行,如果schedule==1,BGSAVE被提上日程
        if (schedule) {
            server.rdb_bgsave_scheduled = 1;
            addReplyStatus(c,"Background saving scheduled");
        } else {
            addReplyError(c,
            "An AOF log rewriting in progress: can't BGSAVE right now. "
            "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
            "possible.");
        }
    } else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {// 否則調用rdbSaveBackground執行備份操作
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

有了RDB文件之後,如果伺服器關機了,或者需要新增一個伺服器,重新啟動資料庫伺服器之後,就可以通過載入RDB文件恢復之前備份的數據。
但是bgsave會耗費較長時間,不夠實時,會導致在停機的時候丟失大量數據。

AOF(Append Only File)

RDB文件保存的是資料庫的鍵值對數據,AOF保存的是資料庫執行的寫命令。

AOF的實現流程有三步:

append->write->fsync

append追加命令到AOF緩衝區,write將緩衝區的內容寫入到程式緩衝區,fsync將程式緩衝區的內容寫入到文件。
當AOF持久化功能處於開啟狀態時,伺服器每執行完一個命令,就會將命令以協議格式追加寫入到redisServer結構體的aof_buf緩衝區,具體的協議這裡不展開闡述。

AOF的持久化發生時期有個配置選項:appendfsync。該選項有三個值:
always:所有內容寫入並同步到aof文件
everysec:將aof_buf緩衝區的內容寫入到AOF文件,如果距離上次同步AOF文件的
no:將aof_buf緩衝區中的所有內容寫入到AOF文件,但並不對AOF文件進行同步,由操作系統決定何時進行同步,一般是預設情況下的30s。

AOF持久化模式每個寫命令都會追加到AOF文件,隨著伺服器不斷運行,AOF文件會越來越大,為了避免AOF產生的文件太大,伺服器會對AOF文件進行重寫,將操作相同key的相同命令合併,從而減少文件的大小。

舉個例子,要保存一個員工的名字、性別等信息:

> hset employee_12345 name "hoohack"
> hset employee_12345 good_at "php"
> hset employee_12345 gender "male"

只是錄入這個哈希鍵的狀態,AOF文件就需要保存三條命令,如果還有其他,比如刪除,或者更新值的操作,那命令將會更多,文件會更大,有了重寫後,就可以適當地減少文件的大小。

AOF重寫的實現原理是先伺服器中的資料庫,然後遍曆數據庫,找出每個資料庫中的所有鍵對象,獲取鍵值對的鍵和值,根據鍵的類型對鍵值對進行重寫。比如上面的例子,可以合併為下麵的一條命令:

> hset employee_12345 name "hoohack" good_at "php" gender "male"。

AOF的重寫會執行大量的寫入操作,Redis是單線程的,所以如果有伺服器直接調用重寫,伺服器就不能處理其他命令了,因此Redis伺服器新起了單獨一個進程來執行AOF重寫。

Redis執行重寫的流程:
redis rewrite

在子進程執行AOF重寫時,服務端接收到客戶端的命令之後,先執行客戶端發來的命令,然後將執行後的寫命令追加到AOF緩衝區中,同時將執行後的寫命令追加到AOF重寫緩衝區中。
等到子進程完成了重寫工作後,會發一個完成的信號給伺服器,伺服器就將AOF重寫緩衝區中的所有內容追加到AOF文件中,然後原子性地覆蓋現有的AOF文件。

RDB和AOF的優缺點

RDB持久化方式可以只通過伺服器讀取數據就能載入備份中的文件到程式中,而AOF方式必須創建一個偽客戶端才能執行。

RDB的文件較小,保存了某個時間點之前的數據,適合做災備和主從同步。

RDB備份耗時較長,如果數據量大,在遇到宕機的情況下,可能會丟失部分數據。另外,RDB是通過配置使達到某種條件的時候才執行,如果在這段時間內宕機,那麼這部分數據也會丟失。

AOF方式,在相同數據集的情況下,文件大小會比RDB方式的大。

AOF的持久化方式也是通過配置的不同,預設配置的是每秒同步,最快的模式是同步每一個命令,最壞的方式是等待系統執行fsync將緩衝同步到磁碟文件中,大部分操作系統是30s。通常情況下會配置為每秒同步一次,所以最多會有1s的數據丟失。

怎樣的同步方式更好?

RDB和AOF方式結合。起一個定時任務,每小時備份一份伺服器當前狀態的數據,以日期和小時命名,另外起一個定時任務,定時刪除無效的備份文件(比如48小時之前)。AOF配置為1s一次。這樣一來,最多會丟失1s的數據,同時如果redis發生雪崩,也能迅速恢復為前一天的狀態,不至於停止服務。

總結

Redis的持久化方案也不是一成不變的,紙上的理論還需要結合實踐成果來證明其可行性。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

更多精彩內容,請關註個人公眾號。

參考文章:
http://oldblog.antirez.com/post/redis-persistence-demystified.html
http://blog.httrack.com/blog/2013/11/15/everything-you-always-wanted-to-know-about-fsync/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 不廢話,直奔主題,可以覆蓋安裝。 下載並安裝MySQL官方的 Yum Repository 使用上面的命令就直接下載了安裝用的Yum Repository,大概25KB的樣子,然後就可以直接yum安裝了。 之後就開始安裝MySQL伺服器。 這步可能會花些時間,安裝完成後就會覆蓋掉之前的mariadb ...
  • 本文為mariadb官方手冊:DECLARE HANDLER的譯文。 原文:https://mariadb.com/kb/en/library/declare-handler/我提交到MariaDB官方手冊的譯文:https://mariadb.com/kb/zh-cn/declare-handle ...
  • GT(隨身調)是APP的隨身調測平臺,它是直接運行在手機上的“集成調測環境”(IDTE, Integrated Debug Environment)。利用GT,僅憑一部手機,無需連接電腦,您即可對APP進行快速的性能測試(CPU、記憶體、流量、電量、幀率/流暢度等等)、開發日誌的查看、Crash日誌查 ...
  • 1、下載mysql 下載的話先確認好版本。 system:centos7 mysql:5.7 下麵的版本自己選擇,一般是86位的。 下載好的文件 2、上傳到伺服器 soft文件夾,終端也進入了soft文件夾 第一種是用xshell上傳 $ rz 需要安裝 yum install lrzsz -y 第 ...
  • 本文目錄:1.集合的特征2.集合的無序性3.表中記錄的無序性4.集合的"序"和物理存儲順序之間的關係5.查詢結果(虛擬表)的無序性、隨機性6.為什麼總是強調"無序"?7.什麼時候的結果是有序的?8.索引的"序" 1.集合的特征 關係型資料庫,一方面它是資料庫,可以存儲數據,另一方面,它是關係的,也就 ...
  • mongodb使用BSON格式存儲數據記錄. 如下圖: 文檔結構 文檔有鍵值對組成, 有以下結構: { field1: value1, field2: value2, ... fieldN: valueN}​ 欄位的值可以是任意BSON 數據類型,包括其他文檔, 數組和文檔數組. 例如,以下文檔包含 ...
  • 前言: 我們都知道事務的幾種性質,資料庫為了維護這些性質,尤其是一致性和隔離性,一般使用加鎖這種方式。同時資料庫又是個高併發的應用,同一時間會有大量的併發訪問,如果加鎖過度,會極大的降低併發處理能力。所以對於加鎖的處理,可以說就是資料庫對於事務處理的精髓所在。這裡通過分析MySQL中InnoDB引擎 ...
  • 官方鏈接 http://gpdb.docs.pivotal.io/530/pdf/PSQLQuickRef-A02.pdf ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...