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

来源:https://www.cnblogs.com/bsfz/archive/2018/10/28/9867952.html
-Advertisement-
Play Games

第四篇打算作為系列最後一篇,這裡嘗試談談緩存的一些併發交互場景,包括與資料庫(特指 RDBMS)交互,和一些獨立的高併發場景相關補充處理方案(若涉及具體應用同樣將主要以Redis舉例)。 另見:分散式系統之緩存的微觀應用經驗談(三)(數據分片和集群篇) (https://yq.aliyun.com/... ...


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

 


前言

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


正文

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

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

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

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

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

    為了便於後面的相關討論,這裡約定文中的資料庫(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中的一些細節)。


  二、談談緩存的穿透雪崩等相關問題


    在項目發展到後期,一些業務場景整體都處於高併發狀態,大量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.  

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • //count down from 5 to 1, a useful animation. show the code to you: <!DOCTYPE html> <html> <style> #contain { width: 400px; height: 300px; overflow: h ...
  • 前言 什麼是gulp?gulp有什麼用?為什麼用gulp? gulp是前端開發的一種構建工具。 構建工具可以幫助我們工程化地開發項目,比如搭建本地伺服器、編譯CSS預處理器、保存文件後自動刷新瀏覽器而不用我們手動去刷新、多個文件合併並壓縮、壓縮圖片等。 gulp在多種構建工具中,算是簡單的了,其他構 ...
  • 前言 前端自動化構建工具從最開始的grunt, gulp, fis等到現在比較流行的webpack可謂層出不窮,個人還是比較傾向於gulp,雖然有的時候會因為某個插件的配置問題頭疼很久,但不可否認gulp真的很靈活,而且個人覺得它和node結合起來比較舒服,再有對項目目錄結構的要求比較低,即使再老再 ...
  • <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv ...
  • 在項目中會有這樣的需求,echars生成圖表導入到word中 在項目中用的插件 博主有一篇文章將的是 vue使用jquery的三方插件jquery.wordexport.js https://blog.csdn.net/sinat_37984999/article/details/81164818 ...
  • 周五看見React v16.7.0 alpha Hooks,今早起來看見圈裡已經刷屏了Hooks,正好周末,正好IG和G2的比賽還沒開始,研究下。。。 剛剛接觸react時候非常喜歡用函數式組件,因為太簡潔了寫起來非常快,然後然後。。寫到後面發現很多自己以前寫的組件需要改。。為什麼呢,因為自己當時寫 ...
  • 定義 ECMAScript規範為所有函數都包含兩個方法(這兩個方法非繼承而來), call 和 apply 。這兩個函數都是在特定的作用域中調用函數,能改變函數的作用域,實際上是改變函數體內 this 的值 。 用法 我們看到通過方法本身的call 和 apply 執行了該函數。 我們改變了函數運行 ...
  • JavaScript ES6 數組新方法 學習隨筆 新建數組 includes 方法 includes 查找數組有無該參數 有返回true map方法 map 遍歷處理返回新數組 原數組不會改變 reduce方法 reduce 遍歷處理數組返回結果 prev與next中間的符號以及順序控制處理方式 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...