Redis套路,一網打盡

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d8/d800399b0223fc49fdafc49a7ce5144c.gif","alt":"圖片","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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"幾乎涵蓋了Redis常見知識點,希望對大家有幫助","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文內容提要","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Redis爲什麼這麼快","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 1.1. 數據結構SDS的妙用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 1.2. 性能優良的事件模型驅動","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 1.3. 基於內存的操作","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Redis爲什麼這麼靠譜","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 2.1. AOF持久化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 2.2. RDB持久化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 2.3. Sentinel高可用","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Redis6.x多線程一覽","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Redis最佳實踐","attrs":{}}]}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Part1 Redis爲什麼這麼快","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.1數據結構SDS的妙用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們知道redis的底層是用c語言來編寫的,但是,數據結構確沒有直接套用C的結構,而是根據redis的定位自建了一套數據結構。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"C語言中的字符串結構:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0f/0f43e7551249012e03b8662b5cafbe6f.png","alt":"圖片","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","marks":[{"type":"strong","attrs":{}}],"text":"SDS定義下的字符串結構:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ef/ef1455e7ff67541026434702bf14867e.png","alt":"圖片","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":"可以看到,相比於C語言來說,也就多了幾個字段,分別用來標識空閒空間和當前數據長度,但簡直是神來之筆:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以O(1)複雜度獲取字符串長度;有","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"len","attrs":{}},{"type":"text","text":"字段的存在,無需像C結構一樣遍歷計數。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"杜絕緩存區溢出;C字符串不記錄已佔用的長度,所以需要提前分配足夠空間,一旦空間不夠則會溢出。而有","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"free","attrs":{}},{"type":"text","text":"字段的存在,讓SDS在執行前可以判斷並分配足夠空間給程序","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":"free","attrs":{}},{"type":"text","text":"字段的存在,使SDS有了空間預分配和惰性釋放的能力。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對二進制是安全的;二進制可能會有字符和C字符串結尾符 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"'\\0'","attrs":{}},{"type":"text","text":" 衝突,在遍歷和獲取數據時產生截斷異常,而SDS有了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"len","attrs":{}},{"type":"text","text":"字段,準確了標識了數據長度,不需擔心被中間的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"'\\0'","attrs":{}},{"type":"text","text":" 截斷。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的內容以字符串來說明SDS和C語言數據結構的差異和優勢。順便來看看鏈表、hash表、跳錶分別被Redis設計成了什麼樣的數據結構:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c3/c3244d12dd53c73a849367046babbc60.png","alt":"圖片","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}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/48/484716b0bc529dda00d2c1912a507c4d.png","alt":"圖片","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}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c2/c23bcaba0dec1deaf2f0f0f4dff73c14.png","alt":"圖片","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":"<<< 左右滑動見更多 >>>","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,Redis在設計數據結構的時候出發點是一致的。總結起來就是一句話:空間換時間。","attrs":{}}]},{"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":"1.2性能優良的事件驅動模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"redis6.x之前,一直在說單線程如何如之何的好。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"那麼,具體單線程體現在哪裏,又是怎麼完成數據讀寫工作的呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"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},"content":[{"type":"text","text":"所謂單線程是指對數據的所有操作都是由一個線程按順序挨個執行的,使用單線程可以:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗CPU;","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},"content":[{"type":"text","text":"然而,使用了單線程的處理方式,就意味着到達服務端的請求不可能被立即處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼怎麼來保證單線程的資源利用率和處理效率呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"$ IO多路複用和事件驅動","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis服務端,從整體上來看,其實是一個事件驅動的程序,所有的操作都以事件的方式來進行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b6/b6bb994f89ce255869b940dc372d4e4e.png","alt":"圖片","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":"如圖所示,Redis的事件驅動架構由套接字、I/O多路複用、文件事件分派器、事件處理器四個部分組成:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"套接字(Socket)","attrs":{}},{"type":"text","text":",是對網絡中不同主機上的應用進程之間進行雙向通信的端點的抽象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"I/O多路複用","attrs":{}},{"type":"text","text":",通過監視多個描述符,當描述符就緒,則通知程序進行相應的操作,來幫助單個線程高效的處理多個連接請求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis爲每個IO多路複用函數都實現了相同的API,因此,底層實現是可以互換的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Reids默認的IO多路複用機制是epoll,和select/poll等其他多路複用機制相比,epoll具有諸多優點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|| 併發連接限制 | 內存拷貝 | 活躍連接感知 | | --- | --- | --- | --- | | epoll | 沒有最大併發連接的限制 | 共享內存,無需內存拷貝 | 基於event callback方式,只感知活躍連接 | | select | 受fd限制,32位機默認1024個/64位機默認2048個 | 把fd集合從用戶態拷貝到內核態 | 只能感知有fd就緒,但無法定位,需要遍歷+輪詢 | | poll | 採用鏈表存儲fd無最大併發連接數限制 | 同select | 同select,需遍歷+輪詢 |","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"事件驅動","attrs":{}},{"type":"text","text":",Redis設計的事件分爲兩種,文件事件和時間事件,文件事件是對套接字操作的抽象,而時間事件則是對一些定時操作的抽象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"文件事件:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端連接請求(AE_READABLE事件)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端命令請求(AE_READABLE事件)和事","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服務端命令回覆(AE_WRITABLE事件)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"時間事件:","attrs":{}},{"type":"text","text":" 分爲定時事件和週期性時間;redis的所有時間事件都存放在一個無序鏈表中,當時間事件執行器運行時,需要遍歷鏈表以確保已經到達時間的事件被全部處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,Redis整個執行方案是通過高效的I/O多路複用件驅動方式加上單線程內存操作來達到優秀的處理效率和極高的吞吐量。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.3基於內存的操作","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的小節也提到了,redis之所以可以使用單線程來處理,其中的一個原因是,內存操作對資源損耗較小,保證了處理的高效性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"如此寶貴的內存資源,Redis是怎麼維護和管理的呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"$ 除了增刪改查還有哪些維護性操作[1]","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"命中率統計","attrs":{}},{"type":"text","text":",在讀取一個鍵之後,服務器會根據鍵是否存在來更新服務器的鍵空間命中次數或鍵空間不命中次數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"LRU時間更新","attrs":{}},{"type":"text","text":",在讀取一個鍵之後,服務器會更新鍵的LRU時間,這個值可以用於計算鍵的閒置時間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"惰性刪除","attrs":{}},{"type":"text","text":",如果服務器在讀取一個鍵時發現該鍵已經過期,那麼服務器會先刪除這個過期鍵,然後才執行餘下的其他操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"鍵的dirty標識","attrs":{}},{"type":"text","text":",如果有客戶端使用WATCH命令監視了該鍵,服務器會將這個鍵標記爲dirty,讓事務程序注意到這個鍵已經被修改過。每次修改都會對dirty加一,用於_觸發持久化和複製_。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"數據庫通知","attrs":{}},{"type":"text","text":",“如果服務器開啓了數據庫通知功能,那麼在對鍵進行修改之後,服務器將按配置發送相應的數據庫通知”","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"$ Redis何如管理內存","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"過期鍵刪除","attrs":{}},{"type":"text","text":",內存和CPU資源都是寶貴的,Redis通過定期刪除設定合理的執行時長和執行頻率,配合惰性刪除兜底的方式,來達到CPU時間佔用和內存浪費之間的平衡。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"數據淘汰","attrs":{}},{"type":"text","text":",如果key生產的太快,定期刪除操作跟不上新生產的速率,而這些key又很少被訪問無法觸發惰性刪除,是否會把內存撐爆?回答是不會,因爲redis有數據淘汰策略:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"allkeys-lru:當內存不足以容納新寫入數據時,,移除最近最少使用的 Key。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"allkeys-random:當內存不足以容納新寫入數據時,隨機移除某個 Key。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的 Key。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個 Key。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的 Key 優先移除。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"值得一提的是,這裏的lru和平常我們所熟知的lru還不完全一樣,redis使用的是採樣概率的思想,省略了雙向鏈表的內存消耗。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis 會在每一次處理命令的時候判斷是否達到了最大限制,如果達到則使用對應的算法去刪除涉及到的Key,這時,我們前面所維護過鍵的LRU值就會派上用場了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Part2 Redis爲什麼這麼靠譜","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"天有不測風雲,服務器也有趴窩的時候,Redis這個基於內存的存儲遇到服務器宕機該怎麼應對呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.1RDB持久化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"持久化是一種常見的解決方案,那麼,我們首先能想到的最簡單的持久化方案,就是每隔一段時間把內存裏的數據保存一次,來避免絕大部分數據的丟失。這也是Redis的RDB持久化得思路。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB有兩種方式,save和bgsave","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"save","attrs":{}},{"type":"text","text":",會阻塞服務器的其他操作,直到save執行完成,所以,這個期間的所有命令請求都會被拒絕。對客戶端影響較大。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"BGSave","attrs":{}},{"type":"text","text":",由子進程進行數據保存,期間redis仍然可以繼續處理客戶端請求。爲了防止競爭和衝突,bgsave被設計成和save/bgrewriteaof操作互斥。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis服務器默認每100毫秒執行一次,如果數據庫修改次數(dirty計數器)大於設置的閾值,並且距離上次執行保存的時間(lastsave屬性)大於設置的閾值,則執行保存操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲是統一批量的保存操作,rdb文件有二進制存儲、結構緊湊、空間消耗少、恢復速度快等特點,在持久化方案上不可或缺。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.2AOF持久化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而,因爲bgsave的週期間隔和保存觸發條件等原因,在服務器宕機時,不可避免的會丟失一部分最新的數據。這就需要一些輔助手段來做持久化補充。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RDB保存的是鍵值對,而AOF則用來保存寫命令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼AOF保存的是命令,而不是鍵值對呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Coder的技術之路認爲,一是因爲aof刷盤,是在文件事件處理過程當中的,具體位置是在結束一個事件循環之前,調用追加函數進行,所以,使用請求命令來存儲更方便;二是如果遇到追加過程中命令被破壞,也可以通過redis-check-aof來恢復(命令恢復起來比較方便)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AOF刷盤策略","attrs":{}},{"type":"text","text":",由於aof追加動作是和客戶端請求處理串行執行的,所以每次都刷盤對性能影響較大,因此都是先追加到aof_buf緩存區裏,而是否同步到AOF文件中則依賴always、everysec(默認)、no的刷盤配置。想比everysec ,always對性能影響較大,而no則容易丟失數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"AOF文件重寫壓縮","attrs":{}},{"type":"text","text":",AOF因爲保存了請求命令,自然要比RDB更大,並且隨着程序的運行,會越來越大,然而,文件中有很多冗餘的命令數據是可以壓縮的,因爲對於某個鍵值對,某一時刻只會有一個狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d9/d93647ad0b24ea734f5727f4db1c1d5f.png","alt":"圖片","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":"那麼,在重寫過程中新產生的操作該怎麼辦呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8e/8e76e7521479f4930ae84859e89d9307.png","alt":"圖片","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":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.3Sentinel高可用解決方案","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"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":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e1/e19667c76ae38673e5798d17ad6f3b3f.png","alt":"圖片","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":"當主節點宕機時,由哨兵感知,並在從節點中重新選舉主節點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/4654626e62ced413c78508fdbd74aa4c.png","alt":"圖片","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":"同時,sentinel還會監視宕機的master節點,恢復之後會將其設置爲從節點加入集羣。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了主從切換的sentinel方案,還有Cluster集羣模式來保障redis的高可用,用來解決主從複製的存儲浪費問題。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Part3 Redis6.x的多線程","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},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"Redis的多線程模型,不是傳統意義上的多線程併發,而是把socket解析回寫的這部分操作並行化,以解決IO上的時間消耗帶來的系統瓶頸。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/56b929a6992b5e3e1c774d9c4d42a5f6.png","alt":"圖片","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":"對客戶端的任何請求,其實還是主線程在執行,避免了操作相同數據時線程間的競爭,把io部分並行化,降低了io對資源的損耗,從而提升了系統的吞吐量。仔細想來,感覺和rpc中的異步調用差不多意思,都是綁定來源,等待處理完成後給給各來源返回對應結果。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Part4 Redis最佳實踐","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis被當做分佈式緩存的應用場景非常普遍,有關緩存穿透、緩存擊穿、緩存雪崩、數據漂移、緩存踩踏、緩存污染、熱點key等常見問題,在上一篇文章 ","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650001219&idx=1&sn=6c41849dbfa9f7078d455014aa8781c0&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"諸多策略,緩存爲王","attrs":{}}]},{"type":"text","text":"中已經有了詳細闡述,這裏不再重複。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏主要給出一些日常開發中的關注點:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Key的設計。儘量控制key的長度,一是過長會佔用較多空間,二是我們知道鍵空間是字典類型,即時本身在查找過程中很快,過長的鍵也會對比較判斷時間有所增加。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"批量命令的使用。因爲redis操作絕大部分都耗在網絡傳輸上,將多次傳輸改爲一次傳輸,大概率會提升效果。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"value的大小。儘量避免大value,原因同上,value太大會影響網絡傳輸效率。比如,之前的一次經歷,批量獲取了200個商品的信息(信息比較多,可以認爲是大value),發現很慢,後來把200拆成了4個50,並行去調用,效果提升的比較明顯。這個問題也可以考慮用數據壓縮的方式進行優化","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":"善用數據結構。redis豐富的數據結構對支撐業務有天然的優勢,比如,之前曾用消息隊列配合bitmap數據結構存儲和維護商品的多個狀態(庫存、上下架、秒殺、黑白名單等),getbit來直接判斷該商品是否允許展示。","attrs":{}}]}]}],"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},"content":[{"type":"text","text":"推薦閱讀:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650000954&idx=1&sn=a9ee98310e583b1712e1e64988d2a796&chksm=882fa49abf582d8cc62d7275f49449be185ca31381788be3cfdc3909a0435ffe70c222478c52&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"1. 高併發架構優化:細說負載均衡","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650000991&idx=1&sn=4cd73cc5aa4ccb97d9823db82737d14b&chksm=882fa4ffbf582de9d5819f7bc97f8509cf4699d8a5d8b01c16c7952c118e5f9a81e988638d6b&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"2. 高併發架構優化:萬億流量下的負載均衡實戰","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650001031&idx=1&sn=75b0eea86788b7b59c61875745b38c4c&chksm=882fa427bf582d31e60fcc7dcecec9c2594f0a69a9333524b0d6296a53d9c32118fe8d709e06&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"3. 高併發架構優化:從BAT實際案例看消息中間件的妙用","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650001071&idx=1&sn=fe00cfd25ae6c8595bcc2aef84ed102f&chksm=882fa40fbf582d19be791b763a2ebf0f8d753c1c498a8ae31ee31db1b4984d9e3afeeac4ac5c&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"4. 高併發存儲優化:細說數據庫索引原理及其優化策略","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650001108&idx=1&sn=5c246e6888438575f74147892671c2d1&chksm=882fa474bf582d62a63d7e0d6dddd2655657c64107520b96ab85fe107679eb66e953100999bd&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"5. 高併發存儲優化:也許是史上最詳盡分庫分表文章之一","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650001136&idx=1&sn=2585d7dcf8b0e4328fe07eca4e7fe085&chksm=882fa450bf582d468bc4d92389c9ad6f92ffccf7203495ba7ef139a05904b668d1fcf32ad1b2&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"6. 高併發存儲優化:數據庫索引優化Explain實戰","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650001189&idx=1&sn=87606749ae2be031a9171d272fd73819&chksm=882fa585bf582c932863bd0bfb282c3e8d4b3ff975cabbf9a7c13707ca52cc5d8c17e9eb5551&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"7. 高併發存儲優化番外:阿里數據中間件源碼不完全解析","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://mp.weixin.qq.com/s?__biz=MzA4ODUzMDg5NQ==&mid=2650001219&idx=1&sn=6c41849dbfa9f7078d455014aa8781c0&chksm=882fa5e3bf582cf521e4c6a71b20020eba215af83ba61983f9989db85ed90e9f6944875878b3&scene=21#wechat_redirect","title":null,"type":null},"content":[{"type":"text","text":"8. 高併發存儲優化:諸多策略,緩存爲王","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","marks":[{"type":"strong","attrs":{}}],"text":"歡迎關注同","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}},{"type":"strong","attrs":{}}],"text":"名公衆號:Coder的技術之路","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":",","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"大家的每個鼓勵都是我堅持的動力!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"參考資料","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"[1]","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Redis設計與實現: ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"黃健宏.著","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章