HBase建模、使用以及優化

友德發表於2018-07-11
HBase建表的基本準則
常見rowKey設計遇到的問題以及相應的解決方案
建模案例分析:電商中寶貝點選量建模示例
HBase客戶端的使用
HBase優化
HBase連續分頁問題查詢的參考解決方案

分享的目的:

合理地使用HBase,發揮HBase本身所不具有的功能,提高HBase的執行效率

一、HBase建表的基本準則

1. family數量的控制:

不能太多,建議不要超過2個,一般的情況 下,一張表,一個family最好, 可以避免在刷快取的時候一個Region下多個Store的相互影響,減少不必要的IO操作;

2. rowKey的設計:

  1. rowKey寫入不能連續,儘量散開,避免寫入region熱點的問題, 導致regionServer負載不均衡,不能充分發揮HBase叢集高併發寫入的效能,極端情況下會出現regionServer當機下線;
  2. 在滿足業務的情況下,rowKey的長度越短越好,rowkey的長度越短,越節約記憶體和硬碟的儲存空間;
  3. 設定好rowKey的分割符,多個業務欄位拼接,設定好分隔符,如#, $(方便字首範圍查詢, 又沒有熱點問題)

3. 版本數量的控制:

業務上沒有特別要求的情況 下,用一個版本,即最大版本和最小版本一樣,均為1;

4. 失效時間的控制:

根據具體業務的需求,合理的設定好失效時間,從節約儲存空間的情況考慮,當然是在滿足業務的情況下儲存的時間越短越好,永久儲存的情況除外

二、常見rowKey設計遇到的問題以及相應的解決方案

1. 連續rowKey導致的region熱點問題:

解決方案:

  1. rowKey整個逆序, 主要針對流水資料,字首的範圍查詢變得不可用

               逆序前分別是: 20170227204355331和20170227204355339  (同一個region)
               逆序後分別是: 13355340272207102 和93355340272207102 (不同的region)
  2. rowKey的部分逆序, 主要針對一些特徵的流水資料,而且還可以實現部分字首的範圍查詢

               逆序前分別是: 20170227204355TNG和20170227204355TFF  (同一個region)
               逆序後分別是: GNT20170227204355 和FFT20170227204355 (不同的region)
  3. 對rowKey取MD5 Hash, 相領的值做md5 Hash之後,完全不同
  4. 直接使用UUID , 完全雜湊開

2. 相同rowKey的問題, 即業務 上沒唯一欄位:

  1. 加隨機字串字尾;
  2. 加時間戳字尾, 根據需要實現按時間遞增或遞減;
    注意:加字首和字尾,最好設定好分隔符,方便業務上的處理,因為大部分用是的是26個大小寫字母和數字,非常適合常見的分割符如 $, #

三、建模案例分析:電商中寶貝點選量建模示例

需求: 統計電商中某個寶貝最近一週的點選量
表的設計:

  1. 以分鐘為單位的近實時統計表 rowKey=itemId + ‘#’+’yyyyMMddHHmm’
  2. 歷史點選量彙總表 rowKey = itemId;
  3. 最近一週點選量近似彙總表 rowKey = itemId;

在業務上層每半個小時做定時更新
1.新的一週點選量= 最近的半個小時量-7天前的半小時量 + 原來一週的歷史量
2.新的歷史點選量 += 最近半個小時的點選量;

四、HBase客戶端的使用

1 原生客戶端Api

1.1連線

Configuration conf = HBaseConfiguraton.create(); 
conf 設定 HBase  zk 和其它引數; 
HTable table = new HTable(conf, “test.testTable”);

1.2 單個操作

Put put = new Put(Bytes.toBytes(“row1”));
put.add(Bytes.toBytes(“colfam1”), Bytes.toBytes(“qual1”), Bytes.toBytes(“val1”));
put.add(Bytes.toBytes(“colfam1”), Bytes.toBytes(“qual2”), Bytes.toBytes(“val1”));
table.put(put);

// delete操作
Delete delete = new Delete(toBytes(“row2”));
table.delete(delete);

// Get操作
Get get = new Get(toBytes(“row1”));
Result result = table.get(get);

1.3 批量操作

List<Put> puts = new ArrayList<Put>(2);
Put put2 = new Put(Bytes.toBytes(“row2”));
put2.add(Bytes.toBytes(“colfam1”), Bytes.toBytes(“qual1”), Bytes.toBytes(“val2”));
put2.add(Bytes.toBytes(“colfam1”), Bytes.toBytes(“qual2”), Bytes.toBytes(“val2”));
Put put3 = new Put(Bytes.toBytes(“row3”));
put3.add(Bytes.toBytes(“colfam1”), Bytes.toBytes(“qual1”), Bytes.toBytes(“val3”));
put3.add(Bytes.toBytes(“colfam1”), Bytes.toBytes(“qual2”), Bytes.toBytes(“val3”));
puts.add(put2);
puts.add(put3);
table.put(puts);

1.4 原子自增操作

Long result = table.incrementColumnValue(toBytes(“row1”), toBytes(“colfam1”),  

1.5 過濾器的使用

Bytes.toBytes(“qual3”), 3);
PageFilter pageFilter = new PageFilter(1);
Scan scan = new Scan();
scan.setFilter(pageFilter);
Long start = System.currentTimeMillis();
String rowKey = hbaseTemplate.find(tableName, scan, new MinRowKeyMapperExtractor());

注意: 不到萬不得已,不要使用Filter, 有的Filter的查詢 效率很低,最好是結合rowKey範圍掃描進行查詢

1.6 HBase 管理介面API

管理API, 查詢一個HBase叢集所有表

HBaseAdmin admin = new HBaseAdmin(configuration);
HTableDescriptor[] htds = admin.listTables();

2 Spring整合HBase客戶端

1. 新增maven依賴

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>0.98.6-hadoop2</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.6.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-hadoop</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

2.引入Spring HBase工具類

HbaseConfigurationSpringFactoryBean (二次開發的封裝類)

/**
 * hbase工廠類
 * @author tiandesheng
 *
 */
public class HbaseConfigurationSpringFactoryBean implements InitializingBean, FactoryBean<Configuration> {
 
    private static Logger logger = LoggerFactory.getLogger(HbaseConfigurationSpringFactoryBean.class);
    
    private Configuration configuration;
    
    private Properties properties;
    
    public Configuration getObject() throws Exception {
        return configuration;
    }

    public Class<Configuration> getObjectType() {
        return Configuration.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public void afterPropertiesSet() throws Exception {
        
        configuration = HBaseConfiguration.create();
        addProperties(configuration, properties);
        if (logger.isInfoEnabled()) {
            logger.info("Hbase連線初始化完畢!");
        }
    } 
    
    public void addProperties(Configuration configuration, Properties properties) {
        
        if (properties != null) {
            for (Entry<Object, Object> entry : properties.entrySet()) {
                String key = entry.getKey().toString();
                String value = entry.getValue().toString();
                configuration.set(key, value);
            }
        }
    }

    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

HTableInterfacePoolFactory (二次開發的封裝類)

/**
 * 基於執行緒池的HTable實現工廠
 */
@SuppressWarnings("deprecation")
public class HTableInterfacePoolFactory implements HTableInterfaceFactory, DisposableBean, InitializingBean {

    private static final Logger        logger   = LoggerFactory.getLogger(HTableInterfacePoolFactory.class);

    private int                        poolSize = 50;
    private HTablePool                 pool     = null;
    private Configuration              configuration;
    private Map<String, AtomicInteger> initLock;
    
    public void releaseHTableInterface(HTableInterface table) {
        close(table);
    }
    
    public HTableInterface createHTableInterface(Configuration config, byte[] tableName) {
        
        if (tableName == null) {
            return null;
        }
        if (initLock != null) {
            AtomicInteger tlock = initLock.get(new String(tableName).trim());
            if (tlock != null && tlock.get() == 0) { 
                if (logger.isInfoEnabled()) {
                    logger.info("get Htable:{} connection lock", new String(tableName));
                }
                tlock.getAndAdd(1);
                return pool.getTable(tableName);
            }
        }
        return pool.getTable(tableName);
    }

    public void afterPropertiesSet() throws Exception {
        // 初始化HTablePool
        if (pool == null) {
            pool = new HTablePool(configuration, poolSize);
            if (logger.isInfoEnabled()) {
                logger.info("hbase 連線池建立並初始化完畢!");
            }
        }
    }
    
    private void close(HTableInterface hTableInterface) {
        if(hTableInterface != null) {
            try {
                hTableInterface.close();
            } catch(Throwable t) {
                logger.error("close異常 {},", t);
            }
        }
    }

    public void destroy() throws Exception {
        
        initLock.clear();
        if (logger.isInfoEnabled()) {
            logger.info("Hbase連線池已經關閉!");
        }
    }

    public void setPoolSize(int poolSize) {
        this.poolSize = poolSize;
    }

    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }
}

HbaseTemplate (核心類)

     Spring的模板設定模式, 用法型別於JdbcTemplate, JmsTemplate, TransactionTemplate)
 通過以Spring 配置檔案方式和純程式碼的方式都可以實現Spring整合HBase客戶端,不管以那種

試,都是要首先設定HBase連線的幾個引數,其中zk和zk的埠這兩個引數一定要包含,然後依次
初始化上面三個類的物件,最終得到的HbaseTemplate物件就是我們要直接要對HBase進行操作對
象, spring配置檔案 和純程式碼實現方式如下:

1. spring配置檔案的方式:

<bean id="hBaseConfiguration" class="com.xxx.hbase.client.HbaseConfigurationSpringFactoryBean>
      <property name="properties">
          <props>
               <prop key="hbase.zookeeper.quorum", value="${hbase.zookeeper.quorum}" />
               <prop key="hbase.zookeeper.property.clientPort" ,
                               value="${hbase.zookeeper.property.clientPort}" />
               <prop key="hbase.master.port",value="${hbase.master.port}" />
          </props>
     </property>
</bean>

<bean id="hTableInterfacePoolFactory" class="com.xxx.hbase.client.HTableInterfacePoolFactory" >
       <property name="configuration" ref="hBaseConfiguration" />
       <property name="poolSize" value="${poolSize}" />
</bean>

<bean id="hbaseTemplate" class="org.springframework.data.hadoop.hbase.HbaseTemplate"> 
       <property name="encoding" value="utf-8" />
       <property name="configuration" ref="hBaseConfiguration" />
       <property name="tableFactory" ref="hTableInterfacePoolFactory" />
</bean>

2. 純程式碼的方式:

public HbaseTemplate getHbaseTemplate(String hbaseClusterName) {
    HbaseTemplate hbaseTemplate = hbaseTemplates.get(hbaseClusterName);
    if (hbaseTemplate == null ) {
          DcHbaseCluster dcHbaseCluster = 
                dcHbaseClusterDao.selectByHbaseClusterName(hbaseClusterName);
         if (dcHbaseCluster == null) {
                throw new RuntimeException("getHbaseTemplate(String hbaseClusterName1) 出錯
                                                                hbaseClusterName=" + hbaseClusterName);
         }
        String hbaseZookeeperQuorum = dcHbaseCluster.getHbaseZookeeperQuorum();
        String hbaseZookeeperPropertyClientPort = 
                          dcHbaseCluster.getHbaseZookeeperPropertyClientPort();
        String hbaseMasterPort = dcHbaseCluster.getHbaseMasterPort();
        Integer poolSize = dcHbaseCluster.getPoolSize();
        this.addHbaseTemplate(hbaseClusterName, hbaseZookeeperQuorum,
                              hbaseZookeeperPropertyClientPort, hbaseMasterPort, poolSize);
        hbaseTemplate = hbaseTemplates.get(hbaseClusterName);
        if (hbaseTemplate == null) {
              throw new RuntimeException("getHbaseTemplate(String hbaseClusterName2) 出錯
                                                             hbaseClusterName=" + hbaseClusterName);
    }
    }
   return hbaseTemplate;
}


public boolean addHbaseTemplate(String hbaseClusterName,String hbaseZookeeperQuorum,
                                                        String hbaseZookeeperPropertyClientPort,
                                                        String hbaseMasterPort,
                                                        Integer poolSize) {
     HbaseConfigurationSpringFactoryBean hbaseConfigurationSpringFactoryBean =
                                                                           new HbaseConfigurationSpringFactoryBean();
     Properties properties = new Properties();
     //設定zk地址
     properties.put(“hbase.zookeeper.quorum”, hbaseZookeeperQuorum); 
     // 設定zk 埠
     properties.put(“hbase.zookeeper.property.clientPort”, hbaseZookeeperPropertyClientPort);
        // 設定Hmaster埠 
    properties.put(“hbase.master.port”, hbaseMasterPort);   
    hbaseConfigurationSpringFactoryBean.setProperties(properties);
    try {
                 // 初始化
                hbaseConfigurationSpringFactoryBean.afterPropertiesSet(); 
                hbaseConfigurationSpringFactoryBean
     } catch (Exception e) {
              e.printStackTrace();
     throw new RuntimeException("hbaseConfigurationSpringFactoryBean.afterPropertiesSet()出錯");
    }
    Configuration configuration = null;
    try {
                 // 得到連線Configuration物件
                 configuration = (Configuration)hbaseConfigurationSpringFactoryBean.getObject();  
    } catch (Exception e) {
                e.printStackTrace();
               throw new RuntimeException("hbaseConfigurationSpringFactoryBean.getObject()出錯!");
    }
    HTableInterfacePoolFactory hTableInterfacePoolFactory = new HTableInterfacePoolFactory();
    hTableInterfacePoolFactory.setConfiguration(configuration);
    hTableInterfacePoolFactory.setPoolSize(poolSize);
    try {
          hTableInterfacePoolFactory.afterPropertiesSet();   // 初始化hTableInterfacePoolFactory物件
    } catch (Exception e) {
          e.printStackTrace();
          throw new RuntimeException("hTableInterfacePoolFactory.afterPropertiesSet()出錯!");
    }
    HbaseTemplate hbaseTemplate = new HbaseTemplate();
    hbaseTemplate.setEncoding("UTF-8");
    hbaseTemplate.setConfiguration(configuration);
    hbaseTemplate.setTableFactory(hTableInterfacePoolFactory);
    try {
          hbaseTemplate.afterPropertiesSet();   // 初始化hbaseTemplate 物件
         } catch (Exception e) {
         e.printStackTrace();
          throw new RuntimeException("hbaseTemplate.afterPropertiesSet()出錯!");
    }
    configurations.put(hbaseClusterName, configuration);
    hbaseTemplates.put(hbaseClusterName, hbaseTemplate);
    return true;
}

單個Get讀資料操作程式碼示例

public Map<String, String> find(HbaseTemplate hbaseTemplate, 
                            String tableName, 
                    final String rowKey, 
                            final List<String> columns) {
    
       if (logger.isInfoEnabled()) {
              logger.info("find method tableName={}, rowKey={}", tableName, rowKey);
    }
    Result result = hbaseTemplate.execute(tableName, new TableCallback<Result>() {

                 public Result doInTable(HTableInterface table) throws Throwable {
        // 1.產生Get物件
        Get get = getObjectGenerator.generateGet(rowKey, columns);
        // 2.單個查詢hbase
        Long start = System.currentTimeMillis();
        Result result = table.get(get);
        if (logger.isInfoEnabled()) {
               logger.info("find method 消耗時間:{}ms", System.currentTimeMillis() - start);
        }
        return result;
                }
    });
    if (result == null) {
               return null;
    }
    Map<String, String> res = mapFrom(result);
    return res;
}

批量Get讀資料操作程式碼示例

public List<Map<String, String>> batchFind(HbaseTemplate hbaseTemplate, 
                                                       String tableName, 
                                                       final List<String> columns, 
                                                       final List<String> rowKeys) {
     if (logger.isInfoEnabled()) {
      logger.info("batchFind method tableName={}, rowKey={}", tableName, rowKeys);
     }
     Object object = hbaseTemplate.execute(tableName, new TableCallback<Object>() {
          public Object doInTable(HTableInterface table) throws Throwable {
            List<Get> listGets = new ArrayList<Get>(10);
            for (String rowKey : rowKeys) {
                     // 1.產生Get物件
                     Get get = getObjectGenerator.generateGet(rowKey, columns);
                      listGets.add(get);
             }
             // 2.批量查詢hbase
            Long start = System.currentTimeMillis();
            Result[] results = table.get(listGets);
            if (logger.isInfoEnabled()) {
             logger.info("batchFind method 消耗時間:{}ms", System.currentTimeMillis() - start);
            }
           return results;
       }
       });
      if (object == null) {
              return null;
      }
      Result[] results = (Result[])object;
      List<Map<String, String>> res = new ArrayList<Map<String, String>>(10);
      for (Result result : results) {
            Map<String, String> map = mapFrom(result);
            if (map != null) {
                 res.add(map);
             }
       }
     return res;
}

Spring整合HBase的本質

從上面程式碼,可以 看出,Spring整合HBase客戶端的本質還是對原始的Spring Client進行封裝, 從HBase Template的execute方法主要有三個重要的步驟:

  1. 根據表名,獲得或建立HBase表連線例項 HTableInterface;
  2. 執行業務 上實現的doInTable回撥方法, 即執行真正讀寫HBase操作;
  3. 把HBase連線例項放回到表池中;

五、HBase優化

1.JVM引數優化:

  1. –Xmn=12G –Xms=24G  -Xmx=24G  根據實際機器情況調整,一般為整個機器記憶體的一半,同時建議regionServer的堆記憶體建議不要超過32G ;
  2. -XX:PermSize=512M -XX:MaxPermSize=512M;
  3. -XX:+UseConcMarkSweepGC -XX:+UseParNewGC (建議使用CMS垃圾收集器, 其它JVM引數使用預設比較好)

2.HBase客戶端使用優化:

  1. 用表池或建立多個客戶端,適當提高HBase客戶端寫入的併發性;
  2. 儘可能批量寫和批量讀,減少RPC互動的次數;
  3. 不要讀取不必要的列資料, 限定scan的資料範圍;
  4. 讀取到客戶端的結果後,一定要關閉結果集, 即關閉Results和ReultsScanner;
  5. 根據HBase CPU的利用率,合時設定壓縮演算法,前提是要服務需要安裝壓縮演算法包;
    6.關閉AutoFlush , 設定setAutoToFlush(false) 不建議,有丟資料的風險;

7.關閉WAL Flag,設定setWriteToWAL(false), 不建議,有丟資料的風險;

3. 建表時優化:

1.根據不同的業務,合理地設定好HBase表名稱空間;

  1. 建立預分割槽, 減少region分裂的次數,並且寫入的負載也比較 好;
  2. 動態新增分割槽, 在HBase後面新的穩定版本中會有這個功能,華為、阿里、facebook公司內部二次開發的HBase已經新增了這個功能;

4. 運維時優化:

  1. 業務低峰期時,手動或定進均衡;
  2. 業務 高峰期時,關閉自動負載(不建議使用);

5 .配置引數優化:

  1. 設定StoreFile的大小: 根據業務場景適當增大hbase.hregion.max.filesize減少寫入過程中split的次數,同時也減少了Region的數量,新版本預設是10G,老版本預設是512M,新版本建議用預設值;
  2. 設定memstore固定緩衝區塊的大小:hbase.hregion.memstore.mslab.chunksize, 預設是2M, 最好是根據一次寫入資料大小進行評估,建議用預設值;
  3. 減少Zookeeper超時的發生: zookeeper.session.timeout, 預設是3分鐘, 可以修改為一分鐘或半分鐘, 加快HMaster發現故障的regionServer;
  4. 增加處理執行緒: hbase.regionserver.handler.cout, 預設值為10, 如果批量寫,這個值可以設定小些,如果是單 個讀寫,這個值可以適當設定大些;
  5. 啟用資料壓縮: 推薦使用Snappy或者LZO壓縮, 前提是需要安裝這個壓縮演算法的jar包,然後再進行配置,重啟;
  6. 適當增加塊快取的大小: perf.hfile.block.cache.size 預設為0.2,這個需要檢視記憶體刷寫到磁碟的頻率,如果不是很頻繁,可以適當增加這個值的設定,建議0.2 ~ 0.3之間;
  7. 調整memStore限制: hbase.regionsever.global.memstore.upperLimit 預設為0.4 hbase.regionsever.global.memstore.lowerLimit 預設為0.35, 建議把這兩個值設定近些或相等;
  8. 增加阻塞時儲存 檔案數目: hbase.hstore.blockingStoreFiles 預設值 為7,當一個region的StoreFile的個數超過值的時候,更新就會阻塞, 在高並寫的情況 下,設定 為10左右比較 為合理;
  9. 增加阻塞倍率 : hbase.region.memstore.block.multiplier 預設值是2, 當memstore達到multiplier 乘以flush的大小時,寫入就會阻塞, 對於寫壓力比較大,可以增加這個值,一般 為設定為2-4;
  10. 減少最大日誌 檔案 限制: hbase.regionserver.maxlogs 預設是32, 對於寫壓力比較大的情況 ,可以減少這個值的設定, 加快後臺非同步執行緒的定時清理工作;

六、HBase連續分頁問題查詢的參考解決方案

在實際的應用中,有很多少分頁查詢顯示的功能,但HBase中分頁過濾器在跨region的時候,會出現各種無法預測的問題,導致讀取的資料丟失,為了解決這個問題,需要在業務端進行多次連續查詢 ,在基於region根據rowKey連續遞增的情況下,給出如下的解決方案:

10.jpg

原始碼如實現如下:

public Map<String, Map<String, String>> prefixScanOfPageFilter(HbaseTemplate hbaseTemplate, 
                                                                         String tableName,
                                                                          List<String> columns, 
                                                                          String prefix, 
                                                                          String startRow, 
                                                                          int pageSize, 
                                                                          final String isProcessedNostringColumn, 
                                                                          final Set<String> tableColumnSet) {
        
        if (logger.isInfoEnabled()) {
            logger.info("prefixScanOfPageFilter method tableName={}", tableName);
        }
        
        // 每次都要重新查詢
        TreeSet<HbaseRegionInfo> hbaseRegionInfos = new TreeSet<HbaseRegionInfo>(new Comparator<HbaseRegionInfo>() {
            @Override
            public int compare(HbaseRegionInfo o1, HbaseRegionInfo o2) {
                
                if (o1 == null && o2 == null) {
                    return 0;
                } else if (o1 != null && o2 == null) {
                    return 1;
                } else if (o1 == null && o2 != null) {
                    return -1;
                }
                return Bytes.compareTo(o1.getStartRow(), o2.getStartRow());
            }
        });
        
        try {
            HBaseAdmin admin = new HBaseAdmin(hbaseTemplate.getConfiguration());
            List<HRegionInfo> regionInfos = admin.getTableRegions(tableName.getBytes());
            if (regionInfos == null) {
                return null;
            }
            
            for (HRegionInfo hRegionInfo : regionInfos) {
            HbaseRegionInfo hbaseRegionInfo = new HbaseRegionInfo(hRegionInfo.getStartKey(), hRegionInfo.getEndKey() );
                hbaseRegionInfos.add(hbaseRegionInfo);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        StringBuffer buffer = new StringBuffer(2);
        String endRow = buffer.append((char)((int)prefix.charAt(0) + 1)).toString();
        
        // 確定region區間範圍
        TreeSet<HbaseRegionInfo> regionInfoRanges = decideRegionRange(hbaseRegionInfos, startRow, endRow);

        // 返回結果
        Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>(pageSize+1);

        Long start = System.currentTimeMillis();

        byte[] byteBegin = toBytes(startRow);
        byte[] byteEnd = toBytes(endRow);

        HbaseRegionInfo first = regionInfoRanges.first();
        
        while (result.size() < pageSize + 1 ) {
            
            // 分頁過濾器
            PageFilter pageFilter = new PageFilter(pageSize + 1 - result.size());
            // 當前查詢的結果
            Map<String, Map<String, String>> currentResult = null;
            
            byte[] firstEndRow = first.getEndRow();
            
            // 考慮為空的情況  1 >> 結束
            if (firstEndRow == null || firstEndRow.length == 0) {
                Scan scan = new Scan( byteBegin, byteEnd  );
                scan = getObjectGenerator.generateScan(scan, columns);
                scan.setFilter(pageFilter);
                currentResult = hbaseTemplate.find(tableName, scan, 
                      new MapperAndRowKeyDetailExtractor(isProcessedNostringColumn, tableColumnSet));
                result.putAll(currentResult);
                if (logger.isInfoEnabled()) {
                    logger.info("findByPageFilter2 method 消耗時間:{}ms", System.currentTimeMillis() - start);
                }
                return result;
            }
            
            // 比較確定區間
            int cmp = Bytes.compareTo( byteEnd, firstEndRow );
            
            if ( cmp < 0 ) { 
                // 在region範圍之內, 1 >> 結束
                Scan scan = new Scan( byteBegin, byteEnd );
                scan = getObjectGenerator.generateScan(scan, columns);
                scan.setFilter(pageFilter);
                currentResult = hbaseTemplate.find(tableName, scan, 
                      new MapperAndRowKeyDetailExtractor(isProcessedNostringColumn, tableColumnSet));
                result.putAll(currentResult);
                if (logger.isInfoEnabled()) {
                    logger.info("findByPageFilter2 method 消耗時間:{}ms", System.currentTimeMillis() - start);
                }
                return result;
            } else if ( cmp == 0 ) { // 還有特殊處理(暫時不要和1的情況合併)
                // 剛好在region範圍之內, 2 >> 結束
                Scan scan = new Scan( byteBegin, byteEnd );
                scan = getObjectGenerator.generateScan(scan, columns);
                scan.setFilter(pageFilter);
                currentResult = hbaseTemplate.find(tableName, scan, 
                     new MapperAndRowKeyDetailExtractor(isProcessedNostringColumn, tableColumnSet));
                result.putAll(currentResult);
                if (logger.isInfoEnabled()) {
                    logger.info("findByPageFilter2 method 消耗時間:{}ms", System.currentTimeMillis() - start);
                }
                return result;
            } else {
                // 在範圍之外(跨region), 需要再次查詢
                Scan scan = new Scan( byteBegin, firstEndRow );
                scan = getObjectGenerator.generateScan(scan, columns);
                scan.setFilter(pageFilter);
                currentResult = hbaseTemplate.find(tableName, scan, 
                     new MapperAndRowKeyDetailExtractor(isProcessedNostringColumn, tableColumnSet));
                result.putAll(currentResult);
                if (result.size() < pageSize + 1) {
                    if ( first.getStartRow() == null || first.getStartRow().length == 0 ) {
                        HbaseRegionInfo tmp = new HbaseRegionInfo(first.getEndRow(), first.getEndRow());
                        first = regionInfoRanges.ceiling(tmp);
                    } else {
                        first = regionInfoRanges.higher(first);
                    }
                    if (first == null) {
                        return result;
                    }
                    byteBegin = first.getStartRow();
                } else {
                    // 數量滿足要求  4 >> 結束
                    if (logger.isInfoEnabled()) {
                        logger.info("findByPageFilter2 method 消耗時間:{}ms", System.currentTimeMillis() - start);
                    }
                    return result;
                }
            }
        }
    return null;
    }
/**
     * 確定region的範圍
     * @param sets
     * @param startRow
     * @param endRow
     * @return
     */
private TreeSet<HbaseRegionInfo> decideRegionRange(TreeSet<HbaseRegionInfo> sets, String startRow, String endRow) {
        
        HbaseRegionInfo first = new HbaseRegionInfo(toBytes(startRow), toBytes(startRow));
        HbaseRegionInfo second = new HbaseRegionInfo(toBytes(endRow), toBytes(endRow));
        
        HbaseRegionInfo firstLower = sets.lower(first);
        HbaseRegionInfo firstHigher = sets.ceiling(first);
        HbaseRegionInfo secondLower = sets.lower(second);
        HbaseRegionInfo secondHigher = sets.ceiling(second);
        
        HbaseRegionInfo firstRegion = null;
        HbaseRegionInfo secondRegion = null;
        
        if ( (firstLower != null  &&  (firstLower.getEndRow() == null || firstLower.getEndRow().length == 0) ) &&  
                      firstHigher == null) {
            TreeSet<HbaseRegionInfo> result = new TreeSet<HbaseRegionInfo>(new Comparator<HbaseRegionInfo>() {
                @Override
                public int compare(HbaseRegionInfo o1, HbaseRegionInfo o2) {
                    
                    if (o1 == null && o2 == null) {
                        return 0;
                    } else if (o1 != null && o2 == null) {
                        return 1;
                    } else if (o1 == null && o2 != null) {
                        return -1;
                    }
                    return Bytes.compareTo(o1.getStartRow(), o2.getStartRow());
                }
            });
            result.add(firstLower);
            return result;
        }
        
        // 連續相連的情況
//        if (firstLower.getEndRow().equals(firstHigher.getStartRow())) {
            if (startRow.equals(firstHigher.getStartRow())) {
                // 在region邊緣,即第一個值
                firstRegion = firstHigher;
            } else {
                // 在region區間
                firstRegion = firstLower;
            }
//        } 
        
        if ((secondLower != null && (secondLower.getEndRow() == null || secondLower.getEndRow().length == 0) ) &&  
                     secondHigher == null) {
            secondRegion = secondLower;
        }
        
//        if (secondLower.getEndRow().equals(secondHigher.getStartRow())) {
            if (endRow.equals(secondHigher.getStartRow())) {
                secondRegion = secondHigher;
            } else {
                secondRegion = secondLower;
            }
//        }
        
        return (TreeSet<HbaseRegionInfo>)sets.subSet(firstRegion, true, secondRegion, true);
    }

因為region一般都比較大,在實踐中,我們發現絕大數查詢,是不存在跨頁查詢,跨頁查詢的情況下,大部分也是跨1 個頁,跨2個以上頁的情況非常罕見,因此查詢效率沒有感覺到明顯變慢


相關文章