Reed-Solomon 是
目前工业界最常用的纠删码(Erasure Code)之一,本文主要讲述其在存储领域如何进行数据容错,并且附带简要实现思路。
Reed-Solomon 原理
Reed-Solomon 通过数据冗余实现容错,并且根据冗余数据的大小实现与其一致的容错。
比如我有两个数字 1 和 2,那么可以通过构造出 3 = 1 + 2,并将其存储起来,就能在某个数字丢失的时候将 3 取出并恢复原来的数字。假设数字 1 丢失了,那么可以通过 3-2 = 1 的方式重新得到正确的数据。
上面通过简单相加的过程实现了最简单的数据冗余,但怎么实现更强的容错呢?我们可以设定不一样的系数。比如令 x = 1, y = 2,那么可以通过构造不同的系数得到不同的冗余数据,下面随便举几个例子。
$$0 \times x + 0 \times y = 0$$ $$0 \times x + 1 \times y = 2$$ $$1 \times x + 0 \times y = 1$$ $$1 \times x + 2 \times y = 5$$ $$2 \times x + 1 \times y = 4$$
当 x 丢失的时候,我们可以发现公式 1、2 无法为重构 x 提供帮助的,因为其系数为 0,冗余的值与 x 线性无关。
所以,正常情况下,我们需要尽可能地保证方程要包含所有数据块的信息。
P.S. XORing Elephants: Novel Erasure Codes for Big Data 论文中的 LRCs 能够通过构造两个半数线性无关的行列式,通过 14%的追加冗余,提高近乎两倍的数据恢复效率
但是我们需要对现实中计算机存储的数据冗余,这又要如何做到呢?
首先,我们知道计算机存储的数据都是由 0 和 1 组成的,因此可以对文件读进内存,并且以 byte 为单位对数据本身进行计算。
RS 的冗余计算方式是将文件切成 K 个数据块(data shards),假设 N 个数据块排成一列,通过对数据从开头到结尾进行多项式计算来生成 M 个校验块(parity shards),这样得到的校验块就包含了 N 个数据块的信息。
刚刚提到的“对数据从开头到结尾进行多项式计算”,其实就是用线性代数的知识对矩阵进行简单的加减乘除。
首先是编码矩阵的选取,常见的有 Vandermonde 和 Cauchy,klauspost/rs 的默认实现是 Vandermonde 矩阵,通过将其与上半部分(k*k)的逆矩阵相乘得到(k* k)的单位矩阵和(m*k)的下半部分矩阵。
$$ \begin{bmatrix} 1 & 1 & 1 & \dots & 1 \\ 1 & 2 & 2^2 & \dots & 2^{k-1} \\ 1 & 3 & 3^2 & \dots & 3^{k-1} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & k+m & (k+m)^2 & \dots & (k+m)^{k-1} \end{bmatrix} \Longrightarrow \begin{bmatrix} 1 & 0 & 0 & \dots & 0 \\ 0 & 1 & 0 & \dots & 0 \\ 0 & 0 & 1 & \dots & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & 0 & \dots & 1 \\ \alpha & \beta & \gamma & \dots & \delta \\ \epsilon & \zeta & \eta & \dots & \theta \end{bmatrix} $$
得到编码矩阵后,将其与数据块相乘,就能得到校验块。
但是如果直接相乘,就会导致一个问题:因为 K 个 1byte 数据的行列式计算结果很有可能会大于 1byte,导致校验块的大小不可控。
这个时候就要引入伽罗华域,伽罗华域又名有限域,包含有限个元素,这里我们选取包含 2^8 个元素的伽罗华域,在这个域中,每个元素可以表示为 8bit 的字节,所以校验块的大小不会膨胀。GF(2^8)中的所有多项式都可以通过多项式生成元 g 通过求幂得到。即域中的任意元素 $a \in \lbrace 0, 1, 2, \ldots, 255 \rbrace$,都存在一个 k,使得 a = g^k。为了更方便地在 GF 中进行乘除,我们需要创建两个表:对数表和指数表。对数表可以根据给定的指数查找 GF 中相应的对数;指数表则是反过来,根据对数得到 GF 中对应的指数。
打表:先确定 GF(2^8)的素多项式 $P_8(x) = x^8 + x^4 + x^3 + x^2 + 1 = 285$ 和生成元 $g(x)=2$,而后按顺序递推得到
|
|
下面以 ABCDEFGHIJKLNMOP
为例,模拟 RS 生成校验块并在部分数据块丢失的情况下恢复全量数据的过程。
令 K = 4,M = 2,将其切成 4 份,并且读取其 byte,我们可以得到其二进制表示(ASCII)。
$$ \begin{bmatrix} A& B& C& D \\ E& F& G& H \\ I& J& K& L \\ N& M& O& P \\ \end{bmatrix} → \begin{bmatrix} 65 & 66 & 67 & 68 \\ 69 & 70 & 71 & 72 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ \end{bmatrix} $$
再通过与(4+2,4)的 GF(2^8)编码矩阵相乘,得到校验块 $$ \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ 27 & 28 & 18 & 20 \\ 28 & 27 & 20 & 18 \\ \end{bmatrix}\times \begin{bmatrix} 65 & 66 & 67 & 68 \\ 69 & 70 & 71 & 72 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ \end{bmatrix}= \begin{bmatrix} 65 & 66 & 67 & 68 \\ 69 & 70 & 71 & 72 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ 81 & 82 & 83 & 73 \\ 85 & 86 & 87 & 37 \\ \end{bmatrix} $$
令第一和第二个数据块丢失,模拟重构数据块的过程 $$ \begin{bmatrix} 1& 0& 0& 0 \\ 0& 1& 0& 0 \\ 0& 0& 1& 0 \\ 0& 0& 0& 1 \\ 27& 28& 18& 20 \\ 28& 27& 20& 18 \\ \end{bmatrix}× \begin{bmatrix} d_1 \\ d_2 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ \end{bmatrix}= \begin{bmatrix} d_1 \\ d_2 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ 81 & 82 & 83 & 73 \\ 85 & 86 & 87 & 37 \\ \end{bmatrix} $$
$$\downarrow$$ $$ \begin{bmatrix} 0& 0& 1& 0 \\ 0& 0& 0& 1 \\ 27& 28& 18& 20 \\ 28& 27& 20& 18 \\ \end{bmatrix}× \begin{bmatrix} d_1 \\ d_2 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ \end{bmatrix}= \begin{bmatrix} 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ 81 & 82 & 83 & 73 \\ 85 & 86 & 87 & 37 \\ \end{bmatrix} $$ $$\downarrow$$ $$ \begin{bmatrix} d_1 \\ d_2 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ \end{bmatrix}= \begin{bmatrix} 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ 81 & 82 & 83 & 73 \\ 85 & 86 & 87 & 37 \\ \end{bmatrix}× \begin{bmatrix} 0& 0& 1& 0 \\ 0& 0& 0& 1 \\ 27& 28& 18& 20 \\ 28& 27& 20& 18 \\ \end{bmatrix}^{-1}= \begin{bmatrix} 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ 81 & 82 & 83 & 73 \\ 85 & 86 & 87 & 37 \\ \end{bmatrix}× \begin{bmatrix} 208 & 107 & 104& 210\\ 107 & 208 & 210& 104\\ 1& 0& 0& 0 \\ 0& 1& 0& 0\\ \end{bmatrix}= \begin{bmatrix} 65 & 66 & 67 & 68 \\ 69 & 70 & 71 & 72 \\ 73 & 74 & 75 & 76 \\ 77 & 78 & 79 & 80 \\ \end{bmatrix} $$ $$\downarrow$$ $$ d_1 = [65,66,67,68] $$ $$ d_2 = [69,70,71,72] $$
因此,我们可以通过构造校验块并将每个数据块和校验块分开存储的方式对数据进行容错。
reference
https://drmingdrmer.github.io/tech/distributed/2017/02/01/ec.html