Previously
前兩篇文章(緩存穩(wěn)定性 和 緩存正確性)跟大家討論了緩存的『穩(wěn)定性』和『正確性』琳拭,緩存常見問題還剩下『可觀測性』和『規(guī)范落地&工具建設(shè)』
- 穩(wěn)定性
- 正確性
- 可觀測性
- 規(guī)范落地和工具建設(shè)
上周文章發(fā)完之后,很多同學(xué)對我留的問題進行了深入的討論霜浴,我相信經(jīng)過深度的思考盐捷,會讓你對緩存一致性的理解更加深刻澳厢!
首先鸦采,各個 Go 群和 go-zero 群里有很多的討論,但是大家也都沒有找到非常滿意的答案稽坤。
讓我們來一起分析一下這個問題的幾種可能解法:
利用分布式鎖讓每次的更新變成一個原子操作丈甸。這種方法最不可取,就相當(dāng)于自廢武功尿褪,放棄了高并發(fā)能力睦擂,去追求強一致性,別忘了我之前文章強調(diào)過『這個系列文章只針對非追求強一致性要求的高并發(fā)場景杖玲,金融支付等同學(xué)自行判斷』顿仇,所以這種解法我們首先放棄。
把 A刪除緩存 加上延遲,比如過1秒再執(zhí)行此操作臼闻。這樣的壞處是為了解決這種概率極低的情況鸿吆,而讓所有的更新在1秒內(nèi)都只能獲取舊數(shù)據(jù)。這種方法也不是很理想述呐,我們也不希望使用惩淳。
-
把 A刪除緩存 這里改成設(shè)置一個特殊占位符,并讓 B設(shè)置緩存 用 redis 的
setnx
指令乓搬,然后后續(xù)請求遇到這個特殊占位符時重新請求緩存思犁。這個方法相當(dāng)于在刪除緩存時加了一種新的狀態(tài),我們來看下圖的情況是不是又繞回來了进肯,因為A請求在遇到占位符時必須強行設(shè)置緩存或者判斷是不是內(nèi)容為占位符激蹲。所以這也解決不了問題。
那我們看看 go-zero 是怎么應(yīng)對這種情況的江掩,我們選擇對這種情況不做處理学辱,是不是很吃驚?那么我們回到原點來分析這種情況是怎么發(fā)生的:
- 對讀請求的數(shù)據(jù)沒有緩存(壓根沒加載到緩存或者緩存已失效)频敛,觸發(fā)了DB讀取
- 此時來了一個對該數(shù)據(jù)的更新操作
- 需要滿足這樣的順序:B請求讀DB -> A請求寫DB -> A請求刪除緩存 -> B請求設(shè)置緩存
我們都知道DB的寫操作需要鎖行記錄项郊,是個慢操作,而讀操作不需要斟赚,所以此類情況相對發(fā)生的概率比較低着降。而且我們有設(shè)置過期時間,現(xiàn)實場景遇到此類情況概率極低拗军,要真正解決這類問題任洞,我們就需要通過 2PC 或是 Paxos 協(xié)議保證一致性,我想這都不是大家想用的方法发侵,太復(fù)雜了交掏!
做架構(gòu)最難的我認(rèn)為是懂得取舍(trade-off),尋找最佳收益的平衡點是非橙婿考驗綜合能力的盅弛。當(dāng)然,如果大家有什么好的想法叔锐,可以通過群或者公眾號聯(lián)系我挪鹏,感謝!
本文作為系列文章第三篇愉烙,主要跟大家探討『緩存監(jiān)控和代碼自動化』
緩存可觀測性
前面兩篇文章我們解決了緩存的穩(wěn)定性和數(shù)據(jù)一致性問題讨盒,此時我們的系統(tǒng)已經(jīng)充分享受到了緩存帶來的價值,解決了從零到一的問題步责,那么我們接下來要考慮的是如何進一步降低使用成本返顺,判斷哪些緩存帶來了實際的業(yè)務(wù)價值禀苦,哪些可以去掉,從而降低服務(wù)器成本遂鹊,哪些緩存我需要增加服務(wù)器資源振乏,各個緩存的 qps
是多少,命中率多少秉扑,有沒有需要進一步調(diào)優(yōu)等昆码。
上圖是一個服務(wù)的緩存監(jiān)控日志,可以看出這個緩存服務(wù)的每分鐘有5057個請求邻储,其中99.7%的請求都命中了緩存,只有13個落到DB了旧噪,DB都成功返回了吨娜。從這個監(jiān)控可以看到這個緩存服務(wù)把DB壓力降低了三個數(shù)量級(90%命中是一個數(shù)量級,99%命中是兩個數(shù)量級淘钟,99.7%差不多三個數(shù)量級了)宦赠,可以看出這個緩存的收益是相當(dāng)可以的。
但如果反過來米母,緩存命中率只有0.3%的話就沒什么收益了勾扭,那么我們就應(yīng)該把這個緩存去掉,一是可以降低系統(tǒng)復(fù)雜度(如非必要铁瞒,勿增實體嘛)妙色,二是可以降低服務(wù)器成本。
如果這個服務(wù)的 qps
特別高(足以對DB造成較大壓力)慧耍,那么如果緩存命中率只有50%身辨,就是說我們降低了一半的壓力,我們應(yīng)該根據(jù)業(yè)務(wù)情況考慮增加過期時間來增加緩存命中率芍碧。
如果這個服務(wù)的 qps
特別高(足以對緩存造成較大壓力)煌珊,緩存命中率也很高,那么我們可以考慮增加緩存能夠承載的 qps
或者加上進程內(nèi)緩存來降低緩存的壓力泌豆。
所有這些都是基于緩存監(jiān)控的定庵,只有可觀測了,我們才能做進一步有針對性的調(diào)優(yōu)和簡化踪危,我也一直強調(diào)『沒有度量蔬浙,就沒有優(yōu)化』。
如何讓緩存被規(guī)范使用陨倡?
了解 go-zero 設(shè)計思路或者看過我的分享視頻的同學(xué)可能對我經(jīng)常講的『工具大于約定和文檔』有印象敛滋。
對于緩存來說,知識點是非常繁多的兴革,每個人寫出的緩存代碼一定會風(fēng)格迥異绎晃,而且所有知識點都寫對是非常難的蜜唾,就像我這種寫了那么多年程序的老鳥來說,一次讓我把所有知識點都寫對庶艾,依然是非常困難的袁余。那么 go-zero 是怎么解決這個問題的呢?
- 盡可能把抽象出來的通用解決方法封裝到框架里咱揍。這樣整個緩存的控制流程就不需要大家來操心了颖榜,只要你調(diào)用正確的方法,就沒有出錯的可能性煤裙。
- 把從建表 sql 到 CRUD + Cache 的代碼都通過工具一鍵生成掩完。避免了大家去根據(jù)表結(jié)構(gòu)寫一堆結(jié)構(gòu)和控制邏輯。
這是從 go-zero 的官方示例 bookstore
里截的一個 CRUD + Cache 的生成說明硼砰。我們可以通過指定的建表 sql 文件或者 datasource 來提供給 goctl 所需的 schema且蓬,然后 goctl
的 model
子命令可以一鍵生成所需的 CRUD + Cache
代碼。
這樣就確保了所有人寫的緩存代碼都是一樣的题翰,工具生成能不一樣嗎恶阴?:P
未完待續(xù)
本文跟大家一起討論了緩存的可觀測性和代碼自動化,下一篇我來跟大家分享一下我們是怎么提煉和抽象緩存的通用解決方法的豹障,大家可以預(yù)先了解一下聚族索引的設(shè)計冯事,自己先思考一下緩存該如何做,畢竟經(jīng)過深度思考血公,你的理解會更加深刻嘛昵仅!
所有這些問題的解決方法都已包含在 go-zero 微服務(wù)框架里,如果你想要更好的了解 go-zero 項目累魔,歡迎前往官方網(wǎng)站上學(xué)習(xí)具體的示例岩饼。
視頻回放地址
ArchSummit架構(gòu)師峰會-海量并發(fā)下的緩存架構(gòu)設(shè)計
項目地址
https://github.com/tal-tech/go-zero
歡迎使用 go-zero 并 star 支持我們!
微信交流群
關(guān)注『微服務(wù)實踐』公眾號并點擊 交流群 獲取社區(qū)群二維碼薛夜。
go-zero 系列文章見『微服務(wù)實踐』公眾號