天天看點

高性能緩存架構一、緩存的價值二、緩存的架構設計要點三、實作方式

目錄

一、緩存的價值

二、緩存的架構設計要點

1.緩存穿透

2.緩存雪崩

3.緩存熱點

三、實作方式

一、緩存的價值

某些複雜的業務場景下,單靠存儲系統的性能提升是遠遠不夠的,比如:

  • 需要經過複雜的計算

比如論壇首頁展示使用者同時線上數,MYSQL需要count(*)大量資料得到,此時無論怎麼優化 MySQL,性能都不會太高,如果是實時展示的話,當使用者資料量較大時,MySQL性能無法支撐。

  • 讀多寫少的資料,存儲系統有心無力

目前的網際網路業務大多是讀多寫少的情況。例如微網誌、微信、淘寶等。讀業務占了整體業務的90%以上。比如某個明星發了一條微網誌,可能有幾千萬人來看,寫一條微網誌可能隻需要一條insert語句,但是如果每個使用者浏覽時都要select一次,即使有索引,幾千萬條select語句對MySQL資料庫的壓力也會非常大。

緩存的出現為了彌補存儲系統在這些複雜業務場景下的不足,其基本原理是将可能重複使用的資料放到記憶體中,一次生成、多次使用,避免每次使用都去通路存儲系統。

緩存能夠帶來性能的大幅提升,以 Memcache 為例,單台 Memcache 伺服器簡單的 key-value 查詢能夠達到 TPS 50000 以上。

緩存雖然可以大大減輕存儲系統的壓力,但是給業務架構引入更多的複雜性。架構設計時如果沒有針對緩存的複雜性進行處理,某些場景下甚至會導緻整個系統崩潰。

緩存的架構設計要點有哪些呢?

二、緩存的架構設計要點

1.緩存穿透

緩存穿透:緩存沒有發揮實際作用,業務系統雖然去緩存查詢資料,但緩存中沒有對應資料,業務系統需要再次去存儲系統查詢真實的資料。常見的2種情況:

(1)存儲資料不存在

一般情況下,業務讀取不存在的資料的請求量不會太大,不然就是設計邏輯不合理了。但是我們也需要考慮一些異常情況,比如被黑客攻擊,故意大量通路某些不存在業務的資料,有可能将存儲系統拖垮。

解決方法:如果查詢存儲資料時也沒有對應的資料,則可以寫個預設值(可以是空值,也可以去具體的值,需要同步給業務并做相應處理)到緩存中,這樣就可以避免頻繁的通路存儲系統。

(2)存資料生成耗費大量時間或者資源,業務通路

這個聽起來可能不是很好了解,意思就是存儲系統存在資料,但是生成緩存資料需要耗費較長時間或者耗費大量資源。如果剛好在業務通路的時候緩存失效了(比如緩存達到過期時間後自動删除),那麼也會出現緩存沒有發揮作用,通路壓力全部集中在存儲系統上的情況。

有什麼具體的示例呢?

典型的就是電商的商品分頁,假設我們在某個電商平台上選擇“手機”這個類别檢視,由于資料巨大,不能把所有資料都緩存起來,隻能按照分頁來進行緩存,由于難以預測使用者到底會通路哪些分頁,是以業務上最簡單的就是每次點選分頁的時候按分頁計算和生成緩存。通常情況下這樣實作是基本滿足要求的,但是如果被競争對手用爬蟲來周遊的時候,系統性能就可能出現問題。

具體的場景有:

  • 分頁緩存的有效期設定為 1 天,因為設定太長時間的話,緩存不能反應真實的資料。
  • 通常情況下,使用者不會從第 1 頁到最後 1 頁全部看完,一般使用者通路集中在前 10 頁,是以第 10 頁以後的緩存過期失效的可能性很大。
  • 競争對手每周來爬取資料,爬蟲會将所有分類的所有資料全部周遊,從第 1 頁到最後 1 頁全部都會讀取,此時很多分頁緩存可能都失效了。
  • 由于很多分頁都沒有緩存資料,從資料庫中生成緩存資料又非常耗費性能(order by limit 操作),是以爬蟲會将整個資料庫全部拖慢。

這種情況并沒有太好的解決方案,因為爬蟲會周遊所有的資料,而且什麼時候來爬取也是不确定的,可能是每天都來,也可能是每周,也可能是一個月來一次,我們也不可能為了應對爬蟲而将所有資料永久緩存。通常的應對方案要麼就是識别爬蟲然後禁止通路,但這可能會影響 SEO 和推廣;要麼就是做好監控,發現問題後及時處理,因為爬蟲不是攻擊,不會進行暴力破壞,對系統的影響是逐漸的,監控發現問題後有時間進行處理。

2.緩存雪崩

緩存雪崩:當緩存失效(過期)後引起系統性能急劇下降的情況。

當緩存過期被清除後,業務系統需要重新生成緩存,是以需要再次通路存儲系統,再次進行運算,這個處理步驟耗時幾十毫秒甚至上百毫秒。而對于一個高并發的業務系統來說,幾百毫秒内可能會接到幾百上千個請求。由于舊的緩存已經被清除,新的緩存還未生成,并且處理這些請求的線程都不知道另外有一個線程正在生成緩存,是以所有的請求都會去重新生成緩存,都會去通路存儲系統,進而對存儲系統造成巨大的性能壓力。這些壓力又會拖慢整個系統,嚴重的會造成資料庫當機,進而形成一系列連鎖反應,造成整個系統崩潰。

解決方法:更新鎖機制或背景更新機制

(1)更新鎖

對緩存更新操作進行加鎖保護,保證隻有一個線程能夠進行緩存更新,未能擷取更新鎖的線程要麼等待鎖釋放後重新讀取緩存,要麼就傳回空值或者預設值。

對于采用分布式叢集的業務系統,由于存在幾十上百台伺服器,即使單台伺服器隻有一個線程更新緩存,但幾十上百台伺服器一起算下來也會有幾十上百個線程同時來更新緩存,同樣存在雪崩的問題。是以分布式叢集的業務系統要實作更新鎖機制,需要用到分布式鎖,如 ZooKeeper。

(2)背景更新

由背景線程來更新緩存,而不是由業務線程來更新緩存,緩存本身的有效期設定為永久,背景線程定時更新緩存。

背景定時機制需要考慮一種特殊的場景,當緩存系統記憶體不夠時,會“踢掉”一些緩存資料,從緩存被“踢掉”到下一次定時更新緩存的這段時間内,業務線程讀取緩存傳回空值,而業務線程本身又不會去更新緩存,是以業務上看到的現象就是資料丢了。解決的方式有兩種:

  • 背景線程除了定時更新緩存,還要頻繁地去讀取緩存(例如,1 秒或者 100 毫秒讀取一次),如果發現緩存被“踢了”就立刻更新緩存,這種方式實作簡單,但讀取時間間隔不能設定太長,因為如果緩存被踢了,緩存讀取間隔時間又太長,這段時間内業務通路都拿不到真正的資料而是一個空的緩存值,使用者體驗一般。
  • 業務線程發現緩存失效後,通過消息隊列發送一條消息通知背景線程更新緩存。可能會出現多個業務線程都發送了緩存更新消息,但其實對背景線程沒有影響,背景線程收到消息後更新緩存前可以判斷緩存是否存在,存在就不執行更新操作。這種方式實作依賴消息隊列,複雜度會高一些,但緩存更新更及時,使用者體驗更好。

背景更新既适應單機多線程的場景,也适合分布式叢集的場景,相比更新鎖機制要簡單一些。

背景更新機制還适合業務剛上線的時候進行緩存預熱。緩存預熱指系統上線後,将相關的緩存資料直接加載到緩存系統,而不是等待使用者通路才來觸發緩存加載。

3.緩存熱點

雖然緩存系統本身的性能比較高,但對于一些特别熱點的資料,如果大部分甚至所有的業務請求都命中同一份緩存資料,則這份資料所在的緩存伺服器的壓力也很大。例如,某明星微網誌釋出“我們”來宣告戀愛了,短時間内上千萬的使用者都會來圍觀。

緩存熱點的解決方案就是複制多份緩存副本,将請求分散到多個緩存伺服器上,減輕緩存熱點導緻的單台緩存伺服器壓力。以微網誌為例,對于粉絲數超過 100 萬的明星,每條微網誌都可以生成 100 份緩存,緩存的資料是一樣的,通過在緩存的 key 裡面加上編号進行區分,每次讀緩存時都随機讀取其中某份緩存。

緩存副本設計有一個細節需要注意,就是不同的緩存副本不要設定統一的過期時間,否則就會出現所有緩存副本同時生成同時失效的情況,進而引發緩存雪崩效應。正确的做法是設定一個過期時間範圍,不同的緩存副本的過期時間是指定範圍内的随機值。

三、實作方式

由于緩存的各種通路政策和存儲的通路政策是相關的,是以上面的各種緩存設計方案通常情況下都是內建在存儲通路方案中,可以采用“程式代碼實作”的中間層方式,也可以采用獨立的中間件來實作。

參考:李運華老師《從0開始學架構》(極客時間)

繼續閱讀