18 | 波動(dòng)的響應(yīng)延遲:如何應(yīng)對(duì)變慢的Redis飞蛹?(上)
1、前言
Redis 突然變慢厂抖,不僅影響用戶體驗(yàn)茎毁,而且會(huì)影響數(shù)據(jù)庫(kù)等。
包括在 MySQL 上執(zhí)行一個(gè)寫事務(wù)忱辅,在 Redis 上插入一個(gè)標(biāo)記位七蜘,并通過一個(gè)第三方服務(wù)給用戶發(fā)送一條完成消息。
這三個(gè)操作都需要保證事務(wù)原子性墙懂,所以橡卤,如果此時(shí) Redis 的延遲增加,就會(huì)拖累 AppServer 端整個(gè)事務(wù)的執(zhí)行损搬。這個(gè)事務(wù)一直完成不了碧库,又會(huì)導(dǎo)致 MySQL 上寫事務(wù)占用的資源無法釋放,進(jìn)而導(dǎo)致訪問 MySQL 的其他請(qǐng)求被阻塞巧勤。很明顯嵌灰,Redis 變慢會(huì)帶來嚴(yán)重的連鎖反應(yīng)。
從問題認(rèn)定颅悉、系統(tǒng)性排查沽瞭、應(yīng)對(duì)方案這 3 個(gè)方面分析。
2剩瓶、Redis真的變慢了么驹溃?
1)柒瓣、最直接方法:查看 Redis 的響應(yīng)延遲。
某些時(shí)刻吠架,執(zhí)行時(shí)間突然增長(zhǎng)到幾秒-->變慢
2)、基于當(dāng)前環(huán)境下的 Redis 基線性能做判斷搂鲫,只能在服務(wù)器端直接運(yùn)行傍药,不然有其他因素
#打印 120 秒內(nèi)監(jiān)測(cè)到的最大延遲
-redis-cli --intrinsic-latency 120
Max latency so far: 692 microseconds.
Max latency so far: 915 microseconds.
3、如何應(yīng)對(duì) Redis 變慢魂仍?
Redis 自身的操作特性拐辽、文件系統(tǒng)和操作系統(tǒng),它們是影響 Redis 性能的三大要素擦酌。
4俱诸、Redis 自身操作特性的影響
1)、慢查詢命令--時(shí)間復(fù)雜度是王道
a赊舶、用其他高效命令替代睁搭,比如SSCAN替換SMEMSMEMBERS
b、需要執(zhí)行排序笼平、交集园骆、并集操作時(shí),可以在客戶端完成寓调,而不要用 SORT锌唾、SUNION、SINTER 這些命令夺英,以免拖慢 Redis 實(shí)例晌涕。
c、容易忽略的慢查詢命令KEYS痛悯,因?yàn)?KEYS 命令需要遍歷存儲(chǔ)的鍵值對(duì)余黎,所以操作延時(shí)高,生產(chǎn)環(huán)境不建議用
-KEYS *name*?
1) "lastname"?
2) "firstname"
2)灸蟆、過期Key操作
過期 key 的自動(dòng)刪除機(jī)制驯耻。它是 Redis 用來回收內(nèi)存空間的常用機(jī)制,應(yīng)用廣泛炒考,本身就會(huì)引起 Redis 操作阻塞可缚,導(dǎo)致性能變慢。
Redis 鍵值對(duì)的 key 可以設(shè)置過期時(shí)間斋枢。默認(rèn)情況下帘靡,Redis 每 100 毫秒會(huì)刪除一些過期key,具體的算法如下:
1. 采樣 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 個(gè)數(shù)的 key瓤帚,并將其中過期的key 全部刪除描姚;
2. 如果超過 25% 的 key 過期了涩赢,則重復(fù)刪除的過程,直到過期 key 的比例降至 25% 以下轩勘。?
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 是 Redis 的一個(gè)參數(shù)筒扒,默認(rèn)是 20,那么绊寻,一秒內(nèi)基本有 200 個(gè)過期 key 會(huì)被刪除花墩。這一策略對(duì)清除過期 key、釋放內(nèi)存空間很有幫助澄步。如果每秒鐘刪除 200 個(gè)過期 key冰蘑,并不會(huì)對(duì) Redis 造成太大影響。
如果觸發(fā)了上面這個(gè)算法的第二條村缸,Redis 就會(huì)一直刪除以釋放內(nèi)存空間祠肥。注意,刪除操作是阻塞的梯皿。
怎么觸發(fā)第二條仇箱?頻繁使用帶有相同時(shí)間參數(shù)的 EXPIREAT 命令設(shè)置過期 key,這就會(huì)導(dǎo)致东羹,在同一秒內(nèi)有大量的 key 同時(shí)過期工碾。
解決方案:盡量讓Key不在同一時(shí)間過期,如果一批 key 的確是同時(shí)過期百姓,你還可以在EXPIREAT 和 EXPIRE 的過期時(shí)間參數(shù)上渊额,加上一個(gè)一定大小范圍內(nèi)的隨機(jī)數(shù),這樣垒拢,既保證了 key 在一個(gè)鄰近時(shí)間范圍內(nèi)被刪除旬迹,又避免了同時(shí)過期造成的壓力。
5求类、小結(jié)
首先介紹了 Redis 性能變慢帶來的重要影響奔垦,希望你能充分重視這個(gè)問題。我重點(diǎn)介紹了判斷 Redis 變慢的方法:一個(gè)是看響應(yīng)延遲尸疆,一個(gè)是看基線性能椿猎。
同時(shí),還給了你兩種排查和解決 Redis 變慢這個(gè)問題的方法:
1. 從慢查詢命令開始排查寿弱,并且根據(jù)業(yè)務(wù)需求替換慢查詢命令犯眠;
2. 排查過期 key 的時(shí)間設(shè)置,并根據(jù)實(shí)際使用需求症革,設(shè)置不同的過期時(shí)間筐咧。
性能診斷通常是一件困難的事,所以一定不能毫無目標(biāo)地“亂找”。這節(jié)課的內(nèi)容量蕊,就是排查和解決 Redis 性能變慢的章法铺罢,你一定要按照章法逐一排查,這樣才可能盡快地找出原因残炮。
當(dāng)然韭赘,要真正把 Redis 用好,除了要了解 Redis 本身的原理势就,還要了解和 Redis 交互的各底層系統(tǒng)的關(guān)鍵機(jī)制辞居,包括操作系統(tǒng)和文件系統(tǒng)。通常情況下蛋勺,一些難以排查的問題是 Redis 的用法或設(shè)置和底層系統(tǒng)的工作機(jī)制不協(xié)調(diào)導(dǎo)致的。
19 | 波動(dòng)的響應(yīng)延遲:如何應(yīng)對(duì)變慢的Redis鸠删?(下)
1抱完、前言
文件系統(tǒng)、操作系統(tǒng)對(duì) Redis 性能的影響
一方面:Redis 持久化保存數(shù)據(jù)到磁盤刃泡,依賴于文件系統(tǒng)巧娱,這個(gè)寫會(huì)機(jī)制會(huì)影響 Redis 持久化效率,在持久化過程中烘贴,還會(huì)接收新的請(qǐng)求禁添,持久化效率高低又會(huì)影響 Redis 處理請(qǐng)求的效率。
另一方面:Redis 是內(nèi)存數(shù)據(jù)庫(kù)桨踪,內(nèi)存操作非常頻繁老翘,所以,操作系統(tǒng)的內(nèi)存機(jī)制會(huì)直接影響到 Redis 的處理效率锻离。比如說铺峭,如果 Redis 的內(nèi)存不夠用了,操作系統(tǒng)會(huì)啟動(dòng) swap 機(jī)制汽纠,這就會(huì)直接拖慢 Redis卫键。
2、文件系統(tǒng):AOF 模式
AOF日志提供了三種日志寫回策略:no虱朵、everysec莉炉、always。
這三種寫回策略依賴文件系統(tǒng)的兩個(gè)系統(tǒng)調(diào)用完成碴犬,也就是 write 和 fsync絮宁。
write:只要把日志記錄寫到內(nèi)核緩沖區(qū),就可以返回了服协,并不需要等待日志實(shí)際寫回到磁盤羞福;
fsync:需要把日志記錄寫回到磁盤后才能返回,時(shí)間較長(zhǎng)蚯涮。
寫回策略是 everysec治专、always 時(shí)卖陵,Redis 需要調(diào)用 fsync 把日志寫回磁盤。但是张峰,這兩種寫回策略的具體執(zhí)行情況還不太一樣泪蔫。
everysec 允許1s數(shù)據(jù)丟失,Redis 主線程并不需要確保每個(gè)操作記錄日志都寫回磁盤喘批,而且 fsync 寫回磁盤時(shí)間很長(zhǎng)撩荣,容易阻塞主線程,所以 Redis 采用后臺(tái)子線程異步完成 fsync 的操作饶深。
always?Redis 需要確保每個(gè)操作記錄日志都寫回磁盤餐曹,如果用后臺(tái)子線程異步完成,主線程就無法及時(shí)地知道每個(gè)操作是否已經(jīng)完成敌厘,這就不符合 always 策略要求台猴。所以,always 策略并不使用后臺(tái)子線程來執(zhí)行俱两。
另外饱狂,為了防止 AOF 日志變大,會(huì)進(jìn)行 AOF 日志重寫宪彩,重寫采用的是子線程異步休讳,
潛在風(fēng)險(xiǎn)點(diǎn):重寫會(huì)對(duì)磁盤IO進(jìn)行大量操作,同時(shí)尿孔,fsync 又需要等到數(shù)據(jù)寫到磁盤后才能返回俊柔,所以,當(dāng) AOF 重寫的壓力比較大時(shí)活合,就會(huì)導(dǎo)致 fsync 被阻塞婆咸。雖然 fsync 是由后臺(tái)子線程負(fù)責(zé)執(zhí)行的,但是芜辕,主線程會(huì)監(jiān)控 fsync 的執(zhí)行進(jìn)度尚骄。
當(dāng)主線程使用后臺(tái)子線程執(zhí)行了一次 fsync,需要再次把新接收的操作記錄寫回磁盤時(shí)侵续,如果主線程發(fā)現(xiàn)上一次的 fsync 還沒有執(zhí)行完倔丈,那么它就會(huì)阻塞。所以状蜗,如果后臺(tái)子線程執(zhí)行的 fsync 頻繁阻塞的話(比如 AOF 重寫占用了大量的磁盤 IO 帶寬)需五,主線程也會(huì)阻塞,導(dǎo)致 Redis 性能變慢轧坎。
由于 fsync 后臺(tái)子線程和 AOF 重寫子進(jìn)程的存在宏邮,主IO 線程一般不會(huì)被阻塞。但是,如果在重寫日志時(shí)蜜氨,AOF 重寫子進(jìn)程的寫入量比較大械筛,fsync 線程也會(huì)被阻塞,進(jìn)而阻塞主線程飒炎,導(dǎo)致延遲增加埋哟。
關(guān)于 AOF 模式問題的排查跟解決建議
1)、首先郎汪,你檢查 Redis 配置文件中的 appendfsync 配置項(xiàng)赤赊,查詢 Redis 實(shí)例使用的 AOF 日志寫回策略。
2)煞赢、如果 AOF 寫回策略使用了 everysec 或 always 配置抛计,先確認(rèn)業(yè)務(wù)方對(duì)數(shù)據(jù)可靠性要求,明確是否需要每一秒或每一個(gè)操作都記日志照筑。是否設(shè)置為always吹截?
3)、如果業(yè)務(wù)應(yīng)用對(duì)延遲非常敏感朦肘,但同時(shí)允許一定量的數(shù)據(jù)丟失,那么双饥,可以把配置項(xiàng) no?appendfsync-on-rewrite 設(shè)置為 yes媒抠。表示在 AOF 重寫時(shí),不進(jìn)行 fsync 操作咏花。
當(dāng)然這個(gè)設(shè)置會(huì)導(dǎo)致趴生,如果此時(shí)實(shí)例發(fā)生宕機(jī),就會(huì)導(dǎo)致數(shù)據(jù)丟失昏翰。
反之苍匆,如果這個(gè)配置項(xiàng)設(shè)置為 no(也是默認(rèn)配置),在 AOF 重寫時(shí)棚菊,Redis 實(shí)例仍然會(huì)調(diào)用后臺(tái)線程進(jìn)行 fsync 操作浸踩,這就會(huì)給實(shí)例帶來阻塞。
4)统求、如果的確需要高性能检碗,同時(shí)也需要高可靠數(shù)據(jù)保證,建議考慮采用高速的固態(tài)硬盤作為 AOF 日志的寫入設(shè)備(高速比傳統(tǒng)的帶寬高十倍以上)码邻。
3折剃、操作系統(tǒng):swap
Redis 的 AOF 日志配置只是 no,或者就沒有采用 AOF 模式像屋,
1)怕犁、潛在瓶頸:操作系統(tǒng)的內(nèi)存 swap。
內(nèi)存 swap 是操作系統(tǒng)里將內(nèi)存數(shù)據(jù)在內(nèi)存和磁盤間來回?fù)Q入和換出的機(jī)制,涉及到磁盤的讀寫奏甫,所以戈轿,一旦觸發(fā) swap,無論是被換入數(shù)據(jù)的進(jìn)程扶檐,還是被換出數(shù)據(jù)的進(jìn)程凶杖,其性能都會(huì)受到慢速磁盤讀寫的影響。
Redis 是內(nèi)存數(shù)據(jù)庫(kù)款筑,內(nèi)存使用量大智蝠,如果沒有控制好內(nèi)存的使用量,或者和其他內(nèi)存需求大的應(yīng)用一起運(yùn)行了奈梳,就可能受到 swap 的影響杈湾,而導(dǎo)致性能變慢。
swap 觸發(fā)后影響的是 Redis 主 IO 線程攘须,這會(huì)極大地增加 Redis 的響應(yīng)時(shí)間漆撞。
2)、什么時(shí)候出發(fā) swap 于宙?----觸發(fā) swap 的原因主要是物理機(jī)器內(nèi)存不足浮驳,對(duì)于Redis,兩種捞魁。
1)至会、Redis 實(shí)例自身使用了大量的內(nèi)存,導(dǎo)致物理機(jī)器的可用內(nèi)存不足谱俭;
2)奉件、和 Redis 實(shí)例在同一臺(tái)機(jī)器上運(yùn)行的其他進(jìn)程,在進(jìn)行大量的文件讀寫操作昆著。文件讀寫本身會(huì)占用系統(tǒng)內(nèi)存县貌,這會(huì)導(dǎo)致分配給 Redis 實(shí)例的內(nèi)存量變少,進(jìn)而觸發(fā) Redis 發(fā)生swap凑懂。
3)煤痕、swap 解決思路:增加機(jī)器的內(nèi)存或者使用 Redis 集群(考慮主從切換一下接谨,大內(nèi)存變主庫(kù))。
4疤坝、操作系統(tǒng):內(nèi)存頁(yè)大
除了內(nèi)存 swap,還有一個(gè)和內(nèi)存相關(guān)的因素跑揉,即內(nèi)存大頁(yè)機(jī)制(Transparent Huge Page, THP)锅睛,也會(huì)影響 Redis 性能埠巨。
Linux 內(nèi)核支持2KB大小內(nèi)存頁(yè)分配现拒,常規(guī)4KB。
trade-off :
1)印蔬、內(nèi)存大頁(yè)可以給 Redis 帶來內(nèi)存分配方面的收益勋桶;
2)、持久化 Rdb 時(shí)修改數(shù)據(jù)侥猬,采用寫時(shí)復(fù)制技術(shù),保證正在修改的數(shù)據(jù)也被持久化退唠。
一旦有數(shù)據(jù)要被修改,Redis 并不會(huì)直接修改內(nèi)存中的數(shù)據(jù)屎债,而是將這些數(shù)據(jù)拷貝一份垢油,然后再進(jìn)行修改。
如果采用大內(nèi)存頁(yè)滩愁,即使客戶端請(qǐng)求只修改 100B 的數(shù)據(jù),Redis 也需要拷貝 2MB 的大頁(yè)惊楼。相反秸讹,如果是常規(guī)內(nèi)存頁(yè)機(jī)制,只用拷貝 4KB弧可。
所以劣欢,當(dāng)客戶端請(qǐng)求修改或新寫入數(shù)據(jù)較多時(shí),內(nèi)存大頁(yè)機(jī)制將導(dǎo)致大量的拷貝凿将,這就會(huì)影響Redis 正常的訪存操作,最終導(dǎo)致性能變慢笛匙。
解決:關(guān)閉大內(nèi)存頁(yè)
#執(zhí)行下面命令,返回always妹孙,說明啟動(dòng)了大內(nèi)存頁(yè),如果是nvner說明關(guān)閉骇笔。
-cat /sys/kernel/mm/transparent_hugepage/enabled
always
在 Redis 實(shí)例部署之前嚣崭,執(zhí)行下面命令,關(guān)閉大內(nèi)存頁(yè)
-echo never /sys/kernel/mm/transparent_hugepage/enabled
5有鹿、小結(jié)
梳理了一個(gè)包含 9 個(gè)檢查點(diǎn)的 Checklist,遇到 Redis性能變慢時(shí)持寄,按照這些步驟逐一檢查,高效地解決問題:
1)稍味、 獲取 Redis 實(shí)例在當(dāng)前環(huán)境下的基線性能荠卷。
2)、是否用了慢查詢命令油宜?如果是的話,就使用其他命令替代慢查詢命令疼燥,或者把聚合計(jì)算命令放在客戶端做。
3)醉者、是否對(duì)過期 key 設(shè)置了相同的過期時(shí)間披诗?對(duì)于批量刪除的 key,可以在每個(gè) key 的過期時(shí)間上加一個(gè)隨機(jī)數(shù)呈队,避免同時(shí)刪除。
4)粒竖、是否存在 bigkey? 對(duì)于 bigkey 的刪除操作温圆,如果你的 Redis 是 4.0 及以上的版本,可以直接利用異步線程機(jī)制減少主線程阻塞得运;如果是 Redis 4.0 以前的版本锅移,可以使用 SCAN 命令迭代刪除;對(duì)于 bigkey 的集合查詢和聚合操作非剃,可以使用 SCAN 命令在客戶端完成。
5)备绽、Redis AOF 配置級(jí)別是什么?業(yè)務(wù)層面是否的確需要這一可靠性級(jí)別恨锚?如果我們需要高性能倍靡,同時(shí)也允許數(shù)據(jù)丟失,可以將配置項(xiàng) no-appendfsync-on-rewrite 設(shè)置為 yes塌西,避免 AOF 重寫和 fsync 競(jìng)爭(zhēng)磁盤 IO 資源,導(dǎo)致 Redis 延遲增加办桨。當(dāng)然栖忠, 如果既需要高性能又需要高可靠性贸街,最好使用高速固態(tài)盤作為 AOF 日志的寫入盤。
6)薛匪、Redis 實(shí)例的內(nèi)存使用是否過大?發(fā)生 swap 了嗎古沥?如果是的話,就增加機(jī)器內(nèi)存岩齿,或者是使用 Redis 集群盹沈,分?jǐn)倖螜C(jī) Redis 的鍵值對(duì)數(shù)量和內(nèi)存壓力。同時(shí)乞封,要避免出現(xiàn) Redis 和其他內(nèi)存需求大的應(yīng)用共享機(jī)器的情況。
7)锚贱、在 Redis 實(shí)例的運(yùn)行環(huán)境中关串,是否啟用了透明大頁(yè)機(jī)制?如果是的話晋修,直接關(guān)閉內(nèi)存大頁(yè)機(jī)制就行了飞蚓。
8)、是否運(yùn)行了 Redis 主從集群趴拧?如果是的話,把主庫(kù)實(shí)例的數(shù)據(jù)量大小控制在 2~4GB添履,以免主從復(fù)制時(shí)好爬,從庫(kù)因加載大的 RDB 文件而阻塞书聚。
9)灼芭、是否使用了多核 CPU 或 NUMA 架構(gòu)的機(jī)器運(yùn)行 Redis 實(shí)例象对?使用多核 CPU 時(shí)严卖,可以給 Redis 實(shí)例綁定物理核;使用 NUMA 架構(gòu)時(shí)哮笆,注意把 Redis 實(shí)例和網(wǎng)絡(luò)中斷處理程序運(yùn)行在同一個(gè) CPU Socket 上(但是不同核)汰扭。
除此之外福铅,檢查 Redis 所在的機(jī)器上有沒有一些其他占內(nèi)存滑黔、磁盤 IO 和網(wǎng)絡(luò) IO 的程序,比如說數(shù)據(jù)庫(kù)程序或者數(shù)據(jù)采集程序拷沸。如果有的話,我建議你將這些程序遷移到其他機(jī)器上運(yùn)行秧了。
保證要給 Redis 充足的計(jì)算序无、內(nèi)存和 IO 資源。