Hadoop 3.1.3学习笔记1

Hadoop 3.1.3学习笔记1

本部分主要分析EC策略下的条带化读取、写入过程,包括StripeReader、StripeWriter、StripedBlockReader、StripedBlockWriter、StripedBlockUtil部分源码。另外,整个过程还涉及EC策略下的数据重构,包括StripedReconstruction、StripedBlockReconstruction、BlockRecoveryWorker,还涉及HDFS的数据读写流DFSStripedOutputStream、DFSStripedInputStream。本文旨在回答以下问题:

1、hdfs中,file, block, datanode, stripe, cell, chunk这些概念之间的关系;

2、一个文件是如何被以条带化的形式存储在不同datanode的不同block上的,又是如何读出的;

3、EC策略下,数据如何被encode, decode, reconstruction, recovery。

基本概念

block group:通俗的理解就是组成“一次”编码的若干块;

Internal blocks:即block group中的一个块;

striping cell:块中的一个条带化的单元;

cell size:一个条带单元的尺寸。

striping chunk:是读取buffer的最小单位,index与cell相同但是范围可以不同

aligned stripe:读取的基本单位,可以理解为stripe但是范围可以不同

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cjMAJAOU-1592839623320)(C:\Users\hitzzk\AppData\Roaming\Typora\typora-user-images\image-20200622000215161.png)]

过程解读

条带化读取和写入的过程,就是将文件按照规则写入到不同datanode中的不同block中去,也就是,将文件的逻辑字节映射到物理存储器上,这样就可以理解,为什么在上述基本概念中,读取写入过程的chunk和stripe和逻辑上的cell和stripe并不完全一致。另外,在hdfs中另有定义,没两个cell之间间隔1byte:

the end offset of the previous range should be 1 byte lower than the start offset of the next one

官方源码中给出的示意图:

*  | <----  Block Group ----> |   <- Block Group: logical unit composing
*  |                          |        striped HDFS files.
*  blk_0      blk_1       blk_2   <- Internal Blocks: each internal block
*    |          |           |          represents a physically stored local
*    v          v           v          block file
* +------+   +------+   +------+
* |cell_0|   |cell_1|   |cell_2|  <- {@link StripingCell} represents the
* +------+   +------+   +------+       logical order that a Block Group should
* |cell_3|   |cell_4|   |cell_5|       be accessed: cell_0, cell_1, ...
* +------+   +------+   +------+
* |cell_6|   |cell_7|   |cell_8|
* +------+   +------+   +------+
* |cell_9|
* +------+  <- A cell contains cellSize bytes of data

Method解读

StripedBlockUtil类的第一个方法就是parseStripedBlockGroup这个方法(或称函数)的功能是

This method parses a striped block group into individual blocks.
public static LocatedBlock[] parseStripedBlockGroup(LocatedStripedBlock bg,
              int cellSize, int dataBlkNum, int parityBlkNum, int localParityBlkNum) {
  int locatedBGSize = bg.getBlockIndices().length;
  LocatedBlock[] lbs = new LocatedBlock[dataBlkNum + parityBlkNum + localParityBlkNum];
  for (short i = 0; i < locatedBGSize; i++) {
    final int idx = bg.getBlockIndices()[i];
    // for now we do not use redundant replica of an internal block
    if (idx < (dataBlkNum + parityBlkNum + localParityBlkNum) && lbs[idx] == null) {
      lbs[idx] = constructInternalBlock(bg, i, cellSize,
              dataBlkNum, idx);
    }
  }
  return lbs;
}

该函数的输入参数分别为一个block group,一个cell的大小,数据块数量、校验快数量、局部校验快数量(后增加),返回值为该block group中各个block的参数,LocatedBlock类在LocatedBlock.java文件中给出定义,在这里不再详细描述,只需知道该类给出了block的基本信息,如存储位置、存储id、安全令牌等,也就是我们所说的metadata(元数据)。parseStripedBlockGroup方法的关键是调用了constructInternalBlock方法,显然,从名字可以看出该方法具体实现了构建内部块的方法

public static LocatedBlock constructInternalBlock(LocatedStripedBlock bg,
    int idxInReturnedLocs, int cellSize, int dataBlkNum,
    int idxInBlockGroup) {
  final ExtendedBlock blk = constructInternalBlock(
      bg.getBlock(), cellSize, dataBlkNum, idxInBlockGroup);
  final LocatedBlock locatedBlock;
  if (idxInReturnedLocs < bg.getLocations().length) {
    locatedBlock = new LocatedBlock(blk,
        new DatanodeInfo[]{bg.getLocations()[idxInReturnedLocs]},
        new String[]{bg.getStorageIDs()[idxInReturnedLocs]},
        new StorageType[]{bg.getStorageTypes()[idxInReturnedLocs]},
        bg.getStartOffset(), bg.isCorrupt(), null);
  } else {
    locatedBlock = new LocatedBlock(blk, null, null, null,
        bg.getStartOffset(), bg.isCorrupt(), null);
  }
  Token<BlockTokenIdentifier>[] blockTokens = bg.getBlockTokens();
  if (idxInReturnedLocs < blockTokens.length) {
    locatedBlock.setBlockToken(blockTokens[idxInReturnedLocs]);
  }
  return locatedBlock;
}

此方法的输入参数为一个blockgroup,返回的block的位置标号(即实际存储它的datanode),cell的大小,数据块数量,block在blockgroup中的位置,返回值是该block在datanode上的位置(此处也包含存储类型、副本信息等其他metadata)。在以上代码块第4行可以看到这里调用了本函数的一个多态函数

public static ExtendedBlock constructInternalBlock(ExtendedBlock blockGroup,
    int cellSize, int dataBlkNum, int idxInBlockGroup) {
  ExtendedBlock block = new ExtendedBlock(blockGroup);
  block.setBlockId(blockGroup.getBlockId() + idxInBlockGroup);
  block.setNumBytes(getInternalBlockLength(blockGroup.getNumBytes(),
      cellSize, dataBlkNum, idxInBlockGroup));
  return block;
}

本函数构造了一个实际的块,本方法的关键显然在第5行,即设置本块的大小(字节数),而本块的大小又是由getInternalBlockLength方法所决定。在hdfs中,block的最大尺寸是确定的,一般设置为64M,128M,256M等,但是实际块的大小是由具体存储需求决定的。实际文件的大小,不一定能整除stripe的大小,也即会存在不完整的stripe,也不一定能整除cell的大小,也即会存在不完整的cell。

public static long getInternalBlockLength(long dataSize,
    int cellSize, int numDataBlocks, int idxInBlockGroup) {
  Preconditions.checkArgument(dataSize >= 0);
  Preconditions.checkArgument(cellSize > 0);
  Preconditions.checkArgument(numDataBlocks > 0);
  Preconditions.checkArgument(idxInBlockGroup >= 0);
  // 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, idxInBlockGroup);
}

可以看到getInternalBlockLength方法输入参数为数据大小,cell大小,数据块数量,该块在blockgroup中的标号。注意在这里我们都是在block group的范围内讨论,这里的datasize也是指一个blockgroup的数据大小。从以上代码可以看出,stripe的大小是cell的大小乘上数据块的数量,要注意,这里只计算了数据块,实际上stripe是包含校验块的。第11行可以看到,显然数据大小不一定是stripe大小的整数倍,若能整除,那么块的大小都相同,一个块的大小就是总的数据量除以块的数量(13行),若不能整除余数就是最后一个stripe的大小,此时各个块的大小显然就不再相同,而且最后一个块未必能存满一个cell。

return (numStripes - 1L)*cellSize
      + lastCellSize(lastStripeDataLen, cellSize,
      numDataBlocks, idxInBlockGroup);

从返回值上可以看到,此种条件下块的大小为条带的数量-1再乘上cell的大小,再加上本块最后一个cell的大小,下面再来看lasCellsize是如何计算最后一个cell大小的:

private static int lastCellSize(int size, int cellSize, int numDataBlocks,
    int i) {
  if (i < numDataBlocks) {
    // parity block size (i.e. i >= numDataBlocks) is the same as
    // the first data block size (i.e. i = 0).
    size -= i*cellSize;
    if (size < 0) {
      size = 0;
    }
  }
  return size > cellSize? cellSize: size;
}

本函数非常简单,若本块是数据块(i

继续看stripedBlockUtil的下一个方法

public static long getSafeLength(ErasureCodingPolicy ecPolicy,
    long[] blockLens) {
  final int cellSize = ecPolicy.getCellSize();
  final int dataBlkNum = ecPolicy.getNumDataUnits();
  Preconditions.checkArgument(blockLens.length >= dataBlkNum);
  final int stripeSize = dataBlkNum * cellSize;
  long[] cpy = Arrays.copyOf(blockLens, blockLens.length);
  Arrays.sort(cpy);
  // full stripe is a stripe has at least dataBlkNum full cells.
  // lastFullStripeIdx is the index of the last full stripe.
  int lastFullStripeIdx =
      (int) (cpy[cpy.length - dataBlkNum] / cellSize);
  return lastFullStripeIdx * stripeSize; // return the safeLength
  // TODO: Include lastFullStripeIdx+1 stripe in safeLength, if there exists
  // such a stripe (and it must be partial).
}

第11,12行可以看出这里通过整除后强制取整的方式得到最后一个完整的stripe,但是在这里为什么要取cpy[cpy.length - dataBlkNum]令我百思不得其解,这里按常理应该是取最后一个block的大小除以cellsize,即可得到完整的stripe的数量,也就是 dataBlkNum - 1而不是cpy.length - dataBlkNum。

spaceConsumedByStripedBlock方法功能较为简单不做赘述。

offsetInBlkToOffsetInBG方法功能较为简单不做赘述。

divideOneStripe方法:

public static AlignedStripe[] divideOneStripe(ErasureCodingPolicy ecPolicy,
    int cellSize, LocatedStripedBlock blockGroup, long rangeStartInBlockGroup,
    long rangeEndInBlockGroup, ByteBuffer buf) {
  final int dataBlkNum = ecPolicy.getNumDataUnits();
  // Step 1: map the byte range to StripingCells
  StripingCell[] cells = getStripingCellsOfByteRange(ecPolicy, cellSize,
      blockGroup, rangeStartInBlockGroup, rangeEndInBlockGroup);

  // Step 2: get the unmerged ranges on each internal block
  VerticalRange[] ranges = getRangesForInternalBlocks(ecPolicy, cellSize,
      cells);

  // Step 3: merge into stripes
  AlignedStripe[] stripes = mergeRangesForInternalBlocks(ecPolicy, ranges);

  // Step 4: calculate each chunk's position in destination buffer. Since the
  // whole read range is within a single stripe, the logic is simpler here.
  int bufOffset =
      (int) (rangeStartInBlockGroup % ((long) cellSize * dataBlkNum));
  for (StripingCell cell : cells) {
    long cellStart = cell.idxInInternalBlk * cellSize + cell.offset;
    long cellEnd = cellStart + cell.size - 1;
    for (AlignedStripe s : stripes) {
      long stripeEnd = s.getOffsetInBlock() + s.getSpanInBlock() - 1;
      long overlapStart = Math.max(cellStart, s.getOffsetInBlock());
      long overlapEnd = Math.min(cellEnd, stripeEnd);
      int overLapLen = (int) (overlapEnd - overlapStart + 1);
      if (overLapLen > 0) {
        Preconditions.checkState(s.chunks[cell.idxInStripe] == null);
        final int pos = (int) (bufOffset + overlapStart - cellStart);
        buf.position(pos);
        buf.limit(pos + overLapLen);
        s.chunks[cell.idxInStripe] = new StripingChunk(buf.slice());
      }
    }
    bufOffset += cell.size;
  }

  // Step 5: prepare ALLZERO blocks
  prepareAllZeroChunks(blockGroup, stripes, cellSize, dataBlkNum);
  return stripes;
}

此方法是较为关键的一个方法,功能为将字节数据分成条带,步骤已经在方法内的注释中写明,第一步将要条带化存储的数据,即一堆字节,分成若干cell,第二步,计算每个block需要存多少cell,也就是垂直范围(VerticalRange),第三步将数据形成条带,第四步将数据读到缓冲区(buffer)中

下一个方法getStripingCellsOfByteRange作用是得到blockgroup中的各个cell,不做赘述

getRangesForInternalBlocks与getStripingCellsOfByteRange类似,不做赘述,

下一个方法mergeRangesForInternalBlocks功能也类似,不做赘述。在这里阐明hdfs对于“长度”和"宽度"的定义。一个alignedstripe占用的block数量称为width,在一个块中占的byte数就是length。例如采用RS-3-2-1024k的EC策略,则alignedstripe的width是3+2=5,length是1024k。

以上三个方法基本可以搞清楚cell,stripe,block的关系。

未完待续。

sOfByteRange类似,不做赘述,

下一个方法mergeRangesForInternalBlocks功能也类似,不做赘述。在这里阐明hdfs对于“长度”和"宽度"的定义。一个alignedstripe占用的block数量称为width,在一个块中占的byte数就是length。例如采用RS-3-2-1024k的EC策略,则alignedstripe的width是3+2=5,length是1024k。

以上三个方法基本可以搞清楚cell,stripe,block的关系。

未完待续。

你可能感兴趣的:(hadoop)