做者介紹:
徐祥曦,七牛雲工程師,獨立開發了多套高性能糾刪碼/再生碼編碼引擎。
柳青,華中科技大學博士,研究方向爲基於糾刪碼的分佈式存儲系統。git
前言:
隨着數據的存儲呈現出集中化(以分佈式存儲系統爲基礎的雲存儲系統)和移動化(互聯網移動終端)的趨勢,數據可靠性愈發引發你們的重視。集羣所承載的數據量大大上升,但存儲介質自己的可靠性進步卻很小,這要求咱們必須以更加經濟有效的方式來保障數據安全。github
副本與糾刪碼都是經過增長冗餘數據的方式來保證數據在發生部分丟失時,原始數據不發生丟失。但相較於副本,糾刪碼能以低得多的存儲空間代價得到類似的可靠性。好比 3 副本下,存儲開銷爲 3,由於一樣的數據被存儲了三份,而在 10+3(將原始數據分爲 10 份,計算 3 份冗餘)的糾刪碼策略下,存儲開銷爲爲 1.3。採用糾刪碼可以極大地減小存儲系統的存儲開銷,減小硬件、運維和管理成本,正是這樣巨大的收益驅使各大公司紛紛將糾刪碼應用於本身的存儲系統,好比 Google、Facebook、Azure、EMC 等等國際巨頭,在國內以淘寶、華爲、七牛雲等爲表明的公司也在本身的存儲系統上應用了糾刪碼。算法
最典型的糾刪碼算法是裏德-所羅門碼(Reed-Solomon 碼,簡稱 RS 碼)。RS 碼最先應用於通訊領域,通過數十年的發展,其在存儲系統中獲得普遍應用,好比光盤中使用 RS 碼進行容錯,防止光盤上的劃痕致使數據不可讀;生活中常用的二維碼就利用了RS 碼來提升識別的成功率。近年 RS 碼在分佈式存儲系統中的應用被逐漸推廣,一方面是分佈式存儲系統存儲的存儲容量和規模增大的需求;另外一方面是因爲糾刪碼編碼速度在近年獲得迅猛提高。隨着對高性能糾刪碼引擎在實際系統中應用須要,也催生了對糾刪碼在具體系統中實現的各類優化手段。併爲相關的決策者帶來了困擾——究竟什麼樣的編碼引擎纔是高效的呢?數組
咱們將以這個問題展開對糾刪碼技術的剖析,幫助企業更全面,深刻的瞭解糾刪碼在存儲系統中的應用並更好地作出技術選型。本系列文章將從糾刪碼的基本原理開始,隨後引出如何判斷編碼引擎優劣這個問題,接下來將深度分析代碼實現,幫助開發者順利完成定製開發。緩存
本系列共計上下兩篇篇文章:安全
(上篇)如何選擇糾刪碼編碼引擎網絡
(下篇)實現高性能糾刪碼引擎運維
本文做爲系列首篇,咱們將一塊兒探討糾刪碼的編碼原理與如何選擇編碼引擎這兩個問題。分佈式
在展開分析以前,咱們先來看一看 RS 碼是如何工做的。post
下圖展現了 3+2(3 份數據,2 份冗餘)下對 2 字節長度的數據進行編碼與數據修復過程:
爲了計算冗餘數據,首先咱們須要選舉出一個合適的編碼矩陣。編碼矩陣的上部爲一個單位矩陣,這樣保證了在編碼後原始數據依然能夠直接讀取。經過計算編碼矩陣和原始數據的乘積,能夠到最終的結果。
下面介紹解碼過程,當 1,2 兩塊數據丟失,即:
當數據塊發生丟失,在編碼矩陣中去掉相應行,等式仍然保持成立。這爲咱們接下來恢復原始數據提供了依據。
原始數據的修復過程以下:
爲了恢復數據,首先咱們求剩餘編碼數據的逆矩陣,等式兩邊乘上這個逆矩陣仍然保持相等。與此同時,互逆矩陣的乘積爲單位矩陣,所以能夠被消掉。那麼所求得的逆矩陣與剩餘塊的數據的乘積就是原始數據了。
數據編碼以字節爲單位,若是將被編碼數據看作一個「數組」,「數組」中每一個元素是一個字節,數據按照字節順序被編碼。編碼過程是計算編碼矩陣中元素和「數組」的乘積過程。爲保證乘積的運算結果仍舊在一個字節大小之內(即 0-255),必須應用到有限域[1]。有限域上的算術運算不一樣於一般實數的運算規則。咱們一般事先準備好乘法表,並在算術運算時對每一次乘法進行查表獲得計算結果。早期的編碼引擎之因此性能不佳,是由於逐字節查表的性能是很是低的。假若能一次性對多字節進行查表以及相應的吞吐和運算,引擎的工做效率必將大幅度提高。
許多 CPU 廠商提供了包含更多位數的寄存器(大於 64 位),這類寄存器和相應支持的運算使得用戶程序能夠同時對大於機器位數的數據進行運算,支持這類寄存器和運算的指令稱之爲SIMD(Single Instruction Multiple Data)指令集,好比 Intel 支持的 SSE 指令集最大支持 128 bits 的數據運算,AVX2 指令集最大支持 512 bits 的數據運算。它們爲咱們對一個「數組」數據分別執行相同的操做,提升了數據運算的並行性。目前,市面上全部高性能的糾刪碼引擎均採用了該項技術以提升編解碼性能。
咱們將從如下幾個關鍵指標來對編碼引擎進行分析:
一、 高編/解碼速度;
二、參數可配置;
三、編碼速度穩定性;
四、代碼簡潔、穩定;
5 、下降修復開銷等。
上文提到,依賴於SIMD 技術 RS 碼編碼性能有了大幅度的提升。其中,咱們能夠利用多種指令集擴展以供加速,引擎應該能自動根據 CPU 的特性而選擇最優的指令集擴展進行加速。
速度是最基本的要求。不過在這裏我很難給出一個絕對的數字來衡量速度,由於其受參數,運行平臺的影響極大。在下文中提到的三款引擎均有出色的性能表現,能夠以它們爲基準來衡量引擎的編碼速度。除此以外,咱們還能夠將逐字節查表(下稱基本方法)的編碼速度與利用 SIMD 技術加速的編碼速度作對比,二者之間應該有很是直觀的差距。以個人我的電腦爲例(i5-4278U 2.6GHz),在 10+4 的策略下(每一個數據塊大小爲 128KB),基本方法的速度爲(原始數據總量/編碼耗時)318.1 MB/s,而經過 AVX2 指令集加速後達到了 5558.6 MB/s[2],在 SSSE3 指令集的加速下也有 2978.87 MB/s 。
另外,解碼速度應該大於或等於編碼速度(視丟失的數據塊數量而定),下圖截自在我本機上運行的修復原始數據塊的性能測試結果:
一款合理的糾刪碼引擎必須能作到編碼策略在理論範圍內可隨意切換,這指的是若是要將編碼策略進行變化時,僅需從接口傳入不一樣參數而不須要改動引擎自己。這大大下降了後續的開發和維護所須要的精力。一個可配置參數的編碼引擎能夠根據數據的冷熱程度和數據重要程度選擇不一樣的編碼係數,好比可靠性要求高的數據能夠選擇更多冗餘。
速度的穩定性指的是對於不一樣尺寸的數據塊會有相近的性能表現。因爲系統緩存的影響,當被編碼數據的大小和緩存大小至關時,編碼應該具備最快的速度。當編碼數據的大小大於緩存大小時,內存帶寬成爲編碼速度的瓶頸,文件大小和編碼時間呈現近似線性關係。這樣,數據編碼時間是可預期的,用戶的服務質量也是可保障的。在實際中,咱們對於大文件進行定長分塊,依次編碼,分塊大小和緩存大小保持必定關係:以 10+4 編碼方法爲例,對比數據塊尺寸分別爲取 L3 Cache Size 的 1/12 以及 12 倍。如 L3 Cache Size 的大小爲 12MB,則每一塊的數據尺寸分別取 1MB,144MB。假若大數據塊下編碼速度遠遠低於小數據塊,則說明該引擎 CPU cache 的優化工做作得不充分。對於上述參數來講,大數據塊的速度應該不低於小數據塊的 70% 。一樣以個人我的電腦爲例(L3 Cache 大小爲 3MB):
爲了利用 SIMD 加速咱們不得不引入彙編代碼或者封裝後的 CPU 指令,所以代碼形式並不常見。爲了加強可讀性可將部分邏輯抽離到高級語言,然而會損失部分性能,這其中的利弊須要根據團隊的研發實力進行權衡。
接下來的可維護性也很是重要。首先是接口穩定,不會隨着新技術的引入而致使代碼大規模重構;另外代碼必須通過有合理的測試模塊以便在後續的更新中校驗新算法。
好比早先的 SIMD 加速是基於 SSE 指令集擴展來作的,隨後 Intel 又推出 AVX 指令集進一步提升了性能,引擎應該能即時跟上硬件進步的步伐。在比方說,再生碼(能夠理解爲能減小修復開銷的糾刪碼)是未來發展的趨勢,但咱們不能由於算法的升級而隨意改變引擎的接口。
糾刪碼的一大劣勢即是修復代價數倍於副本方案。k+m 策略的 RS 碼在修復任何一個數據塊時,都須要k 份的其餘數據從磁盤上讀取和在網絡上傳輸。好比 10+4 的方案下,丟失一個數據塊將必須讀取 10 個塊來修復,這個修復過程佔用大量磁盤 I/O 和網絡流量,並使得系統暴露在一種降級的不穩定狀態。所以,實際系統中應該儘可能避免使用過大的 k 值。
再生碼[2] 即是爲了緩解數據修復開銷而被提出的,它可以極大減小節點失效時所須要的吞吐的數據量。然而其複雜度大,一方面下降了編碼速度,另一方面犧牲了傳統 RS 碼的一些優秀性質,在工程實現上的難度也大於傳統糾刪碼。
目前被應用最普遍並採用了 SIMD 加速的引擎有以下幾款:
Intel 出品的 ISA-L[4]
J.S.Plank 教授領導的 Jerasure[4]
klauspost 的我的項目(in Golang)[6]
這三款引擎的執行效率都很是高,在實現上略有出入,如下是具體分析:
糾刪碼做爲 ISA-L 庫所提供的功能之一,其性能應該是目前業界最佳。須要注意的是 Intel 採用的性能測試方法與學術界經常使用的方式略有出路,其將數據塊與冗餘塊的尺寸之和除以耗時做爲速度,而通常的方法是不包含冗餘塊的。另外,ISA-L 未對 vandermonde 矩陣作特殊處理,而是直接拼接單位矩陣做爲其編碼矩陣,所以在某些參數下會出現編碼矩陣線性相關的問題。好在 ISA-L 提供了cauchy 矩陣做爲第二方案。
ISA-L 之因此速度快,一方面是因爲 Intel 諳熟彙編優化之道,其次是由於它將總體矩陣運算搬遷到彙編中進行。但這致使了彙編代碼的急劇膨脹,使人望而生畏。
另外 ISA-L 支持的指令集擴展豐富,下至 SSE,上到 AVX512,平臺適應性最強。
不一樣於 ISA-L 直接使用匯編代碼,Jerasure2.0 使用 C 語言封裝後的指令,這樣代碼更加的友好。另外 Jerasure2.0 不只僅支持 GF(2^8) 有限域的計算,其還能夠進行 GF(2^4) - GF(2^128) 之間的有限域。而且除了 RS 碼,還提供了 Cauchy Reed-Solomon code (CRS 碼)等其餘編碼方法的支持。它在工業應用以外,其學術價值也很是高。目前其是使用最爲普遍的編碼庫之一。目前 Jerasure2.0 並不支持 AVX 加速,儘管如此,不過在僅使用 SSE 的狀況下,Jerasure2.0 依然提供了很是高的性能表現。不過主要做者之一 James S. Plank 教授轉了研究方向,另一位做者 Greenan 博士早已加入工業界。所以後續的維護將是個比較大的問題。
klauspost 利用 Golang 的彙編支持,友好地使用了 SIMD 技術,此款引擎的 SIMD 加速部分是目前我看到的實現中最爲簡潔的,矩陣運算的部分邏輯被移到了外層高級語言中,加上 Golang 自帶的彙編支持,使得彙編代碼閱讀起來更佳的友好。不過 Go 並無集成全部指令,部分指令不得不利用 YASM 等彙編編譯器將指令編譯成字節序列寫入彙編文件中。一方面致使了指令的徹底不可讀,另一方面這部分代碼的語法風格是 Intel 而非 Golang 彙編的 AT&T 風格,平添了迷惑。這款引擎比較明顯的缺陷有兩點:1.對於較大的數據塊,編碼速度會有巨大的下滑;2.修復速度明顯慢於編碼速度。
多是因爲對開源庫後續維護問題的擔心,也有多是現有方案並不能知足企業對某些特定需求和偏好,不少公司選擇了自研引擎。那麼如何寫出高效的代碼呢?在上面的簡單介紹中,受限於篇幅我跳過了不少細節。好比 SIMD 技術是如何爲糾刪碼服務的,以及如何利用 CPU Cache 作優化等諸多重要問題。咱們會在後續的文章中逐步展開其實現,歡迎你們繼續關注。
附錄:
Alexandros G Dimakis, P Godfrey, Yunnan Wu, Martin J Wainwright, and Kannan Ramchan-dran. Network coding for distributed storage systems. Information Theory, IEEE Transactions on, 56(9):4539–4551, 2010.