面對海量請求,快取設計還應該考慮哪些問題?

tianxiaoxu發表於2018-05-25

【本文轉自部落格園  作者:陳樹義  原文連結:https://www.cnblogs.com/chanshuyi/p/how_to_deal_with_massive_request_in_redis.html】
從第一個快取框架 Memcached 誕生以來,快取就廣泛地存在於網際網路應用中。如果你的應用流量很小,那麼使用快取可能並不需要做多餘的考慮。但如果你的應用流量達到了成百上千萬,那麼你就不得不考慮深層次的快取問題:快取穿透、快取擊穿與快取雪崩。

  快取穿透

  快取穿透是指查詢一個一定不存在的資料,因為這個資料不存在,所以永遠不會被快取,所以每次請求都會去請求資料庫。

  例如我們請求一個 UserID 為 -1 的使用者資料,因為該使用者不存在,所以該請求每次都會去讀取資料庫。在這種情況下,如果某些心懷不軌的人利用這個存在的漏洞去偽造大量的請求,那麼很可能導致DB承受不了那麼大的流量就掛掉了。

  對於快取穿透,有幾種解決方案,一種是事前預防,一種是事後預防。

  事前預防。其實就是對所有請求都進行引數校驗,把絕大多數非法的請求抵擋在最外層。在我們舉的這個例子中,那麼就是做引數校驗,對於 UserID 小於 0 的請求全部拒絕。但即使我們做了全面的引數校驗,還是可能存在漏網之魚,會出現一些我們沒想到的情況。

  例如我們的 UserID 是遞增的,那麼如果有人請求一個 UserID 很大的使用者資訊(例如:1000000),而我們的 UserID 最大也就 10000。這個時候,你不可能限制 UserID 大於 1 萬的就是非法的,或者說大於 10 萬就是非法的,所以該使用者ID肯定可以透過引數校驗。但該使用者確實不存在,所以每次請求都會去請求資料庫。

  其實上面只是我所能想到的一種情況,我們沒想到的情況肯定還有很多。對於這些情況,我們能做的就是時候預防。

  事後預防。事後預防說的就是當查詢到一個空的結果時,我們仍然將這個空的結果進行快取,但是設定一個很短的過期時間(例如一分鐘)。在這裡我們可以看到,其實我們並沒有完全預防非法請求,只不過是將非法請求的風險讓承受能力更強的redis去承擔,讓承受能力稍弱的資料庫更安全。

  透過上面這兩種處理方式,我們基本可以解決快取穿透的問題。事前預防解決80%的非法請求,剩下的20%非法請求則使用Redis轉移風險。

  快取擊穿

  如果你的應用中有一些訪問量很高的熱點資料,我們一般會將其放在快取中以提高訪問速度。另外,為了保持時效性,我們通常還會設定一個過期時間。但是對於這些訪問量很高的KEY,我們需要考慮一個問題:當熱點KEY在失效的瞬間,海量的請求會不會產生大量的資料庫請求,從而導致資料庫崩潰?

  例如我們有一個業務 KEY,該 KEY 的併發請求量為 10000。當該 KEY 失效的時候,就會有 1 萬個執行緒會去請求資料庫更新快取。這個時候如果沒有采取適當的措施,那麼資料庫很可能崩潰。

  其實上面這個問題就是快取擊穿的問題,它發生在快取KEY的過期瞬間。對於這種情況,現在常用的解決方式有這麼兩種:互斥鎖、永遠不過期。

  互斥鎖

  互斥鎖指的是在快取KEY過期去更新的時候,先讓程式去獲取鎖,只有獲取到鎖的執行緒才有資格去更新快取KEY。其他沒有獲取到鎖的執行緒則休眠片刻之後再次去獲取最新的快取資料。透過這種方式,同一時刻永遠只有一個執行緒會去讀取資料庫,這樣也就避免了海量資料庫請求對於資料庫的衝擊。

  而對於上面說到的鎖,我們可以使用快取提供的一些原則操作來完成。例如對於 redis 快取來說,我們可以使用其 SETNX 命令來完成。

面對海量請求,快取設計還應該考慮哪些問題?

  上面的 key_mutex 其實就是一個普通的 KEY-VALUE 值,我們使用 setnx 命令去設定其值為 1。如果這時候已經有人在更新快取KEY了,那麼 setnx 命令會返回 0,表示設定失敗。

  永遠不過期

  從快取的角度來看,如果你設定了永遠不過期,那麼就不會有海量請求資料庫的情形出現。此時我們一般透過新起一個執行緒的方式去定時將資料庫中的資料更新到快取中,更加成熟的方式是透過定時任務去同步快取和資料庫的資料。

  但這種方案會出現資料的延遲問題,也就是執行緒讀取到的資料並不是最新的資料。但對於一般的網際網路功能來說,些許的延遲還是能接受的。

  快取雪崩

  快取雪崩是指在我們設定快取時採用了相同的過期時間,導致快取在某一時刻同時失效,請求全部轉發到資料庫,最終導致資料庫瞬時壓力過大而崩潰。

  例如我們有 1000 個KEY,而每個 KEY 的併發請求不大,只有 10 次。而快取雪崩指的就是這 1000 個 KEY 在同一時間,同時失效,這個時候就突然有 1000 ** 10 = 一萬次查詢。

  快取雪崩導致的問題一般很難排查,如果沒有事先預防,很可能要花很大力氣才能找得到原因。對於快取雪崩的情況,最簡單的方案就是在原有失效時間的基礎上增加一個隨機時間(例如1-5分鐘),這樣每個快取過期時間的重複率就會降低,從而減少快取雪崩的發生。

  總結

  對於快取穿透、快取擊穿、快取雪崩這三個情景,許多人會搞不明白,甚至會混淆。

  「快取穿透」指的是請求不存在的資料,從而使得快取形同虛設,快取層被穿透了。例如我們請求一個 UserID 為 -1 的使用者資料,因為該使用者不存在,所以該請求每次都會去讀取資料庫。在這種情況下,如果某些心懷不軌的人利用這個存在的漏洞去偽造大量的請求,那麼很可能導致DB承受不了那麼大的流量就掛掉了。

  「快取擊穿」指的是併發量很高的 KEY,在該 KEY 失效的瞬間有很多請求同同時去請求資料庫,更新快取。例如我們有一個業務 KEY,該 KEY 的併發請求量為 10000。當該 KEY 失效的時候,就會有 1 萬個執行緒會去請求資料庫更新快取。這個時候如果沒有采取適當的措施,那麼資料庫很可能崩潰。

  「快取雪崩」則是指快取在同一時間同時過期,就像所有雪塊同一時刻掉下來,像雪崩一樣。例如我們有 1000 個KEY,而每個 KEY 的併發請求不大,只有 10 次。而快取雪崩指的就是這 1000 個 KEY 在同一時間,同時失效,這個時候就突然有 1000 ** 10 = 一萬次查詢。

  對於它們出現的情形,我們可以做一些總結:

  「快取穿透」是業務層面的漏洞導致非法請求,與請求量、快取失效沒關係。「快取擊穿」則只會出現在熱點資料上,發生在快取失效的瞬間,與業務沒多大關係。「快取雪崩」則是因為多個 KEY 同時失效,導致資料庫請求太多。非熱點資料也會導致快取雪崩,只要同時失效的 KEY 足夠多。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31137683/viewspace-2155097/,如需轉載,請註明出處,否則將追究法律責任。

相關文章