Lily HBase Indexer同步HBase二级索引到Solr丢失数据的问题分析
二、分析步骤
2.1 查看日志
2.2 修改Solr的硬提交
2.3 寻求StackOverFlow帮助
2.4 修改了read-row="never"后,丢失部分字段
2.5 修改代码
2.6 重新打包分发
三、结果
四、思考
一、问题描述
部分业务需要使用HBase的数据进行多维度分析,我们采用了将部分数据同步到Solr,通过Solr进行多维度查询返回对应的Rowkey,再从HBase批量获取数据。因此我们使用了一个比较成熟的方案Lily HBase Indexer来同步二级索引到Solr。但是使用的时候出现了Solr丢失数据的问题。基本上每天Solr都会比HBase少几千条数据。
二、分析步骤
由于我们使用的是CDH集群,下面所有操作都是基于该环境
2.1 查看日志
到每个节点的/var/log/hbase-solr和/var/log/solr查看了日志,都没发现写入失败的记录
2.2 修改Solr的硬提交
由于日志没有发现错误,猜测是Solr的数据在缓存中没提交上去。
在solr的collecttion目录下的conf/solrconfig.xml文件,将Solr的硬提交激活,操作如下
<autoCommit><maxTime>${solr.autoCommit.maxTime:60000}</maxTime><openSearcher>true</openSearcher></autoCommit>
然后保存配置,将修改update 到Solr集群。然后测试仍旧出现上述问题
2.3 寻求StackOverFlow帮助
目前是没看到问题出在哪里了,因此只能去网上搜索一下具体原因了。网上有这么两个帖子
hbase-indexer solr numFound different from hbase table rows size
HBase Indexer导致Solr与HBase数据不一致问题解决
他们都提到了修改morphline-hbase-mapper.xml,添加read-row
如下:

重新刷新hbase-indexer配置
这次发现数目对了,但是字段缺了
2.4 修改了read-row="never"后,丢失部分字段
由于设置了read-row之后数据不会再次从HBase中获取,因此只会读取WAL。假如修改了部分字段,HBaseIndexer就会提交相应的字段上去。例如
HBase中有name和age两个字段
put 'HBase_Indexer_Test','001','cf1:name','xiaoming'put 'HBase_Indexer_Test','002','cf1:name','xiaohua'
此时的数据为

然后执行
put 'HBase_Indexer_Test','001','cf1:age','12'
最后只能看到
说明这种模式只从WAL获取数据,并且将获取的数据覆盖到了Solr里面。
那么这样看来只能修改HBase indexer的代码了
2.5 修改代码
Lily HBase Indexer的代码是托管在github 上的,如果是单独安装的请直接访问NGDATA的这个工程:http://ngdata.github.io/hbase-indexer/
如果是使用的CDH版本,请访问:https://github.com/cloudera/hbase-indexer
我这里使用CDH 5.7.0版本进行测试。在releases选项中可以找到对应版本号的包,下载解压之后可以看到一个Maven工程。可以看到它包含如下模块

在./hbase-indexer-engine/src/main/java/com/ngdata/hbaseindexer/indexer/Indexer.java文件中有一个calculateIndexUpdates方法,其中有如下代码:
Result result = rowData.toResult();if(conf.getRowReadMode()==RowReadMode.DYNAMIC){if(!mapper.containsRequiredData(result)){result = readRow(rowData);}}boolean rowDeleted = result.isEmpty();
privateResult readRow(RowData rowData)throwsIOException{TimerContext timerContext = rowReadTimer.time();try{HTableInterface table = tablePool.getTable(rowData.getTable());try{Getget= mapper.getGet(rowData.getRow());return table.get(get);}finally{table.close();}}finally{timerContext.stop();}}
从代码中可以看出其执行的流程图如下:

假如我们使用默认的Dynamic模式写入了大量的数据,那么意味着有部分数据会在WAL生成后一段时间内无法“落地”,那么就可能出现下面的情况:
- HBase RegionServer 将Put操作先写WAL (这个时候Put还没保存到Region);
- 异步处理的HBase Indexer获取到这个WAL日志,对数据进行处理,进入了我们上面说的这段条件逻辑代码,恰巧Result里面没有一部分Solr索引列,那么需要调用readRow方法从HBase重新读取数据,这个时候调用HTable.get(Get) 并没有获取到数据(Result.isEmpty()为真);
- HBase RegionServer把Put保存到Region ;
- 那么对于第二个步骤里面的HBase Indexer,那条记录将被当成delelet操作,所以在后面的逻辑将其当成solr delete document的操作。所以在Solr中才会出现部分数据丢失和数值不对。
知道了问题在哪里之后,我们尝试修改他的源码。由于HBase将预写日志的内容写到HBase region中会有一定的滞后性,因此我们可以认为预写日志中的内容总是最新的数据。假设我们有一条rowkey =001的数据如下:
| 列名 | 值 |
|---|---|
| Rowkey | 001 |
| cf1:A | a |
| cf1:B | b |
| cf1:C | c |
我们将C的值改成D。由于夹杂在很多条数据中,可能日志中拿到了C = 'd',但是HBase中仍旧是'c',我们需要将HBase的数据拿出来,再将预写日志中的数据覆盖它,便有了下面的代码
privateResult readRow(RowData rowData)throwsIOException{TimerContext timerContext = rowReadTimer.time();try{HTableInterface table = tablePool.getTable(rowData.getTable());try{Get get = mapper.getGet(rowData.getRow());return merge(table.get(get), rowData.toResult());//return table.get(get);}finally{table.close();}}finally{timerContext.stop();}}privateResult merge(Result data,Result wal)throwsIOException{//如果data为空,则直接返回WAL的数据if(data.isEmpty()){return wal;}/* //如果rowkey不相同,则返回wal的数据if (!Bytes.toString(data.getRow()).equals(Bytes.toString(wal.getRow()))) {return wal;}*/TreeMap<String,Cell> cellMap =newTreeMap<String,Cell>();CellScanner dataScanner = data.cellScanner();CellScanner walScanner = wal.cellScanner();while(dataScanner.advance()){Cell cell = dataScanner.current();String cf =Bytes.toString(CellUtil.cloneFamily(cell));String cq =Bytes.toString(CellUtil.cloneQualifier(cell));String key = cf +"->"+ cq;cellMap.put(key, cell);}while(walScanner.advance()){Cell cell = walScanner.current();String cf =Bytes.toString(CellUtil.cloneFamily(cell));String cq =Bytes.toString(CellUtil.cloneQualifier(cell));String key = cf +"->"+ cq;cellMap.put(key, cell);}ArrayList<Cell> cells =newArrayList<Cell>();cells.addAll(cellMap.values());returnResult.create(cells);}
值得一提的是,HBase返回的result中,列的排序是按照"列族名+列名"的字典排序。比如表中有["cf1:name","cf2:cellphone","cf1:age"] 三个列,那么返回的时候会排列成["cf1:age","cf1:name","cf2:cellphone"]。在创建新的Result对象的时候也必须遵循这样的规则,因此这里使用了treemap。不要问我为什么,我特么调了一整天才发现这个问题。
2.6 重新打包分发
进入hbase-indexer-engine的工程,执行mvn clean install -DskipTests进行打包,稍等片刻便好了

在target下面有一个hbase-indexer-engine-1.5-cdh5.7.0.jar文件(这里的版本号对应自己的环境),将这个jar文件分发到集群的hbase-indexer的目录下,CDH版本放在在/opt/cloudera/parcels/CDH/jars/下即可。
然后重启服务进行测试。
三、结果
数据跑了一天,Solr中对应的条数和HBase的一样。
因此我们修改的代码是有效的。
四、思考
上面我们是合并了数据然后全部覆盖到Solr的,如果HBase存在大量的Update操作,那么势必每次列数都会和映射到Solr里面的列不一致,因此每次都会从HBase中get一次数据,这样肯定会影响性能。那么我们能否使用ReadRow.Never模式 + Solr的原子更新的方式来实现呢?
Lily HBase Indexer同步HBase二级索引到Solr丢失数据的问题分析的更多相关文章
- HBase协处理器同步二级索引到Solr(续)
一. 已知的问题和不足二.解决思路三.代码3.1 读取config文件内容3.2 封装SolrServer的获取方式3.3 编写提交数据到Solr的代码3.4 拦截HBase的Put和Delete操作 ...
- phoenix连接hbase数据库,创建二级索引报错:Error: org.apache.phoenix.exception.PhoenixIOException: Failed after attempts=36, exceptions: Tue Mar 06 10:32:02 CST 2018, null, java.net.SocketTimeoutException: callTimeou
v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...
- HBase协处理器同步二级索引到Solr
一. 背景二. 什么是HBase的协处理器三. HBase协处理器同步数据到Solr四. 添加协处理器五. 测试六. 协处理器动态加载 一. 背景 在实际生产中,HBase往往不能满足多维度分析,我们 ...
- solr6.3 + Hbase Indexer使用MR创建索引,错误Bad return type
使用solr6.3 + Hbase Indexer ,通过Hbase-indexer从Hbase建立索引到solr中,进行全文搜索. 两种实现方式:① 开启hbase-indexer进行实时同步新数据 ...
- CDH版本Hbase二级索引方案Solr key value index
概述 在Hbase中,表的RowKey 按照字典排序, Region按照RowKey设置split point进行shard,通过这种方式实现的全局.分布式索引. 成为了其成功的最大的砝码. 然而单一 ...
- Phoneix(三)HBase集成Phoenix创建二级索引
一.Hbase集成Phoneix 1.下载 在官网http://www.apache.org/dyn/closer.lua/phoenix/中选择提供的镜像站点中下载与安装的HBase版本对应的版本. ...
- hbases索引技术:Lily HBase Indexer介绍
Lily HBase Indexer 为hbase提供快速查询,他允许不写代码,快速容易的把hbase行索引到solr.Lily HBase Indexer drives HBase indexing ...
- HBase 二级索引与Coprocessor协处理器
Coprocessor简介 (1)实现目的 HBase无法轻易建立“二级索引”: 执行求和.计数.排序等操作比较困难,必须通过MapReduce/Spark实现,对于简单的统计或聚合计算时,可能会因为 ...
- HBase的二级索引
使用HBase存储中国好声音数据的案例,业务描述如下: 为了能高效的查询到我们需要的数据,我们在RowKey的设计上下了不少功夫,因为过滤RowKey或者根据RowKey查询数据的效率是最高的,我们的 ...
随机推荐
- zoj2112 主席树动态第k大 ( 参考资料链接)
参考链接: http://blog.csdn.net/acm_cxlove/article/details/8565309 http://www.cnblogs.com/Rlemon/archive/ ...
- JSF框架整理
JSP体系结构: JSF主要优势之一就是它既是Java web 应用程序的用户界面标准又是严格遵循 模型-视图-控制器(MVC)设计模式的框架. 用户界面代码(视图)和应用程序数据和逻辑(模型)的清晰 ...
- PAT天梯赛练习题——L3-005. 垃圾箱分布(暴力SPFA)
L3-005. 垃圾箱分布 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 大家倒垃圾的时候,都希望垃圾箱距离自己比较近,但是谁 ...
- mysql5.7.23版本安装教程
亲身实践安装mysql,用时居然花费了三个小时,在有那么多教程的情况下,依然在不该花费时间的路上浪费了太多时间.希望这篇文章能够帮助大家少走弯路~~ 1.下载我下载的是64位. 2.解压下载之后,我选 ...
- gevent 使用踩坑
简单介绍 gevent 基本概念: 调度器: hub 上下文切换管理: switch 主循环: loop 协程: greenlet gevent 特性: ...
- 转载: 找不到MSVCR90.dll、Debug vs Release及cppLapack相关
今天调试程序时出现了,找不到MSCVR90.dll的错误,最好查找到了解决办法,原文链接如下: http://hi.baidu.com/wpzhao/blog/item/72dc08f77ce9b ...
- jenkins执行自动化用例(详细、有用、mark 优先级高高高)
http://blog.sina.com.cn/s/blog_68f262210102vx8o.html 第七章 测试用例接入jenkins自动运行 ------Web自动化测试之Webdriver+ ...
- Mysql 使用存储过程添加新字段
-- 1, 注意SQL 语句开始处不要空格 -- 2, 在使用 [--] 进行注释时,后面请加空格 USE `test`; -- lastUpdateTime drop procedure if ex ...
- 同时在windows和linux环境开发时换行符的处理
Git 的 core.autocrlf 參數默认为true,即每次 checkin 時,Git 會將純文字類型的檔案中的所有 CRLF 字元轉換為 LF,也就是版本庫中的換行符號一律存成 LF:在 c ...
- Netty构建游戏服务器(二)--Hello World
一,准备工作 1,netty-all-4.1.5.Final.jar(官网下载) 2,eclipse 二,步骤概要 1,服务器开发 (1),创建Server类 该类是程序的主入口,有main方法,服务 ...