帶上問題來學redis,看到不喫虧(什麼是redis?緩存問題、數據一致性、redis配置文件漢化版)

前言

細想了一下,這篇標題上這些東西,每一塊拿出來都能長篇大論。那我就,長話挑着說,中間件用鏈接。

文章目錄

redis是什麼?

官方套話我就不多說了,做後端的朋友多多少少肯定耳濡目染了。

redis
是一個NOSQL類型數據庫,
是一個高性能的key-value數據庫,
是爲了解決高併發、高可用、大數據存儲等一系列的問題而產生的數據庫解決方案,
是一個非關係型的數據庫,
但是,它也是不能替代關係型數據庫,只能作爲特定環境下的擴充。

這麼說可還算中肯?沒有神話它,也把它的情況點出來了。

總的來說,它是一個很好用且應用範圍很廣的數據庫中間件,緩存中間件

爲什麼說redis是緩存中間件??

redis由於數據的讀取和操作都在內存當中操作,讀寫的效率較高,所以經常被用來做數據的緩存。把一些需要頻繁訪問的數據,而且在短時間之內不會發生變化的,放入redis中進行操作,從而提高用戶的請求速度和降低MySQL數據庫(後面都默認數據庫 = MySQL)的負載。

弄了兩張對比圖:

沒有用redis時,服務器對數據庫的訪問情況是這樣的:

用了redis之後,服務器對數據的訪問是這樣的:

瞭解設計模式嗎? 我以“享元模式”(傳送門)的思想來理解。

爲什麼要這麼做?別急,等我們看完“緩存穿透”就知道了。

redis.conf翻譯與配置

博主不辭辛勞翻譯了一下redis.conf配置文件,感覺裏面東西還是挺好的。
redis.conf翻譯與配置(一)【redis6.0.6】
redis.conf翻譯與配置(二)【redis6.0.6】
redis.conf翻譯與配置(三)【redis6.0.6】
redis.conf翻譯與配置(四)【redis6.0.6】
redis.conf翻譯與配置(五)【redis6.0.6】
redis.conf翻譯與配置(六)【redis6.0.6】
翻譯亦是不易,大家多多支持

緩存穿透

什麼是緩存穿透?

業務系統要查詢的數據根本就存在!當業務系統發起查詢時,按照上述流程,首先會前往緩存中查詢,由於緩存中不存在,然後再前往數據庫中查詢。由於該數據壓根就不存在,因此數據庫也返回空。這就是緩存穿透。

緩存穿透的危害!!!

如果存在海量請求查詢壓根就不存在的數據,那麼這些海量請求都會落到數據庫中,數據庫壓力劇增,可能會導致系統崩潰(你要知道,目前業務系統中最脆弱的就是IO,稍微來點壓力它就會崩潰,所以我們要想種種辦法保護它)。

那麼我們現在再來想想,爲什麼需要用緩存。答案已經很明顯了,不用緩存,相當於直接擊穿。

該當如何?????

方案一:緩存空值

這個方案簡單講一下。
之所以發生緩存穿透,是因爲緩存中沒有存儲這些空數據的key,導致這些請求全都打到數據庫上。
那麼,我們可以稍微修改一下業務系統的代碼,將數據庫查詢結果爲空的key也存儲在緩存中。當後續又出現該key的查詢請求時,緩存直接返回null,而無需查詢數據庫。

方案二:布隆過濾器


使用布隆過濾器。在緩存之前在加一層布隆過濾器,在查詢的時候先去布隆過濾器查詢 key 是否存在,如果不存在就直接返回,存在再查緩存和DB。

布隆過濾器

關於布隆過濾器,如果要講的話又可以來一篇博客了,但是不瞭解又不好,這是一篇我之前轉的布隆過濾器的文章:傳送門,既然講到這裏,那後面我會去在原文基礎上再進行修改,地址不會變。

布隆過濾器特性:
如果布隆過濾器判斷該元素存在,那麼該元素大概率存在,如果布隆過濾器判斷該元素不存在,那麼該元素則一定不存在。

兩種方案比較

第一種方案:容易出現緩存太多空值佔用了更多的空間,得不償失。

對於空數據的key各不相同、key重複請求概率低的場景而言,應該選擇第二種方案。而對於空數據的key數量有限、key重複請求概率較高的場景而言,應該選擇第一種方案。

簡單的說:第二種方案比較應景。

緩存雪崩

通過上文可知,緩存其實扮演了一個保護數據庫的角色。它幫數據庫抵擋大量的查詢請求,從而避免脆弱的數據庫受到傷害。

如果緩存因某種原因發生了宕機,那麼原本被緩存抵擋的海量查詢請求就會像瘋狗一樣湧向數據庫。此時數據庫如果抵擋不了這巨大的壓力,它就會崩潰。

這就是緩存雪崩。

雪崩?到點了,鍵值通通下班了。。。

某一個時間段內,緩存大量失效或者緩存服務器掛掉(重啓)時,導致大量請求直接去訪問數據庫,導致數據庫崩潰。

雪崩時,每一片雪花都在想着勇闖天涯! – 看,未來

這就像下班高峯期一樣,高速路、大馬路、小馬路通通堵上了,交通陷入了困境。。。

如何處置乎???

方案一:永不下班(設置永不過期)

開個玩笑啊,這個方法簡直是,沒話說。咱還沒到富得流油的時候,沒那麼多內存空間啊。

方案二:錯峯(隨機key值過期時間)

既然讓大家“不畏懼上班,不想着下班”是不現實的,又要解決下班高峯期的問題,怎麼辦?
之前不是有個方法,叫流量錯峯嘛,錯開高峯期,比方說你六點下班,我五點下班,他九點半,是吧。

key的失效期分散開,不同的key設置不同的有效期,這樣就可以有效的避免大量key值下班而導致的窘境了。

方案三:設置二級緩存

加一層本地緩存(例如Guava Cache、ECache等),採用本地緩存+分佈式緩存redis的方式。

方案四:redis高可用

這個思想的含義是,既然redis有可能掛掉,那我多增設幾臺redis,這樣一臺掛掉之後其他的還可以繼續工作,其實就是搭建的集羣。

方案五:降級

依賴隔離組件爲後端限流並降級。在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。

個人認爲,二、四比較好一些。

緩存擊穿(熱點數據集中失效)

其實理解了前面的緩存穿透和緩存雪崩之後,就很好理解緩存擊穿了。

如果某一個熱點數據失效,那麼當再次有該數據的查詢請求時就會前往數據庫查詢。但是,從請求發往數據庫,到該數據更新到緩存中的這段時間中,由於緩存中仍然沒有該數據,因此這段時間內到達的查詢請求都會落到數據庫上,這將會對數據庫造成巨大的壓力。此外,當這些請求查詢完成後,都會重複更新緩存。

解決方案

方案一:鎖

此方法只允許一個線程重建緩存,其他線程等待重建緩存的線程執行完,重新從緩存獲取數據即可.

當某一個熱點數據失效後,只有第一個數據庫查詢請求發往數據庫,其餘所有的查詢請求均被阻塞,從而保護了數據庫。但是,由於採用了互斥鎖,其他請求將會阻塞等待,此時系統的吞吐量將會下降。這需要結合實際的業務考慮是否允許這麼做。

互斥鎖可以避免某一個熱點數據失效導致數據庫崩潰的問題,而在實際業務中,往往會存在一批熱點數據同時失效的場景。那麼,對於這種場景該如何防止數據庫過載呢?

參考“雪崩”,錯峯。

方案二:永不過期

在處理雪崩問題上這個方法會比較扯,但是在處理熱鍵問題是可以考慮的。

方案比較

互斥鎖 (mutex key):這種方案思路比較簡單,但是存在一定的隱患,如果構建緩存過程出現問題或者時間較長,可能會存在死鎖和線程池阻塞的風險,但是這種方法能夠較好的降低後端存儲負載並在一致性上做的比較好。
” 永遠不過期 “:這種方案由於沒有設置真正的過期時間,實際上已經不存在熱點 key 產生的一系列危害,但是會存在數據不一致的情況,同時代碼複雜度會增大。

數據一致性

讀取緩存步驟一般沒有什麼問題,但是一旦涉及到數據更新:數據庫和緩存更新,就容易出現緩存和數據庫間的數據一致性問題。不管是先寫數據庫,再刪除緩存;還是先刪除緩存,再寫庫,都有可能出現數據不一致的情況。舉個例子:

1、如果刪除了緩存Redis,還沒有來得及寫庫MySQL,另一個線程就來讀取,發現緩存爲空,則去數據庫中讀取數據寫入緩存,此時緩存中爲髒數據。

2、如果先寫了庫,在刪除緩存前,寫庫的線程宕機了,沒有刪除掉緩存,則也會出現數據不一致情況。

因爲寫和讀是併發的,沒法保證順序,就會出現緩存和數據庫的數據不一致的問題。如何解決?

解決方案

結合前面例子的兩種刪除情況,我們就考慮前後雙刪加懶加載模式。

什麼是懶加載?

就是當業務讀取數據的時候再從存儲層加載的模式,而不是更新後主動刷新。
如果你有伸展樹(傳送門)的基礎,那理解這個“懶加載”就會融會貫通。

延遲雙刪

在寫庫前後都進行redis.del(key)操作,並且第二次刪除通過延遲的方式進行。
異步延遲刪除:

  • 1)先刪除緩存;
  • 2)再寫數據庫;
  • 3)觸發異步寫入串行化mq(也可以採取一種key+version的分佈式鎖);
  • 4)mq接受再次刪除緩存。

異步刪除對線上業務無影響,串行化處理保障併發情況下正確刪除。

爲什麼要雙刪?

db更新分爲兩個階段,更新前及更新後,更新前的刪除很容易理解,在db更新的過程中由於讀取的操作存在併發可能,會出現緩存重新寫入數據,這時就需要更新後的刪除。

雙刪失敗如何處理?

**1、設置緩存過期時間 **
從理論上來說,給緩存設置過期時間,是保證最終一致性的解決方案。所有的寫操作以數據庫爲準,只要到達緩存過期時間,則後面的讀請求自然會從數據庫中讀取新值然後回填緩存。

結合雙刪策略+緩存超時設置,這樣最差的情況就是在超時時間內數據存在不一致。

**2、重試方案 **
重試方案有兩種實現,一種在業務層做,另外一種實現中間件負責處理。

**業務層實現重試如下: **


然而,該方案有一個缺點,對業務線代碼造成大量的侵入。於是有了方案二,在方案二中,啓動一個訂閱程序去訂閱數據庫的binlog,獲得需要操作的數據。在應用程序中,另起一段程序,獲得這個訂閱程序傳來的信息,進行刪除緩存操作。

**中間件實現重試如下: **


其他

如何發現熱key

  1. 預估熱key,比如秒殺的商品、火爆的新聞等
  2. 在客戶端進行統計,實現簡單,加一行代碼即可
  3. 如果是Proxy,比如Codis,可以在Proxy端收集
  4. 利用Redis自帶的命令,monitor、hotkeys。但是執行緩慢(不要用)
  5. 利用基於大數據領域的流式計算技術來進行實時數據訪問次數的統計,比如 Storm、Spark、Streaming、Flink,這些技術都是可以的。發現熱點數據後可以寫到zookeeper中

解決方案

  1. 變分佈式緩存爲本地緩存,發現熱key後,把緩存數據取出後,直接加載到本地緩存中。可以採用Ehcache、Guava Cache都可以,這樣系統在訪問熱key數據時就可以直接訪問自己的緩存了。(數據不要求時時一致)

  2. 在每個Redis主節點上備份熱key數據,這樣在讀取時可以採用隨機讀取的方式,將訪問壓力負載到每個Redis上。

  3. 利用對熱點數據訪問的限流熔斷保護措施,每個系統實例每秒最多請求緩存集羣讀操作不超過 400 次,一超過就可以熔斷掉,不讓請求緩存集羣,直接返回一個空白信息,然後用戶稍後會自行再次重新刷新頁面之類的。(首頁不行,系統友好性差)通過系統層自己直接加限流熔斷保護措施,可以很好的保護後面的緩存集羣.

如何發現Big key

Big key

大key指的是存儲的值(Value)非常大。

  • 大key會大量佔用內存,在集羣中無法均衡
  • Redis的性能下降,主從複製異常
  • 在主動刪除或過期刪除時會操作時間過長而引起服務阻塞

如何發現Big key

  • redis-cli --bigkeys命令。可以找到某個實例5種數據類型(String、hash、list、set、zset)的最大key。但如果Redis 的key比較多,執行該命令會比較慢。

  • 獲取生產Redis的rdb文件,通過rdbtools分析rdb生成csv文件,再導入MySQL或其他數據庫中進行分析統計,根據size_in_bytes統計big key

解決方案:

  • string類型的big key,儘量不要存入Redis中,可以使用文檔型數據庫MongoDB或緩存到CDN上。如果必須用Redis存儲,最好單獨存儲,不要和其他key一起存儲。採用一主一從或多從。

  • 單個簡單key存儲的value很大,可以嘗試將對象分拆成幾個key-value, 使用mget獲取值,這樣分拆的意義在於分拆單次操作的壓力,將操作壓力平攤到多次操作中,降低對redis的IO影響。
    hash, set,zset,list 中存儲過多的元素,可以將這些元素分拆。

先到這兒啦,如果覺得點進來不虧,不妨順手來個關注收藏。

作者:看_未來
鏈接: https://juejin.cn/post/6937193030478888973
來源:juejin

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