日期:2014-05-16  浏览次数:20524 次

hbase写数据过程

博文说明:1、研究版本hbase0.94.12;2、贴出的源代码可能会有删减,只保留关键的代码

?

从client和server两个方面探讨hbase的写数据过程。

一、client端

?

1、写数据API

? ? 写数据主要是HTable的单条写和批量写两个API,源码如下:

//单条写API

public void put(final Put put) throws IOException {

??? doPut(put);

??? if (autoFlush) {

????? flushCommits();

??? }

? }

//批量写API

public void put(final List<Put> puts) throws IOException {

??? for (Put put : puts) {

????? doPut(put);

??? }

??? if (autoFlush) {

????? flushCommits();

??? }

? }

//具体的put实现

private void doPut(Put put) throws IOException{

??? validatePut(put);

??? writeBuffer.add(put);

??? currentWriteBufferSize += put.heapSize();

??? if (currentWriteBufferSize > writeBufferSize) {

????? flushCommits();

??? }

? }

public void close() throws IOException {

??? if (this.closed) {

????? return;

??? }

??? flushCommits();

??? ….

? }

? ? 通过两个put API可以看出如果autoFlush为false,则无论是否是批量写效果均是相同,均是等待写入的数据超过配置的writeBufferSize(通过hbase.client.write.buffer配置,默认为2M)时才提交写数据请求,如果最后的写入数据没有超过2M,则在调用close方法时会进行最后的提交,当然,如果使用批量的put方法时,自己控制flushCommits则效果不同,比如每隔1000条进行一次提交,如果1000条数据的总大小超过了2M,则实际上会发生多次提交,导致最终的提交次数多过只由writeBufferSize控制的提交次数,因此在实际的项目中,如果对写性能的要求比对数据的实时可查询和不可丢失的要求更高则可以设置autoFlush为false并采用单条写的put(final Put put)API,这样即可以简化写操作数据的程序代码,写入效率也更优,需要注意的是如果对数据的实时可查询和不可丢失有较高的要求则应该设置autoFlush为true并采用单条写的API,这样可以确保写一条即提交一条。

?

2、关于多线程写

在0.94.12这个版本中,对于写操作,hbase内部就是多线程,线程数量与批量提交的数据涉及的region个数相同,通常情况下不需要再自己写多线程代码,自己写的多线程代码主要是解决数据到HTable的put这个过程中的性能问题,数据进入put的缓存,当达到writeBufferSize设定的大小后才会真正发起写操作(如果不是自己控制flush),这个过程的线程数与这批数据涉及的region个数相同,会并行写入所有相关region,一般不会出现性能问题,当涉及的region个数过多时会导致创建过多的线程,消耗大量的内存,甚至会出现线程把内存耗尽而导致OutOfMemory的情况,比较理想的写入场景是调大writeBufferSize,并且一次写入适量的不同regionserver的region,这样可以充分把写压力分摊到多个服务器。

hbase写数据的客户端核心方法是HConnectionManager的processBatchCallback方法,相关源码如下:

public void flushCommits() throws IOException {

??? try {

????? Object[] results = new Object[writeBuffer.size()];