redis 簡單整理——持久化的問題定位和優化[二十一]
前言
Redis持久化功能一直是影響Redis效能的高發地,簡單介紹一下持久化的問題定位和優化。
正文
當Redis做RDB或AOF重寫時,一個必不可少的操作就是執行fork操作創 建子程序,對於大多數作業系統來說fork是個重量級錯誤。
雖然fork建立的 子程序不需要拷貝父程序的實體記憶體空間,但是會複製父程序的空間記憶體頁表。
例如對於10GB的Redis程序,需要複製大約20MB的記憶體頁表,因此fork 操作耗時跟程序總記憶體量息息相關,如果使用虛擬化技術,特別是Xen虛擬 機,fork操作會更耗時。
fork耗時問題定位:對於高流量的Redis例項OPS可達5萬以上,如果fork 操作耗時在秒級別將拖慢Redis幾萬條命令執行,對線上應用延遲影響非常 明顯。正常情況下fork耗時應該是每GB消耗20毫秒左右。可以在info stats統 計中查latest_fork_usec指標獲取最近一次fork操作耗時,單位微秒。
如何改善fork操作的耗時:
1)優先使用物理機或者高效支援fork操作的虛擬化技術,避免使用 Xen。
2)控制Redis例項最大可用記憶體,fork耗時跟記憶體量成正比,線上建議 每個Redis例項記憶體控制在10GB以內。
3)合理配置Linux記憶體分配策略,避免實體記憶體不足導致fork失敗
4)降低fork操作的頻率,如適度放寬AOF自動觸發時機,避免不必要 的全量複製等。
子程序負責AOF或者RDB檔案的重寫,它的執行過程主要涉及CPU、記憶體、硬碟三部分的消耗。
子程序開銷監控和優化
子程序負責AOF或者RDB檔案的重寫,它的執行過程主要涉及CPU、內 存、硬碟三部分的消耗。
cpu
CPU開銷分析。子程序負責把程序內的資料分批寫入檔案,這個過程 屬於CPU密集操作,通常子程序對單核CPU利用率接近90%.
CPU消耗優化。Redis是CPU密集型服務,不要做繫結單核CPU操作。 由於子程序非常消耗CPU,會和父程序產生單核資源競爭。
不要和其他CPU密集型服務部署在一起,造成CPU過度競爭。
如果部署多個Redis例項,儘量保證同一時刻只有一個子程序執行重寫工作.
記憶體
·記憶體消耗分析。子程序通過fork操作產生,佔用記憶體大小等同於父進 程,理論上需要兩倍的記憶體來完成持久化操作,但Linux有寫時複製機制 (copy-on-write)。
父子程序會共享相同的實體記憶體頁,當父程序處理寫請 求時會把要修改的頁建立副本,而子程序在fork操作過程中共享整個父程序記憶體快照。
·記憶體消耗監控。RDB重寫時,Redis日誌輸出容如下:
* Background saving started by pid 7692 * DB saved on disk * RDB: 5 MB of memory used by copy-on-write * Background saving terminated with success
如果重寫過程中存在記憶體修改操作,父程序負責建立所修改記憶體頁的副 本,從日誌中可以看出這部分記憶體消耗了5MB,可以等價認為RDB重寫消耗 了5MB的記憶體。
AOF重寫時,Redis日誌輸出容如下:
* Background append only file rewriting started by pid 8937
* AOF rewrite child asks to stop sending diffs.
* Parent agreed to stop sending diffs. Finalizing AOF... * Concatenating 0.00 MB of AOF diff received from parent.
* SYNC append only file rewrite performed
* AOF rewrite: 53 MB of memory used by copy-on-write
* Background AOF rewrite terminated with success
* Residual parent diff successfully flushed to the rewritten AOF (1.49 MB)
* Background AOF rewrite finished successfully
父程序維護頁副本消耗同RDB重寫過程類似,不同之處在於AOF重寫需 要AOF重寫緩衝區,因此根據以上日誌可以預估記憶體消耗為: 53MB+1.49MB,也就是AOF重寫時子程序消耗的記憶體量。
編寫shell指令碼根據Redis日誌可快速定位子程序重寫期間記憶體過度消耗情況。
記憶體消耗優化:
1)同CPU優化一樣,如果部署多個Redis例項,儘量保證同一時刻只有一個子程序在工作
2)避免在大量寫入時做子程序重寫操作,這樣將導致父程序維護大量 頁副本,造成記憶體消耗。
Linux kernel在2.6.38核心增加了Transparent Huge Pages(THP),支援 huge page(2MB)的頁分配,預設開啟。
當開啟時可以降低fork建立子程序 的速度,但執行fork之後,如果開啟THP,複製頁單位從原來4KB變為 2MB,會大幅增加重寫期間父程序記憶體消耗。
建議設定“sudo echo never>/sys/kernel/mm/transparent_hugepage/enabled”關閉THP。
硬碟:
·硬碟開銷分析。子程序主要職責是把AOF或者RDB檔案寫入硬碟持久化。勢必造成硬碟寫入壓力。根據Redis重寫AOF/RDB的資料量,結合系統 工具如sar、iostat、iotop等,可分析出重寫期間硬碟負載情況。
·硬碟開銷優化。優化方法如下:
a)不要和其他高硬碟負載的服務部署在一起。如:儲存服務、訊息隊 列服務等。
b)AOF重寫時會消耗大量硬碟IO,可以開啟配置no-appendfsync-on- rewrite,預設關閉。表示在AOF重寫期間不做fsync操作。
c)當開啟AOF功能的Redis用於高流量寫入場景時,如果使用普通機械 磁碟,寫入吞吐一般在100MB/s左右,這時Redis例項的瓶頸主要在AOF同步硬碟上。
d)對於單機配置多個Redis例項的情況,可以配置不同例項分盤儲存 AOF檔案,分攤硬碟寫入壓力。
配置no-appendfsync-on-rewrite=yes時,在極端情況下可能丟失整個AOF 重寫期間的資料,需要根據資料安全性決定是否配置。
當開啟AOF持久化時,常用的同步硬碟的策略是everysec,用於平衡效能和資料安全性。對於這種方式,Redis使用另一條執行緒每秒執行fsync同步硬碟。當系統硬碟資源繁忙時,會造成Redis主執行緒阻塞.
如下圖:
阻塞流程分析:
1)主執行緒負責寫入AOF緩衝區。
2)AOF執行緒負責每秒執行一次同步磁碟操作,並記錄最近一次同步時 間。
3)主執行緒負責對比上次AOF同步時間:
·如果距上次同步成功時間在2秒內,主執行緒直接返回。
·如果距上次同步成功時間超過2秒,主執行緒將會阻塞,直到同步操作完成。
通過對AOF阻塞流程可以發現兩個問題:
1)everysec配置最多可能丟失2秒資料,不是1秒。
2)如果系統fsync緩慢,將會導致Redis主執行緒阻塞影響效率
AOF阻塞問題定位:
1)發生AOF阻塞時,Redis輸出如下日誌,用於記錄AOF fsync阻塞導致 拖慢Redis服務的行為:
Asynchronous AOF fsync is taking too long (disk is busy). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis
2)每當發生AOF追加阻塞事件發生時,在info Persistence統計中, aof_delayed_fsync指標會累加,檢視這個指標方便定位AOF阻塞問題。
3)AOF同步最多允許2秒的延遲,當延遲發生時說明硬碟存在高負載問 題,可以通過監控工具如iotop,定位消耗硬碟IO資源的程序。
多例項部署
Redis單執行緒架構導致無法充分利用CPU多核特性,通常的做法是在一 臺機器上部署多個Redis例項。當多個例項開啟AOF重寫後,彼此之間會產 生對CPU和IO的競爭。本節主要介紹針對這種場景的分析和優化。
Redis在info Persistence中為我們提供了監控 子程序執行狀況的度量指標:
1)外部程式定時輪詢監控機器(machine)上所有Redis例項。
2)對於開啟AOF的例項,檢視(aof_current_size- aof_base_size)/aof_base_size確認增長率。
3)當增長率超過特定閾值(如100%),執行bgrewriteaof命令手動觸發 當前例項的AOF重寫。
4)執行期間迴圈檢查aof_rewrite_in_progress和 aof_current_rewrite_time_sec指標,直到AOF重寫結束。
5)確認例項AOF重寫完成後,再檢查其他例項並重復2)~4)步操作。 從而保證機器內每個Redis例項AOF重寫序列化執行。
總結
1)Redis提供了兩種持久化方式:RDB和AOF。
2)RDB使用一次性生成記憶體快照的方式,產生的檔案緊湊壓縮比更 高,因此讀取RDB恢復速度更快。由於每次生成RDB開銷較大,無法做到實 時持久化,一般用於資料冷備和複製傳輸。
3)save命令會阻塞主執行緒不建議使用,bgsave命令通過fork操作建立子 程序生成RDB避免阻塞。
4)AOF通過追加寫命令到檔案實現持久化,通過appendfsync引數可以 控制實時/秒級持久化。因為需要不斷追加寫命令,所以AOF檔案體積逐漸 變大,需要定期執行重寫操作來降低檔案體積。
5)AOF重寫可以通過auto-aof-rewrite-min-size和auto-aof-rewrite- percentage引數控制自動觸發,也可以使用bgrewriteaof命令手動觸發。
6)子程序執行期間使用copy-on-write機制與父程序共享記憶體,避免內 存消耗翻倍。AOF重寫期間還需要維護重寫緩衝區,儲存新的寫入命令避免 資料丟失。
7)持久化阻塞主執行緒場景有:fork阻塞和AOF追加阻塞。fork阻塞時間 跟記憶體量和系統有關,AOF追加阻塞說明硬碟資源緊張。
8)單機下部署多個例項時,為了防止出現多個子程序執行重寫操作, 建議做隔離控制,避免CPU和IO資源競爭。
結
下一節,介紹redis的複製。