【緩存】redis緩存設計

1:緩存技術和框架的重要性mysql

互聯網的一些高併發,高性能的項目和系統中,緩存技術是起着功不可沒的做用。緩存不只僅是key-value的簡單存取,它在具體的業務場景中,仍是很複雜的,須要很強的架構設計能力。我曾經就遇到過由於緩存架構設計不到位,致使了系統崩潰的案例。redis

2:緩存的技術方案分類算法

1)是作實時性比較高的那塊數據,好比說庫存,銷量之類的這種數據,咱們採起的實時的緩存+數據庫雙寫的技術方案,雙寫一致性保障的方案。sql

2)是作實時性要求不高的數據,好比說商品的基本信息,等等,咱們採起的是三級緩存架構的技術方案,就是說由一個專門的數據生產的服務,去獲取整個商品詳情頁須要的各類數據,通過處理後,將數據放入各級緩存中。數據庫

3:高併發以及高可用的複雜系統中的緩存架構都有哪些東西緩存

1)在大型的緩存架構中,redis是最最基礎的一層。高併發,緩存架構中除了redis,還有其餘的組成部分,可是redis相當重要。安全

  • 若是你的數據量不大(10G之內),單master就能夠。redis持久化+備份方案+容災方案+replication(主從+讀寫分離)+sentinal(哨兵集羣,3個節點,高可用性)網絡

  • 若是你的數據量很大(1T+),採用redis cluster。多master分佈式存儲數據,水平擴容,自動進行master -> slave的主備切換。架構

2)最經典的緩存+數據庫讀寫的模式,cache aside pattern。讀的時候,先讀緩存,緩存沒有的話,那麼就讀數據庫。更新緩存分如下兩種方式:併發

  • 數據發生變化時,先更新緩存,而後再更新數據庫。這種適用於緩存的值相對簡單,和數據庫的值一一對應,這樣更新比較快。

  • 數據發生變化時,先刪除緩存,而後再更新數據庫,讀數據的時候再設置緩存。這種適用於緩存的值比較複雜的場景。好比可能更新了某個表的一個字段,而後其對應的緩存,是須要查詢另外兩個表的數據,並進行運算,才能計算出緩存最新的值的。這樣更新緩存的代價是很高的。若是你頻繁修改一個緩存涉及的多個表,那麼這個緩存會被頻繁的更新,頻繁的更新緩存代價很高。並且這個緩存的值若是不是被頻繁訪問,就得不償失了。

大部分狀況下,建議適用刪除更新的方式。其實刪除緩存,而不是更新緩存,就是一個lazy計算的思想,不要每次都從新作複雜的計算,無論它會不會用到,而是讓它到須要被使用的時候再從新計算。

舉個例子,一個緩存涉及的表的字段,在1分鐘內就修改了20次,或者是100次,那麼緩存跟新20次,100次; 可是這個緩存在1分鐘內就被讀取了1次,有大量的冷數據。28黃金法則,20%的數據,佔用了80%的訪問量。實際上,若是你只是刪除緩存的話,那麼1分鐘內,這個緩存不過就從新計算一次而已,開銷大幅度下降。每次數據過來,就只是刪除緩存,而後修改數據庫,若是這個緩存,在1分鐘內只是被訪問了1次,那麼只有那1次,緩存是要被從新計算的。

3)數據庫與緩存雙寫不一致問題的解決方案

問題:併發請求的時候,數據發生了變動,先刪除了緩存,而後要去修改數據庫,此時還沒修改。另外一個請求過來,去讀緩存,發現緩存空了,去查詢數據庫,查到了修改前的舊數據,放到了緩存中。

方案:數據庫與緩存更新與讀取操做進行異步串行化。(引入隊列)

更新數據的時候,將相應操做發送到一個jvm內部的隊列中。讀取數據的時候,若是發現數據不在緩存中,那麼將從新讀取數據的操做也發送到同一個jvm內部的隊列中。隊列消費者串行拿到對應的操做,而後一條一條的執行。這樣的話,一個數據變動的操做,先執行刪除緩存,而後再去更新數據庫,可是還沒完成更新。此時若是一個讀請求過來,讀到了空的緩存,那麼能夠先將緩存更新的請求發送到隊列中,此時會在隊列中積壓,而後同步等待緩存更新完成。

這裏有兩個能夠優化的點:

  • 一個隊列中,其實多個讀緩存,更新緩存的請求串在一塊兒是沒意義的,並且若是讀同一緩存的大量請求到來時,會依次進入隊列等待,這樣會致使隊列最後一個的請求響應時間超時。所以能夠作過濾,若是發現隊列中已經有一個讀緩存,更新緩存的請求了,那麼就不用再放個新請求操做進去了,直接等待前面的更新操做請求完成便可。若是請求還在等待時間範圍內,不斷輪詢發現能夠取到值了,那麼就直接返回; 若是請求等待的時間超過必定時長,那麼這一次直接從數據庫中讀取當前的舊值。

  • 若是請求量特別大的時候,能夠用多個隊列,每一個隊列對應一個線程。每一個請求來時能夠根據請求的標識id進行hash路由進入到不一樣的隊列。

最後,必定要作根據實際業務系統的運行狀況,去進行一些壓力測試,和模擬線上環境,去看看最繁忙的時候,內存隊列可能會擠壓多少更新操做,可能會致使最後一個更新操做對應的讀請求,會hang多少時間,若是讀請求在200ms返回,若是你計算事後,哪怕是最繁忙的時候,積壓10個更新操做,最多等待200ms,那還能夠的。若是一個內存隊列可能積壓的更新操做特別多,那麼你就要加機器,讓每一個機器上部署的服務實例處理更少的數據,那麼每一個內存隊列中積壓的更新操做就會越少。其實根據以前的項目經驗,通常來講數據的寫頻率是很低的,所以實際上正常來講,在隊列中積壓的更新操做應該是不多的。

舉個例子:一秒就100個寫操做。單臺機器,20個內存隊列,每一個內存隊列,可能就積壓5個寫操做,每一個寫操做性能測試後,通常在20ms左右就完成,那麼針對每一個內存隊列中的數據的讀請求,也就最多hang一下子,200ms之內確定能返回了。若是把寫QPS擴大10倍,可是通過剛纔的測算,就知道,單機支撐寫QPS幾百沒問題,那麼就擴容機器,擴容10倍的機器,10臺機器,每一個機器20個隊列,200個隊列。大部分的狀況下,應該是這樣的,大量的讀請求過來,都是直接走緩存取到數據的,少許狀況下,可能遇到讀跟數據更新衝突的狀況,如上所述,那麼此時更新操做若是先入隊列,以後可能會瞬間來了對這個數據大量的讀請求,可是由於作了去重的優化,因此也就一個更新緩存的操做跟在它後面。

4)大型緩存全量更新問題的解決方案

問題:緩存數據很大時,可能致使redis的吞吐量就會急劇降低,網絡耗費的資源大。若是不維度化,就致使多個維度的數據混合在一個緩存value中。並且不一樣維度的數據,可能更新的頻率都大不同。拿商品詳情頁來講,若是如今只是將1000個商品的分類批量調整了一下,可是若是商品分類的數據和商品自己的數據混雜在一塊兒。那麼可能致使須要將包括商品在內的大緩存value取出來,進行更新,再寫回去,就會很坑爹,耗費大量的資源,redis壓力也很大

方案:緩存維度化。舉個例子:商品詳情頁分三個維度:商品維度,商品分類維度,商品店鋪維度。將每一個維度的數據都存一份,好比說商品維度的數據存一份,商品分類的數據存一份,商品店鋪的數據存一份。那麼在不一樣的維度數據更新的時候,只要去更新對應的維度就能夠了。大大減輕了redis的壓力。

5)經過多級緩存,達到高併發極致,同時給緩存架構最後的安全保護層。具體能夠參照上一篇文章【億級流量的商品詳情頁架構分析】。

6)分佈式併發緩存重建的衝突問題的解決方案

問題:假如數據在全部的緩存中都不存在了(LRU算法弄掉了),就須要從新查詢數據寫入緩存。對於分佈式的重建緩存,在不一樣的機器上,不一樣的服務實例中,去作上面的事情,就會出現多個機器分佈式重建去讀取相同的數據,而後寫入緩存中。

方案:分佈式鎖:若是你有多個機器在訪問同一個共享資源,那麼這個時候,若是你須要加個鎖,讓多個分佈式的機器在訪問共享資源的時候串行起來。分佈式鎖固然有不少種不一樣的實現方案,redis分佈式鎖,zookeeper分佈式鎖。

zookeeper分佈式鎖的解決併發衝突的方案

  • (1)變動緩存重建以及空緩存請求重建,更新redis以前,都須要先獲取對應商品id的分佈式鎖

  • (2)拿到分佈式鎖以後,須要根據時間版本去比較一下,若是本身的版本新於redis中的版本,那麼就更新,不然就不更新

  • (3)若是拿不到分佈式鎖,那麼就等待,不斷輪詢等待,直到本身獲取到分佈式的鎖

7)緩存冷啓動的問題的解決方案

問題:新系統第一次上線,此時在緩存裏多是沒有數據的。或者redis緩存全盤崩潰了,數據也丟了。致使全部請求打到了mysql。致使mysql直接掛掉。

方案:緩存預熱。

  • 提早給redis中灌入部分數據,再提供服務

  • 確定不可能將全部數據都寫入redis,由於數據量太大了,第一耗費的時間太長了,第二根本redis容納不下全部的數據,須要根據當天的具體訪問狀況,實時統計出訪問頻率較高的熱數據,而後將訪問頻率較高的熱數據寫入redis中,確定是熱數據也比較多,咱們也得多個服務並行讀取數據去寫,並行的分佈式的緩存預熱。

8)恐怖的緩存雪崩問題的解決方案

問題:緩存服務大量的資源所有耗費在訪問redis和源服務無果,最後本身被拖死,沒法提供服務。

方案:相對來講,考慮的比較完善的一套方案,分爲事前,事中,過後三個層次去思考怎麼來應對緩存雪崩的場景。

  • 事前:高可用架構。主從架構,操做主節點,讀寫,數據同步到從節點,一旦主節點掛掉,從節點跟上。

  • 事中:多級緩存。redis cluster已經完全崩潰了,緩存服務實例的ehcache的緩存還能起到做用。

  • 過後:redis數據能夠恢復,作了備份,redis數據備份和恢復,redis從新啓動起來。

9)緩存穿透問題的解決方案

問題:緩存中沒有這樣的數據,數據庫中也沒有這樣的數據。因爲緩存是不命中時被動寫的,而且出於容錯考慮,若是從存儲層查不到數據則不寫入緩存,這將致使這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊咱們的應用,這就是漏洞。

方案:有不少種方法能夠有效地解決緩存穿透問題,最多見的則是採用布隆過濾器,將全部可能存在的數據哈希到一個足夠大的bitmap中,一個必定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更爲簡單粗暴的方法(咱們採用的就是這種),若是一個查詢返回的數據爲空(不論是數 據不存在,仍是系統故障),咱們仍然把這個空結果進行緩存,但它的過時時間會很短,最長不超過五分鐘。

相關文章
相關標籤/搜索