首頁  >  文章  >  資料庫  >  Redis的八個經典問題是什麼

Redis的八個經典問題是什麼

王林
王林轉載
2023-06-03 14:44:481212瀏覽

1、為什麼使用Redis

部落客認為專案中使用redis的主要考慮因素是效能和並發。當然,redis還具備可以做分散式鎖等其他功能,但是如果只是為了分散式鎖這些其他功能,完全還有其他中間件(如zookpeer等)代替,並不是非要使用redis。因此,這個問題主要從性能和並發兩個角度去答。

回答:如下圖所示,分成兩點

(一)效能

當遇到需要執行時間很長且結果不常變化的SQL時,我們建議將結果儲存在快取中。這樣,後面的請求就去快取中讀取,使得請求能夠快速回應。

Redis的八個經典問題是什麼

題外話:忽然想聊聊這個迅速回應的標準。其實根據互動效果的不同,這個回應時間沒有固定標準。不過曾經有人這麼告訴我:"在理想狀態下,我們的頁面跳轉需要在瞬間解決,對於頁內操作則需要在剎那間解決。為了讓使用者體驗最佳,耗時超過一秒的操作應提供進度提示並允許隨時中止或取消。"

那麼瞬間、剎那、一彈指具體是多少時間呢?

根據《摩訶僧祗律》記載

一剎那者為一念,二十念為一瞬,二十瞬為一彈指,二十彈指為一羅預,二十羅預為一須臾,一日一夜有三十須臾。

那麼,經過周密的計算,一瞬間為0.36 秒,一剎那有 0.018 秒.一彈指長達 7.2 秒。

(二)並發

如下圖所示,在大並發的情況下,所有的請求直接存取資料庫,資料庫會出現連線例外。在這種情況下,使用Redis緩衝操作是必要的,以便讓請求首先存取Redis,而不是直接存取資料庫。

Redis的八個經典問題是什麼

2、使用Redis有什麼缺點

分析:大家用redis這麼久,這個問題是必須要了解的,基本上使用redis都會碰到一些問題,常見的也就幾個。

回答:主要是四個問題

(一)快取與資料庫雙寫一致性問題

##(二)快取雪崩問題

(三)快取擊穿問題

#(四)快取的並發競爭問題

這四個問題,我個人是覺得在專案中,比較常遇見的,具體解決方案,後文給出。

可以參考:《快取雪崩、快取穿透、快取預熱、快取更新、快取降級等問題! 》

3、單線程的Redis為什麼這麼快

分析:這個問題其實是對redis內部機制的一個考察。根據部落客面試經驗所述,許多人實際上並不了解Redis的單線程工作模型。所以,這個問題還是應該要複習一下的。

回答:主要是以下三點

(一)純記憶體操作

(二)單執行緒操作,避免了頻繁的上下文切換

(三)採用了非阻塞I/O多路復用機制

題外話:我們現在要仔細的說一說I/O多路復用機制,因為這個說法實在是太通俗了,通俗到一般人都不懂是什麼意思。博主打一個比方:小曲在S城開了一家快遞店,負責同城快遞服務。由於資金有限,小曲先僱用了一批快遞員,但後來發現資金不足,只能購買一輛車用於快遞送貨。

經營方式一

每當客戶送來一份快遞,小曲就會指派一位快遞員盯著,然後由該快遞員駕車送快遞。慢慢的小曲發現了這種經營方式存在下述問題

  •  幾十個快遞員基本上時間都花在了搶車上了,大部分快遞員都處在閒置狀態,誰搶到了車,誰就能去送快遞

  •  隨著快遞的增多,快遞員也越來越多,小曲發現快遞店裡越來越擠,沒辦法僱用新的快遞員了

  •  快遞員之間的協調很花時間

綜合上述缺點,小曲痛定思痛,提出了下面的經營方式

經營方式二

小曲只僱用一個快遞員。小曲會把客戶送來的快遞依照送達地點標示好,放在同一個地方。最終,那名快遞員依序取件,每次取一個,然後驅車送快遞,送完後再回來取下一個快遞。

對比

上述兩種經營方式對比,是不是明顯覺得第二種,效率更高,更好呢。在上述比喻中:

  •  每位快遞員------------------>每個執行緒

  •  每個快遞-------------------->每個socket(I/O流)

  •  快遞的送達地點-------------->socket的不同狀態

  •  客戶送快遞請求-------------->來自客戶端的請求

  •  小曲的經營方式- ------------->服務端運行的程式碼

  •  一輛車--------------- ------->CPU的核數

於是我們有以下結論

1、經營方式一就是傳統的並發模型,每個I /O流(快遞)都有一個新的線程(快遞員)管理。

2、經營方式二就是I/O多工。只有單一執行緒(一個快遞員),透過追蹤每個I/O流的狀態(每個快遞的送達地點),來管理多個I/O流。

下面類比到真實的redis執行緒模型,如圖所示

Redis的八個經典問題是什麼

#參考上圖,簡單來說,就是。我們的 Redis 用戶端在操作過程中,會建立具有不同事件類型的 socket。在服務端,有一段I/0多重化程序,將其置入佇列之中。然後,檔案事件分派器,依序去佇列中取,轉送到不同的事件處理器。

要說明的是,這個I/O多工機制,redis也提供了select、epoll、evport、kqueue等多工函數庫,大家可以自行去了解。

4、Redis的資料類型,以及每種資料類型的使用場景

分析:是不是覺得這個問題很基礎,其實我也這麼覺得。然而根據面試經驗發現,至少百分之八十的人答不上這個問題。建議,在項目中用到後,再類比記憶,體會更深,不要硬記。基本上,一個合格的程式設計師,五種類型都會用到。

回答:總共五種

(一)String

這其實很普通,涉及最基本的取得/設定運算,value可以是字串或數字。一般做一些複雜的計數功能的快取。

(二)hash

這裡value存放的是結構化的對象,比較方便的就是操作其中的某個欄位。部落客在做單一登入的時候,就是用這種資料結構儲存使用者訊息,以cookieId作為key,設定30分鐘為快取過期時間,能很好的模擬出類似session的效果。

(三)list

使用List的資料結構,可以做簡單的訊息佇列的功能。除此之外,我們還可以運用lrange指令來實現基於Redis的分頁功能,這種方法具有出色的效能和使用者體驗。

(四)set

因為set堆疊的是一堆不重複值的集合。所以可以做全域去重的功能。為什麼不用JVM自帶的Set進行去重?因為我們的系統通常都是叢集部署,使用JVM自帶的Set,比較麻煩,難道為了一個做一個全域去重,再起一個公共服務,太麻煩了。

另外,就是利用交集、並集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。

(五)sorted set

sorted set多了一個權重參數score,集合中的元素能夠按score進行排列。可以做排行榜應用,取TOP N操作。另外,在一篇名為《分散式延時任務方案解析》的文章中,提到了sorted set可用來實作延時任務的方案。最後一個應用就是可以做範圍查找。

5、Redis的過期策略以及記憶體淘汰機制

這個問題的重要性不言而喻,是否使用了Redis就能夠看出來。例如,如果你在Redis中只能儲存5 GB的數據,但你卻寫入了10 GB的數據,那麼其中的5 GB數據將會被刪除。怎麼刪的,這個問題思考過?還有,你的資料已經設定了過期時間,但是時間到了,記憶體佔用率還是比較高,有思考過原因麼?

回答:

redis採用的是定期刪除惰性刪除策略。

為什麼不用定時刪除策略?

定時刪除,用定時器來負責監視key,過期則自動刪除。雖然記憶體及時釋放,但是十分消耗CPU資源。由於在高並發請求下,CPU需要專注於請求處理,而不是鍵值刪除操作,因此我們放棄了採用該策略

定期刪除惰性刪除是如何工作的呢?

定期刪除,redis預設每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。如果只使用定期刪除策略,就會導致許多key在到期時間後沒有被刪除。

於是,惰性刪除派上用場。也就是說在你取得某個key的時候,redis會檢查一下,這個key如果設定了過期時間那麼是否過期了?如果過期了此時就會刪除。

採用定期刪除 惰性刪除就沒其他問題了麼?

不是的,如果定期刪除沒刪除key。然後你也沒即時去請求key,也就是說惰性刪除也沒生效。這樣,redis的記憶體會越來越高。那就應該採用記憶體淘汰機制。

在redis.conf中有一行配置

# maxmemory-policy volatile-lru

該配置就是配內存淘汰策略的(什麼,你沒配過?好好反省自己)

1)noeviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯。應該沒人用吧。

當記憶體空間不足以儲存新的資料時,allkeys-lru演算法會從鍵空間中移除最近最少被使用的鍵。推薦使用,目前項目在用這種。

3)allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個key。應該也沒人用吧,你不刪最少使用Key,去隨機刪。

4)volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的key。一般情況下,只有在將redis同時用作快取和持久化儲存時才會使用這種方法。不推薦

5)volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個key。依然不推薦

6)volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的key優先移除。不推薦

ps:如果沒有設定expire 的key, 不滿足先決條件(prerequisites); 那麼volatile-lru, volatile-random 和volatile-ttl 策略的行為, 和noeviction(不刪除) 基本上一致。

6、Redis和資料庫雙寫一致性問題

在分散式系統中,一致性問題是常見問題。此問題可進一步區分為最終一致性和強一致性。資料庫和快取雙寫,就必然會存在不一致的問題。答這個問題,先明白一個前提。就是如果對資料有強一致性要求,不能放快取。我們所做的一切,只能保證最終一致性。我們所提出的方案,實際上只能減少不一致事件的發生機率,無法完全消除。因此,有強一致性要求的數據,不能放快取。

在這裡簡單介紹《分散式資料庫與快取雙寫一致性方案解析》一文所進行的詳細分析。首先,採取正確更新策略,先更新資料庫,再刪除快取。提供一個備用措施,例如使用訊息佇列,以應對可能出現刪除快取失敗的情況。

7、如何處理快取穿透和快取雪崩問題

中小型傳統軟體企業很少遇到這兩個問題,說實話。如果有大並發的項目,流量有幾百萬左右。這兩個問題一定要深刻考慮。

回答:如下所示

快取穿透,即駭客故意去請求快取中不存在的數據,導致所有的請求都懟到資料庫上,從而資料庫連線異常。

解決方案:

當快取失效時,使用互斥鎖,先取得鎖,一旦取得到鎖,再去請求資料庫。沒被鎖,則休眠一段時間重試

(二)採用非同步更新策略,無論key是否取到值,都直接回傳。在value值中保存一個快取失效時間,一旦快取過期,就會非同步啟動一個執行緒來讀取資料庫並更新快取。需要做快取預熱(項目啟動前,先載入快取)操作。

提供一種攔截機制,可以迅速判斷請求是否有效。例如,使用布隆過濾器來內部維護一系列合法有效的 key。迅速判斷出,請求所攜帶的Key是否合法有效。如果不合法,則直接返回。

快取雪崩,也就是快取同一時間大面積的失效,這個時候又來了一波請求,結果請求都懟到資料庫上,導致資料庫連線異常。

解決方案:

(一)給予快取的失效時間,加上一個隨機值,避免集體失效。

(二)使用互斥鎖,但是該方案吞吐量明顯下降了。

(三)雙緩存。我們有兩個緩存,緩存A和緩存B。快取A的失效時間為20分鐘,快取B不設失效時間。自己做快取預熱操作。然後細分以下幾個小點

  •  I 從快取A讀取資料庫,有則直接返回

  • ## II A沒有數據,直接從B讀數據,直接返回,並且非同步啟動一個更新線程。

  •  III 更新執行緒同時更新快取A和快取B。

8、如何解決Redis的並發競賽key問題#

分析:這個問題大致就是,同時有多個子系統去set一個key。這個時候要注意什麼呢?大家思考過麼。部落客在事先查閱了百度搜尋結果後發現,幾乎所有答案都建議使用redis事務機制。部落客不建議使用redis的事務機制。因為我們的生產環境,基本上都是redis叢集環境,做了資料分片作業。當單一任務涉及多個key操作時,這些key不一定都儲存在同一台Redis伺服器上。因此,redis的事務機制,十分雞肋。

回答:如下

#(1)如果對這個key運算,不要求順序

這種情況下,準備一個分散式鎖,大家去搶鎖,搶到鎖就做set操作即可,比較簡單。

(2)如果對這個key操作,要求順序

假設有一個key1,系統A需要將key1設為valueA,系統B需要將key1設為valueB,系統C需要將key1設定為valueC.

期望依照key1的value值依照valueA-->valueB-->valueC的順序變化。這種時候我們在資料寫入資料庫的時候,需要保存一個時間戳記。假設時間戳如下

系統A key 1 {valueA 3:00}

系統B key 1 {valueB 3:05}

系統C key 1 {valueC 3: 10}

設想一下,如果系統B搶先取得鎖,它會將key1的值設為{valueB 3:05}。當系統A取得到鎖時,如果發現自己儲存的valueA的時間戳早於快取中儲存的時間戳,那麼系統A將不進行set操作。以此類推。

其他方法,例如利用佇列,將set方法變成串列存取也可以。總之,靈活變通。

以上是Redis的八個經典問題是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除