首頁/ 汽車/ 正文

Redis 快取的解決方案

1

快取概述

在很久很久以前人類和洪水作鬥爭的過程中,水庫發揮了至關重要的作用 : 在發洪水時可以蓄水,緩解洪水對下游的衝擊;在乾旱時可以把庫存的水釋放出來以供人們使用。這裡的水庫就起著

快取

的作用。在如今網際網路的世界裡隨著網際網路的普及,內容資訊越來越複雜,使用者數和訪問量越來越大,我們的應用需要支撐更多的併發量,同時我們的

應用伺服器

資料庫伺服器

所做的計算也越來越多。

但是往往我們的應用伺服器資源是有限的,且伺服器技術變革是緩慢的,資料庫每秒能接受的請求次數也是有限的,那麼如何能夠有效利用有限的資源來提供儘可能大的吞吐量呢?一個有效的辦法就是引入快取,打破標準流程,每個環節中請求可以從快取中直接獲取目標資料並返回,從而減少計算量,有效提升響應速度,讓有限的資源服務更多的使用者。

2

快取的定

快取就是資料交換的

緩衝區(稱作Cache)

,這個概念最初是來自於記憶體和 CPU。當某一硬體要讀取資料時,會首先從快取中查詢需要的資料,如果找到了則直接使用執行,快取找不到的話則從記憶體中找。由於快取的執行速度比記憶體快得多,故快取的作用就是幫助硬體更快地執行。

3

快取的分類

當用戶從鍵入一個地址到頁面的展示過程中通常包含了很多種快取。有前端快取、本地快取(協商快取,強快取等)到我們的閘道器快取(CDN 快取)、最後到我們服務端快取。服務端快取又區分為程序快取(本地快取),還有比較火的分散式快取,最後到了資料庫層面的快取。如下圖所示:

Redis 快取的解決方案

4

快取是一把雙刃劍

在我們通常的軟體設計中,有一些熱點資料需要展示到頁面,我們通常當這些資料快取到記憶體或者其他讀寫速度優異的框架中。減少與資料庫進行 I/O 操作。提升資料的響應速度。這一切看起來就是這麼完美。

實際上,在快取系統的設計架構中,還有很多坑。如果設計不當會導致很多嚴重的後果。設計不當,輕則請求變慢、效能降低,重則會資料不一致、系統可用性降低,甚至會導致快取雪崩,整個系統無法對外提供服務。

接下來我們著重講述一下在快取設計過程中幾大經典的問題。

快取失效

先解釋一下什麼叫做

快取失效

我們在存放快取的時候,可以指定快取 Key 的失效時間,當失效時間到了,此快取就會失效,由於在快取中找不到該資料,所以這個時候如果使用者有請求該資料就繞過快取直接到資料庫中請求資料。

看到這裡小夥伴們肯定有很多問號?

Redis 快取的解決方案

這不是很正常的現象嘛?為什麼要把這個問題拿出來說呢?莫急看下圖圖示

Redis 快取的解決方案

這裡我們透過兩個場景來說明一下

場景一

:這種情況下一般不會對資料庫造成比較嚴重的影響,因為失效的 key 的數量比較少,即使同時請求到資料庫層面也是可以接受的。

場景二

:在這種場景中,當快取裡面的大量 Key

同時

失效,這個時候如果有請求過來,會穿過失效的 Key全部落到資料庫層面。導致資料庫的負荷瞬間新增。可能會出現資料庫宕機等特大事故。

解決方案

看到這裡很多聰明的小夥伴其實已經想到了。場景 2 的事故主要因為很多 key 一起失效的原因,跟我們日常寫快取的過期時間息息相關。如果我們在日常的開發過程中需要將一批 Key 設定到快取中並制定失效時間。這個時候就要注意場景 2 發生的情況。我們可以在

失效時間 + 隨機時間

。避免大量 Key 失效衝擊我們的資料庫。

Redis 快取的解決方案

快取擊穿

通常情況下,我們去查詢資料都是存在的。那麼如果請求去查詢一條壓根兒資料庫中根本就不存在的資料,也就是快取和資料庫都查詢不到的這條資料會怎麼樣呢?這樣會導致每次訪問都會直接打到資料庫上面去。這種查詢不存在資料的現象我們稱為

快取穿透

下面是快取失效的場景

很多夥伴看到這裡肯定又會覺得這是一件很正常的事情。試想一下,如果有駭客會對你的系統進行攻擊,拿一個不存在的 key 不停的去查詢資料,會產生大量的請求到資料庫去查詢。可能會導致你的資料庫由於壓力過大而宕掉。

Redis 快取的解決方案

解決方案一

首先我們能想到的就是在閘道器引數進行過濾。校驗請求的 key 是否是我們系統 key 的格式等

當然這閘道器層所能做到的只是一些簡單過濾。每個後端的設計人員應該對服務的可用性和健壯性負責。接下來我們看看服務端應該如何處理

服務端可以將不存在的 key 暫時儲存到我們的快取中,再次接收到同樣的請求後如果直接命中快取並且值為空那麼就會直接返回,不會穿透到資料庫層面,這樣就避免了快取擊穿。

但是駭客/惡意攻擊者是不會這麼輕易被打發的。每次請求都會傳不同的 key 來攻擊我們的服務。這個時候這個方案起不到作用了。

解決方案二

構建一個

BloomFilter(布隆過濾器)

快取過濾器,記錄全量資料。這樣訪問資料時,可以直接透過 BloomFilter 判斷這個 key 是否存在,如果不存在直接返回即可,根本無需查快取和 DB。這樣在快取之前加了一層校驗。如果key 值不存在,就不會請求到我們的快取更加不會到我們的資料庫中。

布隆過濾器可以理解為一個不怎麼精確的

set

結構,當你使用它的 contains 方法判斷某個物件是否存在時,它可能會誤判。但是布隆過濾器也不是特別不精確,只要引數設定的合理,它的精確度可以控制的相對足夠精確,只會有小小的誤判機率。當布隆過濾器說某個值存在時,這個值可能不存在;當它說不存在時,那就肯定不存在。即使誤判不存在走到快取和後端服務也是可以接受的。

Redis 快取的解決方案

快取雪崩

快取雪崩是指快取的部分節點不可用導致整個快取體系甚至整個服務系統不可用

Redis 快取的解決方案

那麼你可能會有疑問,快取雪崩和快取擊穿有什麼關係呢?

從概念上來看,快取擊穿是因為查詢不存在的 key 穿透快取直接訪問我們的資料庫。而快取雪崩是因為我們的快取節點不可用,請求未經過快取就直到了我們的資料庫層面。然而兩者都會影響我們的服務穩定性。

快取節點的不可用會導致快取雪崩,那麼我們快取元件叢集部署是不是就解決了這個問題呢?

叢集部署有兩種情況:

一種就是簡單的主從例如 redis 的哨兵之殤

採取一致性 hash 演算法叢集部署例如 redis 的分片叢集

第一種情況:傳送雪崩的時候一般是多個節點同時不可用,例如我們的節點伺服器內容不足,雖然分主從節點都是儲存的資料都是一樣的。如果快取中的資料過大導致節點不可用。那大部分節點也會存在這個問題。請求會大面積的落到資料庫層面導致後端系統崩潰。

第二種情況: 首先看一下下圖雖然資料根據會根據取模演算法分配到不同的節點中,假設節點 A 不可用,資料 A 會按照逆時針找到節點 B,會因為本來應該存放到節點 A 的資料存放到節點 B,以此類推會導致整個快取節點不可用。請求也會大面積落到我們後端的資料庫層面導致系統崩潰。

Redis 快取的解決方案

解決方案

對快取體系進行實時監控,當請求訪問的慢速比超過閥值時,及時報警,透過機器替換、服務替換進行及時恢復。

對快取增加多個副本,快取異常或請求 miss 後,再讀取其他快取副本。

ehcache 本地快取 + Hystrix 限流&降級,避免 MySQL被打死

業務 DB 的訪問增加讀寫開關,當發現 DB 請求變慢、阻塞,慢請求超過閥值時,就會關閉讀開關,部分或所有讀 DB 的請求進行 failfast 立即返回,待 DB 恢復後再開啟讀開關。

資料不一致

資料不一致的概念很簡單:

就是快取中的資料和資料庫中的資料不一致

那為什麼會不一致呢?我們的資料被快取之後,一旦資料被修改(修改時也是刪除快取中的資料)或刪除,我們就需要同時操作快取和資料庫。這時就會存在一個數據不一致的問題。

Redis 快取的解決方案

如上圖所示當我們先刪除資料庫再去操作快取,快取中未刪除資料庫其實已經不存在該資料了。這個時候就會出現快取不一致的情況。

聰明的小夥伴肯定想到了我們還是需要先做快取刪除操作,再去完成資料庫操作。則會去資料庫中查詢,如果快取中沒有該資料,則會去資料庫中查詢,之後再放入到快取中。這樣就完美了嘛?答案肯定不會這麼簡單。請看下圖:

Redis 快取的解決方案

解決方案

這裡其實沒有什麼很完美的解決方法。可以將變更的 key 新增到安全佇列中。當另一個查詢請求 B 進來時,如果發現快取中沒有該值,則會先去佇列中檢視該資料是否正在被更新或刪除,如果佇列中有該資料,則阻塞等待,直到 A 操作資料庫成功之後,喚醒該阻塞執行緒,再去資料庫中查詢該資料。這裡其實也是有很多缺陷的。執行緒需要阻塞等待。

最好的解決方案就是如果資料更新比較頻繁且對資料有一定的一致性要求,我通常不建議使用快取。看到這裡是不是發出了一句切!!!!

Redis 快取的解決方案

5

總結

快取雖然能大幅度的提高伺服器的效能以及使用者的體驗感。但是隨著而來的就是各種由於快取導致的一系列問題。所以當我們使用快取的過程中需要注意以上的經典問題。

關注我,帶你更好的學習程式設計。

相關文章

頂部