Redis系列(三):緩存過期該如何剔除?RDB和AOF又是什麼?

{"type":"doc","content":[{"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":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相信很多朋友和我一樣,平時工作中經常用到 Redis 的過期特性,還有通過 RDB 和 AOF 文件恢復數據,但是它們是如何工作的,本文就來介紹一下;通過了解底層實現原理,從而更好的整體把控系統的正常運行。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"目錄"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Redis 的緩存過期策略"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"RDB 的實現原理"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"AOF 的實現原理"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Redis 的緩存過期策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"常見的緩存過期策略大概有如下幾種:"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"定時剔除策略"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"定期剔除策略"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"惰性剔除策略"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"定時剔除策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定時剔除是指在 Redis 後臺啓動一些定時任務,每隔一段時間會觸發定時任務,然後由定時任務去掃描設置了過期時間的 key,一旦發現過期就會立即從內存中剔除。其執行流程大概如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4e/4ea4aedc3a2350eac54fac10b2945593.png","alt":null,"title":"定時剔除流程圖","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"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":"dict:用於存儲 Redis 數據庫中所有的 key,所以可以理解成 Redis 是由一個大字典組成的。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"expires:用於存儲所有設置了過期時間的 key。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從圖中可以看出,每次定時任務執行的時候,都會遍歷 expires 中的所有 key 並判斷 key 是否已經過期,若過期則將其從 expires 中剔除,同時將其從 dict 中的 key-value 也刪除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從流程圖中可以看出,如果在同一時間過期的 key 特別多時,此時 CPU 一直忙於清理過期的 key,從而造成 Redis 服務的卡頓;但是使用此種清除策略可以做到儘早的過期的 key 從內存中剔除,而不會造成過期 key 遺留在內存。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"定期剔除策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定期剔除與定時剔除不同的是,每隔一段時間會執行一次檢查操作,而每次檢查時間最長爲 25ms(假設每次執行週期爲 25ms),在 25ms 內,每次採用貪心算法隨機從 expires 中挑選一些 key 查看是否已經過期,如果已過期則從 Redis 中刪除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此種策略的好處是不會長時間佔用 CPU,但是一些過期的 key 不會立馬被刪除,但隨着時間點的推移,所有設置了過期時間的 key 都會被遍歷一遍,最終還是可以被剔除掉。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"惰性剔除策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"惰性剔除是指 key 到了過期時間,但還是\"賴在\"緩存中不被剔除,只有 key 發生讀寫操作的時候,纔會檢查當前 key 是否已經過期,如果過期了則將 key 從緩存中剔除,反之則對 key 進行正常的讀寫操作。基本操作流程如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bf/bf73a073ca1a7b18a089322bb8582ce8.png","alt":null,"title":"惰性剔除流程圖","style":[{"key":"width","value":"100%"},{"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":"關於惰性剔除策略的幾點思考"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"從流程圖可以看出,key 過期後並不會立即被剔除,而只有 key 被使用時纔會檢測是否應該被剔除,此種方式剔除的好處是降低了 CPU 的壓力。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"惰性剔除既然是隻有 key 在被使用時纔會剔除,那麼如果某些 key 一直不被用到的話,那麼就會在內存中常駐,從而在一定程度上浪費了內存。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對了了以上三種過期 key 的清理策略後,最終 Redis 採用了"},{"type":"text","marks":[{"type":"strong"}],"text":"定期剔除和惰性剔除兩種策略相結合的方式"},{"type":"text","text":"對過期的 key 進行剔除操作。一方面可以避免 CPU 的長時間佔用導致卡頓的問題,另外一方面可以防止過期 key 長時間無法剔除的問題。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"RDB 的實現原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e1/e195dd349905be3a075d286dcbcf26db.png","alt":null,"title":"RDB文件構成圖","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"各部分組成說明"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"REDIS:常量,表示 RDB 文件的開頭。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"db_version:表示 RDB 文件的版本號。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"EOF:表示 RDB 文件解析結束的位置表示。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"check_num:校驗和,通過前四部分計算得出的一個數值,當 RDB 文件被解析完成後得到的值與此值相比較,以此來判斷解析 RDB 文件是否正常。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"data_bases:表示數據庫,用於存儲實際數據的,data_bases 實際是由多個數據庫組成,比如 0 號庫和 2 號庫裏面都有數據,那麼它表示兩個庫的數據。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"TYPE:表示 value 的類型,比如字符串類型,List 類型,Set 類型等等。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"key:表示 key 的內容,其一直是字符串類型。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"value:表示值的內容,通過 Type 指定的類型,選擇相應的解析方式來解析 value 中的值。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"SELECTDB:表示開始選擇數據庫的標識。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"db_number:表示當前數據要被恢復到那個數據庫中,比如 db_number 爲 2 時,則執行 select 命令,將庫切到 2 號庫中,然後將後續的數據寫入到 2 號中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"key_values:表示存儲的鍵值對"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過上述簡要分析後可以大概瞭解到 RDB 文件是怎麼構成的,通過相應的標識判斷接下來的部分內容應該使用何種解析方式來解析。接下來我們來看看常見的幾種類型的 value 組成結構長什麼樣子。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"String 在 RDB 文件中的結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/79/7965066d769510b15cead4a342e93ced.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"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":"左側爲普通的不帶壓縮的 value 在 RDB 文件中的展現形式,右側爲帶壓縮的 value 在 RDB 文件中的展現形式。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"List 在 RDB 文件中的結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c87b86b030f16bdfc9285182e5c60d23.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"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":"list_length:表示 list 中的元素個數。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"value_length:表示接下來的 value 的長度是多少。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"value:表示實際存儲的 value 的內容是什麼。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於 SET、ZSET 和 HASH 等在 RDB 文件中的存儲結構和 List 基本上大同小異,所以在這裏就不在展開了,感興趣的朋友自行百度研究下。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"AOF 的實現原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AOF(Append Only File)是以寫命令追加寫入文件的方式來記錄 Redis 中存儲的數據,當 Redis 重啓時,通過解析 AOF 文件即可將數據重新寫入內存中。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"AOF 文件寫入流程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/27/27c90cbed09760ebc65424bf4c06f1e2.png","alt":null,"title":"AOF寫入流程圖","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"關於流程圖的幾點說明"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"只有寫命令纔會被追加到 AOF 文件中。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"寫命令是否同步追加到 AOF 文件中是由 appendfync 參數決定的,而 appendfync 可以配置三種情況,分別如下:"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"appendfync=always"},{"type":"text","text":":表示每條寫命令在被寫入緩存 buffer 後,同時由該線程將命令追加寫入到 AOF 文件中,此種做法最多隻會有一條命令丟失的風險,但是由於由同一條線程進行操作,會導致該線程工作內容過多,從而使得處理寫操作 QPS 相對比較低。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"appendfync=everysce"},{"type":"text","text":":表示寫操作只需要將寫命令寫入緩存中,該工作線程的任務就算完成了,然後子線程會每隔一秒將緩存中的命令追加到 AOF 文件中,此種做法在一定程度上提高了寫入的 QPS,但同時可能會丟失 1 秒內的寫命令。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"appendfync=no"},{"type":"text","text":":表示寫操作線程只需要將寫命令寫入緩存中,至於緩存中的寫命令是什麼時候被追加到 AOF 文件中,完全取決於操作系統,這種做法會使得寫命令丟失的概率大大增加。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"寫命令不斷的被追加到 AOF 文件中,當寫入速度增加時,勢必會導致 AOF 快速變大,有可能會導致磁盤空間被大大佔用,同時也增加了 AOF 文件恢復數據的時間,Redis 官方通過使用子進程的方式重寫 AOF 文件,使得 AOF 文件減小。具體流程如下:"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c0/c015db4abcd247e01bfb56252bb5f93d.png","alt":null,"title":"AOF寫入流程圖","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"重寫數據不一致的問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果子進程正在重寫 AOF 文件的時候,有新的寫命令進來的時候,則勢必會造成通過 AOF 文件恢復的數據與當前 Redis 數據庫中的數據不一致的問題。舉個例子。"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"子進程從數據庫中讀取 'key1' 對應的值爲 'value1'。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"在子進程準備將 set 'key1' 'value1' 命令追加到 AOF 文件中時,此時來了一條寫命令 set 'key1' 'value2',將 'key1'對應的值修改成了 'vlaue2',但 AOF 文件中實際追加的命令對應的值爲 'value1'。這就造成了數據不一致的問題。"}]}]}]},{"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/bd/bde8b86e3229d20cdce1e48238d155c3.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"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":"在數據寫入的時候,同時將寫命令在重寫緩衝區中寫人一份,然後在重寫 AOF 文件的時候,將 Redis 中讀取的數據和重寫緩衝區的數據進行合併之後,然後在寫入到重寫的 AOF 文件中。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本篇文章講解了 Redis 過期 key 的剔除策略,其採用了定期剔除和惰性剔除兩種相結合的方式剔除過期 key;然後講解了 RDB 文件內容的存儲格式,通過了解數據的存儲方式,可以更加清晰地理解 RDB 文件的數據恢復方式;最後講解了 AOF 文件的實現原理,從而更加清晰地理解通過 AOF 文件如何進行數據恢復操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下篇文章我們來講講"},{"type":"text","marks":[{"type":"strong"}],"text":"生產環境中集羣是怎麼工作的"},{"type":"text","text":",敬請期待。"}]},{"type":"horizontalrule"},{"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","marks":[{"type":"size","attrs":{"size":16}},{"type":"strong"}],"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=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":"一文讀懂消費者背後的那點\"貓膩\""}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章