懂得取捨纔是緩存設計的真諦

Previously

前兩篇文章(緩存穩定性緩存正確性)跟你們討論了緩存的『穩定性』和『正確性』,緩存常見問題還剩下『可觀測性』和『規範落地&工具建設』git

  • 穩定性
  • 正確性
  • 可觀測性
  • 規範落地和工具建設

上週文章發完以後,不少同窗對我留的問題進行了深刻的討論,我相信通過深度的思考,會讓你對緩存一致性的理解更加深入!github

首先,各個 Go 羣和 go-zero 羣裏有不少的討論,可是你們也都沒有找到很是滿意的答案。redis

讓咱們來一塊兒分析一下這個問題的幾種可能解法:sql

  • 利用分佈式鎖讓每次的更新變成一個原子操做。這種方法最不可取,就至關於自廢武功,放棄了高併發能力,去追求強一致性,別忘了我以前文章強調過『這個系列文章只針對非追求強一致性要求的高併發場景,金融支付等同窗自行判斷』,因此這種解法咱們首先放棄。
  • A刪除緩存 加上延遲,好比過1秒再執行此操做。這樣的壞處是爲了解決這種機率極低的狀況,而讓全部的更新在1秒內都只能獲取舊數據。這種方法也不是很理想,咱們也不但願使用。
  • A刪除緩存 這裏改爲設置一個特殊佔位符,並讓 B設置緩存 用 redis 的 setnx 指令,而後後續請求遇到這個特殊佔位符時從新請求緩存。這個方法至關於在刪除緩存時加了一種新的狀態,咱們來看下圖的狀況緩存

    是否是又繞回來了,由於A請求在遇到佔位符時必須強行設置緩存或者判斷是否是內容爲佔位符。因此這也解決不了問題。服務器

那咱們看看 go-zero 是怎麼應對這種狀況的,咱們選擇對這種狀況不作處理,是否是很吃驚?那麼咱們回到原點來分析這種狀況是怎麼發生的:微信

  • 對讀請求的數據沒有緩存(壓根沒加載到緩存或者緩存已失效),觸發了DB讀取
  • 此時來了一個對該數據的更新操做
  • 須要知足這樣的順序:B請求讀DB -> A請求寫DB -> A請求刪除緩存 -> B請求設置緩存

咱們都知道DB的寫操做須要鎖行記錄,是個慢操做,而讀操做不須要,因此此類狀況相對發生的機率比較低。並且咱們有設置過時時間,現實場景遇到此類狀況機率極低,要真正解決這類問題,咱們就須要經過 2PC 或是 Paxos 協議保證一致性,我想這都不是你們想用的方法,太複雜了!架構

作架構最難的我認爲是懂得取捨(trade-off),尋找最佳收益的平衡點是很是考驗綜合能力的。固然,若是你們有什麼好的想法,能夠經過羣或者公衆號聯繫我,感謝!併發

本文做爲系列文章第三篇,主要跟你們探討『緩存監控和代碼自動化』框架

緩存可觀測性

前面兩篇文章咱們解決了緩存的穩定性和數據一致性問題,此時咱們的系統已經充分享受到了緩存帶來的價值,解決了從零到一的問題,那麼咱們接下來要考慮的是如何進一步下降使用成本,判斷哪些緩存帶來了實際的業務價值,哪些能夠去掉,從而下降服務器成本,哪些緩存我須要增長服務器資源,各個緩存的 qps 是多少,命中率多少,有沒有須要進一步調優等。

上圖是一個服務的緩存監控日誌,能夠看出這個緩存服務的每分鐘有5057個請求,其中99.7%的請求都命中了緩存,只有13個落到DB了,DB都成功返回了。從這個監控能夠看到這個緩存服務把DB壓力下降了三個數量級(90%命中是一個數量級,99%命中是兩個數量級,99.7%差很少三個數量級了),能夠看出這個緩存的收益是至關能夠的。

但若是反過來,緩存命中率只有0.3%的話就沒什麼收益了,那麼咱們就應該把這個緩存去掉,一是能夠下降系統複雜度(如非必要,勿增實體嘛),二是能夠下降服務器成本。

若是這個服務的 qps 特別高(足以對DB形成較大壓力),那麼若是緩存命中率只有50%,就是說咱們下降了一半的壓力,咱們應該根據業務狀況考慮增長過時時間來增長緩存命中率。

若是這個服務的 qps 特別高(足以對緩存形成較大壓力),緩存命中率也很高,那麼咱們能夠考慮增長緩存可以承載的 qps 或者加上進程內緩存來下降緩存的壓力。

全部這些都是基於緩存監控的,只有可觀測了,咱們才能作進一步有針對性的調優和簡化,我也一直強調『沒有度量,就沒有優化』。

如何讓緩存被規範使用?

瞭解 go-zero 設計思路或者看過個人分享視頻的同窗可能對我常常講的『工具大於約定和文檔』有印象。

對於緩存來講,知識點是很是繁多的,每一個人寫出的緩存代碼必定會風格迥異,並且全部知識點都寫對是很是難的,就像我這種寫了那麼多年程序的老鳥來講,一次讓我把全部知識點都寫對,依然是很是困難的。那麼 go-zero 是怎麼解決這個問題的呢?

  • 儘量把抽象出來的通用解決方法封裝到框架裏。這樣整個緩存的控制流程就不須要你們來操心了,只要你調用正確的方法,就沒有出錯的可能性。
  • 把從建表 sql 到 CRUD + Cache 的代碼都經過工具一鍵生成。避免了你們去根據表結構寫一堆結構和控制邏輯。

這是從 go-zero 的官方示例 bookstore 裏截的一個 CRUD + Cache 的生成說明。咱們能夠經過指定的建表 sql 文件或者 datasource 來提供給 goctl 所需的 schema,而後 goctlmodel 子命令能夠一鍵生成所需的 CRUD + Cache 代碼。

這樣就確保了全部人寫的緩存代碼都是同樣的,工具生成能不同嗎?:P

未完待續

本文跟你們一塊兒討論了緩存的可觀測性和代碼自動化,下一篇我來跟你們分享一下咱們是怎麼提煉和抽象緩存的通用解決方法的,你們能夠預先了解一下聚族索引的設計,本身先思考一下緩存該如何作,畢竟通過深度思考,你的理解會更加深入嘛!

全部這些問題的解決方法都已包含在 go-zero 微服務框架裏,若是你想要更好的瞭解 go-zero 項目,歡迎前往官方網站上學習具體的示例。

視頻回放地址

ArchSummit架構師峯會-海量併發下的緩存架構設計

項目地址

https://github.com/tal-tech/go-zero

歡迎使用 go-zero 並 star 支持咱們!

微信交流羣

關注『微服務實踐』公衆號並點擊 交流羣 獲取社區羣二維碼。

go-zero 系列文章見『微服務實踐』公衆號
相關文章
相關標籤/搜索