Hbase查询Scan优化和Row设计策略

Hbase查询Scan优化和Row设计策略

好久没有分享工作和学习经验了,工作太忙,好多学习计划都落下了,后面得加油了,本次就分享下在项目中运用的Hbase查询和RowKey设计相关的东西。

前言

  1. startKey和stopKey,scan中我建议必须要设置,限制扫描的分区,大数据量情况下,不设置所要查询的分区是不明智的,这样会导致全表扫描,对于web业务来讲是不可取的。
  2. 由于需要设置分区,即startKey和stopKey,那么我们需要设计好我们的rowKey,如何设计好我们的rowKey呢?没有完美的rowKey设计方案,都需要根据业务和数据来进行合理的设计我们的rowKey。那么如何根据业务?比如我们我们业务中,需要以某个字段的值作为搜索条件,那么这个字段的值就可以作为rowKey的一部分,注意了,这里说的是作为rowKey的一部分,而不是直接用它来直接作为rowKey,比如我们还要有时间的查询条件在里面,一般而言,原始数据表都是需要有时间这个因素在里面的,我们的离线任务都是需要根据时间来做的T+1。
  3. 预分区,在设计表的时候,我们都需要考虑到预分区,这个分区数,一方面取决于我们的写入数据量,并发量。另一方面,我们集群机器的配置。一般设置为50-200个分区,如果是测试服务器,极端情况下就一台机器,那么我强烈建议分区数在10左右,如果分区数过多,会导致regionServer的小合并拖慢Hbase,导致无法正常对外提供服务
  4. 分页查询,该场景在web业务常见,需要对数据进行分区,那么我们就进行分页,有人会说了,PageFilter可以支持分页,这个没错,但是PageFilter的分页是在某一个分区的前提条件上的,比如分页数20,那么一页的数据就是20*partitionNm,那么显然这是不符合要求的。

分区号设计

  1. 这里我就介绍我目前使用的分区设计方案,其余的我就不介绍了,见下图,vale代表的是上网人员的身份证,那么首先是将该身份证号进行取hash,然后其余,最好对hash值位数规整化。最后的rowKey为09|420624199311217865X,这里就保证了同一vale值在同一个分区,这样就大大提升了查询效率。
    分区前缀

时间因素

我们需要在查询的时候加入时间因素,时间不可以直接作为rowKey,这是因为,会导致数据倾斜,数据都会落在同一个分区上,还有一种方法就是,时间翻转,那么这个就会引发另外一个问题,就是相同时间段的同一类数据不是有序的,这样就大大降低了查询效率,需要遍历整张表。那么时间应该放在第二位,作为查询的条件使用,上面介绍了rowKey的分区前缀号,那么加上时间就变成了,09|1576337865|420624199311217865X,那么一般情况下,我们是需要时间逆序操作的,那么需要简单处理下,9999999999-时间戳,那么就是09|8423662134|420624199311217865X

java查询代码

查询流程

  1. 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 {
}

  1. 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;
}

  1. 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使用的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值