Redis常見問題答疑

Redis常見問題答疑

答疑內容持續更新,歡迎大家踊躍提問,一起完善這個知識庫!

 

關於更詳細的Redis細節問題,我會整理成系列文章發出來,大家關注一下我的公衆號:

image.png

長按關注「水滴與銀彈」公衆號

 

數據類型

 

image.png

一個數據類型都對應了很多種底層數據結構。以List爲例,什麼情況下是雙向鏈表,反之又在什麼情況下是壓縮列表呢?還是說是並存狀態?

 

1、Hash 和 ZSet 是數據量少採用壓縮列表存儲,數據量變大轉爲哈希表或跳錶存儲

2、但 List 不是這樣,是並存的狀態,List 是雙向鏈表 + 壓縮列表

 

key過期相關

 

請問Redis裏面的惰性刪除指的是什麼,我看網上說是在訪問到這個key的時候才執行刪除纔是惰性刪除,但是這個專欄裏面又表達的像惰性刪除其實就是異步刪除?

 

刪除過期key是定時清理+懶惰刪除。

 

懶惰刪除默認是在主線程刪除,並釋放key內存的。

 

但在4.0+版本又做了優化,釋放key內存可以放到了異步線程中去做了(lazy-free),目的是可以減少主線程釋放內存的耗時,提升主線程處理性能。

 

也就是說4.0之前,懶惰刪除就是同步刪除。4.0優化後,懶惰刪除就是同步刪除全局hash表的鍵值對+子線程程執行釋放內存,但需要開啓lazy-free配置才生效。

 

有百萬的key在一段時間過期,比如2個小時,如果應用訂閱了key失效事件,在兩個小時內,這些通知事件都會發出來嗎?

 

不一定,如果沒有請求到過期的key,那麼Redis清理過期key是懶惰刪除,懶惰刪除有可能導致雖然key已經過期了,但還沒有被掃描到,自然也就不會被清理。

 

maxmemory相關

 

我的Redis的maxmemory是設置4G,但我的服務器是8G,那麼在Redis啓動時,是直接分配給4G內存空間給Redis還是按需分配呢?如果我maxmemory是4G,但我的Redis數據是5G,那麼Redis啓動時是加載4G數據,還是加載4G數據+1G SWAP?

 

1、沒數據的Redis,啓動時按需申請內存,不會一下申請maxmemory內存的。

 

2、設置了maxnemory,Redis必定不會讓你超過maxmemory,但slave除外。

 

如果不超過maxmemory,但是系統內存快滿了,這種會刷到swap吧?

 

開了swap,會把內存數據換到swap上,如果不開swap,Redis進程會被OOM。

 

rehash相關

 

Redis裏的hash容量到一定程度的時候,會做漸進式的rehash,Redis有沒有提供一些可以配置,讓我可以指定hash的大小,這樣可以防止hash在較大的情況下,發生多次rehash的情況?

 

沒有配置,hash擴容是定好的規則。到達什麼閾值,開始擴容,沒辦法自己控制。

 

但6.0+版本提了一個新的函數,如果負載因子不超過1.618,在某些情況會限制rehash。

 

限制rehash雖然損失了查詢性能,但可以有效防止淘汰過多數據(實例內存超過了maxmemory)。

 

Redis如何保證哈希表在擴容時的原子操作呢?如果把數據複製到新哈希表失敗的話,原有的哈希表數據豈不是還存在,這個時候恢復後數據會向哪張哈希表寫數據?

 

如果第一次拷貝後,哈希表2的哈希桶1再次超過了裝載因子,但哈希表1中哈希桶2還有數據。這種情況也會去擴容哈希表1麼?那原本沒有漸進處理的數據是重新rehash放進來還是不會變?當然這種情況可能比較極端了

 

1、都是內存操作,內存沒問題一般不會失敗。

2、下一次rehash想要開始,必須等上一次完成

 

老師的文章裏說漸進式rehash將拷貝分攤到客戶端的多次請求上,是不是可以理解爲請求命中了哈希表1的key,就把表1的數據rehash再分配到表2。那萬一客戶端遲遲不命中表1的某個key,表1的數據豈不是一直都不會遷移到表2?

 

每處理客戶端請求,如果需要rehash,每次請求都會觸發遷移數據,也就是說只要有請求進來,即使沒有命中哈希表1的key,也會觸發遷移一部分數據到哈希表2。

 

擴容的時候,假如哈希表1大小5g,那麼哈希表2假如爲原來的1.5倍,就是7.5g,如果Redis內存限制在15g,是不是會有一些內存因爲擴容機制,沒有得到利用呢?

 

如果限制Redis內存15g情況下,哈希表1已經到了10g,發現需要擴容,擴容爲兩倍,發現內存不夠的情況下,應該怎麼處理呢?

 

擴容是爲了搞一個大一點的哈希桶,所以申請的內存只是新哈希桶所佔內存,並不會申請新數據內存。

 

那即使每次申請的內存容量小,但總有個時間點,會發現內存不夠了,但是還沒到達限制的內存點,所以就乾脆不申請了,繼續在舊的哈希表裏插入數據,最後通過lru再釋放內存。

 

不是。如果實例設置了maxmemory最大10g內存,已經用了9.9g,擴容申請新的哈希桶,需要200m,Redis會照常申請,開始rehash擴容。

 

然後Redis發現,實例現在佔用的內存,超過了maxmemory,那Redis會觸發數據淘汰,開始淘汰刪除key,直到把內存降到10g以下,也就是說,在這期間需要淘汰100m的key。

 

淘汰100m的key,有什麼後果?後果就是,會阻塞住整個Redis(淘汰刪除key是在主線程中執行的),這裏是一個坑,需要格外注意rehash申請內存,撞上了正好超過maxmemory上限,就會引發此問題。

 

這個問題在3.x-6.0版本一直存在,好像在6.0+以上版本,才修復了這個問題,具體可以查閱6.0版本更新記錄確認一下。

 

如果內存超過了maxmemory,但沒有設置淘汰策略,會發生什麼?

 

新寫入的數據,會給客戶端返回寫入失敗。

 

那什麼情況下,Redis纔會OOM?

 

只有機器內存無法申請到的時候:

 

  • 整個機器內存不夠了
  • NUMA架構下,某一個內存節點內存不夠,又配置的不允許去其他內存節點申請內存

 

Redis在RDB或AOF重寫時是不允許進行rehash的,如果現在進行rehash,又觸發了RDB或AOF重寫條件會怎麼處理呢?

 

這個問題非常好。

 

1、如果正在RDB和AOF rewrite,Redis默認會關閉rehash,等RDB和AOF rewrite完成後,Redis再打開rehash開關。但是有一個例外,如果全局哈希表衝突概率已經很大(有一個閾值),超過這個閾值,也會強制觸發rehash(不管是否在RDB和AOF rewrite)

 

2、如果正在rehash,是允許RDB和AOF rewrite的。

 

3、爲什麼RDB和AOF rewrite期間,默認不允許rehash?因爲RDB和AOF rewrite時,父進程rehash需要申請新的哈希表,這會造成父進程大量COW,影響父進程的性能

 

4、爲什麼rehash期間,允許RDB和AOF rewrite?因爲rehash已經開始,說明新的哈希表內存已經申請完成了,之後的rehash過程只是遷移哈希桶下的數據指針,不涉及到內存申請了,所以RDB和AOF rewrite沒影響。

 

持久化

 

RDB

 

爲啥RDB 要 fork 子進程而不是線程?

 

1、先想一下RDB的目的是什麼?就是把內存數據持久化到磁盤上,而且只持久化截止某一時刻的數據即可,不關心之後的數據怎麼改(內存快照)

 

2、性能:如果用子線程做的話,主線程寫,其他線程讀,然後子線程數據寫磁盤,有資源競爭,需要加鎖,加鎖會降低Redis性能,而且在實現上很複雜,成本高。

 

3、基於以上考慮,fork一個子進程來搞,最經濟,成本也最低。因爲fork子進程,操作系統把這些事都做好了,有內存快照數據,沒有鎖競爭,子進程怎麼寫磁盤也不會影響父進程,還有COW不影響主進程寫數據,一舉多得。

 

如果上一次生成RDB快照還沒執行完,又觸發了持久化策略,這個時候是順序執行等上一次持久化完成?還是並行處理?

 

等上一次RDB執行完,才能觸發執行下一次RDB。

 

關於Copy On write問題:數據持久化fork子進程時,子進程不會一次copy所有數據,而是在修改時觸發Copy On write。假設主線程中有1000條數據,fork創建子進程後,主線程有請求新增了100條,修改了200條,這些內存是如何在主進程和子進程分配的?

 

1、首先要理解 Copy On Write 含義:即寫時複製,誰寫誰複製

 

2、fork子進程,此時的子進程和父進程會指向相同的地址空間,當父進程有新的寫請求進來,它想要修改數據,那麼它就把需要修改的key的內存,拷貝一份出來,再修改這塊新內存的數據,此時父進程內存地址就會指向這個新申請的內存空間

 

3、在這期間,子進程不會修改任何數據,所以不會分配任何新的內存,它依舊指向父進程那些數據的內存地址空間,這個過程是操作系統層面做好的。

 

那fork期間會阻塞父進程嗎?爲什麼會阻塞?

 

1、fork完成之前,會阻塞父進程,主要是父進程需要拷貝進程中的內存頁表給子進程,每個進程都要有自己的內存頁表,所以這個父子進程無法共享,必須要拷貝一份

 

2、拷貝內存頁表也需要花費時間,進程佔用的內存越大,拷貝時間越久

 

RDB寫入的時候,通過主線程fork出bgsave子進程時,進行寫入RDB文件,此時主線程也可以接受的寫操作,那麼主線程接收新的寫操作,bgsave子進程還會再把這個數據,寫入到RDB文件嗎?

 

不會。RDB的目的是,只要一份內存快照,即只要fork那一瞬間,父進程所擁有的數據,fork完成後子進程指向父進程的所有內存數據地址空間,所以就與父進程共享數據了,此時子進程把這些數據scan出來,持久化到磁盤就可以了,不需要關心父進程有沒有寫入新數據。

 

子進程做RDB期間,父進程寫入新數據,父進程做Copy On Write申請新的內存,那子進程完成RDB後,進程退出,內存回收是怎樣的?

 

子進程退出時,如果它指向的內存數據,沒有被父進程修改過(對於這塊數據,父進程沒有做COW),那麼這塊內存數據,還是歸父進程所有,子進程不會回收。

 

如果在子進程RDB期間,父進程有新數據寫入或修改,對一部分key的內存做了COW,那這些key的內存,父子進程各自獨立,子進程退出時,就會回收它指向的這些內存空間。

 

AOF

 

AOF 中開啓 always 刷盤策略也會存在數據丟失嗎?

 

可能會。Redis 是先操作內存,後寫AOF磁盤日誌。比如 Redis 內存執行完了,去刷盤的時候宕機了就會導致數據丟失。

 

Redis在bgsave或rewriteAOF期間引起CPU飈高,有應對方案嗎?

 

這是正常情況,在這期間會消耗比較多的CPU,如果實例比較大,持續時間越久。因爲在這期間,子進程需要把進程中的所有數據都掃出來,然後寫到磁盤上,這個掃描過程是需要耗費CPU資源的。

 

AOF假如寫在內存A上,然後fork子進程進行AOF重寫,因爲copy on write機制,AOF重寫子進程也是指向內存A。如果此時有新的key寫入,父進程將其寫入在新拷貝的內存B上,然後父子進程的內存逐漸分離。AOF重寫子進程寫完後將其替換AOF日誌文件,然後釋放內存A。父進程隨後就一直使用內存B,這樣理解對嗎?

 

正確。

 

AOF重寫的時候,如果重寫緩衝區滿了,怎麼處理?是不是直接放棄本次的重寫了?

 

AOF重寫緩衝區不會滿,是個鏈表,只要內存不超過設置的maxmemory。

如果超過maxmemory,執行配置的淘汰策略。

 

AOF配置爲每秒刷盤,有可能阻塞Redis,影響性能嗎?

有可能的。

 

AOF 配置爲每秒刷盤,具體邏輯是這樣的:

 

1、Redis 主線程把命令寫到 AOF page cache(調用 write 系統調用)

2、Redis 後臺線程每間隔 1 秒,把 AOF page cache 持久化到磁盤(調用 fsync 系統調用)

 

如果 2 執行時,遲遲沒有成功,那麼 1 執行時就會阻塞住,原因是在操作同一個 fd 時,fsync 和 write互斥的,一方必須等待另一方完成。

 

步驟 2 執行不成功的原因在於:機器的磁盤 IO 負載非常高(可能有別的程序在瘋狂寫磁盤,把磁盤帶寬佔滿了),此時 1 在執行時,就會阻塞等待,從而影響到了主線程,進而影響整個 Redis 性能。

 

具體可參見 Redis 源碼 aof.c,搜索: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.

image.png

主從複製

 

主從節點全量複製,會同步RDB文件。然後主節點繼續接受寫請求,這些寫命令會存到複製緩衝區。RDB傳輸完成後,再把複製緩衝區的命令,發到各個從節點執行,那在發覆制緩衝區裏的命令時如果主節點又接受了寫請求,這些新的寫請求是怎麼發給從節點的?

 

全量同步過程:

 

1、slave向master發起同步請求,slave會告知master自己的offset,如果是第一次,offset=-1表示需要全量同步

2、slave連上了master,master會給slave分配一個client buffer(這個就是複製緩衝區replication buffer)

3、master fork子進程dump RDB文件,dump完成後告訴父進程,父進程把RDB發給slave(通過slave的socket發過去)

4、在master dump RDB期間,master所有寫命令,都寫到這個client buffer(replication buffer),先積壓在這

5、RDB發送完成,master把client buffer(replication buffer)積壓的命令,都發給slave(寫slave socket),這樣主從數據就追平了

6、之後master寫請求,還是都寫到client buffer(replication buffer),然後實時傳播給slave,主從保持一致

 

那backlog buffer幹嘛的?

 

1、只要master下面有slave存在,master就會分配一塊內存,這塊內存就是backlog buffer

2、只要master有寫命令進來,也都會寫一份到backlog buffer裏,但這個buffer是固定大小的環形緩衝區,寫滿就會覆蓋舊數據

3、它的作用在於,主從複製意外中斷了,slave再次向master發起同步請求,slave會告知master要從哪開始複製(offset),master在backlog buffer裏找,數據能否接得上,接得上就把差異的增量數據發給slave,發的過程也是先寫到上面所說的client buffer(replication buffer),然後寫slave socket達到主從同步

4、如果接不上,走全量同步

 

重點:

 

1、replication buffer:別糾結這個名字,它就是master給client分配的一個buffer,因爲是用於主從同步,所以大家才這麼叫它,它的意義在於master和slave保持數據一致的傳播通道

2、backlog buffer:主要作用在於,主從斷開後,是否能增量同步

 

如果RDB很大,傳輸給從庫時間很長,那replication buffer會不會積壓很多數據,那這個buffer內存會不會無限大?需不需要清理?

 

不會清理。這個buffer大小是可配置的,如果超過了配置的大小,主庫會強制斷開這個從庫的連接,這就會導致主從同步中斷。

配置項:client-output-buffer-limit replica 256mb 64mb 60 這個是默認的buffer大小(低版本叫 client-output-buffer-limit slave)。

 

所以如果RDB很大,這個buffer要配置大一些,防止寫請求太大,主從同步失敗。

 

如果主從同步失敗,從庫又會發起全量複製請求,很有可能又會因爲buffer超限導致失敗,引發複製風暴。

 

repl-diskless-sync配置項要不要開啓?開啓後,主從全量數據複製,不會生成RDB文件,而是master直接通過網絡socket,把全量數據發給slave。主從節點是內網部署的,是不是使用這種配置項更好?

 

關於這個配置,可以看一下配置文件關於這一項配置註釋說明,寫得非常清晰了。簡單講如下:

 

1、repl-diskless-sync = no,表示主從全量同步,master會先在磁盤上生成RDB文件,然後master讀這個文件,把這個RDB文件數據發給slave,達到主從全量同步

2、repl-diskless-sync = yes,表示master不會在磁盤生成RDB文件,而是直接把全量數據,通過socket發給slave,這種也叫無盤複製

 

各有什麼優劣?

 

1、用磁盤RDB文件的方式做全量同步,好處是在RDB文件寫磁盤沒有完成之前,在這期間,如果還有其他slave請求全量同步,那麼這些slave都可以直接複用這個RDB數據,master只做一次RDB即可

2、如果採用無盤複製,master不生成RDB文件在磁盤,缺點是,master給一個slave開始傳輸全量數據了,其他slave又連上來需要全量複製,master還需要掃一遍整個實例,然後給這些slave發數據,沒辦法複用

3、所以,使用無盤複製時,Redis還提供了一個配置repl-diskless-sync-delay,表示開始給slave傳輸數據之前,等待一會再發數據給slave,如果此時有其他slave連上來請求全量同步,那麼在這等待的期間,就可以兼顧其他slave,減少掃描整個實例的次數,降低同步成本

 

什麼情況下會導致主從數據不一致?

 

5.0以下版本,slave如果提前master超過maxmemory,那麼slave自己會淘汰數據,此時主從數據不一致。

 

5.0增加了一個配置replica-ignore-maxmemory,可以控制是否讓slave淘汰數據,默認不淘汰,可以爲了保證主從完全一致。

 

那什麼情況下slave會提前master超過maxmemory?

 

比如slave快要達到maxmemory時,在slave上執行monitor命令,此時這個執行monitor的client輸出緩衝區會佔用很多內存(實例QPS比較大的情況下很明顯),有可能導致slave提前超過maxmemory,需要格外注意。

 

詢問一個Redis數據丟失的問題,如下圖:

image.png

image.png

 

第一種是主從同步的數據丟失,第二種是哨兵選舉導致的腦裂的數據丟失。這是網上找到的解決方案,想問下實戰是這樣解決的嗎?這樣會導致主庫不能寫操作,我感覺是個很危險的行爲。

 

看你具體業務場景,如果主從不一致,對業務影響很大,寧可業務不可用也要保證一致性,就可以用這種方案,缺點是業務有損失。

 

如果需要優先保證業務可用性,只能降低一致性的要求。然後從運維層面保證主從同步的可靠性,降低出問題的概率。

 

場景1出問題的原因是,從庫有問題,或者主從網絡存在問題。那最好解決從庫問題和主從之間的網絡問題。

 

從另一個角度說,主從即使不一致,如果Redis只是當做緩存來用,後面還有數據庫兜底,所以對業務基本沒影響。如果沒有數據庫兜底,那你就需要把握好業務了,評估丟數據的損失,從運維層面規避降低出問題的概率。

 

場景2腦裂的問題,細節很多。Redis主從集羣本身不是分佈式強一致的,所以遇到這種問題也沒辦法,還是得運維層面做好,例如哨兵怎麼部署,要不要和master在一臺機器之類的。

 

能應對腦裂的集羣,內部都是有共識協議保證的。比如master腦裂被孤立了,自己退下來不讓寫入類似這種規則,但是Redis沒有。

 

AOF和RDB同時開啓,那麼數據恢復是按照什麼來恢復的?是4.0之前使用AOF來恢復,4.0之後是按照RDB來恢復嗎?

 

如果同時開啓,恢復時優先使用AOF恢復。因爲AOF數據比RDB全。

 

哨兵

 

現在Redis哨兵集羣中有有三個哨兵,Redis一主兩從。現在主從中有一個主掛了,然後哨兵會選一個主哨兵去切換。問題是,下一次Redis主節點掛了後,還會再次選一個主哨兵去切換嗎?還是說由上一次的主哨兵去切換呢?

 

每次都會選一次哨兵領導者去執行切換。

 

那每次選會不會有時間損耗,從而影響性能?

 

哨兵選舉很快的。哨兵判定主掛了,這期間也需要時間。而哨兵選舉的時間,跟這個時間比,幾乎可以忽略不計了。

 

問題場景:1、發生腦裂(主庫假故障,主從切換期間,舊主庫恢復和客戶端通信,客戶端把數據寫到舊主庫,主從切換完成,舊主庫降爲從庫,清數據,全量同步)。

2、主庫真故障,主從切換期間丟失數據

 

這兩種情況不是都會丟失主從切換期間的數據嗎?兩者區別是不是主從切換完成到舊主庫變成從庫這段期間,是新主庫服務還是新主庫和舊主庫同時服務?

 

1、場景1發生時,業務應用不報錯,以爲寫成功了,過一會卻查不到數據了,結果不符合預期。

2、場景2主庫掛了,寫請求直接失敗,用戶可以感知到,自己可以重試,數據是符合預期的。

 

寧可讓2發生,也不要1,1排查起來很困難的。

 

 

鎖被誤釋放的問題:鎖未過期之前,在什麼情況下鎖會被誤釋放?

 

一個客戶端,自己加鎖後,執行業務邏輯,但自己卻阻塞了很久,然後鎖過期了,這個時候別的客戶端就可以獲取到鎖,然後阻塞的客戶端執行業務結束了,再去刪除鎖的時候,會釋放別人的鎖,那別人的鎖就相當於失效了。

 

redis 主從部署,主庫執行 setnx 加鎖,然後主庫掛了,這個命令還未同步到從庫,會導致什麼結果?

 

由於命令還未同步到從庫,所以這時會導致鎖失效(從庫頂上來,對於客戶端來說卻沒有成功加鎖)。

 

所以Redis作者才提出了Redlock來避免這種情況,可查一下資料瞭解下Redlock原理,並不複雜。

 

事物

 

lua腳本可以會保證原子性嗎?實際測試中,我給了錯誤的參數,部分命令運行成功,部分命令運行失敗,最終還是不符合原子?

 

lua只保證了隔離性,並不保證原子性。Redis和MySQL的原子性,不是一回事。

 

最近面試被問到Redis執行 lua腳本如何保證原子性的?我說執行lua腳本,實際就是開啓了一個事物,就保證了原子性。面試官覺得我答的不完全對。這個要怎麼答?

 

因爲Redis處理請求是單線程的,單線程可以保證執行lua腳本時不會被別的請求打斷(隔離性)。

分片集羣

 

Redis cluster 添加新節點,是不是要手動遷移數據到新節點中, 集羣能自動轉移數據過去麼?

 

需要手動觸發遷移數據。

 

Slot 2 正在從實例 2 往實例 3 遷移,key1 已經遷移過去,key3 和 還在實例 2。如果客戶端向實例 2 請求 key3時,因爲key 3還在實例2中,那麼此時客戶端是需要等待實例 2把key3遷移到實例 3,才能獲取到key3的數據嗎?

 

key還在原來的實例,直接返回。

 

現在實際生產中,是Redis cluster用的多,還是codis用的多?

 

前期codis多,因爲Redis cluster不穩定。現在cluster是主流了,用的越來越多了,而且codis已經不維護了。

 

建議直接上 Redis cluster,沒有歷史包袱。

 

Redis Cluster的數據遷移是在主線程中進行的嗎,也就是說在遷移某個key的是時候,這個key所對應slot的源節點和目標節點都無法響應任何操作?還是說僅僅是這個slot裏面的數據被阻塞,其他slot能被正常訪問?

 

一個key遷移過程中,整個source和target實例都會阻塞,如果一個key很小,遷移時幾乎不影響性能,如果是bigkey,會增加阻塞時間,影響性能。

 

緩存和數據庫一致性

 

先修改了數據庫中的值後,爲什麼刪除緩存中數據比更新緩存中數據好呢?

 

如果去更新緩存,更新過程中數據源又被其他請求再次修改的話,緩存又要面臨處理多次賦值的複雜時序問題。所以直接失效緩存,等下次用到該數據時自動回填,期間無論數據源中的值被改了多少次都不會造成任何影響。

 

緩衝區

 

客戶端緩衝區有個問題,服務器端處理請求的速度過慢,例如,Redis 主線程出現了間歇性阻塞,無法及時處理正常發送的請求,導致客戶端發送的請求在緩衝區越積越多。這個我有點沒理解,拋開阻塞不說,如果客戶端傳過來一個大key,大於32k,這個時候客戶端緩衝區流溢出了嗎?還有如果不溢出,那麼報文不完整,Redis如何處理這個請求呢?

 

Redis 的客戶端輸入緩衝區大小的上限閾值,在代碼中就設定爲了 1GB。也就是說,Redis 服務器端允許爲每個客戶端最多暫存 1GB 的命令和數據。1GB 的大小,對於一般的生產環境已經是比較合適的了。一方面,這個大小對於處理絕大部分客戶端的請求已經夠用了;另一方面,如果再大的話,Redis 就有可能因爲客戶端佔用了過多的內存資源而崩潰。

 

client的output buffer特別高,導致佔用內存非常大,可能的原因?

 

例如client執行了某個命令後,server返回大量數據,但client沒有及時去讀取和處理這些數據,也沒有斷開這個連接,導致server發給client的數據積壓在輸出緩衝區中。

 

或者client執行pipeline,一次發太多數據給server了,server返回給client的數據也非常多,client來不及處理就積壓在緩衝區了,這是典型的業務使用問題,client需要控制pipeline發送命令的數量。

 

如何查看每一個client的output buffer有多大?

 

執行client list命令,可以看到每個client的omem,即是client output buffer。

 

性能問題排查和調優

 

如何排查Redis變慢問題,如何性能調優?

直接看我寫的這篇文章,全網最全性能分析教程:https://mp.weixin.qq.com/s/Qc4t_-_pL4w8VlSoJhRDcg(Redis爲什麼變慢了?一文講透如何排查Redis性能問題 | 萬字長文)

 

其它

 

如何理解Redis數據持久化、主從複製、哨兵、分片集羣它們之間的聯繫?

 

看我寫的這篇文章,把這幾個知識點串聯起來,通俗易懂:https://mp.weixin.qq.com/s/q79ji-cgfUMo7H0p254QRg

 

爲什麼master和slave執行sacn命令,返回的結果和數量不一樣?

 

master和slave的全局哈希表,哈希桶的分佈可能是不同的,而且scan掃描的結果也是無序的。

 

不能只看結果,要重點看cursor返回是不是0,不是0需要繼續執行scan,直到返回0纔拿到了全部數據。而且一次最多返回count元素,有可能少於count,關鍵看cursor的值。

 

最近面試被問到Redis執行 lua腳本如何保證原子性的?我說執行lua腳本,實際就是開啓了一個事物,就保證了原子性。面試官覺得我答的不完全對。這個要怎麼答?

 

因爲Redis處理請求是單線程的,單線程可以保證執行lua腳本時不會被別的請求打斷(隔離性)。

 

Redis集羣的節點的大小2-4g最合適是嗎?

 

這是個經驗值,意思是越小越不容易發生阻塞風險,比如實例很小,執行RDB就很快。

 

但是實例很小,那部署的節點數量會比較多,維護成本高一些。我個人覺得10G以下問題也不大,這樣節點數也不是很多,維護起來方便些。

 

重點注意的是,實例越大,實例阻塞風險越大,而且維護會變得越來越困難,出問題的概率也大。

 

我理解的pipeline 好像只會減少網絡傳輸的時間,並不能保證發送的一批命令不會被其他命令穿插執行?如果pipeline提交的命令較多呢?

 

還是不一樣的,pipline關注的是打包把多個命令發到服務端。事務關注的如何保證ACID這些。

 

Pipeline一次發多個命令,服務端解析一個命令,執行一次。而事務是必須收到exec纔會執行。

 

在一定程度上,pipline能實現和事務一樣的效果,但是同樣這麼操作,如果遇到執行一半命令,Redis崩潰了,pipeline執行了多少命令就是多少,但事務因爲有multi標記,在AOF恢復的時候可以把執行一半的命令撤銷掉,這是不一樣的地方。另外事務可以配合watch使用,pipeline不行。

 

使用pipeline批量發數據給Redis,是不是功能上跟multi/exec一樣了?

 

multi/exec可以一個個發命令到服務端,也可以打包一次全發過去,一次全發就是配合了pipeline使用的。

 

關於CPU綁核:一般業務場景下,應該用不到綁核吧?而且swap線上一般都是關閉的吧?

 

除非追求更好的性能,一般不綁核,綁覈對於DBA要求高,瞭解原理纔可以操作,否則會達到反效果。另外,Swap線上不一定是關的,很多服務器默認都是開的。

 

Redis6.0多線程處理,是先把socket連接放入全局隊列,然後阻塞,讓io多線程解析處理,然後主線程處理讀寫命令,寫到緩衝區,再阻塞,交給io多線程放入socket緩衝區,然後,主線程清空全局隊列,返回。我的疑問是,這個全局隊列在命令處理期間,始終只有一個socket嗎,如果不是的話,那麼最後清空全局隊列,會不會把其他沒完成的socket也清理了呢。

 

所有請求處理完成後,纔會清理的。

 

爲什麼推薦只使用 db0,從而減少 SELECT 命令的消耗,select命令爲什麼會坑?select命令是客戶端發送過來執行的,想知道如果一個客戶端固定用一個db,並且用連接池,不會每次都執行select吧?

 

除非客戶端連接池,1個db建一個連接操作Redis,如果是一個連接會操作多個db的話,每次執行時,肯定需要先執行一次SELECT命令的。如果QPS很高的話,執行SELECT命令也是消耗。

 

另一方面,既然數據要存在不同db下,目的就是爲了做隔離,其實更好的方式是拆分實例,不同實例保存不同業務線的數據,這樣每個實例承擔的QPS也變高了,後期也方便DBA運維。

 

最後,Redis cluster只支持使用db0,如果你後期想往cluster上遷移,使用了多個db就會很麻煩,還得拆分數據,所以只建議使用db0,減少SELECT消耗,也便於遷移到Redis cluster。

 

操作系統的net.core.somaxconn如果設置爲512,而Redis客戶端最大允許10000連接,這個客戶端連接是不是受限於somaxconn,最多不會超過512呢?

 

客戶端向服務端建立一個TCP連接時,服務端先把連接放到半連接隊列,然後再放到全連接隊列(TCP三次握手的細節很多,這裏簡化了,你可以去查細節),somaxconn是用於控制這兩個隊列最大長度的。

 

這些隊列有什麼用?有隊列的好處是,當多個連接同時打到服務端時,服務端只能一個個處理連接,還沒處理到的連接不能丟棄吧?所以服務端有這樣一個隊列來緩衝,把連接都放到這裏來,應用層accept時就從全連接隊列拿一個出來進行交互。

 

至於Redis配置文件設置的最大連接數,Redis服務端每拿出來連接和客戶端交互,都可以在應用層記錄現在服務的連接數,如果服務的連接數已經超過了配置的,那麼就可以直接拒絕掉。你看到的配置文件連接數限制,是在這控制的。

 

一個是TCP層的,一個是應用層的。

 

布隆過濾器第一次還是會直接訪問緩存,緩存沒有再訪問DB上,如果未獲取到數據,就在布隆過濾器上進行添加,下次有相同的請求的時候,直接屏蔽該請求。是這個做法嗎?

 

不是,請求進來先查布隆過濾器,布隆沒有,直接返回。布隆存在,查緩存,查DB,同時在布隆裏設置標記數據存在。查緩存和查DB,看你緩存是否有數據,有的話只查緩存就可以,不需要查DB。

 

示例代碼:

 

if not bloom_filter.exists(user_id):
    return null

user = query_cache(user_id)
if not user:
    user = query_db(user_id)
    set_cache(user_id, user)

bloom_filter.set(user_id)

return user

 

重點:

1、如果是新業務,直接按照上面僞代碼寫就可以

2、如果是老業務,想要上一層布隆過濾無效請求,需要掃數據庫把已存在的數據刷到布隆裏

3、當然,每次新增數據也同步設置標記到布隆裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章