1. 程式人生 > 其它 >對於mysql,redis,Kafka等磁碟快取技術分析

對於mysql,redis,Kafka等磁碟快取技術分析

  大部分元件是基於磁碟儲存的,但由於CPU速度和磁碟速度之間的鴻溝,都會使用快取技術來提高效能,快取簡單來說就是一塊記憶體區域,首先將從磁碟讀到的資料放在快取中,之後查詢或修改時直接操作快取,對於快取中的資料則以一定的頻率重新整理到磁碟上,怎樣快取,快取多少,何時重新整理,這些影響著整個元件的效能。在看過一些關於mysql等元件的架構原理後,會發現不論是基於磁碟的mysql資料庫和Kafka訊息中介軟體zookeeper分散式協調框架,還是基於記憶體的redis資料庫,它們都設計了完善的記憶體和磁碟之間資料互動實現。在快速讀取資料和持久化儲存資料中做出平衡。快取還有空間和時間讀取規則,從空間角度熱點資料相鄰區域的資料不久之後也會被訪問,從時間角度熱點資料第一次訪問後還會被繼續訪問到。

  mysql磁碟快取(僅在使用Innodb引擎下)

  分析mysql將哪些資料進行快取時,可以找到它的根源來看,即mysql中innodb引擎的快取池。

  當然innodb中可以設定多個這樣的快取池例項,從而增加資料庫的併發能力,快取池的大小是可以配置的,快取池中每個頁的大小為16KB,通過LRU演算法來管理快取池,當LRU列表中的頁被修改後,因為與磁碟中的資料產生不一致將該頁稱為髒頁,這是資料庫會通過CHECKPOINT機制將髒頁刷回磁碟,髒頁也會存在與Flush列表中,Flush與LRU列表互不影響,LRU列表管理快取池中頁的可用性,而Flush列表管理頁重新整理回磁碟,髒頁數量可以通過命令來查詢。

  下面需要關注的是與磁碟檔案相關聯的快取

  重做日誌快取

  重做日誌快取為innodb引擎獨有,其對應著reco log檔案,預設為8MB,因為一般情況下每秒鐘會將重做日誌重新整理到日誌檔案,所以不需要設定的太大,通常在以下三種情況下會將重做日誌快取中的內容重新整理到磁碟上重做日誌檔案中。

  Master Thread每秒鐘將重做日誌快取重新整理到重做日誌檔案每個事務提交時會將重做日誌快取重新整理到重做日誌檔案(由innodb_flush_log_at_trx_commit控制)當重做日誌快取剩餘空間小於1/2時,將重做日誌快取重新整理到重做日誌檔案

  因為快取和磁碟資料不可能實時保持一致,為了防止資料丟失,當前事務資料庫都普遍採用Write ahead log策略,即當事務提交時先寫重做日誌,再修改頁,當發生宕機導致資料丟失後,可以通過日誌來進行資料恢復,保證了事務中永續性的要求。為得到高可靠性可以設定多個映象日誌組。

  資料頁索引頁快取

  這裡用到了innodb引擎的關鍵特性,插入快取(Insert/Change Buffer)來對資料進行操作,Innodb對每張表都設定了主鍵,主鍵是行的唯一識別符號,通常行記錄的插入順序也是安裝主鍵遞增的順序進行插入,因此插入聚集索引一般不需要隨機讀取,但表中還會存在多個非聚集的輔助索引,當進行插入時,資料頁的存放還是按聚集索引來順序存放,而對於索引頁中非聚集的輔助索引頁更新存在離散訪問,這樣隨機的讀取會導致效能的下降,所以使用Insert Buffer來對輔助索引進行快取,再根據一定頻率與輔助索引頁進行merge合併。

  二進位制日誌快取(binary log)

  二進位制日誌記錄了對mysql資料庫執行更改的所有操作,但不包含對資料庫本身沒有修改的操作,如select和show,二進位制日誌用於資料庫的恢復,主從資料同步的複製,對日誌中的資訊進行安全審計。

  注意,當使用事務的表儲存引擎時,所有未提交的二進位制日誌會被記錄到快取中,等事務提交時將快取中的二進位制日誌寫入到二進位制日誌檔案中,binlog_cache_size是基於會話而不是全域性的,預設大小32K。

  預設情況下二進位制日誌並不是每次寫的時候都會同步到磁碟,需要設定sync_binlog值來進行調整,預設值為0,表示MySQL不控制binlog的重新整理,由檔案系統自己控制它的快取的重新整理。這時候的效能是最好的。

  Undo日誌快取

  undo是邏輯日誌,根據每行記錄來進行記錄,用來幫助事務回滾及MVCC的功能實現非鎖定讀取,undo日誌存放於共享表空間裡,通過全域性動態引數innodb_purge_batch_size來設定每次purge需要清理的undo page數量,預設為300.

  但凡用了快取肯定需要刷回磁碟,而刷回磁碟的操作由哪些執行緒來進行,一步步來就能發現mysql後臺主要有以下四種執行緒。

  Master Thread:主要負責將快取池中的資料非同步重新整理到磁碟中去(包括頁重新整理,合井快取插入, 回收undo頁

  IO Thread:主要負責請求的回撥處理。((InnoDB 中請求大量使用了A,提高處理性 ) write , read , insert buffer , log IO thread .

  Purge Thread:事務被提交後,所需undolog可能不使用,用來回收undo頁

  Page Cleaner Thread:用來重新整理髒頁

  以上便是mysql涉及到快取和磁碟相關聯的資料更新情況,主要包含四種日誌和資料的同步。

  Redis磁碟快取

  嚴格意義來說,redis與其他元件還是不同的,redis原生就支援在記憶體中使用,而將資料存放到磁碟中反而是可以配置的,並非一定需要將資料持久化,redis的主要作用是快取資料,所以資料的持久儲存應該由後端資料庫來做,業務的場景也應該是先查redis,如果不存在則再去資料庫中查詢,過於依賴redis的資料持久化,可能會造成資料返回不一致。

  redis 的持久化機制有兩種,第一種是快照,第二種是 AOF 日誌。快照是一次全量備份,AOF 日誌是連續的增量備份,這與之後要說zookeeper有點類似。快照是記憶體資料的二進位制序列化形式,而AOP日誌記錄的是記憶體資料修改的指令記錄文字,AOP日誌在長期的執行過程中會逐漸變大,所以也會不斷進行覆蓋。快照可以配置頻率,“save * ”:儲存快照的頻率,第一個 表示多長時間,單位是秒,第二個“*”表示至少執行寫操作的次數,在一定時間內至少執行一定數量的寫操作時,就自動儲存快照,可設定多個條件。

  AOP日誌

  redis在收到客戶端指令,經過校驗後會將該指令儲存到AOF日誌中,再去執行指令,保證在宕機後也能通過AOP日誌的指令重放恢復到宕機前的狀態。對AOP日誌進行寫操作時,實際上是將內容寫到了核心為檔案描述符分配的一個記憶體快取中,然後核心會非同步將髒資料刷回磁碟。linux提供fsync指令可以指定檔案強制從快取中重新整理到磁碟,但如果redis實時呼叫fsync進行日誌同步,這種磁碟IO操作將會嚴重影響redis高效能。一般redis是每隔1s執行一次fsync操作,週期可以配置,或者也可以永不執行,讓作業系統來進行排程,也可以每個指令執行一次。

  Kafka磁碟快取

  Kafka中大量使用了頁快取,這是Kafka實現高吞吐的重要因素之一 。用過Java的都知道兩點事實:

  物件的記憶體開銷非常大,通常會是真實資料大小的幾倍甚至更多,空間使用率低下。Java的垃圾回收會隨著堆內資料的增多而變得越來越慢。

  基於這些因素,使用檔案系統並依賴於頁快取的做法明顯要優於維護一個程序內快取或其他結構,至少我們可以省去了一份程序內部的快取消耗,同時還可以通過結構緊湊的位元組碼來替代使用物件的方式以節省更多的空間。如此,我們可以在32GB的機器上使用28GB至30GB的記憶體而不用擔心GC所帶來的效能問題。此外,即使Kafka服務重啟,頁快取還是會保持有效,然而程序內的快取卻需要重建。這樣也極大地簡化了程式碼邏輯,因為維護頁快取和檔案之間的一致性交由作業系統來負責,這樣會比程序內維護更加安全有效。

  換個角度看,Kafka其實也是一種資料庫,生產者就是在insert資料,而消費者就是在select資料,唯一與磁碟快取進行互動就是borker,borker將生產的資料直接放到快取中,當消費資料時通過零拷貝技術將快取中的資料放到socket進行傳輸,當快取中沒有所需的資料時才會載入磁碟。Kafka的使用場景大部分操作都是順序讀寫,採用檔案追加的方式來寫入訊息,即使使用磁碟,效能依舊很高。

  Kafka把topic中每個parition大檔案分成多個segment小檔案段,索引檔案負責資料的查詢,Kafka的索引檔案以稀疏索引的方式構造,分為偏移量索引和時間戳索引,稀疏索引的方式能夠降低索引在記憶體中佔用率。

  Kafka只負責將訊息寫到系統快取中,並不保證髒資料何時會被重新整理到磁碟上,可以使用l o g . f l u s h . i n t e r v a l . m e s s a g e s 、l o g . f l u s h . i n t e r v a l . m s 等引數來控制,Kafka訊息的可靠性是依賴於多副本機制,而不是由同步刷盤這種嚴重影響效能的行為來保障。

  zookeeper磁碟快取

  zookeeper在記憶體中維護著類似於樹形檔案系統的節點資料模型,其中包含了整棵樹的內容,所有的節點路徑,節點資料等。程式碼中使用DataTree的資料結構來儲存這些資訊,底層是使用一個ConcurrentHashMap鍵值對結構,既然在記憶體中有資料必然需要在磁碟上有對應的持久化,類似於redis,zookeeper中也分為事務日誌和快照資料。

  事務日誌

  存放於dataLogDir配置的路徑下,預設存放在dataDir,使用日誌中第一條事務記錄的ZXID命名,事務日誌每個檔案都是64MB,因為ZooKeeper 對事務日誌檔案的磁碟空間進行預分配,客戶端的每一次事務操作,ZooKeeper 都會將其寫入事務日誌檔案中。因此,事務日誌的寫入效能直接決定了ZooKeeper 伺服器對事務請求的響應,檔案的不斷追加寫入操作會觸發底層磁碟IO為檔案開闢新的磁碟塊,為了避免磁碟Seek的頻率,提高磁碟IO的效率,預先進行磁碟空間分配。當事務操作寫入檔案流的快取中,需要將快取資料強制刷入磁碟,這裡可以通過forceSync引數來配置,forceSync=yes則每次事務提交的時候將寫入操作同步快取並刷盤,forceSync=no表示讓系統來排程刷盤頻率。

  zookeeper更新操作過程:先寫事務日誌,再寫記憶體,週期性落到磁碟(重新整理記憶體到快照檔案)。事務日誌的對寫請求的效能影響很大,快照檔案和事務日誌檔案分別掛在不同磁碟,保證dataLogDir所在磁碟效能良好、沒有競爭者。