常見問題 首頁> 常見問題

    最全面的緩存架構設計

    分享到:
    更新時間:2018年05月11日09:55:25 打印此頁 關閉
    摘要: 1:緩存技術和框架的重要性互聯網的一些高并發,高性能的項目和系統中,緩存技術是起著功不可沒的作用。緩存不僅僅是key-value的簡單存取,它在具體的業務場景中,還是很復雜的,需要很強的架構設計能力。我曾經就遇到過因為緩存架構設計不到位,導致了系統崩潰的案例。2:緩存的技術方案分類1)是做實時性比較高的那塊數據,比如說庫存,銷量之類的這種數據,我們采取的實時...

    最全面的緩存架構設計(全是干貨)

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

    互聯網的一些高并發,高性能的項目和系統中,緩存技術是起著功不可沒的作用。緩存不僅僅是key-value的簡單存取,它在具體的業務場景中,還是很復雜的,需要很強的架構設計能力。我曾經就遇到過因為緩存架構設計不到位,導致了系統崩潰的案例。

    2:緩存的技術方案分類

    1)是做實時性比較高的那塊數據,比如說庫存,銷量之類的這種數據,我們采取的實時的緩存+數據庫雙寫的技術方案,雙寫一致性保障的方案。

    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攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更為簡單粗暴的方法(我們采用的就是這種),如果一個查詢返回的數據為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。

    威海軟件公司半島科技轉載整理2018.05.11

    360彩票网官网下载 天津11选5结果预测 彩色印刷图库彩图 股票配资论坛z贝得来 重庆幸运农场是正规的吗 比较好玩的棋牌游戏 福建快3开奖结果多少钱 广东幸运11选五走势图 鑫牛配资 湖南幸运赛车 35选7辽宁福彩官网 云南山水麻将安卓下载 体彩p5 多乐彩稳赚技巧 胆码拖码各中多少算一等奖 山西体彩十一选五胆拖复式 球探网足球比分即时比分手机版