HBase PerformanceEvaluation机制分析

来源:互联网 发布:mac双系统 没了一个 编辑:程序博客网 时间:2024/06/07 11:23

本文我们来分析一下hbase自带的测试工具——performanceEvaluation。该工具是hbase自带的性能压测工具,基本原因是是用多线程模拟多用户同时访问集群的情况。

运行hbase org.apache.hadoop.hbase.PerformanceEvaluation,可以看到关于performanceEvaluation的用法介绍,如下图中所示:



介绍一下关键的几个参数:
-nomapred,mo mapreduce,加入此参数表示采用本地多线程的方式去读写数据,默认不加,此时本地启动mapreduce任务方式去测试;
-table,待测试的标明;
-rows,在本地多线程读写的模式下,指定每个线程处理的数据行数;
-startRow,每个线程操作的起始数据key;
-compression,-blockEncoding,前者指明了压缩方式,默认是NONE,后者指定了block的encoding策略,默认也是none;
-writeToWAL,写入数据时的WLog落盘策略,包括SYNC_WAL和SKIP_WAL两种;
-multiGet,在RandomRead中,如果指定大于1,则一次返回多条数据,默认一次返回一条;
-inmemory,会将数据尽量放在内存中,默认是false,也即读操作是从磁盘返回,为了保证pe能够准确获取测量结果,建议保持为false;
-presplit和-splitPolicy,两者搭配使用,用于对测试表进行预切割;
-filterAll,这个参数我的理解是在测试scan功能时,加上此参数,则server端scan出来的结果不再返回给client端,用于单纯测试server端的性能;
performanceEvaluation提供了如下几种测试用例:



scanRange10,指定范围内(最多10个数内)的随机scan,其他与此相同,这里的startkey是totalRows之内随机生成的数,如下是startkey和endkey的生成代码:

protected Pair<byte[], byte[]> generateStartAndStopRows(int maxRange) {          int start = this.rand.nextInt(Integer.MAX_VALUE) % opts.totalRows;          int stop = start + maxRange;          return new Pair<byte[],byte[]>(format(start), format(stop));}

sequentialRead/sequentialWrite,顺序读测试,顺序写测试;
randomRead/randomWrite,随机读&随机写,读写的数据量是上文中每个线程发送的数据量,每次读写的rowkey是totalRows内随机生成的数;
scan,scan测试,与普通scan相同,读出所有的数据,testScan的核心代码如下所示:
void testRow(final int i) throws IOException {      if (this.testScanner == null) {        Scan scan = new Scan(format(opts.startRow));        scan.setCaching(opts.caching);        if (opts.addColumns) {          scan.addColumn(FAMILY_NAME, QUALIFIER_NAME);        } else {          scan.addFamily(FAMILY_NAME);        }        if (opts.filterAll) {          scan.setFilter(new FilterAllFilter());        }       this.testScanner = table.getScanner(scan);      }      Result r = testScanner.next();      updateValueSize(r);}
注意这里加入的FilterAllFilter,如果指定了filterAll,那么scan出来的结果并不会返回给客户端,而是直接shortCut返回。

上面介绍了performanceEvaluation的用法,那么hbase是如何设计pe的线程模型,使得它可以对server端的性能进行压测的呢,这样的设计又对我们平时设计代码有什么样的启示,接着我们分析一下performanceEvaluation的编程实现。

程序的入口在performanceEvaluation的main方法中,main方法很简单,代码就两行,构造一个PerformanceEvaluation对象并作为参数传入ToolRunner的run方法中,该run方法会解析用户通过终端传入的参数,变成String[]类型的参数传入cool内部实现的run方法,这里就是performanceEvaluation中的run方法。
 public int run(String[] args) throws Exception {    try {      LinkedList<String> argv = new LinkedList<String>();      argv.addAll(Arrays.asList(args));      TestOptions opts = parseOpts(argv);       //检查参数&检查client线程数      Class<? extends Test> cmdClass = determineCommandClass(opts.cmdName);      if (cmdClass != null) {        runTest(cmdClass, opts);        errCode = 0;      }    } catch (Exception e) {      e.printStackTrace();    }    return errCode;}
run方法首先根据传入的String[]构造Options,完成参数和线程数数量的简单检查后,执行runTest进行测试,runTest中首先会检查table的状态,如果命令传入的table descriptor与table自身的属性不同(主要是检查table的split分区是否与用户指定的相同),那么就会将table删除重建,紧接着会根据用户是否指定了nomapred以决定是启用本地多线程执行测试还是运行mapreduce任务执行测试,runTest的代码如下所示:
 private void runTest(final Class<? extends Test> cmd, TestOptions opts) throws IOException,      InterruptedException, ClassNotFoundException {    try(Connection conn = ConnectionFactory.createConnection(getConf());        Admin admin = conn.getAdmin()) {      checkTable(admin, opts);    }    if (opts.nomapred) {      doLocalClients(opts, getConf());    } else {      doMapReduce(opts, getConf());    }}
doMapReduece就是构造出mr任务然后提交到yarn上去运行,这里不再详述。我们重点看doLocalClients是如何实现的。doLocalClients是一个比较典型的多线程运行模型,关键的代码我们列在下面:
static RunResult[] doLocalClients(final TestOptions opts, final Configuration conf)      throws IOException, InterruptedException {    final Class<? extends Test> cmd = determineCommandClass(opts.cmdName);    assert cmd != null;    @SuppressWarnings("unchecked")    Future<RunResult>[] threads = new Future[opts.numClientThreads];    RunResult[] results = new RunResult[opts.numClientThreads];    ExecutorService pool = Executors.newFixedThreadPool(opts.numClientThreads,           new ThreadFactoryBuilder().setNameFormat("TestClient-%s").build());    final Connection con = ConnectionFactory.createConnection(conf);    for (int i = 0; i < threads.length; i++) {      final int index = i;      threads[i] = pool.submit(new Callable<RunResult>() {        @Override        public RunResult call() throws Exception {          TestOptions threadOpts = new TestOptions(opts);          if (threadOpts.startRow == 0) threadOpts.startRow = index * threadOpts.perClientRunRows;          RunResult run = runOneClient(cmd, conf, con, threadOpts, new Status() {            @Override            public void setStatus(final String msg) throws IOException {              LOG.info(msg);            }          });          return run;        }      });    }    pool.shutdown();    for (int i = 0; i < threads.length; i++) {      try {        results[i] = threads[i].get();      } catch (ExecutionException e) {        throw new IOException(e.getCause());      }    }    final String test = cmd.getSimpleName();    Arrays.sort(results);    long total = 0;    for (RunResult result : results) {      total += result.duration;    }    con.close();    return results;}
初始化了大小为numClientThreads的一个线程池pool,接着向线程池pool中通过submit投了numClientThreads个线程,这里多说一句,线程池的submit接口即可以接受Callable的线程也可以接受Runnable的线程,且无论是Callable还是Runnable为参数,调用submit之后,都可以返回一个Future对象,将来用户可以从Future中获知线程是否执行结束。

不同的是,Callable中的call方法可以返回一个结果,而Runnable的run则不能返回结果,如上面的代码所示,call方法中进一步调用了runOneClient,并将调用的结果包装为一个对象RunResult返回给客户端,客户端调用Future的get方法获得返回结果,根据RunResult的值计算avg、min&max等统计值。

重点看runOneClient的实现,runOneClient是每个单独线程所执行的方法,其中通过传入的参数反射构造了一个Test类,执行Test的test方法,也就是用户通过传入参数制定的test case用例,如ScanRange10、sequenceWrite等等,统计计算结果,并将各统计值构造一个RunResult类作为当前线程的返回结果返回给客户端。
static RunResult runOneClient(final Class<? extends Test> cmd, Configuration conf, Connection con,                           TestOptions opts, final Status status) throws IOException, InterruptedException {    long totalElapsedTime;    final Test t;    try {      Constructor<? extends Test> constructor =        cmd.getDeclaredConstructor(Connection.class, TestOptions.class, Status.class);      t = constructor.newInstance(con, opts, status);    } catch (NoSuchMethodException e) {      //省略    } catch (Exception e) {      //省略    }    totalElapsedTime = t.test();    return new RunResult(totalElapsedTime, t.getLatency());}
这一部反射构造test case实例实在是精彩,Test是抽象类,每个不同的Test用例都是Test类的一个实现,实现的关键在于testRow方法,文章前面已经例举了两个不同测试用例的testRow方法实现,这里不再赘述。

hbase的performanceEvaluation虽然只有一个类,但是同样用到了多线程、反射等java程序设计中经典的方法,对于java学习者研究其中的设计技巧受益很大。

原创粉丝点击