1. 程式人生 > 實用技巧 >《Redis深度歷險》原理篇

《Redis深度歷險》原理篇

原理1:鞭辟入裡--執行緒IO模型

  • Redis是個單執行緒程式。Redis的所有資料都在記憶體中,所有的運算都是記憶體級別的運算。對於一些時間複雜度為O(n)級別的指令,需謹慎使用,一不小心就會造成redis卡頓
  • Redis單執行緒處理併發客戶端連線的技術被稱為多路複用,使用select系列的事件輪詢
  • select函式是作業系統提供給使用者程式的。
  • Redis會將每個客戶端套接字都關聯一個指令佇列,客戶端的指令通過佇列來排隊順序執行
  • Redis同樣為每個客戶端套接字關聯一個響應佇列
  • Redis的定時任務會記錄在最小堆的資料結構中

原理2:交頭接耳--通訊協議

  • RESP是redis序列化協議的簡寫,優勢:實現簡單、解析效能好
127.0.0.1:6379> set author codehole
*3
$3
set
$6
author
$8
codehole
127.0.0.1:6379> scan 0
1) "0"
2) 1) "info"
   2) "books"
   3) "author"
*2
$1
0
*3
$4
info
$5
books
$6
author

原理3:未雨綢繆--持久化

  • Redis的持久化有兩種,快照和AOF日誌。快照是一次全量備份,AOF日誌是連續的增量備份
  • 快照是記憶體資料的二進位制序列化形式,在儲存上非常緊湊,而AOF日誌記錄的是記憶體資料修改的指令記錄文字。Redis重啟時需要載入AOF日誌進行指令重放,過程漫長,所以需要定期進行AOF重寫
  • Redis為了邊持久化邊響應客戶端請求,Redis使用作業系統的多程序COW(copy on write)。
  • Redis在持久化時會呼叫glibc的函式fork產生一個子程序來處理快照持久化,與父程序共享記憶體裡的程式碼段和資料段
  • 子程序只是對資料進行遍歷然後序列化寫道磁碟,父程序需要根據客戶端請求對記憶體資料進行修改。這時候就會使用作業系統的COW機制來進行資料段頁面的分離。當父程序對其中一個系統頁面資料進行修改時,會將被共享的頁面複製一份並進行修改。
  • AOF日誌儲存的是Redis伺服器的順序指令序列,只記錄對記憶體進行修改的指令記錄。
  • Redis收到客戶端修改指令後,先進行引數校驗,沒有問題就立即將改指令文字儲存到AOF日誌中,然後再執行指令。
  • Redis提供了bgrewriteaof指令對AOF日誌進行重寫瘦身。其原理就是開闢一個子程序對記憶體進行遍歷轉換成一系列Redis的操作指令,序列化到一個新的AOF日誌檔案中,序列化完成後再將操作期間發生的增量AOF日誌追加到新的AOF日誌中。追加完成後立即替代舊的AOF日誌檔案
  • 當程式對檔案進行寫操作時,實際上是將內容寫到了核心為檔案描述符分配的一個記憶體緩衝中,然後核心會非同步將資料刷回磁碟。這時候機器宕機,AOF日誌內容可能還沒有來得及刷到磁碟就會出現日誌丟失。
  • Linux的glibc提供fsync(int fd)函式可以將指定檔案的內容強制從核心緩衝刷到磁碟。因為fsync時磁碟IO操作,所以一般生產環境中通常每個1s執行一次fsync操作,週期1s是可配置的。在資料安全性和效能中做一個折中。
  • 因為持久化是耗時的IO操作,會加重系統負載,降低redis效能,增加系統IO負擔。所以一般主節點不做持久化,由從節點進行持久化操作。
  • Redis4.0為了解決rdb丟失大量資料和aof效能慢的兩個問題,採用了新的持久化方案--混合持久化。將rdb檔案的內容和增量aof日誌檔案存在一起。redis重啟時,先載入rdb內容再重放增量aof日誌,重啟效率因此大幅得到提升。

原理4:雷厲風行--管道

  • Redis管道Pipeline本質上是由客戶端提供的,跟伺服器沒什麼直接關係。
  • 管道的本質就是Redis客戶端改變了訊息的讀寫順序。redis的write只負責將資料寫到本地作業系統核心的傳送緩衝舊返回。read只負責將資料從本地作業系統核心的緩衝中取出來。對於管道來說,連續的write操作根本沒有耗時,之後第一個read操作會等待一個網路的來回開銷,然後所有的響應訊息就都會送到核心的讀緩衝了。後續的read操作可以從緩衝拿到結果。

原理5:同舟共濟--事務

  • Redis事務具有簡單性,能夠確保多個操作的原子性,但事務模型不嚴格。
  • Redis事務的關鍵字:multi/exec/discard
  • Redis事務中的所有指令再exec之前不執行,而是緩衝再伺服器的一個事務佇列中,一旦收到exec指令,才開始執行整個事務佇列,執行完畢後一次性返回所有指令的執行結果。
  • Redis事務根本不能算"原子性",僅僅滿足事務的隔離性。事務在遇到指令執行失敗後,後面的指令還繼續執行。
  • 事務一般會結合pipeline一起使用,將多次IO操作壓縮為單次IO操作。
  • watch機制相當於樂觀鎖。在事務開始之前watch一個或多個關鍵變數,當事務執行時,Redis會檢查關鍵變數自watch之後是否被修改,如果被修改,則exec指令返回null,告知客戶端事務執行失敗。

原理6:小道訊息--PubSub

  • 為了支援訊息多播,Redis單獨使用了PubSub來支援訊息多播,也就是釋出訂閱者模型。
  • 客戶端發起訂閱命令後,Redis會立即給予一個反饋訊息通知訂閱成功。
  • PubSub在生產者傳遞訊息的時候會直接找到相應的消費者傳遞過去。如果沒有消費者,訊息直接丟棄。如果消費者中間掛掉,則斷連期間的訊息就徹底丟失。

原理7:開源節流--小物件壓縮

  • 如果Redis內部管理的集合資料結構很小,它會使用緊湊儲存形式壓縮儲存。
  • Redis的ziplist是一個緊湊的位元組陣列結構。每個元素之間都是緊挨著的。
  • Redis的intset是一個緊湊的整數陣列結構,用於存放元素都是整數且元素個數較少的set集合。如果整元素可以用用uint16表示,那麼intset的元素就是16位的陣列,如果新加入元素超過uint16的範圍,那麼就是用uint32,如果超時uint32就是用uint64。如果新增元素為字串,則立即升級為hashtable結構。
  • 當集合物件的元素不斷增加,或者某個value值過大,小物件儲存也會被升級為標準結構。
  • Redis並不總是可以將空閒記憶體立即歸還給作業系統。作業系統回收記憶體是以頁為單位的,如果這個頁上有一個key還在使用,就不能被回收。記憶體雖然不會被回收,但會被重用。
  • Redis的記憶體分配細節由第三方記憶體分配庫實現。jemalloc或者tcmalloc,jemalloc效能稍好一點,也是Redis的預設記憶體分配器

原理8:有備無患--主從同步

  • CAP 一致性、可用性、分割槽容錯性。網路分割槽發生時,一致性和可用性兩難全。
  • Redis的主從資料是非同步同步的,所以分散式的Redis系統並不滿足"一致性"要求。在主從網路斷開情況下,主節點依舊可以提供服務,所以是滿足"可用性"的。
  • Redis保證"最終一致性",從節點會努力追趕主節點,最終從節點狀態與主節點狀態將保持一致。
  • Redis的主從同步細分為主從和從從同步。Redis同步的是指令流,將產生修改的指令記錄在本地的記憶體buffer中,然後非同步將buffer中的指令同步到從節點。記憶體buffer是一個定長的環形陣列。陣列內容滿了會從頭開始覆蓋
  • 如果從節點網路不好沒有同步的指令被覆蓋,這時候需要用到更加複雜的同步機制--快照同步
  • 快照同步:在主庫上執行一次bgsave將記憶體資料快照到磁碟檔案,然後將快照檔案內容全部傳送到從節點,從節點執行一次全量載入,載入完畢通知主節點繼續進行增量同步
  • 如果快照同步時間過長,指令同步的buffer又被覆蓋,則同步失敗,再次發起快照同步。所以需要配置合適的複製buffer大小,避免快照複製的死迴圈
  • 增加從節點會首先進行一次快照同步,然後進行增量同步
  • 無盤複製:主節點直接通過套接字將快照內容傳送到從節點,從節點將接受的內容儲存到磁碟檔案再進行一次性載入