Impossible Mission - 單機百億文件的極致索引(設計篇)

一. 背景

當下信息社會天天都產生大量須要保存的數據,這些數據在刺激海量存儲技術發展的同時也帶來了新的挑戰。好比,海量數據爲存儲系統增長了大量的小文件,這些小文件的元數據如何管理?如何控制定位某個文件的時間和空間開銷?node

隨着對數據實時性要求的提升, 文件也愈來愈趨於碎片化,像短視頻、直播類業務, 每每一個視頻只有幾百KB, 甚至幾十KB大小。能夠說, 一個成熟的對象存儲系統最後都會面臨巨量元數據管理的挑戰, 如HDFS, openstack-swift等, 在軟件總體進入相對成熟的階段, 小文件成爲最頭疼的問題。 以100TB數據(大約是平常的單機容量)爲例,若所有存儲10KB的文件(文件名<=1KB),僅是管理這些文件所需的索引數據就將須要約10,000GB的內存空間。這是任何成(sheng)熟(qian)的存儲系統都沒法接受的巨大壓(cheng)力(ben)。git

爲了應對當前環境給存儲帶來的挑戰,通過不懈研究和探索,白山雲存儲在兩個方面進行了優化:github

  • 總體上對元數據管理採用無中心的設計,索引採用分層的思想,拋棄中心化元數據管理的策略, 將元數據分散到每一個單機存儲服務器。
  • 單機上, 咱們部署了一套全新的索引數據結構SlimTrie,對索引數據進行了裁剪、壓縮和聚合,將索引進行了極大的優化, 逼近空間利用率的理論極限。以單機100TB數據爲例, 若是文件都是10KB小文件, 那麼就有100億個文件,咱們的SlimTrie算法最終只需10GB內存空間。

今天咱們就主要聊聊如何能在單機上實現百億文件的索引。golang

二. 巨人的肩膀: 主流索引設計

存儲系統的架構主要由數據的存儲和數據的定位 兩方面構成。數據的存儲更多關注文件佈局、複製、故障檢測、修復等環節,它主要決定系統的可靠性;而數據的定位是最具挑戰的, 尤爲是面對海量數據時,一個存儲系統中索引的設計,直接決定了這個系統的讀寫效率、可擴展能力和成熟度。算法

然而,索引的設計面臨着各類挑戰和難題。好比,當存儲的數據量愈來愈大,如何權衡索引數據的格式、算法、達到最高的空間利用率和查詢效率等問題, 就成爲系統設計的關鍵。 在討論SlimTrie索引設計以前,咱們來回顧一下已知的幾種索引設計。數據庫

1.存儲體系

在分佈式領域,管理大量索引數據時,通常會採用分層的思路(很是相似於兩層b+tree的實現), 若是不是超大規模的系統, 兩層最爲常見,上層索引主要負責sharding, 將查詢路由到一個獨立的服務器,下層負責具體的查詢。swift

通常來講,單集羣規模可能有是幾百到幾千個服務器組成, 這時上層sharding部分的數據可能只有幾千條(或上百萬條,若是使用虛擬bucket等策略, 虛擬節點多是物理節點的幾百倍), 因此上層索引會很小,大部分問題集中在底層索引上。 在咱們的設計中, 上層是一個百萬級別的sharding, 下層直接是存儲服務器, 存儲服務器負責索引整機的文件。這樣, 上層sharding的量級不會很大, 整個系統設計的核心問題就落在了單機的文件索引設計上。數組

Tips:緩存

  • 通常不多有千臺服務器以上的集羣,不是受限於技術,而是爲了簡化運維。 幾百到幾千個服務器已經具有了不錯的容量、負載彈性和單點故障容錯能力, 並且幾百個服務器的小集羣管理相對容易。
  • 若是集羣規模大到須要3層索引, 多一次索引訪問, 性能也會下降。
  • 類Haystack的設計在對象存儲中很常見: Haystack是一個關於單機存儲設計的實現, 爲了提高IO性能, 下降文件系統Inode的讀寫開銷, 將小文件合併成一個大文件存儲, 並在內存中存儲全部文件的元信息(meta), 這樣直接將每一個文件讀取的2次IO(inode+data)轉變成一次內存操做和一次IO操做。

剝去系統架構層面的組件, 剩下的就是單機上文件定位的問題:服務器

2.消滅問題,在URL中嵌入定位信息

這一類方案能夠稱之爲服務器端URL生成。 每次上傳時, 存儲服務器負責生成一個用於下載的URL,如FastDFS的實現:

http://192.168.101.5/group1/M00/00/00/wKhlBVVY2M-AM_9DAAAT7-0xdqM485_big.png

其中, group1, M00, 00, 00是分組和定位信息。

當服務器接到一個URL時,直接從其中解析出文件位置, 而後定位到文件所在的服務器、 磁盤、目錄和文件名,再也不須要額外的索引數據。 這種方案其實是將」數據的定位」繞開了, 交給外層邏輯, 也就是存儲的使用方來處理, 而本身只處理」數據的存儲」。

優點:

  • 簡化了問題, 在實際生成環境中, 有很多應用是傾向於這種策略的,它們對url的組織形式不關心, 只要求能下載到, 例如 「圖牀」 類應用 劣勢:
  • 缺乏通用性, 存儲的使用方必須負責管理每一個URL
  • 通常不適合刪除文件
  • 按照規則自動清理、受權等需求,也會由於URL沒有業務上的規律而變得複雜

3.解決「數據的定位」問題,客戶端指定URL

客戶端指定URL是比較通用的方式, 它容許用戶在上傳時指定下載的URL, 所以它不只要管理」數據的存儲」問題, 同時也關心」數據的定位」問題。存儲系統負責記錄每一個URL到文件數據位置的信息, 至關於一個分佈式的key-value map。

相似aws S3和其餘大部分公有云對象存儲服務, 都屬於第二類,是通用的存儲。

提到 key-value map, 分佈式領域和單機領域有頗多類似, 分佈式存儲系統的」數據的定位」問題, 也就是索引的構建, 基本上也分爲兩個思路: 無序的hash map類結構, 和有序的tree 類結構。 接下來咱們分別分析兩類索引的優劣。

3.1明確問題: 定義索引

提出一個好問題永遠比解決問題更重要:

索引能夠被認爲是一些"額外"的數據, 在這些額外數據的幫助下, 能夠從大量數據中快速找到本身想要的內容。 就像一本書, 通常包括1個"索引"—— 目錄, 它讓讀者只翻閱幾頁的目錄後就能夠定位到某個章節的頁碼。 存儲系統中的索引須要作到:

  • 足夠小: 若是目錄過於詳細, 翻閱目錄的時間成本就會變高
  • 縮小查詢範圍: 目錄的做用不是精確的定位到某一頁某一行某個字, 而是定位到一個足夠小的範圍(幾頁)
  • 足夠準確: 對較小的文件, 訪問一個文件開銷爲1次磁盤IO操做
  • 全內存: 索引信息必須所有在內存中, 訪問一個文件分爲2步——訪問索引、訪問磁盤。 訪問索引的過程當中不能訪問磁盤, 不然延遲變得不可控(這也是爲何leveldb或其餘db在咱們的設計中沒有做爲索引的實現來考慮)

3.2基於Hash map的索引

Hash 類索引例圖

Hash map類索引首先利用hash函數的計算,將要存儲的key映射到一個新的hash值,而後再創建索引。查找定位時也須要這一步來定位到真正數據存儲的位置。上面的例圖簡單展現了其結構和工做原理。

它的優勢很明顯:

  • 一次檢索定位數據. 即, 每一個key均可以經過一步計算找到所需的值的位置.
  • 查找的時間複雜度是 O(k)(k是key的長度)。這個特色很是適合用來作單條數據的定位,然而它有一個前提是查找的key必須是等值匹配的,不支持「>」、「<」的操做 範圍查找在存儲系統中也是一個很是重要的特性, 在數據清理、合併等操做時, 是必需要支持的一個API 從圖中咱們能明顯看到它的幾個自然缺陷:
  • 無序。當進行查找操做時,若是不是等值的匹配而是範圍查詢,好比,想要順序列出索引中所有的key,最優時間複雜度也須要O(k * n * log(n)),這樣的操做消耗的空間和時間代價都是索引系統不可接受的。
  • 內存開銷大。 Hash map 要求在內存中保存完整的key, 也就是說內存開銷是O(k*n), 這對單機百億文件級別的目標來講無疑是致命的缺陷。 有一種優化方式是: 使用MD5(key)的前8字節做爲索引的key, 能夠將任意長度key縮減到8字節, 並在必定範圍內把碰撞概率控制到很小。 但咱們沒有選擇這種方案的緣由仍是由於hash的無序。

3.3 基於Tree 的索引

Tree 類索引例圖

Tree 類索引利用樹的中間節點和分支將全量的key分紅一個個更小的部分。上圖是一個典型的B+Tree實現,其中間節點只保存了key,數據部分所有保存在葉子節點裏。這樣的結構在查詢時,經過樹的中間節點一步步縮小查找範圍,從而找到要查找的key。

Tree 類中表明性的數據結構有:

  • B+tree、 RBTree、SkipList、LSM Tree : 通常以平衡性最優爲特色, 適用於數據庫中實現索引等場合。
  • 排序數組: 也能夠認爲是Tree類的數據結構, 它的空間開銷、查詢性能都跟平衡樹至關。

Tree 類索引的特色也很明顯:

  • 優點: 對保存的key是排序的。如例圖所示,經過一個順序訪問數據的指針,就可以方便地順序列出所有數據,這彌補了Hash類索引不可以範圍查詢的缺點。 此外,Tree類索引有許多成熟的實現,如B樹、B+樹的設計在查詢性能方面也有很好的表現,MySQL的默認索引類型就是B+樹。
  • 劣勢: 跟Hash map同樣, 用Tree作索引的時候, map.set(key = key, value = (offset, size)) 內存中必須保存完整的key, 內存開銷爲O(k * n),也很大。

4.小結

以上是兩種經典的索引結構設計案例,它們都存在一個沒法避免的問題:首先這兩種索引結構首先都會存儲全量的key信息,當key的數量快速增加時,它們對內存空間的需求會變的很是巨大。

小文件索引數據量大的困境,致使以上的經典索引結構沒法支持在索引海量數據的同時,將索引緩存在內存中。而一旦索引數據須要磁盤IO,時間消耗會增大幾個量級,存儲系統的性能將因索引效率低而大打折扣。 優化索引結構以提升存儲性能,纔是解決這個問題的惟一出路。 對此,目前業界也有本身的一些方案,

好比LevelDB採用skiplist創建索引,但skiplist內存佔用太大,須要2n個指針的開銷,並且沒法作前綴壓縮。仔細研究過這些已有方案後,咱們認爲都不太理想。

是否有一種數據結構可以索引海量數據,而且佔用空間不大,可以緩存在內存中呢?

三. 魚和熊掌我都要: 低內存, 高性能的 SlimTrie 索引

1.理論極限:

若是要索引n個key, 那至少須要log2(n) 個bit, 才能區分出n個不一樣的key。

若是一共有n個key, 所以理論上所需的內存空間最低是log2(n) * n, 這就是咱們空間優化的目標。

在這個極限中, key的長度不會影響空間開銷, 而僅僅依賴於key的數量, 這也是咱們要達到的一個目標——容許很長的key出如今索引中而不須要增長額外的內存。

實際上咱們在實現時限制了n的大小, 將整個key的集合拆分紅多個指定大小的子集, 這樣有2個好處:

  • n 和 log2(n) 都比較肯定, 容易進行優化
  • 佔用空間更小, 由於: a * log(a) + b * log(b) < (a+b) * log(a+b)

最終達到每一個文件的索引均攤內存開銷與key的長度無關: 每條索引一共10 byte, 其中6 byte是key的信息、4 byte是value: offset。

2.SlimTrie 的前輩: Trie

Tree的順序性、查詢效率均可以知足預期, 但空間開銷仍然很大。 在以字符串爲key的索引結構中, Trie的特性恰好能夠優化key存儲的問題。 Trie 是一個前綴樹, 例如:

保存了8個key的trie結構 "A", "to", "tea", "ted", "ten", "i", "in", and "inn"

Trie的特色在於原生的前綴壓縮, 而Trie上的節點數最少爲O(n), 但Trie的空間開銷比較大, 由於每一個節點都要保存若干個指針(指針單獨要佔8字節), 致使它的空間複雜度雖然是O(n), 但實際內存開銷很大。

若是能將Trie的空間開銷降到足夠低, 它就是咱們想要的東西!

3.SlimTrie的設計

  • 靜態數據索引

數據生成以後在使用階段不修改。依賴於這個假設咱們能夠對索引進行更多的優化: 預先對全部的key進行掃描, 提取特徵, 大大下降索引信息的量。 在存儲系統中, 須要被索引的數據大部分是靜態的,數據的更新是經過Append 和 Compact這2個操做完成的, 通常不須要隨機插入一條記錄。

  • SlimTrie保證存在的key被正肯定位, 但被索引到的key不必定存在

索引的目的在於快速定位一個對象所在的位置範圍, 但不保證定位到的對象必定存在,就像Btree的中間節點, 用來肯定key的範圍, 但要查找的key是否真的存在, 須要在Btree的葉子節點(真實數據)上來肯定。

  • SlimTrie支持順序查找和遍歷key

索引不少狀況下須要支持範圍查詢,SlimTrie 做爲索引的數據結構,必定是支持順序遍歷的特色。SlimTrie 在結構上與樹形結構有類似點,順序遍歷的實現並不難。

  • SlimTrie的內存開銷只與key的個數n相關,不依賴於key的長度k
  • SlimTrie支持最大16KB的key
  • SlimTrie查詢速度要很是快

假設n個key,每一個key的長度爲k,各數據結構的特性以下表:

4.生成SlimTrie的三個步驟

1)用全部的key建立一個標準的Trie樹, 而後在標準Trie樹基礎上作裁剪。 裁剪掉標準Trie中無效的節點,即Trie樹中的單分支節點,對索引key沒有任何的幫助,將索引數據的量級從O(n * k)下降到O(n)。

2)Trie的壓縮, 經過一個compacted array來存儲整個Trie的數據結構, 在實現上將內存開銷下降。 接下來還要在實現上壓縮Trie實際的內存開銷。樹形結構在內存中多以指針的形式來實現, 但指針在64位系統上佔用8個字節, 至關於最差狀況下, 內存開銷至少爲 8*n,這樣的內存開銷仍是太大了,因此咱們使用compacted array來壓縮內存開銷。

3)對小文件進行優化, 將多個相鄰的小文件用1條索引來標識, 平衡IO開銷和內存開銷。 索引的設計以下降IO和下降內存開銷爲目的,這兩方面有矛盾的地方, 若是要下降IO就須要索引儘量準確, 這將帶來索引的容量增長;若是要減少索引的內存開銷, 則可能帶來對磁盤上文件定位的不許確而致使額外的IO。在作這個設計時, 有一個假設是, 磁盤的一次IO, 開銷是差很少的, 跟此次IO的讀取的數據量大小關係不大,因此能夠在一次IO中讀取更多的數據來有效利用IO。

四.實測 SlimTrie 索引

使用 SlimTrie 數據結構的索引相比於使用其餘類索引 ,在保證索引功能的狀況下壓縮了索引中的 key 所佔用的空間。理論上講,使用 SlimTrie 作索引能夠極大的節約內存佔用。 如今咱們來看看實際測試的結果。

1.內存的低開銷

首先咱們用一個基本的實驗來證實咱們的實現和上文說到的理論是相符的。實驗選取Hash 類數據結構的 map 和 Tree 類數據結構的B-Tree 與 SlimTrie 作對比,計算在同等條件下,各個數據結構創建索引所耗費的內存空間。 實驗在go語言環境下進行,map 使用 golang 的 map 實現,B-Tree使用Google的BTree implementation for Go (github.com/google/btre… 。 key 和 value 都是 string 類型(咱們更多關心它的大小)。

實驗的結果數據以下:

1)索引內存佔用對比

能夠得出明顯結論:

  • SlimTrie 做爲索引在內存的節省上碾壓 map 和 B-Tree。
  • SlimTrie 做爲索引其內存佔用的決定因素是 value 的大小,與key的大小無關。

在此實驗的基礎上咱們再作一個理論上的計算:1PB 的數據量,使用 SlimTrie 作索引,小文件合併到 1MB,索引的 value 是每個 1MB 數據塊的起始位置,4 byte 的 int 足夠,根據測試,索引的 key 在 SlimTrie 中佔的空間不會超過 6 Byte。

那麼,1GB內存即可創建 100TB 數據量的索引: 100TB / 1M * (4+6) = 1GB

2)SlimTrie 在通用場景中的表現 由於此次測試全部的數據結構都保存了完整的key和value信息,因此咱們只看memory overhead便可比較出誰的空間佔用小。測試獲得的數據,見下面的圖表:

二者進行對比,能夠明顯看出,SlimTrie 所佔用的空間額外開銷仍然遠遠小於 map 和 B-Tree 所佔的內存,每一個 key 可以節省大約 50 Byte。 內存佔用空間大獲全勝以後,咱們還對 SlimTrie 的查詢進行了測試,同時和 map 、Btree 進行了比較,在與內存測試相同的go語言環境下進行實驗。

2.查詢的高性能

1)測量查詢相同的、肯定存在的 key 的查詢時間比較結果以下圖

存在的key的查找耗時對比圖(越小越優)

2)查詢相同的、肯定不存在的 key 的查詢時間比較結果以下圖

不存在的key的查找耗時對比圖(越小越優)

SlimTrie的查詢效率遠好於Btree, 也很是接近Hash map的性能。 • 上面兩個圖中,前半段key的長度k=1000保持不變,隨着key的個數n的增加, SlimTrie 查詢耗時隨之上漲 • 後半段key的個數保持不變,長度減少,SlimTrie 的查詢耗時基本維持不變

從查詢效率上也反應了SlimTrie的內部結構只與n相關的特性. 另外一方面,在上圖中,咱們也可以看到,SlimTrie 的實際查詢耗時在 150ns 左右,加上優秀的空間佔用優化,做爲存儲系統的索引依然有很是強的競爭力。

3.小結

做爲索引,SlimTrie 的優點巨大,能夠在1GB內存中創建100TB數據量的索引,空間節約驚人,令以往的索引結構可望不可即;時間消耗上,SlimTrie 的查找性能與 sorted Array 接近,超過經典的B-Tree。拋下索引這個身份,SlimTrie 在各項性能方面表現依舊不俗,做爲一個通用 Key-Value 的數據結構,內存額外開銷仍遠遠小於經典的 map 和 Btree。

五. SlimTrie ,爲將來而生

咱們生在最好的時代, 科技爆炸和信息指數級的增加, 對IT產業帶來了巨大的挑戰, 嚴酷的競爭纔是誕生奇蹟的角鬥場, 沒有了平庸的溫牀, 每一個人都要嘗試把本身的身體打碎, 去涅槃重生, 纔有機會給時間長河添一道驚豔的波浪。

當下信息爆炸增加,陳舊的索引模式已沒法適應海量數據新環境,存儲系統海量數據的元信息管理面臨巨大挑戰,而SlimTrie 提供了一個全新的解決方法,爲海量存儲系統帶來一絲曙光,爲雲存儲擁抱海量數據時代注入了強大動力,讓咱們看到了將來的無限可能。

SlimTrie 是白山雲存儲團隊通過長時間研究和探索的產物,在實際使用中的表現沒有辜負咱們對它的深厚指望。它的成功不會停下咱們開拓的腳步,這只是個開始,還遠沒有結束。

感興趣的朋友,能夠掃描下方二維碼,加入「白山雲存儲技術交流羣」,一塊兒碰撞,一塊兒進步。

相關文章
相關標籤/搜索