環(huán)境避坑指南)
看到這個(gè)標(biāo)題我先愣了一下RBD是個(gè)什么鬼以我多年的經(jīng)歷Redis持久化里從來(lái)沒(méi)有RBD這個(gè)說(shuō)法十有八九是把RDB打成RBD了。RDBRedis Database是Redis提供的快照持久化機(jī)制也是生產(chǎn)環(huán)境里用得最多、面試被問(wèn)得最頻繁的一塊內(nèi)容。正好借這個(gè)機(jī)會(huì)把RDB持久化徹底聊透從底層原理到配置實(shí)操再到生產(chǎn)環(huán)境里那些文檔不會(huì)寫明白的坑。這篇博文不是我臨時(shí)翻文檔整理的而是踩過(guò)無(wú)數(shù)坑之后的總結(jié)。早年我還在用Redis當(dāng)純緩存重啟就重啟丟了就丟了直到有一次大促前夜緩存節(jié)點(diǎn)因?yàn)閮?nèi)存碎片問(wèn)題被運(yùn)維重啟第二天高峰期一到數(shù)據(jù)庫(kù)瞬間被打爆我才真正意識(shí)到持久化不是選配功能而是保命功能。所以這篇內(nèi)容適合幾類讀者剛接觸Redis、想搞懂持久化到底是怎么回事的入門者正在做Redis選型和配置的開(kāi)發(fā)者以及已經(jīng)上了生產(chǎn)環(huán)境、想優(yōu)化或排查RDB問(wèn)題的運(yùn)維和后端同學(xué)。我會(huì)把RDB的原理講透把配置一行行拆開(kāi)講再把生產(chǎn)環(huán)境里“怎么用安全、怎么用好”的實(shí)踐經(jīng)驗(yàn)?zāi)贸鰜?lái)。最后還會(huì)附上問(wèn)題排查實(shí)錄都是真實(shí)場(chǎng)景里的典型問(wèn)題。1. 先搞懂RDB為什么存在從一次“緩存全丟”事故說(shuō)起很多教程上來(lái)就講RDB是什么、怎么配置我反而覺(jué)得得先講清楚一個(gè)問(wèn)題RDB到底在解決什么。明白了這個(gè)后續(xù)所有配置、參數(shù)、優(yōu)化方向都會(huì)變得順理成章。1.1 一次真實(shí)的生產(chǎn)事故復(fù)盤有一年我做電商平臺(tái)的架構(gòu)優(yōu)化某個(gè)核心服務(wù)用Redis存著熱銷商品、庫(kù)存扣減結(jié)果和用戶會(huì)話數(shù)據(jù)量不大內(nèi)存峰值也就600MB左右。因?yàn)闅v史原因這個(gè)Redis實(shí)例沒(méi)開(kāi)持久化大家都覺(jué)得“反正是緩存掛了就掛了最多回源一次”。結(jié)果一次例行升級(jí)服務(wù)器需要重啟Redis一停再一起來(lái)整個(gè)實(shí)例空空如也。可怕的地方在于這臺(tái)Redis早就被當(dāng)成“緩存準(zhǔn)存儲(chǔ)”在用了調(diào)用方代碼里大量用了“先查Redis查不到就不查庫(kù)”的寫法所以緩存一丟所有請(qǐng)求全部穿透到MySQL。不到兩分鐘數(shù)據(jù)庫(kù)連接數(shù)被打滿一堆慢查詢把CPU頂?shù)?00%接口可用性直接掉到個(gè)位數(shù)。復(fù)盤的時(shí)候所有人的第一反應(yīng)都是“等緩存自己慢慢回填”可問(wèn)題是寫代碼的人根本沒(méi)做回填保護(hù)數(shù)據(jù)庫(kù)扛不住瞬間流量整個(gè)服務(wù)雪崩。那次事故之后我把系統(tǒng)里所有Redis實(shí)例全部梳理了一遍該開(kāi)持久化的一律開(kāi)上并且每周做一次恢復(fù)演練。這里想說(shuō)的是Redis持久化從來(lái)不是給“存儲(chǔ)型Redis”準(zhǔn)備的只要你把數(shù)據(jù)寫進(jìn)Redis就得假設(shè)“重啟之后這份數(shù)據(jù)還有用”按這個(gè)標(biāo)準(zhǔn)來(lái)設(shè)計(jì)才不至于出事。1.2 RDB核心原理快照不是“備份”而是“寫時(shí)復(fù)制”RDB的原理一句話說(shuō)就是把內(nèi)存里的全部數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)做一次完整的二進(jìn)制快照寫到磁盤上的一個(gè)文件里默認(rèn)叫 dump.rdb。這個(gè)“某個(gè)時(shí)間點(diǎn)”很關(guān)鍵它決定了RDB不是實(shí)時(shí)的而是按策略觸發(fā)的。觸發(fā)方式分為手動(dòng)和自動(dòng)兩類手動(dòng)就是執(zhí)行SAVE或者BGSAVE命令自動(dòng)就是在redis.conf里配置save規(guī)則比如“900秒內(nèi)至少有1個(gè)key發(fā)生變化就觸發(fā)一次快照”。那么問(wèn)題來(lái)了Redis是單線程處理命令的如果做快照要把內(nèi)存數(shù)據(jù)全部遍歷一遍這個(gè)過(guò)程中還能不能正常服務(wù)客戶端請(qǐng)求這里就要講到RDB的核心機(jī)制fork 寫時(shí)復(fù)制Copy On Write以下簡(jiǎn)稱COW。當(dāng)執(zhí)行BGSAVE時(shí)Redis主進(jìn)程會(huì)通過(guò)fork系統(tǒng)調(diào)用創(chuàng)建一個(gè)子進(jìn)程子進(jìn)程與主進(jìn)程共享同一份內(nèi)存頁(yè)。子進(jìn)程負(fù)責(zé)把這份共享快照寫入磁盤主進(jìn)程繼續(xù)處理客戶端請(qǐng)求。關(guān)鍵點(diǎn)在于如果主進(jìn)程此時(shí)沒(méi)有寫操作那么子進(jìn)程寫的就是完整內(nèi)存數(shù)據(jù)。可一旦主進(jìn)程收到寫命令需要修改某個(gè)內(nèi)存頁(yè)操作系統(tǒng)就會(huì)先把這塊內(nèi)存復(fù)制一份出來(lái)給主進(jìn)程用子進(jìn)程手里的仍是舊版本的那塊頁(yè)。這樣兩邊數(shù)據(jù)互不干擾子進(jìn)程就能生成一個(gè)一致性快照。生活里你就能找到類似的例子。你去打印店復(fù)印一堆材料打印機(jī)工作的時(shí)候你還能繼續(xù)修改手上的原稿打印出來(lái)的都是你按下開(kāi)始鍵那一刻的版本。新寫的字不會(huì)出現(xiàn)在打印結(jié)果里這就是“快照是某個(gè)時(shí)間點(diǎn)的狀態(tài)”的含義。理解COW機(jī)制后續(xù)看BGSAVE導(dǎo)致的阻塞、磁盤IO等問(wèn)題就會(huì)很清楚真正讓你擔(dān)心的不是快照本身多耗時(shí)而是fork瞬間和寫操作劇烈時(shí)COW帶來(lái)的內(nèi)存開(kāi)銷和系統(tǒng)負(fù)載。1.3 快照與AOF的本質(zhì)差異既然講了RDB就繞不開(kāi)AOFAppend Only File。很多新手搞不清兩者區(qū)別我習(xí)慣用一句話區(qū)分RDB存的是“某一時(shí)刻的數(shù)據(jù)狀態(tài)”AOF存的是“從啟動(dòng)至今的每一條寫命令”。一個(gè)是結(jié)果一個(gè)是過(guò)程。對(duì)比項(xiàng)RDB快照AOF日志存儲(chǔ)內(nèi)容二進(jìn)制數(shù)據(jù)快照可讀的命令文本文件體積小、緊湊通常比RDB大恢復(fù)速度快直接加載快照慢需要逐條執(zhí)行命令數(shù)據(jù)丟失窗口取決于觸發(fā)頻率可能丟最近一次快照后的數(shù)據(jù)取決于刷盤策略默認(rèn)每秒刷盤最多丟1秒數(shù)據(jù)對(duì)性能的影響當(dāng)bgsave時(shí)CPU/內(nèi)存/磁盤有開(kāi)銷持續(xù)寫入有CPU和磁盤開(kāi)銷使用場(chǎng)景數(shù)據(jù)量大、允許少量丟失、需要快速恢復(fù)對(duì)數(shù)據(jù)完整性要求高、不能丟數(shù)據(jù)生產(chǎn)環(huán)境里我見(jiàn)過(guò)很多團(tuán)隊(duì)盲目把兩個(gè)都打開(kāi)結(jié)果機(jī)器負(fù)載蹭蹭漲。實(shí)際上兩者各有適合的場(chǎng)景如果你只是拿Redis做緩存丟了能回源那RDB夠用如果里面有訂單、優(yōu)惠券、計(jì)數(shù)器之類不能丟的數(shù)據(jù)那就得AOF或者RDBAOF組合。這個(gè)選型問(wèn)題后面專門講。2. 實(shí)操配置與核心參數(shù)從零到落地的完整過(guò)程原理搞懂了接下來(lái)就是把RDB真正配置到你的實(shí)例里。這一節(jié)我按配置文件逐行拆解再演示手動(dòng)觸發(fā)和數(shù)據(jù)恢復(fù)的完整流程。2.1 redis.conf里與RDB有關(guān)的參數(shù)一行行拆給你看默認(rèn)安裝好Redis之后redis.conf里自帶了一套R(shí)DB相關(guān)的配置但默認(rèn)值只適合開(kāi)發(fā)環(huán)境生產(chǎn)環(huán)境需要仔細(xì)調(diào)。我們先看最核心的幾個(gè)save 900 1 save 300 10 save 60 10000這個(gè)格式很多新手看不懂我解釋一下save seconds changes意思是“在seconds秒內(nèi)如果至少有changes個(gè)key發(fā)生了變化就觸發(fā)一次BGSAVE”。上面三行合起來(lái)就是900秒15分鐘內(nèi)至少有1次寫操作就做一次快照300秒5分鐘內(nèi)至少有10次寫操作就做一次快照60秒1分鐘內(nèi)至少有10000次寫操作就做一次快照。這種多層配置的含義就是讓快照頻率跟寫入頻率聯(lián)動(dòng)數(shù)據(jù)變化越頻繁備份就做得越勤絕不讓數(shù)據(jù)丟失窗口擴(kuò)大到不可接受。接下來(lái)是和RDB文件本身相關(guān)的參數(shù)dbfilename dump.rdb dir /var/lib/redis rdbcompression yes rdbchecksum yesdbfilename是快照文件名dir是快照存放目錄這兩項(xiàng)決定了你恢復(fù)數(shù)據(jù)時(shí)去哪里找文件。rdbcompression表示是否用LZF算法壓縮RDB文件壓縮可以顯著減小文件體積對(duì)恢復(fù)速度影響不大我建議保持開(kāi)啟。rdbchecksum表示是否對(duì)RDB文件做CRC64校驗(yàn)開(kāi)啟后會(huì)增加一些寫入開(kāi)銷但能及早發(fā)現(xiàn)文件損壞生產(chǎn)環(huán)境建議開(kāi)啟。還有一個(gè)參數(shù)容易被人忽略但生產(chǎn)環(huán)境特別重要stop-writes-on-bgsave-error yes這個(gè)參數(shù)的意思是如果BGSAVE執(zhí)行失敗比如磁盤滿了、權(quán)限錯(cuò)誤Redis是否還繼續(xù)接受寫請(qǐng)求。默認(rèn)是yes即一旦快照保存失敗Redis會(huì)拒絕所有寫操作。有人覺(jué)得這個(gè)設(shè)計(jì)太粗暴但官方這么做的理由很充分如果快照失敗你不停止寫入意味著數(shù)據(jù)正在悄然丟失而所有外部系統(tǒng)還以為Redis是可靠的。生產(chǎn)環(huán)境我建議就保持默認(rèn)yes配合監(jiān)控在BGSAVE失敗時(shí)第一時(shí)間告警把問(wèn)題暴露出來(lái)而不是讓它埋著。2.2 手動(dòng)觸發(fā)RDB快照的三種方式及適用場(chǎng)景自動(dòng)觸發(fā)是日常主力但有時(shí)你需要手動(dòng)制造一份快照比如發(fā)布前備份、遷移前導(dǎo)出。手動(dòng)觸發(fā)有三種方式第一種執(zhí)行SAVE命令。SAVE是同步的Redis主進(jìn)程會(huì)被阻塞執(zhí)行期間所有客戶端請(qǐng)求都得不到響應(yīng)。數(shù)據(jù)量大時(shí)這個(gè)過(guò)程可能長(zhǎng)達(dá)好幾秒甚至更久。因此SAVE只適用于數(shù)據(jù)量極小、能接受停服備份的場(chǎng)景。我在本地開(kāi)發(fā)時(shí)會(huì)用生產(chǎn)環(huán)境絕不用。第二種執(zhí)行BGSAVE命令。BGSAVE是異步的立即返回Redis在后臺(tái)fork子進(jìn)程完成快照。這也是我平時(shí)用得最多的方式。執(zhí)行后用LASTSAVE命令查看上次成功生成快照的時(shí)間確認(rèn)真實(shí)執(zhí)行了。第三種通過(guò)SHUTDOWN SAVE命令關(guān)閉Redis時(shí)Redis會(huì)先執(zhí)行一次SAVE快照再退出。如果你沒(méi)配AOF正常關(guān)閉時(shí)最好用這個(gè)命令確保干凈退出且不丟數(shù)據(jù)。否則如果直接kill -9內(nèi)存里最后一次快照之后的數(shù)據(jù)就沒(méi)了。順便說(shuō)一下從Redis 2.6開(kāi)始支持BGSAVE的同時(shí)還支持異步AOF重寫可以并行執(zhí)行不過(guò)那是AOF相關(guān)的話題。RDB這邊你只需要記住生產(chǎn)環(huán)境手動(dòng)備份一律用BGSAVE。2.3 數(shù)據(jù)恢復(fù)的標(biāo)準(zhǔn)流程從dump.rdb到重新上線RDB恢復(fù)說(shuō)白了就是把dump.rdb放回配置的dir目錄下啟動(dòng)Redis數(shù)據(jù)自動(dòng)加載。但“放回去”這件事在生產(chǎn)環(huán)境常常會(huì)出各種幺蛾子。標(biāo)準(zhǔn)流程是先把Redis停掉防止進(jìn)程還在寫文件導(dǎo)致文件不一致。然后確認(rèn)你手里的dump.rdb文件完整且是最新的必要時(shí)用redis-check-rdb校驗(yàn)一遍后面會(huì)細(xì)說(shuō)。再把文件放到配置文件里dir指定的目錄文件名必須和dbfilename完全一致。確保Redis進(jìn)程對(duì)目錄有讀寫權(quán)限。最后啟動(dòng)Redis觀察啟動(dòng)日志。如果出現(xiàn)類似“DB loaded from disk”的日志說(shuō)明加載成功。啟動(dòng)后隨手執(zhí)行一條命令驗(yàn)證數(shù)據(jù)redis-cli DBSIZE把輸出的數(shù)量和你備份時(shí)的預(yù)期對(duì)比一下數(shù)量對(duì)得上說(shuō)明恢復(fù)成功。如果數(shù)量差很多就要檢查是不是加載了舊文件或者dir配置指向了別的目錄?;謴?fù)做多了之后我養(yǎng)成了一個(gè)習(xí)慣每次配置Redis都會(huì)記下當(dāng)前實(shí)例的CONFIG GET dir輸出因?yàn)橛械娜讼矚g相對(duì)路徑有的人喜歡絕對(duì)路徑而且多實(shí)例部署時(shí)不同實(shí)例的dir可能還不一樣??慈罩厩跋却_認(rèn)路徑能少走很多彎路。2.4 小實(shí)驗(yàn)親眼驗(yàn)證BGSAVE不阻塞業(yè)務(wù)光說(shuō)BGSAVE不阻塞不好使不如動(dòng)手驗(yàn)證一遍。我建議你在測(cè)試環(huán)境做這個(gè)小實(shí)驗(yàn)先往Redis里灌幾萬(wàn)條數(shù)據(jù)然后用redis-cli執(zhí)行BGSAVE同時(shí)用另一個(gè)終端快速執(zhí)行SET命令觀察延遲。再用SAVE做同樣的測(cè)試你會(huì)發(fā)現(xiàn)SAVE期間整個(gè)Redis瞬間沒(méi)有響應(yīng)。這個(gè)對(duì)比能讓你銘記SAVE和BGSAVE的使用邊界。再深入一點(diǎn)你可以在BGSAVE執(zhí)行期間用INFO persistence命令觀察輸出rdb_bgsave_in_progress:1 rdb_current_bgsave_time_sec:3這兩個(gè)字段表示當(dāng)前是否正在生成快照、已經(jīng)耗時(shí)多久。如果耗時(shí)異常長(zhǎng)說(shuō)明可能有大key或者系統(tǒng)負(fù)載很高這就牽涉到生產(chǎn)環(huán)境優(yōu)化的問(wèn)題了。3. 生產(chǎn)環(huán)境最佳實(shí)踐RDB不該“裸奔”配置完RDB你以為就高枕無(wú)憂了差遠(yuǎn)了。生產(chǎn)環(huán)境里RDB只是第一條防線真正讓你數(shù)據(jù)安全的是一整套配套機(jī)制。3.1 備份要有“異地”意識(shí)dump.rdb要定期離開(kāi)服務(wù)器我看到太多團(tuán)隊(duì)Redis和它在同個(gè)機(jī)房連備份都放在同一臺(tái)機(jī)器上更有人把dump.rdb存在系統(tǒng)盤里。結(jié)果服務(wù)器磁盤故障備份和應(yīng)用活在一起一起去見(jiàn)上帝。RDB文件應(yīng)該定期復(fù)制到另一臺(tái)物理機(jī)、獨(dú)立存儲(chǔ)或云對(duì)象存儲(chǔ)上。做法不復(fù)雜寫個(gè)cron腳本每天凌晨把dump.rdb用scp、rsync等方式傳到備份中心同時(shí)對(duì)歷史備份做保留策略比如保留最近7天、最近4周各一份、最近6個(gè)月每月一份。只備份還不夠我見(jiàn)過(guò)最魔幻的場(chǎng)景是某團(tuán)隊(duì)遠(yuǎn)程拷貝了dump.rdb做災(zāi)備三年了從沒(méi)恢復(fù)驗(yàn)證過(guò)。真到機(jī)房故障那天發(fā)現(xiàn)備份文件早就損壞了或者Redis版本升級(jí)不兼容備份文件。所以我堅(jiān)持“備份三步走”定期備份、定期校驗(yàn)、定期演練。3.2 官方推薦組合RDB AOF怎么搭配才合理Redis官方文檔其實(shí)不建議你在生產(chǎn)環(huán)境只用RDB或只用AOF而是推薦兩者結(jié)合RDB用于快速恢復(fù)和冷備份AOF保證數(shù)據(jù)完整性。原因在于如果只有RDB重啟后加載的是最后一次快照的數(shù)據(jù)之后到宕機(jī)前這段時(shí)間的數(shù)據(jù)全丟了。如果只有AOF重啟后雖然數(shù)據(jù)完整但AOF文件通常很大恢復(fù)時(shí)要逐條執(zhí)行命令大實(shí)例可能要幾分鐘這個(gè)時(shí)間窗口服務(wù)是不可用的。RDBAOF的組合在Redis重啟時(shí)優(yōu)先加載AOF文件因?yàn)锳OF里記錄的數(shù)據(jù)通常比RDB新。但同時(shí)保留RDB文件一是可以做冷備二是當(dāng)AOF文件損壞時(shí)還能用RDB兜底恢復(fù)。我的建議是按數(shù)據(jù)重要性分檔處理純緩存業(yè)務(wù)只開(kāi)RDB訂單、庫(kù)存等核心數(shù)據(jù)RDBAOF都開(kāi)且AOF刷盤策略選everysec最后一檔數(shù)據(jù)至關(guān)重要又不能容忍丟失用Redis主從加AOF的always策略但要做好性能開(kāi)銷的準(zhǔn)備。3.3 主從復(fù)制里的RDB全量同步的隱形依賴很多人沒(méi)注意到RDB不只是主節(jié)點(diǎn)自己用來(lái)持久化的主從復(fù)制的全量同步階段也在用RDB。當(dāng)你給Redis主節(jié)點(diǎn)新掛一個(gè)從節(jié)點(diǎn)時(shí)主節(jié)點(diǎn)會(huì)做一次BGSAVE然后把生成的RDB文件傳給從節(jié)點(diǎn)從節(jié)點(diǎn)加載這份RDB實(shí)現(xiàn)數(shù)據(jù)對(duì)齊。理解了這一點(diǎn)你就能在生產(chǎn)環(huán)境里避開(kāi)一些坑。比如主節(jié)點(diǎn)正在內(nèi)存吃緊的時(shí)候你給集群加從節(jié)點(diǎn)主節(jié)點(diǎn)fork生成RDBCOW機(jī)制把內(nèi)存占用翻倍很可能直接OOM。又比如主節(jié)點(diǎn)磁盤性能很差全量同步時(shí)RDB寫入慢從節(jié)點(diǎn)遲遲收不到完整數(shù)據(jù)兩個(gè)節(jié)點(diǎn)之間的同步延遲會(huì)越拉越大。增購(gòu)節(jié)點(diǎn)前先看一眼主節(jié)點(diǎn)的內(nèi)存和磁盤IO情況不要在高峰期做擴(kuò)容。如果數(shù)據(jù)量非常大、網(wǎng)絡(luò)帶寬又不愁可以打開(kāi)repl-diskless-sync yes讓主節(jié)點(diǎn)直接把RDB快照通過(guò)網(wǎng)絡(luò)流向從節(jié)點(diǎn)不落盤減少磁盤壓力。3.4 性能提優(yōu)如何避免BGSAVE“卡”住線上業(yè)務(wù)RDB最讓人頭疼的問(wèn)題是fork瞬間可能造成的服務(wù)停頓。理論上來(lái)講fork之后主進(jìn)程繼續(xù)正常工作但fork本身是要復(fù)制頁(yè)表等內(nèi)核數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)量越大fork耗時(shí)越長(zhǎng)。我見(jiàn)過(guò)一個(gè)32GB的Redis實(shí)例fork花了將近800毫秒期間明顯出現(xiàn)了請(qǐng)求毛刺。優(yōu)化方向有這么幾個(gè)控制單實(shí)例內(nèi)存上限。別把Redis當(dāng)成無(wú)限大的內(nèi)存容器單實(shí)例超過(guò)10GB后Fork開(kāi)銷就相當(dāng)可觀建議拆實(shí)例或用集群分片。避免在短時(shí)間集中大量寫入。COW機(jī)制下寫操作越多復(fù)制內(nèi)存頁(yè)就越多內(nèi)存消耗和磁盤寫入量都翻倍。把你那些批處理腳本、定時(shí)任務(wù)錯(cuò)峰執(zhí)行不要和自動(dòng)快照時(shí)間重合。監(jiān)控系統(tǒng)負(fù)載磁盤IO到瓶頸時(shí)BGSAVE耗時(shí)會(huì)劇增觸發(fā)風(fēng)險(xiǎn)成倍放大。Redis 7.0之后BGSAVE的實(shí)現(xiàn)做了不少優(yōu)化對(duì)Fork開(kāi)銷的緩解有幫助。但算法改進(jìn)不能替代架構(gòu)規(guī)劃基礎(chǔ)的內(nèi)存上限和寫入節(jié)奏控制任何時(shí)候都得做。3.5 別忘了安全這道底線RDB文件是敏感數(shù)據(jù)里面存放的數(shù)據(jù)可能包含用戶信息、訂單數(shù)據(jù)、業(yè)務(wù)密鑰。我見(jiàn)過(guò)有團(tuán)隊(duì)把Redis綁定在0.0.0.0還開(kāi)著默認(rèn)端口RDB文件直接放在一個(gè)所有人都能讀的目錄里。這隱患非常大。生產(chǎn)環(huán)境建議把dump.rdb放到權(quán)限受限的目錄一般用redis用戶運(yùn)行Redis時(shí)把目錄owner設(shè)為redis、權(quán)限設(shè)為750。如果數(shù)據(jù)敏感程度較高還要考慮對(duì)RDB文件加密落盤。Redis本身不提供加密但你可以通過(guò)文件系統(tǒng)加密比如加密盤來(lái)實(shí)現(xiàn)。還要防止RDB文件被惡意替換如果攻擊者能控制Redis寫文件路徑理論上可以構(gòu)造一個(gè)惡意RDB讓服務(wù)器啟動(dòng)時(shí)加載執(zhí)行命令。所以Redis的運(yùn)行用戶一定要獨(dú)立且低權(quán)限不能是root。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄寫配置、看文檔只能入門真正加深理解的是處理那些千奇百怪的問(wèn)題。下面這幾個(gè)是我在實(shí)踐中最常遇到、也最具代表性的場(chǎng)景。4.1 RDB文件損壞了怎么辦redis-check-rdb來(lái)救場(chǎng)RDB文件可能在寫入過(guò)程中遇到斷電、磁盤滿、進(jìn)程被殺等情況而損壞。Redis啟動(dòng)時(shí)如果發(fā)現(xiàn)RDB文件不完整會(huì)拒絕啟動(dòng)并報(bào)錯(cuò)。這時(shí)候不要慌用Redis自帶的工具redis-check-rdb來(lái)檢查修復(fù)。用法很簡(jiǎn)單redis-check-rdb /var/lib/redis/dump.rdb它會(huì)掃描整個(gè)文件揪出哪里損壞能修復(fù)的就自動(dòng)修復(fù)生成一個(gè)正常文件。注意如果是硬盤物理壞道導(dǎo)致的文件損壞修復(fù)后的數(shù)據(jù)可能仍然不完整所以我在前面的備份策略里強(qiáng)調(diào)“多版本備份”目的就是這種情況下還能有備胎。4.2 磁盤IO暴漲罪魁竟是BGSAVE有段時(shí)間我們的Redis服務(wù)器明明只有每秒幾百次讀寫磁盤IO卻時(shí)不時(shí)沖到100%查了半天沒(méi)頭緒。最后用iostat -x 1盯了幾分鐘發(fā)現(xiàn)每次IO飆升都對(duì)應(yīng)著一個(gè)redis進(jìn)程的寫文件操作再看INFO persistencerdb_last_bgsave_status:ok但rdb_last_bgsave_time_sec高達(dá)30多秒。問(wèn)題出在我們的save配置太激進(jìn)60秒內(nèi)只要1萬(wàn)個(gè)key變化就觸發(fā)快照。當(dāng)時(shí)業(yè)務(wù)正在做數(shù)據(jù)遷移大量寫入導(dǎo)致頻繁觸發(fā)BGSAVE文件又大寫盤時(shí)間成倍增加。解決辦法把自動(dòng)save策略改成了一個(gè)每小時(shí)一次的低頻兜底日常高并發(fā)時(shí)段把寫遷移的熱key先行拆分并用監(jiān)控在BGSAVE執(zhí)行期間重點(diǎn)關(guān)注磁盤IO。調(diào)完之后IO峰值降了80%。4.3 重啟之后數(shù)據(jù)沒(méi)恢復(fù)三個(gè)隱藏原因排查“重啟后數(shù)據(jù)丟失”這種問(wèn)題時(shí)大多數(shù)情況并不是Redis真把你數(shù)據(jù)弄丟了而是文件沒(méi)被正確加載。我總結(jié)了三個(gè)高頻原因。第一個(gè)dir配置不一致。你配置文件里寫的dir是/var/lib/redis但CentOS上用systemd部署時(shí)有些啟動(dòng)腳本會(huì)覆蓋工作目錄實(shí)際找的是別處啟動(dòng)后自然找不到dump.rdb。我總是用CONFIG GET dir確認(rèn)運(yùn)行時(shí)目錄。第二個(gè)文件權(quán)限問(wèn)題。Redis用redis用戶跑的dump.rdb的權(quán)限若是root:rootRedis啟動(dòng)時(shí)雖然能建新文件但舊的加載不了也不會(huì)報(bào)特別明顯的錯(cuò)。排查看一下目錄屬主馬上就能定位。第三個(gè)啟動(dòng)方式不對(duì)。沒(méi)有用SHUTDOWN SAVE或正常SIGTERM關(guān)閉Redis而是直接kill -9強(qiáng)制殺掉最后一次快照之后的數(shù)據(jù)自然就丟了文件本身還在?;謴?fù)時(shí)如果發(fā)現(xiàn)時(shí)間對(duì)不上看下機(jī)器有沒(méi)有異常斷電記錄就能印證是“沒(méi)來(lái)得及寫快照”。4.4 大實(shí)例全量同步慢到懷疑人生怎么破主從全量同步慢是運(yùn)維大Redis時(shí)最常見(jiàn)的問(wèn)題之一。數(shù)據(jù)量上了20GB之后主節(jié)點(diǎn)一邊BGSAVE一邊用網(wǎng)絡(luò)傳文件帶寬和磁盤雙雙打滿一個(gè)從節(jié)點(diǎn)完全同步可能要十幾分鐘期間主節(jié)點(diǎn)對(duì)外服務(wù)質(zhì)量明顯下降。我的處理經(jīng)驗(yàn)是先給主節(jié)點(diǎn)和同步目標(biāo)之間限速用repl-diskless-sync-delay和系統(tǒng)級(jí)帶寬限制錯(cuò)峰同步別讓同步流量跟業(yè)務(wù)流量打架。同步盡量在業(yè)務(wù)低峰期做比如凌晨。如果源實(shí)例太大全量同步太傷可以在添加從節(jié)點(diǎn)之前從備份機(jī)上拿一份較新的RDB文件先啟動(dòng)一個(gè)從節(jié)點(diǎn)把它作為級(jí)聯(lián)同步的中間層把主節(jié)點(diǎn)的壓力卸掉。最后說(shuō)一點(diǎn)RDB全家桶雖然好用但定期檢查版本兼容性問(wèn)題也是有必要的。大版本升級(jí)Redis后老版本RDB文件并不是總能被新版本直接加載先看官方Release Notes再在測(cè)試環(huán)境用真實(shí)備份驗(yàn)證一次能省下很多線上事故。結(jié)語(yǔ)回看我這些年的實(shí)踐ROB持久化這塊最深的感悟就是一句話配置寫對(duì)只是及格能排查、能演練、能因地制宜做取舍才算真正把持久化用明白。每次給團(tuán)隊(duì)做Redis培訓(xùn)我都會(huì)強(qiáng)調(diào)不要等項(xiàng)目重啟了才想起沒(méi)有持久化也不要因?yàn)殚_(kāi)了RDB就安心躺著文件的完整性、備份的合規(guī)性、恢復(fù)的可行性都是需要持續(xù)關(guān)注的事。最后再分享一個(gè)小技巧。我習(xí)慣在每個(gè)Redis實(shí)例的配置里以注釋方式記錄持久化策略的決策原因比如“為什么這里只開(kāi)RDB不開(kāi)AOF”寫清楚當(dāng)初的業(yè)務(wù)判斷。半年后回來(lái)看這些注釋比文檔還管用能幫你快速回憶當(dāng)時(shí)的思路也避免繼任者在不了解背景的情況下胡亂改配置。希望這篇關(guān)于RDB的完整拆解能幫你在生產(chǎn)環(huán)境里把Redis持久化用得明明白白。