Redis系列(七):緩存只是讀寫回種這麼簡單嗎?如果是,那麼請你一定看看這篇文章!

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"  "}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一枚用心堅持寫原創的“無趣”程序猿,在自身受益的同時也讓朋友們在技術上有所提升。"}]}]},{"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":"前面利用 6 篇文章講述了 Redis 相關的基礎知識,相信小夥伴們對 Redis 已經有了一個比較深入的認識和理解了;本文來講講實際生產環境中 Redis 作爲常用緩存組件是怎麼和 DB(關係型數據庫,比如 MySQL)配合使用的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到這裏可能有些朋友會內心肯定會淡淡的說上一句:"},{"type":"text","marks":[{"type":"strong"}],"text":"寫操作先更新 DB,然後在更新緩存,讀操作先讀緩存,如果沒有讀 DB 回種緩存,然後返回結果不就完事了麼"},{"type":"text","text":",這有什麼好講的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只要你有這樣的疑問,那麼請你一定認真看完本文,因爲緩存讀寫策略遠不止你想的那麼簡單,下面我們就來分析一下幾種常用的緩存讀寫方案,看看哪種更適合你。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讀寫緩存策略一"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/18/1869454a63c26a13f4cf7ddec0c126c4.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"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,如果 DB 成功,則同步寫入緩存;對於讀操作,首先從緩存中讀取,若緩存未命中,則從 DB 中獲取,如果取到了結果,將結果回種緩存並返回,若 DB 中也沒有結果,則在緩存中設置一個短暫帶有過期時間的空值,防止相同 key 頻繁請求對 DB 造成大量的無效請求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"策略一優缺點對比"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"優點"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"緩存讀寫策略簡單(在很多讀多寫少的場景中,此種策略使用的頻率還是很高的)。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"不需要依賴其他第三方緩存組件協同完成。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"缺點"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"zerowidth"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/90/904029ca6ae8a6225c8170e079333fb6.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉個例子,如上圖所示,假設兩條線程 1 和線程 2 同時對一件特價商品進行價格調整,假設商品初始狀態爲 20 元,線程 1 將其價格修改爲 19 元並寫入緩存,然後線程 2 將商品的價格修改爲 18 元,此時線程 A 進行讀取操作,因爲線程 2 還沒來得及將緩存數據修改爲 18,所以此時線程 1 拿到的價格爲 19 元,但是此時庫裏的價格卻是 18 元。從這個例子可以看出,對於進行頻繁修改的數據,使用此種緩存讀寫策略顯然是不合理的。針對這種緩存讀寫方案的缺陷,我們來看看下一種緩存讀寫策略。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cache Aside 讀寫策略"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/3594adb25dfd791d92623df511f4f2d2.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"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":"Cache Aside 讀寫策略也叫緩存旁路讀寫策略,從上圖可以看出,針對讀寫的策略分別是:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"讀流程:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"首先從緩存中讀取,如果有結果則直接返回結束"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如果緩存中沒有,則讀 DB 並將結果回種緩存,然後返回結束"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"寫流程:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"首先將將結果寫入 DB"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"然後刪除緩存中對應的值"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"zerowidth"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際生產環境中,此種策略的使用也相對比較廣泛,可以作爲一種參考。這裏需要注意一點的是,針對寫流程,不能先刪除緩存,在更新 DB,因爲緩存刪除後,此時 DB 還沒有更新完時,來了一個 get 請求,那麼緩存就有可能會被種入一個已失效的結果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cache Aside 讀寫策略優缺點對比"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"優點:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"從流程圖看,此種策略實現比較簡單。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"對於緩存和 DB 的一致性有了一定的保證,其可以解決第一種緩存方案遇到的問題。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"缺點:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"很顯然,每次更新數據,都會先更新 DB 緊接着就刪除緩存,如果讀寫操作都比較頻繁的情況下,勢必使得緩存的命中率有所折扣,也就意味着緩存的 miss 率升高,從而導致在一定程度上削弱了緩存的作用。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對此種方案的缺點,其實也有一些比較折中的方案可以考慮。比如在更新 DB 完成後,同樣更新緩存,但是在更新緩存的時候增加分佈式鎖避免;在比如如果業務場景並不是要求強一致性的話,可以將數據寫入緩存並增加一個過期時間,這樣即使數據不一致也只是一段時間。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於增加過期時間的這種方式,存在極熱 key 的場景並不是適用的,因爲一旦 key 過期後,在一瞬間大量請求越過緩存,直衝 DB,也就造成了緩存穿透的問題,所以 Cache Aside 方案看起來很不錯,但是也不是萬能的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Write/Read Through 策略"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/50/500c4be18059b6fdeb9d775422ec0012.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Write/Read Through 的緩存策略不同於前兩種,該策略需要引入第三方緩存同步插件。其讀寫流程如下:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"讀流程:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"首先讀緩存,如果緩存命中,則直接返回結果"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如果緩存未命中,則依賴第三方組件從 DB 中加載數據到緩存中,然後將獲取的結果返回。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"寫流程:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"要更新的數據是否在緩存中存在,若存在則直接將數據寫入緩存,之後緩存數據由第三方緩存組件將其更新到 DB"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"若緩存中不存在,則直接將結果寫入 DB,這種稱之爲寫穿透"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"zerowidth"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Write/Read Through 策略優缺點對"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"優點:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"寫緩存 miss 的情況除外,剩餘所有操作都只與緩存進行交互,很大程度上避免了緩存與 DB 一直性的情況"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"缺點:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"從上面流程中不難看出,寫緩存 miss 時直接與 DB 交互,會造成請求耗時增加"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"此種緩存策略引入了第三方緩存組件進行輔助,從而增加了系統的複雜度和系統的維護難度"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"由於需要引入第三方組件,而目前很多緩存如 Redis 原生並不兼容第三方組件,所以很難引入"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"zerowidth"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總和上面這些情況對比,目前採用此種緩存讀寫策略的場景很少,作者到目前還沒有見過使用這種緩存策略的場景。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Write Back 策略"}]}]},{"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":"Write Back 策略是一種操作系統在使用的緩存讀寫策略,由於其實現比較複雜,所以讀者朋友可以做一些瞭解即可,目前沒有在生產環境中實際使用過。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b5/b5da8cf1210f5c73d021b62040ee20ec.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"寫操作流程如下:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"寫請求來之後,首先判斷要寫的 key 在緩存中是否被標記爲“髒數據”,如果不是“髒數據”則直接寫緩存,然後將其標記爲髒數據"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如果緩存中標記的是“髒數據”,則直接將其寫入 DB,然後回種緩存,然後將其標記爲“髒數據”"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"讀操作流程如下:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"首先從緩存中獲取數據,若有則直接返回"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"若緩存中沒有,則尋找可用的緩存快,若緩存快被標記爲“髒數據”,則將“髒數據”寫入 DB,然後將緩存的數據標記爲“非髒數據”,然後返回"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"若緩存快中的數據不是“髒數據”,則從 DB 中加載數據到緩存中,然後將其返回。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"生產環境中其他的緩存讀寫策略"}]}]},{"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":"除了上面我們介紹的 4 中緩存讀寫策略,實際生產環境中還有一些比較常見的策略,比如針對熱點 key 使用的 LRU 緩存策略。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際生產中,某些場景數據量是非常巨大的,比如微博用戶 uid,方便查看某個用戶的最近狀態,如果全部將其全部都寫入到緩存,顯然是不合理的;一是緩存存儲不了那麼多用戶信息,二是對應絕大部分用戶是完全沒有必要寫入緩存的,因爲對於很大一部分用戶信息,只有很少的訪問量。所以對於這種場景,只需要緩存最近經常被訪問的那部分用戶信息即可,針對這種場景,也就誕生了 LRU 緩存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實 LRU 緩存在很多框架中也被廣泛使用,需要的朋友可以自己研究下很簡單的(這裏其實也有有一道算法題,感興趣的朋友可以自行在 LeetCode 上搜索)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結"}]}]},{"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":"本文介紹了 5 中生產環境中經常用到的緩存讀寫策略,讀者朋友們可以根據自己的實際業務場景,對其進行適當的改造就可以應用到自己的環境中了。好了,就講這麼多,更多的需要朋友們自行多多體會和應用。下篇文章見,拜拜了您。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a3/a3f4e6f6c743badafb9e7c13e014c698.jpeg","alt":null,"title":"","style":[{"key":"width","value":"25%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"  "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"往期推薦"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzIzNTIzNzYyNw==&mid=2247484290&idx=1&sn=9e4d47ba8042543c9718ebcb766c8df3&chksm=e8eb7a16df9cf30057e2ccbc224f336a32b92b3342c461c8d54572913bb9cbac095266f5e4d4&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"Redis系列(三):緩存過期該如何剔除?RDB和AOF又是什麼?"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzIzNTIzNzYyNw==&mid=2247483919&idx=1&sn=ee4c393dabadcecd51b34380425d227a&chksm=e8eb7b9bdf9cf28da20a8083d772d6744850256e7b338aa79225889c69f8395ab6019808d946&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"你必須要知道集羣內部工作原理的一些事!"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzIzNTIzNzYyNw==&mid=2247483881&idx=1&sn=6624157a86dea60abd8a6842c863ba4e&chksm=e8eb787ddf9cf16b85561dcd25144a73304709a14f615291a5374f9ca2bf05a2284b181be1af&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"消息是如何在服務端存儲與讀取的,你真的知道嗎?"}]},{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzIzNTIzNzYyNw==&mid=2247483827&idx=1&sn=44cfb50953b0f745b2719977453c3a42&chksm=e8eb7827df9cf131ea8aa1496e9ec2d1ed22ed245225dad0613511006b9428fffb16c92cdd6a&scene=21#wechat_redirect","title":null},"content":[{"type":"text","text":"一文讀懂消費者背後的那點\"貓膩\""}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章