平台中有一个场景是要做用户历史订单数据的查询,并且需要支持分页。
HBase中目前只支持rowkey一级索引,对二级索引还不太支持,我在以前的blog中提出了一种基于solr+hbase的解决方案 ,
下面这种做法是基于HBase的rowkey的设计采用的另外一种方案,这种方案只依赖于HBase,耦合性要小一些。
HBase版本是基于0.94,需要在这个版本的基础上对源码做适当的改造,主要是改造org.apache.hadoop.hbase.client包中的ClientScanner类,增加以下支持分页的方法:
//start,开始的记录数,比如从第10条开始
//nbRows为页的大小,也就是需要获取多少条记录
public Result[] limit(int start, int nbRows){
ArrayList<Result> resultList = new ArrayList<Result>(nbRows);
for(int i = 1;i < start && next() != null; i++);
Result next = null;
for(int i = 0; i < nbRows && (next = next()) != null; i++){
resultList.add(next);
}
return resultList.toArray(new Result[resultList.size()]);
}
需求场景是用户可以根据日期范围以及其他过滤条件分页查询订单历史数据,相关伪代码为:
HBase中提供了根据Rowkey范围进行scan的操作,对于历史投注记录,rowkey设计为:rowkey=userNum$orderDate$field1$field2
利用scan的startrow和stoprow条件确定范围以及filter进行过滤 接口定义:List scanerWithPage(int start,int pageSize,String userNum,String startDate,String endDate,String field1,String field2) 其中start和pageSize分别为起始的记录和每页的大小 相关伪代码: FilterList lf = new FilterList();
//该filter提供对除了userNum、orderDate查询字段的过滤,比如field1字段
RowFilter rowf = new RowFilter(CompareOp.EQUAL,new RegexStringComparator("([^$]+\\$){2}[2]")); lf.addFilter(rowf); scan.setFilter(lf); scan.setCaching(100); scan.setCacheBlocks(true);
//因需求中是按照日期的倒序在页面中显示订单列表,而HBase中是按照rowkey的递增顺序存储的,为了减少不必要的排序,日期字段采用(maxNum-orderDate)存储,其中maxNum取一个比较大的long常量值1000000000,orderDate转换成long,这样在HBase的存储顺序是按照日期的降序排列的。
scan.setStartRow((userNum+"$"+endDate).getBytes()); scan.setStopRow((userNum+"$"+startDate).getBytes()); scan.setBatch(100); ClientScanner clscaner = null; HTableInterface tbl = null; tbl = table.getTable("order_his"); clscaner = (ClientScanner) tbl.getScanner(scan); Result[] rets = clscaner.limit(start, pageSize);
这种做法比较简单,完全是利用了HBase的rowkey的设计原则,在查询条件比较少的情况下,效率还是非常高的。
除了userNum、orderDate是根据rowkey的范围定位的,其他字段在该范围内扫描过滤,因此查询条件尽量不要太多。