Redis核心技術筆記05-06

IT小馬發表於2023-01-05

05 記憶體快照

把記憶體中的資料在某一時刻的狀態以檔案的形式寫到磁碟上就是快照,這個快照檔案就稱為RDB(Redis DataBase)檔案。

和 AOF 相比,RDB 記錄的是某一時刻的資料,並不是操作,所以,在做資料恢復時,我們可以直接把 RDB 檔案讀入記憶體,很快地完成恢復。

Redis RDB快照是全量快照,會將所有資料儲存到磁碟中。

對於Redis的單執行緒模型,要儘量避免所有會阻塞主執行緒的操作。

生成方式

Redis 提供了兩個命令來生成 RDB 檔案,分別是 save 和 bgsave:

  • save:在主執行緒中執行,會導致阻塞;
  • bgsave:建立一個子程式,專門用於寫入 RDB 檔案,避免了主執行緒的阻塞,這也是 Redis RDB 檔案生成的預設配置。

寫時複製

如果快照執行期間資料不能被修改,會給業務服務造成巨大的影響。
Redis 就會藉助作業系統提供的寫時複製技術(Copy-On-Write, COW),在執行快照的同時,正常處理寫操作。

原理:

  1. bgsave 子程式是由主執行緒 fork 生成的,可以共享主執行緒的所有記憶體資料。bgsave 子程式執行後,開始讀取主執行緒的記憶體資料,並把它們寫入 RDB 檔案。
  2. 如果主執行緒要修改一塊資料(例如鍵值對 C),那麼,這塊資料就會被複制一份,生成該資料的副本(鍵值對 C’)。
  3. 然後,主執行緒在這個資料副本上進行修改。同時,bgsave 子程式可以繼續把原來的資料(鍵值對 C)寫入 RDB 檔案。

這既保證了快照的完整性,也允許主執行緒同時對資料進行修改,避免了對正常業務的影響。

快照開銷

  1. 增大磁碟頻寬壓力
  2. fork建立過程本身會阻塞主執行緒,而且主執行緒的記憶體越大,阻塞時間越長。

增量快照

Redis 4.0 中提出了一個混合使用 AOF 日誌和記憶體快照的方法:
設定的引數是: aof-use-rdb-preamble yes

  1. 記憶體快照以一定的頻率執行
  2. 在兩次快照之間,使用 AOF 日誌記錄這期間的所有命令操作。

小結

  • 資料不能丟失時,記憶體快照和 AOF 的混合使用是一個很好的選擇;
  • 如果允許分鐘級別的資料丟失,可以只使用 RDB;
  • 如果只用 AOF,優先使用 everysec 的配置選項,因為它在可靠性和效能之間取了一個平衡。

問題:
2核4G伺服器Redis資料大小2G,讀寫比2:8的場景,用RDB做持久化的風險?
答案:
a、記憶體資源風險:持久化過程中,“寫時複製”會重新分配整個例項80%的記憶體副本,大約1.6GB記憶體,如果此時父程式又有大量新key寫入,很快機器記憶體就會被吃光,如果機器開啟了Swap機制,那麼Redis會有一部分資料被換到磁碟上,當Redis訪問這部分在磁碟上的資料時,效能會急劇下降,已經達不到高效能的標準(可以理解為武功被廢)。如果機器沒有開啟Swap,會直接觸發OOM,父子程式會面臨被系統kill掉的風險。
b、CPU資源風險:雖然子程式在做RDB持久化,但生成RDB快照過程會消耗大量的CPU資源

06 主從一致

高可靠性

  1. 資料儘量少丟失:AOF和RDB
  2. 服務儘量少中斷:增加副本冗餘量,將一份資料同時儲存在多個例項上

讀寫分離

Redis 提供了主從庫模式,以保證資料副本的一致,主從庫之間採用的是讀寫分離的方式。

  • 讀操作:主庫、從庫都可以接收;
  • 寫操作:首先到主庫執行,然後,主庫將寫操作同步給從庫。

主從同步

啟動多個 Redis 例項時,它們相互透過 replicaof 命令形成主庫和從庫的關係,之後會按照三個階段完成資料的第一次同步。

replicaof  172.16.19.3  6379

1. 建立連線,協商同步

從庫給主庫傳送 psync 命令,表示要進行資料同步,主庫根據這個命令的引數來啟動複製。
psync 命令包含了主庫的 runID 和複製進度 offset 兩個引數:

  • runID,是每個 Redis 例項啟動時都會自動生成的一個隨機 ID,用來唯一標記這個例項。當從庫和主庫第一次複製時,因為不知道主庫的 runID,所以將 runID 設為“?”。
  • offset,此時設為 -1,表示第一次複製。

主庫收到 psync 命令後,會用 FULLRESYNC 響應命令帶上兩個引數:主庫 runID 和主庫目前的複製進度 offset,返回給從庫。

FULLRESYNC 響應表示第一次複製採用的全量複製,也就是說,主庫會把當前所有的資料都複製給從庫。

2. 主庫同步資料給從庫

主庫將所有資料同步給從庫。從庫收到資料後,在本地完成資料載入。這個過程依賴於記憶體快照生成的 RDB 檔案。

  1. 主庫執行 bgsave 命令,生成 RDB 檔案,接著將檔案發給從庫。
  2. 從庫接收到 RDB 檔案後,會先清空當前資料庫,然後載入 RDB 檔案。
  3. 為了保證主從庫的資料一致性,主庫會在記憶體中用專門的 replication buffer,記錄 RDB 檔案生成後收到的所有寫操作。

3. 主庫傳送新寫命令給從庫

主庫會把第二階段執行過程中新收到的寫命令,再傳送給從庫。
具體的操作是,當主庫完成 RDB 檔案傳送後,就會把此時 replication buffer 中的修改操作發給從庫,從庫再重新執行這些操作。

主庫壓力

一次全量複製中,對於主庫來說,需要完成兩個耗時的操作:生成 RDB 檔案和傳輸 RDB 檔案。

  1. 如果從庫數量很多且都要全量複製,fork操作會阻塞主執行緒,導致主庫響應速度變慢
  2. 傳輸 RDB 檔案也會佔用主庫的網路頻寬,同樣會給主庫的資源使用帶來壓力。

主-從-主模式

將主庫生成 RDB 和傳輸 RDB 的壓力,以級聯的方式分散到從庫上:

  1. 選擇一個從庫用於級聯其他從庫
  2. 選擇一些從庫和級聯從庫建立主從關係replicaof 級聯從庫IP 6379

風險點:網路斷連或阻塞。

斷網重連

從 Redis 2.8 開始,網路斷了之後,主從庫會採用增量複製的方式繼續同步。

當主從庫斷連後,主庫會把斷連期間收到的寫操作命令,寫入 replication buffer,同時也會把這些操作命令也寫入 repl_backlog_buffer 這個緩衝區。

repl_backlog_buffer 是一個環形緩衝區,主庫會記錄自己寫到的位置,從庫則會記錄自己已經讀到的位置。

  1. 剛開始的時候,主庫和從庫的寫讀位置在一起,這算是它們的起始位置。
  2. 隨著主庫不斷接收新的寫操作,它在緩衝區中的寫位置會逐步偏離起始位置,我們通常用偏移量來衡量這個偏移距離的大小,對主庫來說,對應的偏移量就是 master_repl_offset。主庫接收的新寫操作越多,這個值就會越大。
  3. 從庫在複製完寫操作命令後,它在緩衝區中的讀位置也開始逐步偏移剛才的起始位置,此時,從庫已複製的偏移量 slave_repl_offset 也在不斷增加。正常情況下,這兩個偏移量基本相等。
  4. 主從庫的連線恢復之後,從庫首先會給主庫傳送 psync 命令,並把自己當前的 slave_repl_offset 發給主庫,主庫會判斷自己的 master_repl_offset 和 slave_repl_offset 之間的差距。
  5. 在增量複製時,主庫只用把 master_repl_offset 和 slave_repl_offset 之間的命令操作同步給從庫就行了。

注意:因為 repl_backlog_buffer 是一個環形緩衝區,所以在緩衝區寫滿後,主庫會繼續寫入,此時,就會覆蓋掉之前寫入的操作。如果從庫的讀取速度比較慢,就有可能導致從庫還未讀取的操作被主庫新寫的操作覆蓋了,這會導致主從庫間的資料不一致。從庫的複製進度趕不上主庫,會導致主庫決定從庫重新進行全量複製。
我們可以調整 repl_backlog_size 這個引數。這個引數和所需的緩衝空間大小有關,預設1M。

緩衝空間的計算公式是:
緩衝空間大小 = 主庫寫入命令速度 操作大小 - 主從庫間網路傳輸命令速度 操作大小。
repl_backlog_size = 緩衝空間大小 * 2。

示例:
如果主庫每秒寫入 2000 個操作,每個操作的大小為 2KB,網路每秒能傳輸 1000 個操作,那麼,有 1000 個操作需要緩衝起來,這就至少需要 2MB 的緩衝空間。
repl_backlog_size應設定為4M。

小結

  1. 一個 Redis 例項的資料庫不要太大,一個例項大小在幾 GB 級別比較合適,這樣可以減少 RDB 檔案生成、傳輸和重新載入的開銷。
  2. 調大repl_backlog_size這個引數,可以減少從庫在網路斷連時全量複製的風險。

問題:AOF 記錄的操作命令更全,相比於 RDB 丟失的資料更少。那麼,為什麼主從庫間的複製不使用 AOF 呢?
回答:

  1. RDB是壓縮過的二進位制資料,檔案很小。
  2. AOF需要選擇檔案刷盤策略,選擇不當會影響redis效能。

相關文章