將持久數據從Redis中遷出
前言
Redis 是一個開源、支持網絡、基于內存、鍵值對存儲數據庫,使用ANSI C編寫。從2015年6月開始,Redis 的開發 由Redis Labs贊助 ,在 2013年5月至2015年6月期間,其開發由Pivotal贊助。在2013年5月之前,其開發 由VMware贊助 。根據月度排行 網站DB-Engines.com的數據 顯示,Redis是最流行的鍵值對存儲數據庫。
Redis通常將全部的數據存儲在內存中。2.4版本后可配置為使用 虛擬內存 ,一部分數據集存儲在硬盤上,但這個特性廢棄了。
目前通過兩種方式實現持久化:
- 使用快照,一種半持久耐用模式。不時地將數據集以異步方式從內存以RDB格式寫入硬盤。
- 1.1版本開始使用更安全的AOF格式替代,一種只能追加的日志類型。將數據集修改操作記錄起來。Redis能夠在后臺對只可追加的記錄作修改來避免無限增長的日志。
當數據依賴不再需要,Redis這種基于內存的性質, 與在執行一個事務時將每個變化都寫入硬盤的數據庫系統相比就顯得執行效率非常高 。寫與讀操作速度沒有明顯差別。
但是,Redis的兩種持久化方式也有明顯的缺點:
- RDB需要定時持久化,風險是可能會丟兩次持久之間的數據,量可能很大。
- AOF每秒fsync一次指令硬盤,如果硬盤IO慢,會阻塞父進程;風險是會丟失1秒多的數據;在Rewrite過程中,主進程把指令存到mem-buffer中,最后寫盤時會阻塞主進程。
這兩個缺點是個很大的痛點。為了解決這些痛點,GitHub的兩位工程師 Bryana Knight 和 Miguel Fernández 日前寫了一篇 文章 ,講述了將持久數據從Redis遷出的經驗,
經Bryana Knight和Miguel Fernández的獨家授權,InfoQ翻譯并整理了他們合著的文章,以饗廣大讀者,以下是正文。
GiHub怎樣使用Redis?
最初我們堅持用它作為 LRU緩存 ,方便地將大量的計算結果存儲在Git存儲庫或MySQL中的數據上。我們稱之為瞬態化Redis。
我們還啟用了 持久性 ,這給我們提供了對不存儲在其他任何地方的數據持久性的保證。我們用它來存儲各種各樣的數值:從具有高讀/寫比率的稀疏數據(如配置設置、計數器或質量指標),到為核心功能提供支持的動態信息,如(垃圾郵件分析)。我們稱之為持久化Redis。
最近我們決定禁用Redis的持久性,不再使用它作為我們數據的真實來源。這個選擇背后的主要動機是:
- 通過消除復雜性,來降低我們的持久性基礎架構的運營成本。
- 利用我們的專長來操作MySQL。
- 通過在將服務器狀態的重大變化寫入磁盤的過程中消除I/O延遲,來獲得一些額外的性能。
轉換所有這些信息透明地參與規劃和協調。對于使用持久化Redis的每個問題域,我們考慮了操作量、數據結構和不同的訪問模式,以預測對當前MySQL容量的影響,以及配置新硬件的需求。
對于大多數Callsite(動態調用站點),我們用 GitHub::KV 替換了持久性Redis,這是我們自己在InnoDB上構建的一個MySQL鍵值對存儲,具有密鑰過期的功能。我們能夠使用與我們曾經使用過的Redis幾乎相同的 GutHUB::KV ,從趨勢存儲庫和用戶的瀏覽頁面,來限制垃圾用戶的檢測。
我們最大的挑戰:遷移活動提要
我們在GitHub有很多“事件”。為存儲庫加星標,關閉問題并推送提交都是我們在活動源中顯示的事件,正如你在 GitHub主頁 上找到的事件那樣。
我們使用Redis作為存儲所有事件的MySQL表的輔助索引器。以前,當事件發生時,我們將事件標識符“分派”給與應該顯示事件的每個用戶的提要對應的Redis鍵。有很多寫入操作和Redis鍵,以至沒有單個表能夠處理這個扇出。我們不能簡單地用這個代碼路徑中的 GitHub::KV 替換Redis,并稱之為完工。
我們的第一步是收集一些指標,讓它們告訴我們該做什么。我們為不同類型的提要提取了數字,并為每個時間軸類型計算了每秒的寫入和讀取數(例如, 在存儲庫中發出事件 , 由用戶執行的公共事件 等。)一個時間軸沒有被讀取,所以我們能夠立即砍掉它,并立即敲出一個名單。在剩余的時間軸中,這兩個寫操作如此密集,以致我們都不知道能否移植到MySQL。這就是我們要開始的地方。
讓我們來看看如何處理這兩個有問題的時間軸之一。如果您將主頁上的事件提要切換到您所屬的其中某個組織,那么您可以看到的“組織時間軸”占了Redis針對這些時間軸每天總寫入次數超過3.5億次的67%。記得嗎,我說過給每個應該看到它們的用戶“分派”事件ID給Redis?長話短說吧,我們正在推送事件ID來為每個事件和組織中每個用戶分離出Redis鍵。因此對于每天產生100個時間并具有1000個成員的活動組織,在Redis的100個事件可能就會達到10萬次寫入。這樣效率很低,所需的MySQL容量遠遠超過我們的意愿。
甚至在考慮MySQL之前,我們改變了適用于此時間軸的Redis鍵的寫入和讀取的方式。我們將為組織的一個鍵編寫每個事件,然后在檢索時,我們將拒絕請求用戶不應該看到的那些事件。而不是每次事件被扇出時進行過濾,我們會在讀取時進行。
導致為此功能的寫操作降低了65%,讓我們更接近這個目標:將活動提要完全移動到MySQL。
(點擊放大圖像)
雖然單一目標是停止使用Redis作為持久性數據存儲,我們認為,鑒于這是一個遺留的代碼,在過去多年來一直演變,還有一些提高效率的空間。讀取很快,因為數據緊湊、而且被正確索引。了解這點后,我們決定停止單獨寫入某些可以從其他人包含的事件中的時間軸。因此,可以減少剩余寫入的另外30%(總共~11%)。我們到達了這一點:每秒在98%的時間寫入少于1500個鍵,每秒寫入低于2100個鍵。這是我們想到的一系列操作,可以繼續使用當前的MySQL基礎設施而不必添加新服務器。
當我們準備將活動提要遷移到MySQL時,我們嘗試了不同的模式設計,嘗試了每事件記錄的標準化和每記錄的提要子集大小固定化。我們甚至嘗試使用 MySQL 5.7 JSON數據類型 來為事件ID列表建模。然而,我們最終采用類似 GitHub::KV 的模式,
然而,我們最終采用了類似 GitHub::KV 的模式,只是沒有我們不需要的一些功能,像記錄的上次更新和到期時間戳。
在該模式之上,并且受到 Redis流水線 的啟發,我們創建了一個小型庫,用于批處理和限制分派到不同提要的同一事件的寫入。
萬事俱備后,我們開始遷移所擁有的提要的每種類型,從最小的“風險”開始。我們測量了任何特定類型基于其寫入操作的數量的遷移風險,因為讀取不是真正的瓶頸。
遷移每種提要類型后,我們檢查了集群能力、正用和復制延遲。我們有一些特征標志,能夠寫入MySQL,同時仍可寫入持久化的Redis,如此一來,如果我們必須回滾的話,就不會中斷用戶體驗。一旦我們確認寫入性能良好,并且Redis中所有事件都拷貝到MySQL,我們就翻轉另一個特征標志以從新的數據存儲讀取,再次測量容量,然后繼續下一個活動提要類型。
當我們確定一切都遷移并正常執行時,我們部署了一個新的pull請求,刪除所有的調用者持久性Redis。這些是截至今天的結果性能數據:
(點擊放大圖像)
(點擊放大圖像)
從上述兩圖中,可以看到存儲級別的表現如何,寫入( mset )在峰值低于270wps,讀取( mget )低于460ps。由于事件寫入之前采用了批處理的方式,值得這些值遠低于正在寫入的事件數。
(點擊放大圖像)
復制延遲在峰值時低于180毫秒。與寫入操作次數相關的藍線顯示在寫入任何批處理之前如何檢查延遲,以防止副本失去同步。
從中領悟到了什么
到了最后,我們就將Redis作為一些我們的用例的持久性數據存儲。由于需要一些可以用于github.com和GitHub Enterprise的東西,因此,我們決定利用MySQL的運營經驗。然而,顯然MySQL不是一個一刀切的解決方案,我們不得不依靠數據和指標來指導我們在GitHub的事件提要中使用它。我們的第一個優先事項是不再使用Redis,我們的數據驅動方法使我們能夠優化和提高性能。
來自:http://www.infoq.com/cn/articles/github-moves-persistent-data-out-of-redis