[原創]分佈式系統之緩存的微觀應用經驗談(四) 【交互場景篇】

分佈式系統之緩存的微觀應用經驗談(四) 【交互場景篇】 前端

 


前言

  近幾個月一直在忙些雜事,幾乎年後都沒怎麼閒過。忙忙碌碌中就進入了2018年的秋天了,不得不感嘆時間老是如白駒過隙,也不知道收穫了什麼和失去了什麼。最近稍微休息,買了兩本與技術無關的書,其一是 Yann Martel 寫的《The High Mountains of Portugal》(葡萄牙的高山),發現閱讀此書是須要一些耐心的,對人生暗喻很深,也有足夠的留白,有興趣的朋友能夠細品下。好了,下面迴歸正題,嘗試寫寫工做中緩存技術相關的一些實戰經驗和思考。


正文

  在分佈式Web程序設計中,解決高併發以及內部解耦的關鍵技術離不開緩存和隊列,而緩存角色相似計算機硬件中CPU的各級緩存。現在的業務規模稍大的互聯網項目,即便在最初beta版的開發上,都會進行預留設計。可是在諸多應用場景裏,也帶來了某些高成本的技術問題,須要細緻權衡。本系列主要圍繞分佈式系統中服務端緩存相關技術,也會結合朋友間的探討說起本身的思考細節。文中如有不妥之處,懇請指正。

  爲了方便獨立成文,原諒在內容排版上的一點點我的強迫症。

  第四篇打算做爲系列最後一篇,這裏嘗試談談緩存的一些併發交互場景,包括與數據庫(特指 RDBMS)交互,和一些獨立的高併發場景相關補充處理方案(若涉及具體應用一樣將主要以Redis舉例)。

  另見:分佈式系統之緩存的微觀應用經驗談(三)(數據分片和集羣篇)數據庫

    (https://www.cnblogs.com/bsfz/
    (https://yq.aliyun.com/u/autumnbing
    
  1、簡單談下緩存和數據庫的交互流程

    爲了便於後面的相關討論,這裏約定文中的數據庫(Database)均指傳統的 RDBMS,使用DB標識,同時需區別於緩存(Cache)裏的DB劃分空間。

    我在早前一篇緩存設計細節的文章裏,有闡述關於 Cache 自身 CURD 時的一些具體細節,而這裏將結合DB,就 DB 和 Cache 之間的並行 CURD 操做進行一些討論。固然,這裏面在交互層面上是必定會涉及到分佈式事務(Distributed Transaction)相關的一致性話題,但爲了不表述出現模糊和沒必要要的邊界放大,這裏我儘量剝離開來,專一在基於 Cache 的處理上。

    預先抽象這樣一個基礎場景:後端

      DB中存在一張資金關聯表(FT),這裏 FT 裏存儲的都是熱點條目(屬於極高頻訪問數據),在系統設計時,FT裏的數據將與對應的 Cache 服務 C1 進行關聯存儲(這裏僅指一級緩存),以達到提高必定的併發查詢性能。緩存


    1.1 向 FT 中新增(Create)一條數據

      經過 SQL 向 FT中插入一條數據:若是插入失敗,則不須要對 C1有任何操做;若是插入成功,則此時須要判斷,考慮是否在 C1中同步插入。

      這種情景通常比較簡單,若是沒有特別的狀況,此刻不需對 C1 作主動插入,而是後續被動插入(後面會提到)。可是若是插入 FT 中的數據日後操做只有刪除這個動做,而且 FT的數據常常被批量操做,那麼我的建議同步執行對 C1的插入操做。

      (PS:這裏也順便申明下,若是須要往C1插入,但插入失敗,請根據業務場景加入重試機制,後面對Cache的操做均包含這個潛在的動做。至於重試處理失敗的狀況,如往C1插入一條數據,我的建議是再也不過分處理,最終默認是總體操做成功,並進行對應狀態返回。這裏注意不要與分佈式事務的一致性進行混合類比,後面再也不贅述。)
架構


    1.2 準備更新(Update)一條數據

      當須要更新 FT 中的一條數據時,意味着以前 C1 中的數據已經無效,而在一個高併發環境中這裏沒法作到統一的直接更新 C1。首先就須要考慮的是 C1 的數據是主動更新仍是被動更新,主動更新即更新完 FT後,同時將數據覆蓋進 C1,而被動更新指的是更新完 FT 後,當即淘汰 C1 中的數據,並等待下次查詢時從新寫入C1。

      只要上述請求動做出現了任何併發,好比兩個相同動做,動做1和動做2同時發生請求,那麼會出現一個不一致的問題:動做1先操做 FT,動做2後操做 FT,而後動做2先操做了C1,動做1後操做了C1。

      這樣存在不止一個線程併發的更新 FT 數據時,沒法確認更新 FT 的順序和最終更新 C1 的順序是否保持一致,結果是必定會出現大量 FT 和 C1 中數據出現幻讀,而這個在存在主從Cache的狀況下這種機率會大大提高(可參見上一章主從複製的部分)。推薦的方式是,若是不考慮Cache 屢次須要重寫的損耗,在沒有其餘特殊要求下,能夠直接淘汰 C1 中的數據,也額外照顧到了Cache在合適的時候徹底命中(Hit)。

      其實到這裏還沒結束,當決定是淘汰 C1 的數據,那麼就要選擇一個淘汰時機:一種是先更新 FT,而後對C1 執行淘汰;一種則是,先對 C1 執行淘汰,而後才更新FT。

      雖然兩種方式都有合適的場景,但這裏須要權衡一種機率性問題:當對C1執行淘汰時,又併發了一個對C1的查詢操做,此時,C1會從DB拉取數據從新寫入,那麼C1中即爲髒數據,當併發越大,存在數據一直「髒」下去的機率更大。因此,這裏更推薦的作法是選擇前者。

      (注意,這裏還有一些去討論的細節並不打算在此話題延伸,好比關於 C1和FT之間的原子性問題,是否能夠採用二階段/三階段提交等模擬事務方式和對業務形成的影響。)
併發


    1.3 開始讀取(Read)一條數據

      這裏就沒有太多特別,畢竟應用Cache 的目的就已經說明了讀取數據時,只須要遵循「先讀Cache再讀DB」。即先從C1裏拿取數據,若是C1裏不存在該數據,則從FT中搜索,搜索完成若是依然不存在該數據,則直接返回Empty狀態。若是存在,則同時將該數據保存進C1中,並返回對應狀態。

      順帶提一下,可能有人會說,在某些場景下,即便 C1中有數據,也要先從 FT裏優先獲取。我贊同,沒錯,但注意這裏不要混淆討論的主題了,這本質是屬於基於一種業務結果的導向,就相似在傳統 RDBMS 讀寫分離狀況下,在關鍵數據的驗證處,直接請求主庫獲取並操做。因此上面說的其實並無矛盾,咱們討論時要明確清晰,不要混淆。 運維


    1.4 從FT 中刪除(Delete)一條數據

      與Create相反的操做,經過 SQL 向 FT中移除一條數據:若是移除失敗,則不須要對 C1 有任何操做,如刪除成功,則將對應C1中數據移除(另外請類比1.2中的一些細節)。
分佈式


  2、談談緩存的穿透雪崩等相關問題 高併發


    在項目發展到後期,一些業務場景總體都處於高併發狀態,大量QPS對總體業務的負載要求很高,爲了不不少時候脫離架構優化的初衷,還須要在項目中作到不少預先性的規避和細節把控。
性能


    2.1 優化防止緩存擊穿

      當請求發來的查詢 Key 在 Cache 中存在,但某一時刻數據過時了,而且此時出現了大量併發請求,那麼這裏由於 Cache 中 Miss,就會統一去 DB 中搜索,直接形成在很短的時間內,DB 的 QPS 壓力會陡增。

      對於這種問題的預防和優化,每每從兩方面入手:一是程序中加小粒度的鎖/信號(去年有寫過一篇關於商城系統裏庫存併發管控雜記,裏面有具體話題的細節擴展,詳見:https://www.cnblogs.com/bsfz/ );二是將 DB的讀取延遲 和 Cache的寫入時間儘量拉到最低;三是對其中過於熱點的數據採起一個較大的過時時間並作必定的隨機性(這裏非必要,可自行權衡)。其實還有一點,少數狀況下,可根據場景是否限制,能夠增長適當的到期自動刷新的策略,這裏也能夠考慮在程序中開啓固定的線程通知維護。


    2.2 預防大量緩存穿透

      當請求發來的查詢 Key 在 Cache 中 Miss,天然就會去 DB 裏搜索,這裏自己沒問題,可是假如查詢的 Key 在 DB 中也不存在,那麼意味着每次請求實際上都是實打實落在了 DB 上。這種問題比較常見,而且即便併發不是很大的時候 DB 的鏈接數也輕鬆達到上限,並且自己也不符合咱們設計爲了提升QPS的初衷。

      對於這種漏洞性問題的解決方式,一樣能夠從兩方面入手:一是程序能夠在第一次從DB搜索數據爲 NULL 的時候,直接將 NULL 或者一個標識符 Sign 緩存起來,同時我的建議儘可能設置一個小範圍的隨機過時時間,避免沒必要要的長期內存佔用;二是程序裏限制過濾一些不可能存在的數據KEY,如借鑑 Bloom filter 思想,特別是在前端請求到後端的這裏,儘可能進行一次中間判斷處理(若有時對不合法KEY直接返回NULL)。


    2.3 控制緩存雪崩

      這裏會有某些細節和上面相似,但不徹底。當Cache出現不可用,再或者大量數據同一場景裏同一時刻失效,批量請求直接訪問DB,而且此刻也等同於沒有任何Cache措施了。

      爲了規避這種偏極端的問題,主要能夠考慮從三個方面入手:一是增長完善Cache 的高可用機制,並最好有單獨的運維監控預警;二是相似上面針對Cache的時間再次做隨機,特別是包含預熱和批量的場景裏。(ps:你看不少地方都有相似設計來下降必定機率,我的在設計時,即便是項目初期階段的簡化版本里也會包含進去。);三是,在部分場景增長多級Cache,可是在不少時候會增長其餘的問題(如多級以前的同步問題),因此我的推薦優先增長到二級便可,而後稍微調整下時間儘可能不高於一級Cache。

 


結語

  因爲我的能力和經驗均有限,本身也在持續學習和實踐,文中如有不妥之處,懇請指正。 本系列告一段落,正好也要去忙一些事情,暫時可能不寫相關的東西了。

  我的目前備用地址:
    社區1:https://yq.aliyun.com/u/autumnbing
    社區2:https://www.cnblogs.com/bsfz/

 

End.  

相關文章
相關標籤/搜索