HDFS也可以支持Erasure Coding功能了,将会在Hadoop 3.0中发布,可以凭图为证:
在HDFS-7285中,实现了这个新功能.鉴于此功能还远没有到发布的阶段,可能后面此块相关的代码还会进行进一步的改造,因此只是做一个所谓的预分析,帮助大家提前了解Hadoop社区目前是如何实现这一功能的.本人之前也没有接触过Erasure Coding技术,中间过程也确实有些偶然,相信本文可以带给大家收获.
第一次主动去了解erasure coding这个东西纯粹是好奇,因为我平时主要混迹于Hadoop社区中HDFS模块部分,经常看到有很多的Issue Summary以单词Erasure Coding打头,而且这些任务一般都隶属于HDFS-8031下的子任务,比如下图所示的1个:
原来这是Erasure coding后续1阶段的工作.然后我就上网查了一下Erasure coding的意思于是就萌生了写此文的意图.Erasure coding同样作为一门技术,在学习hadoop 3.0 erasure coding之前,还是非常有必要去了解学习Erasure coding这门技术.
Erasure coding纠删码技术简称EC,是一种数据保护技术.最早用于通信行业中数据传输中的数据恢复,是一种编码容错技术.他通过在原始数据中加入新的校验数据,使得各个部分的数据产生关联性.在一定范围的数据出错情况下,通过纠删码技术都可以进行恢复.下面结合图片进行简单的演示,首先有原始数据n个,然后加入m个校验数据块.如下图所示:
Parity部分就是校验数据块,我们把一行数据块组成为Stripe条带,每行条带由n个数据块和m个校验块组成.原始数据块和校验数据块都可以通过现有的数据块进行恢复,原则如下:
而且m和n的值并不是固定不变的,可以进行相应调整.可能有人会好奇,这其中到底是什么原理呢? 其实道理很简单,你把上面这图看成矩阵,由于矩阵的运算具有可逆性,所以就能使数据进行恢复,给出一张标准的矩阵相乘图,大家可以将二者关联.
至于里面涉及数学推理的方面,同学们可以自行寻找资料进行学习.
纠删码技术作为一门数据保护技术,自然有许多的优势,首先可以解决的就是目前分布式系统,云计算中采用副本来防止数据的丢失.副本机制确实可以解决数据丢失的问题,但是翻倍的数据存储空间也必然要被消耗.这一点却是非常致命的.EC技术的运用就可以直接解决这个问题.
EC技术的优势确实明显,但是他的使用也是需要一些代价的,一旦数据需要恢复,他会造成2大资源的消耗:
概况来讲一句话,就是既耗网络又耗CPU,看来代价也不小.所以这么来看,将此计数用于线上服务可能会觉得不够稳定,所以最好的选择是用于冷数据集群,有下面2点原因可以支持这种选择
出于上述2种原因,冷数据集群无非是一个很好的选择.
前面花了大量的篇幅介绍EC技术,相信大家已经或多或少了解了这项技术.现在才是本文的一个重点,Hadoop Erasure Coding的实现.因为我们都知道,Hadoop作为一个成熟的分布式系统,用的也是3副本策略,所以这项技术的产生对于Hadoop本身来说,意义还是非常重大的.考虑到EC技术在Hadoop中的实现细节可能比较复杂,所以我不会逐行代码般的进行分析,从大的方向上理一理实现思路.
EC概念指的是data block数据块,parity block校验块,stripe条带等这些概念在HDFS中是如何进行转化的,因为要想实现EC技术,至少在概念上相同的.
下面用图形直观展示
上面的横竖结构可以看出来很像之前提到的矩阵.为什么要有stripe条带概念就是因为矩阵运算就会读到每行的数据.OK,接下来我们放大上面这个图
要对应上面的3种概念,需要设计几种逻辑上的单元概念,有下面2个逻辑概念
中间的internal blocks才是最终存储数据的block块,也就是我们平常说的HDFS中的block块.
stripe的大小在HDFS中的计算逻辑如下:
// Size of each stripe (only counting data blocks)
final int stripeSize = cellSize * numDataBlocks;
就是一行的大小.获取block长度的实现逻辑如下
// Size of each stripe (only counting data blocks)
final int stripeSize = cellSize * numDataBlocks;
// If block group ends at stripe boundary, each internal block has an equal
// share of the group
final int lastStripeDataLen = (int)(dataSize % stripeSize);
if (lastStripeDataLen == 0) {
return dataSize / numDataBlocks;
}
final int numStripes = (int) ((dataSize - 1) / stripeSize + 1);
return (numStripes - 1L)*cellSize
+ lastCellSize(lastStripeDataLen, cellSize, numDataBlocks, i);
如果恰好最后一行stripe长度为0,则说明每个block长度相等,直接返回即可,否则还要另外加上lastCellSize的大小.
了解了上述提到的概念,就可以开始真正了解ec在hdfs的实现,实现步骤主要在ErasureCodingWorker#ReconstructAndTransferBlock类中.从注释中可以看出,主要分为3大步.
bufferSize
data from minimum number of sources required by reconstruction.现在我们一步一步的来看.
看官方注释中对第一步骤的描述:
In step1, try to read bufferSize data from minimum number of sources , if there is corrupt or stale sources, read from new source will be scheduled. The best sources are remembered for next round and may be updated in each round.
概况的说,就是他首先会从sources node源节点中选出符合最好的n个节点,如果节点中有坏的或是慢节点,则会重新进行选择一次,代码如下
// step1: read from minimum source DNs required for reconstruction.
// The returned success list is the source DNs we do real read from
Map<ExtendedBlock, Set<DatanodeInfo>> corruptionMap = new HashMap<>();
try {
success = readMinimumStripedData4Reconstruction(success,
toReconstruct, corruptionMap);
} finally {
// report corrupted blocks to NN
reportCorruptedBlocks(corruptionMap);
}
然后会对每个source node新建相应的striperReader进行远程读,远程读会用到striperReader的blockReader和buffer缓冲.
private StripedReader addStripedReader(int i, long offsetInBlock) {
final ExtendedBlock block = getBlock(blockGroup, liveIndices[i]);
StripedReader reader = new StripedReader(liveIndices[i], block, sources[i]);
stripedReaders.add(reader);
BlockReader blockReader = newBlockReader(block, offsetInBlock, sources[i]);
if (blockReader != null) {
initChecksumAndBufferSizeIfNeeded(blockReader);
reader.blockReader = blockReader;
}
reader.buffer = allocateBuffer(bufferSize);
return reader;
}
图形展示效果如下
因为第一步骤的子步骤比较多一些,所以我制作了执行顺序图
同样给出官方源码注释
In step2, typically if source blocks we read are all data blocks, we need to call encode, and if there is one parity block, we need to call decode. Notice we only read once and reconstruct all missed striped block
if they are more than one.
第二个步骤主要在于编解码数据的过程.第一个步骤已经把数据读到缓冲区了,第二步就是计算的过程了.这里提到了很关键的一点.
if source blocks we read are all data blocks, we need to call encode, and if there is one parity block, we need to call decode.
编解码的决定要依靠于恢复的对象决定的,与之前在上半篇幅中提到的原则是一致的.相关代码如下
// step2: decode to reconstruct targets
reconstructTargets(success, targetsStatus, toReconstruct);
...
int[] erasedIndices = getErasedIndices(targetsStatus);
ByteBuffer[] outputs = new ByteBuffer[erasedIndices.length];
int m = 0;
for (int i = 0; i < targetBuffers.length; i++) {
if (targetsStatus[i]) {
targetBuffers[i].limit(toReconstructLen);
outputs[m++] = targetBuffers[i];
}
}
decoder.decode(inputs, erasedIndices, outputs);
...
但是在这里我有一点疑惑,这里直接使用的decode解码的操作,可能在这种使用场景下都是解码的情况.
第三步骤就很简单了,就是transfering data的操作,将buffer中的缓冲数据写入到目标节点即可.
In step3, send the reconstructed data to targets by constructing packet and send them directly. Same as continuous block replication, we don't check the packet ack. Since the datanode doing the reconstruction work are one of the source datanodes, so the reconstructed data are sent remotely.
写的方式很简单,直接远程写即可,因为此类写操作只涉及到1个节点,无须构建后续pipeline的动作.此方面可以阅读我的另外一篇博文从DFSOutputStream的pipeline写机制到Streamer线程泄漏问题.
// step3: transfer data
if (transferData2Targets(targetsStatus) == 0) {
String error = "Transfer failed for all targets.";
throw new IOException(error);
}
OK,以上就是Hadoop 3.0中EC数据恢复技术的一个主要实现.一张完整的顺序图如下
在官方注释中已经提到了2个改进点,在后续应该会被完善.
在学习Erasure Coding技术的过程中,查看了很多的资料,并且提交了一个Issue给社区,HDFS-9832,大家都可以看看.顺便说一句,我是去年9月开始接触Hadoop社区的,参与开源的过程使我对Hadoop的了解程度比以往更加深入了一层,这点感受确实很深.目前Hadoop最快的已经在开发3.0的版本了,而目前apache发布的最高版本是2.7.2,中间还有2.7.3, 2.8.0, 2.9.0等版本,所以Hadoop 3.0的问世还需要一段时间,而且在3.0中还会有许多新功能和旧的功能大改.目前Hadoop社区做的人主要是北美时区的人,大多是cloudera,hortonworks,apache下的人在做,偶有个别huawei的人也在做.希望大家多多参与开源,了解开源,向开源社区做出自己的贡献.
1.wiki Erasure Coding: https://en.wikipedia.org/wiki/Erasure_code
2.http://www.searchstorage.com.cn/whatis/word_6080.htm
3.http://blog.sina.com.cn/s/blog_3fe961ae0102vpxr.html
4.Issue 链接: https://issues.apache.org/jira/browse/HDFS-9832
5.Github patch链接:
https://github.com/linyiqun/open-source-patch/tree/master/hdfs/HDFS-9832