PB級資料持久化快取系統——lest

資料庫頻道發表於2018-10-31

本文根據徐海峰2018年5月12日在【第九屆中國資料庫技術大會】上的演講內容整理而成。

講師介紹:

徐海峰,花名:大嘴。10年網際網路經驗。現任閱文集團首席架構師、技術專家。主要負責閱文集團內容中心分散式系統的架構與實現、海量資料的分散式儲存與分散式計算。兼負責公司的專利、技術等開源佈道。曾任ctrip國際機票計價引擎架構師、5173分散式儲存與計算架構師等工作。多年來一直專心致力於網站的分散式架構、海量資料儲存與計算等中介軟體的研究與實現,並形成有成型的技術認知與理論體系。對大型網站的架構與分散式系統有豐富的實戰經驗。

內容摘要:

通常的快取系統(典型如memcahed)普遍都將資料記憶體化,而不支援持久化。縱使後來的Redis解決了資料無法持久化的“硬傷”,但通常快取系統的持久化功能是否啟用也一直是一個讓人很糾結的問題。之所以糾結主要是幾個原因:1. 快取系統啟用持久化後效能明顯下降;2. 資料開啟了持久化,但機器down機恢復後依然無法使用或者資料無法自動更新到最新版本;3. 主存依然是記憶體,所以資料大小需受制於記憶體,依然無法儲存比記憶體大的資料,故持久化僅僅是備份;4. 設計的時候沒考慮持久化,啟用持久化後使用非常彆扭;而我們的lest從設計開始就解決了這些問題,並且還帶來了更多很有意思、也很有實用價值的技術,比如私有的通訊與儲存協議、全程無鎖的多執行緒模型等等。

正文演講:

今天主要是想和大家分享我們現在用的持久化快取—lest,說是快取,但因為是持久化,所以我個人認為稱作儲存系統可能更好一些,它確實是KV結構,並且包含了String、List、Map等等。目前已上線使用,支援1PB到2PB的資料。

講到快取,在大家的印象中快取像什麼?其實快取和神藥有很多的相同之處,首先它們都是為了解決“行不行”的問題,使用之後99%作用明顯,1%無作用,而且是立馬見效,通常會在幾分鐘或者幾小時失效,而且都是走的“治標治本”的路子,多級快取,從客戶端到資料庫。

除此之外,二者的出發點都是為了穩定、快速和持久,通常使用者都是不管三七二十一,先用了再說,且還會產生心理依賴,領導對於其效果也會比較滿意,自己也感覺從苦逼碼農晉升到了金光閃閃的框架師。

雖然現實生活中我們很難拒絕使用快取,但是快取用多了也會出現很多問題,尤其是當資料量大和機器多了以後,各種問題就會接踵而至。例如現在的快取基本都是記憶體式的,一斷電資料就沒有了,恢復起來也是相當困難。

做了主備之後,你會發現備機其實沒有什麼用,主機宕了,切到備機上,很多資料都是不同步的,想要同步還需要時間。前兩天,我們還討論,主備好像沒什麼用,還是多主比較好用。

最關鍵的問題就是很難管理。快取用了之後就扔不了,只敢加量,不敢減量。快取伺服器越來越多,可能從一臺變成了兩臺、四臺、八臺……不僅管理成本越來越高,寫程式碼也變得很複雜,因為很多快取系統都會為了速度快而設在客戶端,所以,每增加一個機器,所有的客戶端都要配置,可能有的做得好的團隊會有配置系統自動完成,但要是做的不好的團隊,就需要重新發布一下程式,如果要是個新手,很可能還會給你寫成個死的。

所以,歸根結底還是要強身健體的,為了杜絕這些情況,我們實現了Lest。

首先就是快取同步,可以做到擴容時無感知;第二,主機當機了也能很容易的起來,備機可能需要稍微頂一下,但主機必須很快起來,因為我們的訪問基本上一天七、八億次,如果主機宕掉打到資料庫上,快取穿透的話,那就基本上完了。所以我們採用了上述四個策略來解決了這個問題。

我們的快取內容是String、List和Map。這其中List和Map的儲存比較難做,因為其包含有結構的資料。例如,如果要在List中查詢從第二個到第十個的資料,Redis很容易就做到了,但如果是全記憶體,儲存在磁碟上就比較困難。

所以,我們自己做了一些設計來實現,上圖中就是我們的總體架構圖。右上是Tracker,類似於很多大廠都在做的快取代理層,接下來是儲存機器,操作機器會分段,如256段、128段等等,資料會分配到不同的段上去。通訊和儲存的實現,我們用了自己設計的協議。

負載均衡,其實是老生常談了,快取的一個最大特點就是key要自定義。業務自定義因為要儲存到磁碟上,因此很難做類似後設資料管理的工作。我們選擇的方式是Hash,不過使用Hash比較麻煩的地方是,如果機器增加的話,Hash值也會發生變化。所以,我們在增加機器的時候會有一個小竅門,以2*數字的方式去增加,比如一臺變兩臺,兩條邊四臺,四臺變八臺。這種方式同步量是最少的,50%,假設你是一臺變三臺,那麼動的資料就是66.7%。如果大家是使用Hash,我建議大家用2*數字的擴容方式會比較好。

資料儲存下來之後,我們就需要同步,我們有組的標籤,同組之內可以資料同步,相互備份,它是沒有Slave的,全部都是主。這裡會牽扯到版本問題,我們後面會講到。

負載均衡的演算法就是二次Hash到加權二次Hash的演變,剛開始的時候,我們使用兩次Hash去做,第一次Hash得到段,第二次Hash得到是哪臺機器,但其實ID生成器生成的ID因為業務的關係並不均衡,這導致快取的儲存量大小很偏,可能出現一臺機器中有20%,另一臺則有80%。

這種情況也很好處理,加權就可以了,相當於一致性Hash,每臺機器都有一個類似7%這樣的素數百分數加權。為什麼選擇素數呢?這是因為對素數Hash會比較均勻。

我們在磁碟上做了一個256×256的資料夾,在磁碟層面就把一些檔案打亂。我們知道磁碟對小檔案其實是比較可怕的,因為使用SSD,我們現在的成本還是比較高,之後我們會考慮使用磁碟,會加類似B樹這樣子的東西。

目前,因為考慮到有很多小檔案,選用了SSD,從而避免掉了磁碟會遇到的一些問題。如果是1億KB的資料,經過Hash放到一個資料夾中大概也只有幾百KB,不會超過3000KB,這個壓力還是可以接受的。

上圖是資料儲存的模型,最前面是Head,頭部加了很多後設資料。比如整個是一個string,那麼黃色的部分是客戶端存下來的真正內容,len表示長度,Version表示版本,我們整套都是用C來寫的,因此效能大概會提升十倍以上。並且我們還做了一個保證單調遞增的ID生成器,它的演算法其實就是一個時間向量演算法的衍生,解決了版本控制的問題,換句話說就是哪個數字最大,肯定就是最後的版本。Reserved代表型別,這裡存的可能是string、list或者是map。同時為了未來的擴充套件,我們還會有一個預留出來的地方。

其實List和Map與String差不多,大家看圖即可,就不再一一介紹了。

這些儲存如果要手工實現可能有些困難,所以我們去做了一個HMS物件,這是一個支援全部型別的資料協議,包括int、long等等。最經典的使用方式是部署在伺服器端,因為伺服器端使用C無法像Java那樣反射,這時我們會用一個數學結構來代表整個內容,如果使用Key的話可以達到log N的查詢效率。

以上是我們做的API,幾乎可以支援所有的操作,用法與Redis差不多,Redis能做的操作,lest基本也能做。

同步架構如上圖,如果是同組的話,兩個storage之間到Tracker上拿到資料,然後再做同步,這其中還會涉及到一個高速IP協議。

每次記錄都會產生binlog,將binlog分門別類的記下來,然後去其上讀即可。

同步複製狀態,狀態轉移就是1+1大於等於2,控制簡單,傳輸資料量大,而複製狀態機,控制比較複雜,傳輸的資料其實很少。

上圖是我們第一次對lest做效能測試得到的結果。前段時間,我們申請到了新的機器,我們又重新做了一遍測試。

上圖是處理請求正確響應數,其中紅色的是萬兆伺服器+SSD,黃色的是千兆伺服器+SATA硬碟。得到的這個效能結果我個人認為還有提升空間,因為我們的客戶端不夠多,只有十臺,至少要有二三十臺才能壓出它的真實效能。現在的效能資料差不多是它真正效能的60%。

從圖中我們可以看到,當資料達到10K以上,效能其實一直在走下坡路,如果大家去對比Redis也會發現,當資料到10K,Redis效能也會下降45%。這說明快取還是和小資料更合拍。

上圖是最大響應時間,圖中的822我也不知道怎麼來的,可能是一個特別異常的值,除去這個值,其它數值的狀態還是比較平穩的,最大響應時間的單位是毫秒,基本上是在一兩百毫秒之間。

上圖是最小響應時間,其值基本分佈在一點幾毫秒。

上圖是傳輸量,也就是網路卡,很明顯,萬兆佔據優勢,這是因為它本來就比千兆要大,其可能只打到了80%,還有10%的增長空間。

上圖是SSD伺服器壓力,CPU的狀況差不多,如果十個伺服器瘋狂打,那麼CPU的壓力在20%左右,隨著資料變大,處理時間變多,CPU壓力就下降了。圖中還有網路的出和入,其中入會比較小,而出會比較大,如果是Redis,出會更大。

Lest的優劣勢很明顯,它是一個吃磁碟不太吃記憶體的東西,具體優劣勢可參考上圖。相比來說,如果使用Redis伺服器需要二三十臺的場景,lest三臺就可以扛下來。另外,相比其它快取,lest基本可以做到寫程式碼無感知,另外,我比較推薦使用SSD,因為現在SSD還是蠻便宜的,比記憶體要便宜。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31545814/viewspace-2218214/,如需轉載,請註明出處,否則將追究法律責任。

相關文章