Hbase查询Scan优化和Row设计策略
- Hbase查询Scan优化和Row设计策略
- 前言
- 分区号设计
- 时间因素
- java查询代码
- 总结
Hbase查询Scan优化和Row设计策略
好久没有分享工作和学习经验了,工作太忙,好多学习计划都落下了,后面得加油了,本次就分享下在项目中运用的Hbase查询和RowKey设计相关的东西。
前言
- startKey和stopKey,scan中我建议必须要设置,限制扫描的分区,大数据量情况下,不设置所要查询的分区是不明智的,这样会导致全表扫描,对于web业务来讲是不可取的。
- 由于需要设置分区,即startKey和stopKey,那么我们需要设计好我们的rowKey,如何设计好我们的rowKey呢?没有完美的rowKey设计方案,都需要根据业务和数据来进行合理的设计我们的rowKey。那么如何根据业务?比如我们我们业务中,需要以某个字段的值作为搜索条件,那么这个字段的值就可以作为rowKey的一部分,注意了,这里说的是作为rowKey的一部分,而不是直接用它来直接作为rowKey,比如我们还要有时间的查询条件在里面,一般而言,原始数据表都是需要有时间这个因素在里面的,我们的离线任务都是需要根据时间来做的T+1。
- 预分区,在设计表的时候,我们都需要考虑到预分区,这个分区数,一方面取决于我们的写入数据量,并发量。另一方面,我们集群机器的配置。一般设置为50-200个分区,如果是测试服务器,极端情况下就一台机器,那么我强烈建议分区数在10左右,如果分区数过多,会导致regionServer的小合并拖慢Hbase,导致无法正常对外提供服务。
- 分页查询,该场景在web业务常见,需要对数据进行分区,那么我们就进行分页,有人会说了,PageFilter可以支持分页,这个没错,但是PageFilter的分页是在某一个分区的前提条件上的,比如分页数20,那么一页的数据就是20*partitionNm,那么显然这是不符合要求的。
分区号设计
- 这里我就介绍我目前使用的分区设计方案,其余的我就不介绍了,见下图,vale代表的是上网人员的身份证,那么首先是将该身份证号进行取hash,然后其余,最好对hash值位数规整化。最后的rowKey为09|420624199311217865X,这里就保证了同一vale值在同一个分区,这样就大大提升了查询效率。
时间因素
我们需要在查询的时候加入时间因素,时间不可以直接作为rowKey,这是因为,会导致数据倾斜,数据都会落在同一个分区上,还有一种方法就是,时间翻转,那么这个就会引发另外一个问题,就是相同时间段的同一类数据不是有序的,这样就大大降低了查询效率,需要遍历整张表。那么时间应该放在第二位,作为查询的条件使用,上面介绍了rowKey的分区前缀号,那么加上时间就变成了,09|1576337865|420624199311217865X,那么一般情况下,我们是需要时间逆序操作的,那么需要简单处理下,9999999999-时间戳,那么就是09|8423662134|420624199311217865X。
java查询代码
- HbaseQuery,该注解是必须的,只要标记该注解才会使用Hbase查询
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 尹忠政
* @package com.tbl.bigdata.common.query.annotation
* @describe
* @time 2019/9/21
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HbaseQuery {
}
- HbaseQueryField 提供了model字段和Hbase字段映射关系
import com.tbl.yth.commonutil.bigdata.conf.Constant;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 尹忠政
* @package com.tbl.bigdata.common.query.annotation
* @describe
* @time 2019/9/21
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HbaseQueryField {
String field();
String cf() default Constant.CF;
}
- QueryField是查询model,包括了列簇、列限定符、Field、Field的type,用来将result转化为model。
import lombok.Data;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import java.lang.reflect.Field;
/**
* @author 尹忠政
* @package com.tbl.bigdata.common.query.common
* @describe
* @time 2019/9/21
*/
@Data
public class QueryField {
private byte[] qualifier;
private Field field;
private Class type;
private byte[] cf;
public QueryField(Field field, Class type) {
this.field = field;
this.type = type;
}
public void setValue(Result result, Object currentObj) {
boolean b = result.containsColumn(cf, qualifier);
if (!b) {
return;
}
byte[] value = result.getValue(cf, qualifier);
if (type == int.class) {
try {
field.set(currentObj, Bytes.toInt(value));
} catch (Exception e) {
e.printStackTrace();
}
} else if (type == double.class) {
try {
field.set(currentObj, Bytes.toDouble(value));
} catch (Exception e) {
e.printStackTrace();
}
} else if (type == float.class) {
try {
field.set(currentObj, Bytes.toFloat(value));
} catch (Exception e) {
e.printStackTrace();
}
} else if (type == String.class) {
try {
field.set(currentObj, Bytes.toString(value));
} catch (Exception e) {
e.printStackTrace();
}
} else if (type == short.class) {
try {
field.set(currentObj, Bytes.toShort(value));
} catch (Exception e) {
e.printStackTrace();
}
} else if (type == byte.class) {
try {
field.set(currentObj, value[0]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.HbaseQueryHelper 查询的帮助类,自动化查询以model形式输出
import com.tbl.yth.commonutil.bigdata.conf.Constant;
import com.tbl.yth.commonutil.bigdata.query.annotation.HbaseQuery;
import com.tbl.yth.commonutil.bigdata.query.annotation.HbaseQueryField;
import com.tbl.yth.commonutil.bigdata.query.common.QueryField;
import com.tbl.yth.commonutil.bigdata.query.inter.IHbaseQueryConsumSetting;
import com.tbl.yth.commonutil.bigdata.query.inter.IHbaseReader;
import com.tbl.yth.commonutil.bigdata.query.model.HbaseBaseModel;
import com.tbl.yth.commonutil.bigdata.util.HbaseHelper;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.PageFilter;
import org.apache.hadoop.hbase.util.Bytes;
import java.lang.reflect.Field;
import java.util.*;
/**
* @author 尹忠政
* @package com.tbl.bigdata.common.query
* @describe Hbase 查询帮助类
* @time 2019/9/21
* <p>
* <p>
* simple example
* <p>
* import com.tbl.bigdata.common.query.annotation.HbaseQuery;
* import com.tbl.bigdata.common.query.annotation.HbaseQueryField;
* import com.tbl.bigdata.common.query.model.HbaseBaseModel;
* @HbaseQuery public class RhkkVo extends HbaseBaseModel {
* @HbaseQueryField(field = "capture_time")
* private String captureTime = "";
* private long timestamp;
* @HbaseQueryField(field = "device_id")
* private String deviceId = "";
* @HbaseQueryField(field = "device_type")
* private String deviceType;
* private String imei = "";
* private String value = "";
* private String locationId = "";
* private short netSubtype;
* private short netType;
* }
* <p>
* try {
* HbaseQueryHelper<RhkkVo> instance = HbaseQueryHelper.getInstance(RhkkVo.class);
* List<RhkkVo> query = instance.query(Constant.TABLE_RHKK, null, 10, new IHbaseQueryConsumSetting() {
* @Override public void setting(Scan scan, FilterList filterList) throws Exception {
* Filter filter = new RowFilter(CompareFilter.CompareOp.EQUAL,new SubstringComparator("|1|"));
* filterList.addFilter(filter);
* }
* });
* System.out.println(query.size());
* <p>
* } catch (Exception e) {
* e.printStackTrace();
* }
* <p>
* 该helper应该作为全局变量,一张表按理只应该建立一个helper示例,以提高搜索性能
*/
public class HbaseQueryHelper<T extends HbaseBaseModel> {
private List<QueryField> queryFields = new ArrayList<>();
private Set<String> cfs = new HashSet<>();
private Class<T> _class;
private HbaseQueryHelper(Class<T> _class) {
this._class = _class;
//递归获取所有的private字段
makeQueryField(_class);
}
private void makeQueryField(Class<?> _class) {
if (_class.getSuperclass() != Object.class) {
makeQueryField(_class.getSuperclass());
}
doMakeQueryField(_class.getDeclaredFields());
}
private void doMakeQueryField(Field[] declaredFields) {
for (Field field : declaredFields) {
field.setAccessible(true);
QueryField queryField = new QueryField(field, field.getType());
if (field.isAnnotationPresent(HbaseQueryField.class)) {
HbaseQueryField hbaseQueryField = field.getAnnotation(HbaseQueryField.class);
queryField.setQualifier(Bytes.toBytes(hbaseQueryField.field()));
queryField.setCf(Bytes.toBytes(hbaseQueryField.cf()));
cfs.add(hbaseQueryField.cf());
} else {
queryField.setQualifier(Bytes.toBytes(field.getName()));
queryField.setCf(Constant.CF_BYTE);
cfs.add(Constant.CF);
}
queryFields.add(queryField);
}
}
public static HbaseQueryHelper getInstance(Class<?> _class) throws Exception {
if (!_class.isAnnotationPresent(HbaseQuery.class)) {
throw new Exception(_class.getName() + " 需要@HbaseQuery标记");
}
return new HbaseQueryHelper(_class);
}
/**
* 分页查询
*
* @param tableName
* @param startRoeKey
* @param pageSize
* @param hbaseQueryConsumSetting
* @return
* @throws Exception
*/
public List<T> query(String tableName, String startRoeKey, long pageSize, IHbaseQueryConsumSetting hbaseQueryConsumSetting) throws Exception {
return query(tableName, startRoeKey, pageSize, hbaseQueryConsumSetting, null);
}
/**
* scan查询
*
* @param tableName
* @param hbaseQueryConsumSetting
* @param iHbaseReader
* @return
* @throws Exception
*/
public List<T> query(String tableName, IHbaseQueryConsumSetting hbaseQueryConsumSetting, IHbaseReader<T> iHbaseReader) throws Exception {
return query(tableName, null, 0, hbaseQueryConsumSetting, iHbaseReader);
}
/**
* 查询Hbase 表
*
* @param tableName 表名称
* @param startRoeKey 开始的Keyy
* @param pageSize 分页大小
* @param hbaseQueryConsumSetting 查询设置接口
* @param iHbaseReader 查询读取器
* @return
* @throws Exception
*/
public List<T> query(String tableName, String startRoeKey, long pageSize, IHbaseQueryConsumSetting hbaseQueryConsumSetting, IHbaseReader<T> iHbaseReader) {
List<T> ts = new ArrayList<>();
ResultScanner scanner = null;
try {
Table table = HbaseHelper.getTable(tableName);
Scan scan = new Scan();
FilterList filterList = new FilterList();
//设置开始rowKey
if (!StringUtils.isEmpty(startRoeKey)) {
scan.setStartRow(Bytes.toBytes(startRoeKey));
}
//设置分页
if (pageSize > 0) {
filterList.addFilter(new PageFilter(pageSize));
}
//添加列族
for (String cf : cfs) {
scan.addFamily(Bytes.toBytes(cf));
}
if (null != hbaseQueryConsumSetting) {
hbaseQueryConsumSetting.setting(scan, filterList);
}
scan.setFilter(filterList);
scanner = table.getScanner(scan);
Iterator<Result> iterator = scanner.iterator();
while (iterator.hasNext()) {
Result next = iterator.next();
T t = _class.newInstance();
t.setRowKey(Bytes.toString(next.getRow()));
for (QueryField queryField : queryFields) {
queryField.setValue(next, t);
}
if (null != iHbaseReader) {
iHbaseReader.reader(next, t);
} else {
ts.add(t);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != scanner) {
scanner.close();
}
}
return ts;
}
}
总结
分享结束,那么给一句忠告,Scan查询必须要给出startKey和stopKey,强烈建议,如果无法给出,那么建议放弃scan,使用elasticsearch+Hbase的二级索引的查询方式,后面我将介绍elasticsearch在项目中是如何配合Hbase使用的。