本文默認你是使用過redis並瞭解redis的基礎概念,學習redis入門並不難,給你介紹各種API使用也沒啥意思。在這裏 不會給你堆各種專業詞彙,只有我個人理解的大白話
這裏沒別的 先來張總原理圖,花10秒時間記一下,跟遊戲打副本一樣,先看下地圖 做到腦子裏有個大概的脈絡,這裏也是平時工作中比較常用的一種工作思考方式,遇到難的問題如果沒思路就先忙別的,我們的大腦會在後臺保持這個問題並且會時不時的激活一下,同樣的時間做了多件事。高考時老師說拿到試卷先看一遍最後的作文題目在開始答題,一樣的思想
AOF功能工作原理和AOF重寫原理總圖:
10秒已過 ,開始正文
一。AOF
1.說白了就是個日誌記錄文件。redis會把所有寫命令記錄到一個設定好的日誌文件中,做開發的都知道 存儲日誌 是需要有 特定的格式的,這樣後面你才能快速的定位檢索,AOF裏存儲的是Redis自己定義的RESP協議格式的字符串,對,你沒 看錯就是字符串,(resp協議的https://blog.csdn.net/wenmeishuai/article/details/106101762)
2.AOF裏記錄的是每一次寫命令,比如說list數據類型,其中有100條數據,在AOF中可能就有100條RESP協議的記錄, 參考RESP的文章就知道,這其中有大量的重複命令
3.開啓AOF持久化功能只需改兩個配置參數
appendonly yes 開啓aof
appendfilename mads.aof 日誌文件名字,隨便起
4.人是個喜歡刨根問底的生物,用是會用了。它是怎麼實現的?
1)上面圖中 紅色框內是AOF的流程。 主進程接收客戶端請求寫命令,寫入到aof_buf(aof緩衝區)然後主進程就返回了 (redis的優化點)
2)有專門的子進程去調用fsync()函數把數據從aof_buf寫入到aof文件(誰在說redis是單線程就對他說:哼 初級程序員)
3)結合上面2)子進程該什麼時候去觸發調用fsync()這個同步動作呢,redis已經幫我們提前設置了三種策略:
ps:fsync()函數要求操作系統馬上把aof_buf數據寫到磁盤上.
配置文件中
appendfsync always
appendfsync everysec
appendfsync no
# no:不要立刻刷,只有在操作系統需要刷的時候再刷 ,比較快。如果redis重啓了拿到的數據將不是很新的數據
# always:每次寫操作都立刻寫入到aof文件。慢,但是最安全。
# everysec:每秒寫一次。折衷方案。 (默認,如果拿不準就用 "everysec"試水 )
4)AOF追加阻塞
結合上面繼續深入,如果上面我設置的是每1秒同步一次數據,在線上大批量寫請求下aof_buf有大量數據需要同步,此時 就會對磁盤進行高頻寫入,磁盤IO就變成了瓶頸,就會出現上次的同步動作還沒完成
主進程又接收到大批寫命令寫到了緩衝區,此時redis爲了保證aof文件安全性,會阻塞主線程,直到上次fsync同步完成。
主進程吧數據寫入到aof_buf後會對比上次fsync操作時間,此時有兩種情況:
1.如果距離上次fsync操作時間大於2S則阻塞主進程直到fsync結束
2.如果距上次操作時間小於2S則主進程直接返回
這裏雖然我們配置的是每秒同步一次,但是實際上不是丟失1S的數據,實際上可能丟失2S數據,這裏請細品
5)aof_buf同步到文件的流程 執行之前,看總量,依次執行,執行完成了再回收(清空)緩衝區
6)aof_buf裏存儲的格式是 RESP協議格式,(還沒有看源碼確認)
二。AOF重寫
1.通過上面已經知道了AOF記錄的是字符串。真正線上環境寫操作是很多的,AOF的文件大小增加也是很快的,如果一個 AOF文件有10G了再去追加的時候那是非常慢的了。
2. redis就提供了一種壓縮的方式,比如還是上面list數據類型100條數據,經過壓縮以後就變成了一條,這就是AOF重寫。
3.AOF重寫會觸發Redis的緩存淘汰策略,可以參考aof_rewrite函數源碼,參考這篇文章 https://blog.csdn.net/qq_41453285/article/details/103301715
具體流程:
1.Redis開啓了持久化功能,並且達到了重寫的條件。三個參數,
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 自動重寫AOF文件
# 如果AOF日誌文件大到指定百分比,Redis能夠通過 BGREWRITEAOF 自動重寫AOF日誌文件。
# 工作原理:Redis記住上次重寫時AOF日誌的大小(或者重啓後沒有寫操作的話,那就直接用此時的AOF文件),
# 基準尺寸和當前尺寸做比較。如果當前尺寸超過指定比例,就會觸發重寫操作。
# 你還需要指定被重寫日誌的最小尺寸,這樣避免了達到約定百分比但尺寸仍然很小的情況還要重寫。
# 指定百分比爲0會禁用AOF自動重寫特性。
2.調用fork系統級別函數,複製出完全一致的一個子進程,和主進程共用同一塊內存空間(類似淺複製,redis爲了節省內存開 銷的優化點)
3.子進程調用aof_rewrite函數(redis客戶端執行bgrewriteaof命令最終也是調用此函數)可以創建新的AOF文件去執行重寫操作 根據已有數據進行命令的壓縮和過期時間的檢測並將壓縮後的命令寫入到新的AOF文件,直到寫完
4.在AOF重寫過程中,主進程是可以繼續對外服務的,當接收到寫命令,寫入到aof_buf後,然後判斷此時是否正在執行重寫 操作,如果是再將寫命令寫入 到AOF重寫緩衝區,主進程返回
5.當子進程完成對AOF文件重寫之後,它會向父進程發送一個完成信號,父進程接到該完成信號之後,會調用一個信號處理函 數,該函數完成以下工作:
1).將AOF重寫緩存中的內容全部寫入到新的AOF文件中;這個時候新的AOF文件所保存的數據庫狀態和服務器當前的數 據庫狀態一致;
2).對新的AOF文件進行改名,原子的覆蓋原有的AOF文件;完成新舊兩個AOF文件的替換。到這裏纔是一次完整的AOF 重寫流程
3).當這個信號處理函數執行完畢之後,主進程就可以繼續像往常一樣接收命令請求了。在整個AOF後臺重寫過程中,只有 最後的“主進程寫入命令到AOF緩存”和“對新的AOF文件進行改名,覆蓋原有的 AOF文件。”這兩個步驟(信號處理函數 執行期間)會造成主進程阻塞,在其他時候,AOF後臺重寫都不會對主進程造成阻塞,這將AOF重寫對性能造成的影 響降到最低。
6.如果AOF重寫失敗 redis會刪除中間臨時產物,保證流程的健壯性
AOF後臺重寫爲什麼這麼幹
1.aof_rewrite這個函數會進行大量的寫入 操作,所以調用這個函數的線程將被長時間的阻塞,因爲Redis服務器使用單線程來處理命令請求;所以如果直接是服務器進 程調用AOF_REWRITE函數的話,那麼重寫AOF期間,服務器將無法處理客戶端發送來的命令請求;
2.Redis不希望AOF重寫會造成服務器無法處理請求,所以Redis決定將AOF重寫程序放到子進程(後臺)裏執行。這樣處理的最 大好處是:
1)子進程進行AOF重寫期間,主進程可以繼續處理命令請求;
2)子進程帶有主進程的數據副本,使用子進程而不是線程,可以避免在鎖的情況下,保證數據的安全性。
使用子進程進行AOF重寫的問題
3.子進程在進行AOF重寫期間,服務器進程還要繼續處理命令請求,而新的命令可能對現有的數據進行修改,這會讓當前數 據庫的數據和重寫後的AOF文件中的數據不一致。
如何修正
1.爲了解決這種數據不一致的問題,Redis增加了一個AOF重寫緩存,這個緩存在fork出子進程之後開始啓用,Redis服務器主 進程在執行完寫命令之後,會同時將這個寫命令追加到AOF緩衝區和AOF重寫緩衝區
2.即子進程在執行AOF重寫時,主進程需要執行以下三個工作:
執行client發來的命令請求;
將寫命令追加到現有的AOF文件中;
將寫命令追加到AOF重寫緩存中。
最後記錄下我的思考
1.寫文件是很耗時的,數據多時還會出現阻塞。熟悉操作系統的同學會有個印象,數據從緩衝區到磁盤的過程也要經過內 核態到用戶態的轉換出現上下文切換,這個過程會很長,(Netty零拷貝可以看看)
2.Redis設計初衷就是快,10W+的QPS 也就是1ms就要處理100個命令,阻塞對於redis無疑是不可接受的,如果你是 redis作者會怎麼解決呢,很簡單的能想到就是同步轉異步嘛,讓會產生阻塞或者耗時的操作由子線程去幹就好了, 主進程就專注處理客戶端請求就好了(重點記憶,主進程只跟客戶端打交道,其他全交給子進程),專業的人幹專業 的事,人類發展史中 分工產生效能 也是這個思想。
3.追加阻塞這裏,爲什麼不採用隊列或多線程的方式來讓主進程迅速返回?考慮是 增加額外的處理肯定要增加技術複雜 度和資源的消耗,。。。
4.其實最後發現並沒有什麼高大上的解決方案對不對。基礎真的很重要呀。並且優化是從各個細節來的,也是個持續的 過程,成熟的架構都是慢慢演進的,
最後拋幾個問題看看掌握沒有
1.redis是單線程麼?
2.爲什麼要子進程呢?因爲萬一重寫失敗。不會造成redis的死掉
3.爲什麼使用子進程而不是線程?可以避免在鎖的情況下,保證數據的安全性。
4.爲什麼主子公用內存空間? 省空間,redis是內存數據庫,留着內存幹正事不香麼
redis配置參數很全的一篇