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

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Previously","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前兩篇文章(","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/o0qUY5zUjBQuOkx_4XGB6Q","title":"","type":null},"content":[{"type":"text","text":"緩存穩定性","attrs":{}}]},{"type":"text","text":" 和 ","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/DhIv9RACxa5igJTYg4N1mA","title":"","type":null},"content":[{"type":"text","text":"緩存正確性","attrs":{}}]},{"type":"text","text":")跟大家討論了緩存的『穩定性』和『正確性』,緩存常見問題還剩下『可觀測性』和『規範落地&工具建設』","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"穩定性","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"正確性","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可觀測性","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"規範落地和工具建設","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上週文章發完之後,很多同學對我留的問題進行了深入的討論,我相信經過深度的思考,會讓你對緩存一致性的理解更加深刻!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e4/e4591698ddc7f7a9a99831188911df60.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,各個 Go 羣和 go-zero 羣裏有很多的討論,但是大家也都沒有找到非常滿意的答案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讓我們來一起分析一下這個問題的幾種可能解法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"利用分佈式鎖讓每次的更新變成一個原子操作。這種方法最不可取,就相當於自廢武功,放棄了高併發能力,去追求強一致性,別忘了我之前文章強調過『這個系列文章只針對非追求強一致性要求的高併發場景,金融支付等同學自行判斷』,所以這種解法我們首先放棄。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"A刪除緩存","attrs":{}},{"type":"text","text":" 加上延遲,比如過1秒再執行此操作。這樣的壞處是爲了解決這種概率極低的情況,而讓所有的更新在1秒內都只能獲取舊數據。這種方法也不是很理想,我們也不希望使用。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"A刪除緩存","attrs":{}},{"type":"text","text":" 這裏改成設置一個特殊佔位符,並讓 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"B設置緩存","attrs":{}},{"type":"text","text":" 用 redis 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"setnx","attrs":{}}],"attrs":{}},{"type":"text","text":" 指令,然後後續請求遇到這個特殊佔位符時重新請求緩存。這個方法相當於在刪除緩存時加了一種新的狀態,我們來看下圖的情況","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":"none"},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"image","attrs":{"src":"https://oscimg.oschina.net/oscnet/up-e0336684232e33b2e68bf2e68e0cc164aec.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}}]}]},{"type":"listitem","attrs":{"listStyle":"none"},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是不是又繞回來了,因爲A請求在遇到佔位符時必須強行設置緩存或者判斷是不是內容爲佔位符。所以這也解決不了問題。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那我們看看 go-zero 是怎麼應對這種情況的,我們選擇對這種情況不做處理,是不是很喫驚?那麼我們回到原點來分析這種情況是怎麼發生的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對讀請求的數據沒有緩存(壓根沒加載到緩存或者緩存已失效),觸發了DB讀取","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時來了一個對該數據的更新操作","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要滿足這樣的順序:B請求讀DB -> A請求寫DB -> A請求刪除緩存 -> B請求設置緩存","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們都知道DB的寫操作需要鎖行記錄,是個慢操作,而讀操作不需要,所以此類情況相對發生的概率比較低。而且我們有設置過期時間,現實場景遇到此類情況概率極低,要真正解決這類問題,我們就需要通過 2PC 或是 Paxos 協議保證一致性,我想這都不是大家想用的方法,太複雜了!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"做架構最難的我認爲是懂得取捨(trade-off),尋找最佳收益的平衡點是非常考驗綜合能力的。當然,如果大家有什麼好的想法,可以通過羣或者公衆號聯繫我,感謝!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文作爲系列文章第三篇,主要跟大家探討『緩存監控和代碼自動化』","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/33/33a758a7c9eaf4ef43f3301540c3f37a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"緩存可觀測性","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面兩篇文章我們解決了緩存的穩定性和數據一致性問題,此時我們的系統已經充分享受到了緩存帶來的價值,解決了從零到一的問題,那麼我們接下來要考慮的是如何進一步降低使用成本,判斷哪些緩存帶來了實際的業務價值,哪些可以去掉,從而降低服務器成本,哪些緩存我需要增加服務器資源,各個緩存的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"qps","attrs":{}}],"attrs":{}},{"type":"text","text":" 是多少,命中率多少,有沒有需要進一步調優等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f5/f5df79ae83f642cfa5394dfaae8f6d20.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上圖是一個服務的緩存監控日誌,可以看出這個緩存服務的每分鐘有5057個請求,其中99.7%的請求都命中了緩存,只有13個落到DB了,DB都成功返回了。從這個監控可以看到這個緩存服務把DB壓力降低了三個數量級(90%命中是一個數量級,99%命中是兩個數量級,99.7%差不多三個數量級了),可以看出這個緩存的收益是相當可以的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但如果反過來,緩存命中率只有0.3%的話就沒什麼收益了,那麼我們就應該把這個緩存去掉,一是可以降低系統複雜度(如非必要,勿增實體嘛),二是可以降低服務器成本。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這個服務的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"qps","attrs":{}}],"attrs":{}},{"type":"text","text":" 特別高(足以對DB造成較大壓力),那麼如果緩存命中率只有50%,就是說我們降低了一半的壓力,我們應該根據業務情況考慮增加過期時間來增加緩存命中率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這個服務的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"qps","attrs":{}}],"attrs":{}},{"type":"text","text":" 特別高(足以對緩存造成較大壓力),緩存命中率也很高,那麼我們可以考慮增加緩存能夠承載的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"qps","attrs":{}}],"attrs":{}},{"type":"text","text":" 或者加上進程內緩存來降低緩存的壓力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有這些都是基於緩存監控的,只有可觀測了,我們才能做進一步有針對性的調優和簡化,我也一直強調『沒有度量,就沒有優化』。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"如何讓緩存被規範使用?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"瞭解 go-zero 設計思路或者看過我的分享視頻的同學可能對我經常講的『工具大於約定和文檔』有印象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於緩存來說,知識點是非常繁多的,每個人寫出的緩存代碼一定會風格迥異,而且所有知識點都寫對是非常難的,就像我這種寫了那麼多年程序的老鳥來說,一次讓我把所有知識點都寫對,依然是非常困難的。那麼 go-zero 是怎麼解決這個問題的呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘可能把抽象出來的通用解決方法封裝到框架裏。這樣整個緩存的控制流程就不需要大家來操心了,只要你調用正確的方法,就沒有出錯的可能性。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把從建表 sql 到 CRUD + Cache 的代碼都通過工具一鍵生成。避免了大家去根據表結構寫一堆結構和控制邏輯。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4f/4f71ae6111fde5970b2b34188e2518c9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是從 go-zero 的官方示例 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"bookstore","attrs":{}}],"attrs":{}},{"type":"text","text":" 裏截的一個 CRUD + Cache 的生成說明。我們可以通過指定的建表 sql 文件或者 datasource 來提供給 goctl 所需的 schema,然後 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"goctl","attrs":{}}],"attrs":{}},{"type":"text","text":" 的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"model","attrs":{}}],"attrs":{}},{"type":"text","text":" 子命令可以一鍵生成所需的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"CRUD + Cache","attrs":{}}],"attrs":{}},{"type":"text","text":" 代碼。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣就確保了所有人寫的緩存代碼都是一樣的,工具生成能不一樣嗎?:P","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"未完待續","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文跟大家一起討論了緩存的可觀測性和代碼自動化,下一篇我來跟大家分享一下我們是怎麼提煉和抽象緩存的通用解決方法的,大家可以預先了解一下聚族索引的設計,自己先思考一下緩存該如何做,畢竟經過深度思考,你的理解會更加深刻嘛!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有這些問題的解決方法都已包含在 go-zero 微服務框架裏,如果你想要更好的瞭解 go-zero 項目,歡迎前往官方網站上學習具體的示例。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"視頻回放地址","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/9tblPSzLvEagjlYuA-RnUw","title":"","type":null},"content":[{"type":"text","text":"ArchSummit架構師峯會-海量併發下的緩存架構設計","attrs":{}}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"項目地址","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/tal-tech/go-zero","title":"","type":null},"content":[{"type":"text","text":"https://github.com/tal-tech/go-zero","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎使用 go-zero 並 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"star","attrs":{}},{"type":"text","text":" 支持我們!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"微信交流羣","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注『","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"微服務實踐","attrs":{}},{"type":"text","text":"』公衆號並點擊 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"交流羣","attrs":{}},{"type":"text","text":" 獲取社區羣二維碼。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章