什么是持久化
MySQL 的事務,有四個比較核心的特性:
- 原子性
- 一致性
- 持久性==>持久化(說的一回事)
- 把數據存儲在硬盤上==>持久
- 把數據存在內存上==>不持久
- 重啟進程/重啟主機之后,數據是否還存在
- 隔離性
Redis
是一個內存數據庫,是把數據存在內存中的。內存中的數據并不是持久的,要想能做到持久,就需要讓 Redis
把數據存儲在硬盤上
Redis
相比于 MySQL
這樣的關系型數據庫,最明顯的有點/優勢==>效率高/快
- 因為它的數據是存在內存上的
如何實現持久化
為了保證速度快,數據肯定還得再內存中,但是為了持久,數據還得想辦法存儲在硬盤上
redis
決定,內存和硬盤上都存數據- 這樣的兩份數據,理論上是完全相同的(實際上可能存在一個小的概率有差異,取決于我們具體怎么進行持久化)
- 當要插入一個新的數據的時候,就需要把這個數據,同時寫入內存和硬盤。
- 實際上怎么寫入硬盤,還有不同的策略,不會導致每次都要寫兩份,導致效率降低
- 當查詢某個數據的時候,直接從內存讀取
- 硬盤的數據只是在
redis
重啟的時候,用來恢復內存中的數據的
這樣就既能保證高效,又能通過硬盤恢復數據,保證持久化的效果,代價就是消耗了更多空間(一份數據,存了兩遍)(硬盤便宜,不會帶來太多成本)
實現策略
RDB
-->Redis DataBase
AOF
-->Append Only FIle
RDB
定期的把我們 Redis
內存中的數據,都給寫入硬盤中,生成一個“快照”
Redis
給內存中當前存儲的這些數據,趕緊拍個照片,生成一個文件,存儲在硬盤中- 后續
Redis
一旦重啟了(內存數據就沒了),就可以根據剛才的“快照”,把內存中的數據給恢復回來
[!quote] 快照
某個案發現場,警察來了之后,會拉上警戒線,然后開始忙碌地拍照,記錄現場==>后續就可以根據這些記錄的照片,來還原出現場當前發生了什么
“定期”具體又分為兩種方式:
- 手動觸發
- 程序員通過
Redis
客戶端,執行特定的命令,來觸發快照生成(save
、bgsave
)
- 程序員通過
- 自動觸發
- 在
Redis
配置文件中,設置一下,讓Redis
每隔多長時間/每產生多少次修改就出發
- 在
手動觸發
執行 save
的時候,Redis
就會全力以赴的進行“快照生成”操作,此時就會阻塞 Redis
的其他客戶端的命令
- 類似于
keys *
的后果 - 一般不建議使用
save
是在前臺,bgsave
(background
)是在后面偷摸進行,不會影響 Redis
服務器處理其他客戶端的請求和命令
- 這樣既可以保證持久化
- 又可以保證
Redis
可以正常去響應命令
bgsave
Redis
是怎么做到 bgsave
的呢?是不是偷偷搞了個多線程?
- 并非如此,多線程是實現并發編程的場景的一種方式,但不是唯一
Redis
使用的是“多進程”的方式,來完成的并發編程,實現bgsave
的
當 Redis
服務器(父進程)收到 bgsave
的命令之后,首先會進行一個判斷
1 . 判定當前是否已經存在其他正在工作的子進程
比如現在已經有一個子進程正在執行 bgsave
,此時就直接把當前的 bgsave
返回
2 . 如果沒有其他的工作子進程,就通過 fork
這樣的系統調用,創建一個子進程來
fork
是 Linux
系統提供的一個創建子進程的 API
- 如果是其他系統,比如
Windows
,創建子進程就不是fork
(CreatProcess
) fork
創建子進程,簡單粗暴,就是直接把當前的進程(父進程)復制一份,作為子進程。一旦復制完成了,父子進程就是兩個獨立的進程,就格子執行各自的了- 會復制 PCB、虛擬地址空間(內存中的數據)、文件描述附表…
- 本來
redis server
中,有若干變量,保存了一些鍵值對數據。隨著這樣的fork
的進行,子進程的這個內存里也會存在和剛才父進程中一模一樣的變量
因此,復制出來的“克隆體“(子進程),內存中的數據就是和“本體”(父進程)是一樣的。接下來安排子進程去進行“持久化”操作,也就相當于把父進程本體這里的數據給持久化了
父進程打開了一個文件,
fork
了之后,子進程也是可以同時使用這個文件的
- 導致了子進程持久化寫入的那個文件,和父進程本來要寫的文件是同一個
如果當前 redis
服務器中存儲的數據特別多,內存消耗特別大,此時進行上述的復制操作,是否會有很大的性能開銷?
- 此處的性能開銷其實挺小
fork
在進行內存拷貝的時候,不是簡單無腦的直接把所有的數據都拷貝一遍,而是“寫實拷貝”的機制來完成的- 如果子進程里的這個內存數據,和父進程的內存數據完全一樣,此時就不會觸發真正的拷貝動作(而是爺倆其實用一份內存數據)
- 但是,其實這倆進程的內存空間,應該是各自獨立的。一旦某一方針對這個數據進行了修改,就會立即觸發真正的物理內存上的數據拷貝
在進行 bgsave
這個場景中,絕大部分的內存數據,是不需要進行改變的(整體來說這個過程執行的還挺快,這個短時間內,父進程中不會有大批的內存數據變化)
因此,子進程的“寫實拷貝”不會觸發很多次,也就保證了整體的“拷貝時間”是可控的,高效的
- 子進程負責進行寫文件,生成快照的過程,父進程繼續接收客戶端的請求,繼續正常提供服務
- 子進程完成整體的持久化過程之后,就會通知父進程干完了,父進程就會更新一些統計信息,子進程就可以結束銷毀了
總結:
創建子進程,子進程完成持久化操作,持久化會把數據寫入到新的文件中,然后使用新的文件替換舊的文件
- 子進程不好觀察,我們觀察新舊文件(通過
stat
命令,查看文件的inode
號)
RDB 文件
redis
生成的 RDB
文件,是放在 redis
的工作目錄中的,也是在 redis
配置文件中進行設置的
RDB
機制生成的鏡像文件,redis
服務器默認就是開啟了RDB
的- 這是一個二進制文件,把內存中的數據,以壓縮(消耗一定 CPU 資源,但是能省空間)的形式,保存到這個二進制文件中
- 最多打開看看就行了,不要亂改,一旦要是把數據的格式改壞了,就麻煩了
- 后續
redis
服務器重新啟動,就會嘗試加載這個RDB
文件,如果發現格式錯誤,就可能會加載數據失敗
redis
提供了 RDB
文件的檢查工具
當執行生成 RDB
鏡像操作的時候,此時就會把要生產的快照數據,先保存到一個臨時文件中。當這個快照生成完畢之后,再刪除之前的 RDB
文件,把新生成的臨時的 RDB
文件名字改成剛才的 dump.rdb
- 自始至終,
RDB
文件始終只有一個
效果
RDB
文件中的數據,不是你這邊插入了數據,就會立即更新的
- 插入數據之后,觀察
RDB
文件內容 - 發現沒有變化,此時我們執行保存數據的命令
可以發現 RDB
文件內容更新了
RDB
的觸發時機:
- 手動(save、bgsave)
- 自動(配置文件中,進行設置)
- 在
900s
之后,并且至少存在一次key
的修改,就會觸發自動更新
這些數值都是可以自由修改的,但是此處修改數據的時候,有一個基本的原則:
- 生成一次
RDB
快照,這個成本較高,不能讓這個操作執行太頻繁
redis
持久化生成快照操作,不僅僅是手動執行命令才出發,也可以自動觸發
- 通過剛才配置文件中
save
執行M
時間內,修改N
次 - 通過
shutdown
命令(redis
里的一個命令)關閉redis
服務器,也會觸發(service redis-server restart
)(正常關閉) redis
進行主從復制的時候,主節點也會自動生成RDB
快照,然后把RDB
快照文件內容傳輸給從節點
- 如果是通過正常流程重新啟動
redis
服務器,此時redis
服務器會在退出的時候,自動觸發生成RDB
操作- 但如果是異常重啟(
kill -9
或者服務器掉電),此時redis
服務器來不及生成RDB
,內存中尚未保存到快照中的數據,就會隨著重啟而丟失
- 若要修改
RDB
配置,修改完成后需要重啟服務器- 如果
RDB
文件被改壞了,redis
服務器啟動不了了,我們可以去看一下日志,查看報錯信息
redis
提供了 RDB
文件的檢查工具,可以先通過檢查工具,檢查一下 RDB
文件格式是否符合要求
- 檢查工具和
reids
服務器,在5.0
版本是同一個可執行程序,但是我們可以在運行的時候加上不同選項 - 在運行的時候,加上
RDB
文件作為命令行參數,此時就是以檢查工具的方式來運行
- 這里就顯示
RDB
文件里面有錯誤
RDB 小結
- RDB 是一個緊湊壓縮的二進制文件,代表
redis
在某個時間點上的數據快照。非常適用于備份,全量復制等場景。比如每 6 小時執行bgsave
備份,并把RDB
文件復制到遠程機器或者文件系統中(如hdfs
)用于災備 redis
加載RDB
恢復數據遠遠快于AOF
的方式RDB
是使用二進制的方式來組織數據,直接把數據讀取到內存中,按照字節的格式取出來,放到結構體/對象中即可AOF
是使用文本的方式來組織數據
RDB
方式數據沒辦法做到實時持久化,因為bgsave
每次運行都要執行fork
創建子進程,屬于重量級操作,頻繁執行成本過高RDB
文件使用特定二進制格式保存,redis
版本演進過程中有多個RDB
版本,兼容性可能有風險- 老版本的
redis
的RDB
文件,放到新版本的redis
中不一定能識別
- 老版本的
如果確實需要有一些“升級版本”的需求,就可以通過寫一個程序的方式,直接遍歷舊的
redis
中的所有key
,把數據取出來,插入到新的redis
服務器中即可