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

来源: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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...