基于HBase的模糊查询以及分页

最近一直忙于项目的落地,都快把CSDN的博客忘记了。现在有时间就补上一篇吧。

前言

这次讲的是基于HBase的模糊查询和分页。肯定有人问为什么要用HBase,其实我也知道基于大数据量的检索和模糊查询和分页 用ES比HBase强多了。因为HBase是面向列存储的K-V型nosql数据库,先天的特性使它不擅长于业务复杂的查询 甚至是模糊查询。但是实际情况是客户落地的机器数量有限制,只能从已有的数据库中去想办法。还好不是业务比较复杂的查询,只是简单的基于日期和标题的模糊查询。那么就使用HBase就能满足。话不多说,我们开始介绍。

1、构思

现有的元数据是存放在HBase中的,rowkey为了防止热点现象,采取了id倒序的设计。绝大部分场景是用户直接访问rowkey获取对应的内容。现在要求加上搜索功能,原来的rowkey不能改,只能构建二级索引去指向元数据的rowkey,通过二次查找来获取元数据。这里二级索引rowkey设计规则可以是

id倒序 + 日期 + 标题

其中id倒序用来防止热点现象,日期和标题也作为rowkey的组成部分方便后续用RowFilter进行模糊查询,HBase对rowkey的操作要比对column的操作性能要好上很多。
分页功能可用PageFilter实现。

2、实现

这里我实现了一个通用方法。

    /**
     * 获取指定Rowkey正则的资讯列表(分页)
     *
     * @param pageSize   页大小
     * @param lastRowKey 上一页最后的rowkey
     * @param rowkeyReg  Rowkey正则
     * @return 资讯列表
     */
    public List<GeneralNewsInfo> getData(int pageSize, String lastRowKey, String rowkeyReg) {
        List<GeneralNewsInfo> dataList = new ArrayList<>();
        getConnection();
        try {
            // 二级索引表查询索引数据
            Table table = conn.getTable(TableName.valueOf(RcmdHbaseConstants.RCMD_NEWS_TITLE_INDEX));
            Scan scan = new Scan();
            // 构建模糊查询的Filter和分页的Filter
            FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
            if (rowkeyReg != null) {
                RegexStringComparator regex = new RegexStringComparator(rowkeyReg);
                Filter filter = new RowFilter(CompareFilter.CompareOp.EQUAL, regex);
                filterList.addFilter(filter);
            }
            Filter pageFilter = new PageFilter(pageSize);
            Filter rowFilter = new RowFilter(CompareFilter.CompareOp.GREATER,
                    new BinaryComparator(Bytes.toBytes(lastRowKey)));
            filterList.addFilter(pageFilter);
            filterList.addFilter(rowFilter);
            scan.setFilter(filterList);
            ResultScanner rs = table.getScanner(scan);
            Result result;
            int rowNum = 0;
            while ((result = rs.next()) != null) {
                if (rowNum >= pageSize) {
                    break;
                }
                List<Cell> listCells = result.listCells();
                for (Cell cell : listCells) {
                    String rowkey = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                    // 索引拿到rowkey 去元数据表中获取元数据
                    Get get = new Get(Bytes.toBytes(rowkey));
                    GeneralNewsInfo generalNewsInfo = getDataByRowKey(RcmdHbaseConstants.RCMD_NEWS_REPOSITORY, get);
                    if (!StringUtils.isEmpty(generalNewsInfo.getRowkey())) {
                        dataList.add(generalNewsInfo);
                        rowNum++;
                    } else {
                        LOGGER.error("##### rowkey not found {}", rowkey);
                    }
                }

            }
            table.close();
        } catch (IOException e) {
            LOGGER.error("##### Hbase获取资讯列表失败");
            e.printStackTrace();
        }
        return dataList;
    }
    /**
     *  根据rowkey获取hbase元数据
     *  @param tableName 表名
     *  @param get HbaseGet
     */
    public GeneralNewsInfo getDataByRowKey(String tableName, Get get) {
        GeneralNewsInfo generalNewsInfo = new GeneralNewsInfo();
        try {
            getConnection();
            Table table = conn.getTable(TableName.valueOf(tableName));
            Result result = table.get(get);
            Cell[] cells = result.rawCells();
            String rowkey = "";
            if (cells.length >= 1) {
                for (Cell cell : cells) {
                    rowkey = Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
                    String column = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
                    String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                    setGeneralNewsInfo(generalNewsInfo, column, value);
                }
            }
            generalNewsInfo.setRowkey(rowkey);
            table.close();
        } catch (Exception e) {
            LOGGER.error("####获取hbase数据出错:" + e);
        }
        return generalNewsInfo;
    }

对于 rowkeyReg 的生成,要根据实际情况来确定,我这边需要根据上述设计好的二级索引rowkey规则来构建 rowkeyReg ,代码如下:

   /**
     * 获取Hbase 查询Reg
     *
     * @param timeStart 开始时间
     * @param timeEnd   结束时间
     * @param titleStr  标题关键词
     * @return 正则
     */
    private String getTitleRegex(String timeStart, String timeEnd, String titleStr) {
        titleStr = titleStr.replace("*", "\\*").replace("(", "\\(").replace(")", "\\)")
                .replace("}", "\\}").replace("{", "\\{").replace("|", "\\|");
        if (StringUtils.isEmpty(timeStart) && StringUtils.isEmpty(timeEnd) && StringUtils.isEmpty(titleStr)) {
            return null;
        }
        String regex = "";
        if (!StringUtils.isEmpty(timeStart) && !StringUtils.isEmpty(timeEnd)) {
            regex += "(";
            LocalDate ss = LocalDate.parse(timeStart, DateTimeFormatter.ofPattern("yyyyMMdd"));
            LocalDate ee = LocalDate.parse(timeEnd, DateTimeFormatter.ofPattern("yyyyMMdd"));
            LocalDateTime startTime = LocalDateTime.of(ss.getYear(), ss.getMonth(), ss.getDayOfMonth(), 0, 0, 0);
            LocalDateTime endTime = LocalDateTime.of(ee.getYear(), ee.getMonth(), ee.getDayOfMonth(), 0, 0, 0);
            while (startTime.isBefore(endTime) || startTime.isEqual(endTime)) {
                String ymd = startTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
                regex += ymd + "|";
                startTime = startTime.plusDays(1);
            }
            regex = regex.substring(0, regex.lastIndexOf("|"));
            regex += ")";
        }
        if (StringUtils.isEmpty(regex)) {
            regex += "[0-9]{4}";
        }
        if (StringUtils.isEmpty(titleStr)) {
            regex += ".*";
        } else {
            regex += ".*" + titleStr + ".*";
        }
        return "\\S{" + (paramConfig.getIdFixNum() + 2) + "}" + regex;
    }

根据此方法获取rowkey正则,pageSize 和 lastRowKey 根据实际情况进行传入,就可以实现模糊分页查询啦!如图所示功能已经可以使用(涉及公司隐私做了稍许处理哈^_^)。
这里写图片描述
直接一页一页按顺序查询不跳转,速度还是很快的。实测600万多条记录,每次查询在100ms以内。
这里写图片描述

真正实现所有功能。还需要对应用端对查询进行二次封装,对每页的 lastRowKey进行缓存,前端分页控件最好自己实现逻辑,要禁止大范围的跳转,不然后台递归获取 lastRowKey 性能会很差。
对于性能有要求的,可以用异步方法去维护一个从当前页数到后续一段范围内的页数对应的rowkey池。这样用户查询的时候就不用递归获取rowkey了。这些与本次主题无关就暂不赘述啦。

其实真正要实现多维度查询,资源够用的情况下用ES是好的选择。hbase只用来存元数据,es来构建索引存储索引数据和提供查询支持。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值