和贝贝一起深入HBase-2.基于MapReduce的ITBLL原理及使用简介

1,443 阅读5分钟

一.前言

分享之前参与过的一个HBase二次开发项目,以及如何使用ITBLL进行集成测试。

1.1 HBase二次开发

  • HBase写操作(Sync模式)性能瓶颈很大一部分在于WAL(Write Ahead Log)写Hlog

  • HBase的一个RegionServer上的所有的Region都共享一个HLog进行WAL

  • 二次开发的目的是支持RegionServer多并发写多个HLog,提升HBase写性能

WAL是HBase的RegionServer记录Put,Delete操作内容的一种日志,Client往RegionServer提交数据时,会先写WAL,WAL写成功以后,Client才会返回正常结果,如果写WAL失败会告知Client操作失败,详细参考:www.edureka.co/blog/hbase-…

image

1.2 面临问题

多HLog改造面临的重要问题是Data Recovery时,WAL数据的保序性。

比如,key00001,cf.f1数据顺序执行put=1,2,3,4

  • 1HLog

image.png

  • 3HLog

image.png

  • Data Recovery时必须还原出put=1,2,3,4

WAL文件存储了一系列数据操作,每一个操作对应WAL中的一行,新的操作会顺序写在WAL文件的末尾,当RegionServer宕机等异常发生时,会顺序读取WAL并执行恢复操作。

详细参考:nag-9-s.gitbook.io/hbase/hbase…

image

1.3 解决方法

多HLog配置下,使用ITBLL,持续长时间模拟业务操作高并发HBase写过程中,注入Chaos故障事件,最后检查HBase数据的完整性和正确性。

二. ITBLL简介

2.1 ITBLL是什么

ITBLL是IntegrationTestBigLinkList的简称,是hbase源代码hbase-it子项目org.apache.hadoop.hbase.test包下的一个测试类(github.com/apache/hbas… move等各种异常场景下是否存在数据丢失。

2.2 ITBLL原理

顾名思义,ITBLL使用逻辑链表结构来检测一系列操作后是否存在数据丢失,其主要原理如下:

对于一个预先生成的循环链表,从根节点开始分别以parent-node的值为key,child-node的值为value,写入HBase。利用循环链表每个节点的入度和出度应该相等的特点,假设写入HBase的某一个row丢失,则必然有节点的入度不等于出度,来检测是否发生了数据丢失。以循环链表a->b->c->a为例,从节点a开始,写入HBase的将有(a,b)、(b,c)、(c,a)三行数据,如果(b,c)丢失,则链表节点b的入度(1)不等于出度(0)

image.png

三.ITBLL原理

实现方面,ITBLL中默认生成1 million个循环链表,每个链表的长度为25,每个链表节点的value大小为16 bytes,即每轮测试写入800MB(32*25M)数据。链表的个数和长度可以通过长度控制。由于每个链表节点的值都是随机生成的,可以保证数据在hbase的表中均匀分布,从而任何一个region发生数据丢失都可以被检测到。

具体到代码上,上述原理主要由以下两个内部类组成:

  • Generator

    • 通过runRandomInputGenerator方法,启动MapReduce作业生成待插入HBase的HDFS数据

    • 通过runGenerator方法,启动MapReduce作业将数据插入HBase

  • Verifier

    • VerifyMapper读取HBase表中的数据,对于每一个key-value对,生成两组数据,分别为:{key, new BytesWritable(new byte[1])}和{value,key}

    • VerifyReducer对于shuffle的每一组数据,分别统计value.length=1和value.length>1的值的数目,如果两个数目相等则verify成功,否则输出当前key并标记verify失败

image.png

3.1 Generator

3.1.1 runRandomInputGenerator

MapReduce作业(只有Mapper)生成待插入HBase的HDFS文件,文件每行是1条随机生成的value大小为16 bytes

代码实现如下:

public int runRandomInputGenerator(int numMappers, long numNodes, Path tmpOutput, Integer width,
  ...
  job.setJobName("Random Input Generator");
  job.setMapperClass(Mapper.class); // identity mapper
  job.setNumReduceTasks(0);
  job.setInputFormatClass(GeneratorInputFormat.class);
  ...
}

...

static class GeneratorInputFormat extends InputFormat<BytesWritable, NullWritable> {
  static class GeneratorRecordReader extends RecordReader<BytesWritable, NullWritable> {
    ...
    @Override
    public BytesWritable getCurrentKey() throws IOException, InterruptedException {
      byte[] bytes = new byte[ROWKEY_LENGTH];
      rand.nextBytes(bytes);
      return new BytesWritable(bytes);
    }
    ...
}

生成HDFS文件格式如下:

$hadoop dfs -cat hdfs://nameservice1:8020/user/zhangfei.t/itbll/generator/output/part-00000
...
LDOAZANDDS3VA1A3
TJ1J4VNIP6V1D86J
MDODC3PNZ7DJD952
5V5G1HEXB0CF0QR8
0ST5E2OQ6W6JZJCT
...

3.1.2 runGenerator

MapReduce作业(只有Mapper)读取runRandomInputGenerator生成的HDFS文件,以循环链表的形式写入HBase

代码实现如下:

public int runGenerator(int numMappers, long numNodes, Path tmpOutput, Integer width,
  Integer wrapMultiplier, Integer numWalkers) throws Exception {
  ...
  job.setJobName("Link Generator");
  job.setNumReduceTasks(0);
  FileInputFormat.setInputPaths(job, tmpOutput);
  job.setMapperClass(GeneratorMapper.class);
  ...
}
...
static class GeneratorMapper
  extends Mapper<BytesWritable, NullWritable, NullWritable, NullWritable> {
  ...
  @Override
protected void map(BytesWritable key, NullWritable value, Context output) throws IOException {
  current[i] = new byte[key.getLength()];
  System.arraycopy(key.getBytes(), 0, current[i], 0, key.getLength());
  ...
  persist(output, count, prev, current, id);
  ...
  }
  ...
  protected void persist(Context output, long count, byte[][] prev, byte[][] current, byte[] id)
  throws IOException {
  for (int i = 0; i < current.length; i++) {
    ...
    Put put = new Put(current[i]);
    put.addColumn(FAMILY_NAME, COLUMN_PREV, prev == null ? NO_KEY : prev[i]);
    ...
    mutator.mutate(put);
  }
  ...
}
  }

写入HBase数据如下:

# Scan all rows of table 'IntegrationTestBigLinkedList'
hbase> scan 'IntegrationTestBigLinkedList'
ROW               COLUMN+CELL
LDOAZANDDS3VA1A3  column=meta:prev, timestamp = xxxxx, value = 1FF5P2OQ6W6JZNAP
TJ1J4VNIP6V1D86J  column=meta:prev, timestamp = xxxxx, value = LDOAZANDDS3VA1A3
MDODC3PNZ7DJD952  column=meta:prev, timestamp = xxxxx, value = TJ1J4VNIP6V1D86J
5V5G1HEXB0CF0QR8  column=meta:prev, timestamp = xxxxx, value = MDODC3PNZ7DJD952
0ST5E2OQ6W6JZJCT  column=meta:prev, timestamp = xxxxx, value = 5V5G1HEXB0CF0QR8
....

3.2 Verify

MapReduce作业,map阶段读取HBase数据对每行数据写执行context.write(rowkey, rowkey.length),context.write(prev, prev.length),reduce阶段根据聚合后的values长度检查是否有数据丢失。

代码实现如下:

public static class VerifyMapper extends TableMapper<BytesWritable, BytesWritable> {
  ...
  @Override
  protected void map(ImmutableBytesWritable key, Result value, Context context)
    throws IOException, InterruptedException {
    byte[] rowKey = key.get();
    row.set(rowKey, 0, rowKey.length);
    context.write(row, DEF);
    byte[] prev = value.getValue(FAMILY_NAME, COLUMN_PREV);
    ref.set(prev, 0, prev.length);
    context.write(ref, row);
    ...
  }
  ...
  /**
 * Per reducer, we output problem rows as byte arrays so can be used as input for subsequent
 * investigative mapreduce jobs. Each emitted value is prefaced by a one byte flag saying what
 * sort of emission it is. Flag is the Count enum ordinal as a short.
 */
public static class VerifyReducer
  extends Reducer<BytesWritable, BytesWritable, BytesWritable, BytesWritable> {
  ...
  }
}

3.3 Chaos

在生成数据的同时,可以通过参数指定在运行ITBLL的过程中启动的ChaosMonkey类型,默认使用CalmChaosMonkey,即不进行任何异常操作。而我们通常使用“-m slowDeterministic”来使用SlowDeterministicMonkeyFactory类生成的PolicyBasedChaosMonkey,会在测试过程中进行kill RegionServer/Master,Region split/move等异常操作,具体参考SlowDeterministicMonkeyFactory类的build方法(github.com/apache/hbas…

四.ITBLL 使用方法

首先,由于ITBLL需要启动MapReduce作业,因此需要事先搭好Yarn集群(可以部署使用standalone,详细参考Hadoop: Setting up a Single Node Cluster)。

然后,进入hadoop-current目录,通过下述命令启动ITBLL:

./bin/hadoop org.apache.hadoop.hbase.test.IntegrationTestBigLinkedList Loop 1 25 1000000 /tmp/itbll 10

其中参数的含义如下:

Usage: Loop <num iterations> <num mappers> <num nodes per mapper> <output dir> <num reducers> [<width> <wrap multiplier>]