HBase协处理器同步二级索引到Solr
一、 背景
在实际生产中,HBase往往不能满足多维度分析,我们能想到的办法就是通过创建HBase数据的二级索引来快速获取rowkey,从而得到想要的数据。目前比较流行的二级索引解决方案有Lily HBase Indexer,Phoenix自带的二级索引,华为Indexer,以及360的二级索引方案。上面的目前使用比较广泛的应该是Lily HBase Indexer,但是我们有时候只想实现一些简单的功能或者比较特殊的功能的时候,需要自己写协处理器进行处理。学习HBase的协处理器对于了解HBase架构是有帮助的。
二、 什么是HBase的协处理器
协处理器分两种类型,系统协处理器可以全局导入region server上的所有数据表,表协处理器即是用户可以指定一张表使用协处理器。
Hbase的coprocessor分为两类,Observer和EndPoint。其中Observer相当于触发器,EndPoint相当于存储过程。其中Observer的代码部署在服务端,相当于对API调用的代理。
另一个是终端(endpoint),动态的终端有点像存储过程。
Observer
观察者的设计意图是允许用户通过插入代码来重载协处理器框架的upcall方法,而具体的事件触发的callback方法由HBase的核心代码来执行。协处理器框架处理所有的callback调用细节,协处理器自身只需要插入添加或者改变的功能。以HBase0.92版本为例,它提供了三种观察者接口:
- RegionObserver:提供客户端的数据操纵事件钩子:Get、Put、Delete、Scan等。
- WALObserver:提供WAL相关操作钩子。
- MasterObserver:提供DDL-类型的操作钩子。如创建、删除、修改数据表等。
这些接口可以同时使用在同一个地方,按照不同优先级顺序执行.用户可以任意基于协处理器实现复杂的HBase功能层。HBase有很多种事件可以触发观察者方法,这些事件与方法从HBase0.92版本起,都会集成在HBase API中。不过这些API可能会由于各种原因有所改动,不同版本的接口改动比较大。
三、 HBase协处理器同步数据到Solr
实时更新数据需要获取到HBase的插入、更新和删除操作。由于HBase中的插入和更新都是对应RegionServer的Put操作,因此我们需要使用RegionObserver中的"postPut"和"postDelete函数"。至于Truncate操作则需要使用MasterObserver。
我们需要做的就是拦截put和delete操作,将里面的内容获取出来,写入Solr。 对应的协处理器代码如下:
package com.bqjr.bigdata.HBaseObserver.server;import com.bqjr.bigdata.HBaseObserver.entity.SolrServerManager;import org.apache.hadoop.hbase.CellUtil;import org.apache.hadoop.hbase.client.Delete;import org.apache.hadoop.hbase.client.Durability;import org.apache.hadoop.hbase.client.Put;import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;import org.apache.hadoop.hbase.coprocessor.ObserverContext;import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;import org.apache.hadoop.hbase.regionserver.wal.WALEdit;import org.apache.hadoop.hbase.util.Bytes;import org.apache.solr.client.solrj.SolrServerException;import org.apache.solr.common.SolrInputDocument;import java.io.IOException;/*** Created by hp on 2017-02-15. */public class HBaseIndexerToSolrObserver extends BaseRegionObserver{String[] columns = {"test_age","test_name"};String collection = "bqjr";SolrServerManager solrManager = new SolrServerManager(collection);@Overridepublic void postPut(ObserverContext<RegionCoprocessorEnvironment> e,Put put, WALEdit edit, Durability durability) throws IOException {String rowkey= Bytes.toString(put.getRow());SolrInputDocument doc = new SolrInputDocument();for(String column : columns){if(put.has(Bytes.toBytes("cf1"),Bytes.toBytes(column))){doc.addField(column,Bytes.toString(CellUtil.cloneValue(put.get(Bytes.toBytes("cf1"),Bytes.toBytes(column)).get(0))));}}try {solrManager.addDocToCache(doc);} catch (SolrServerException e1) {e1.printStackTrace();}}@Overridepublic void postDelete(ObserverContext<RegionCoprocessorEnvironment> e,Delete delete,WALEdit edit,Durability durability) throws IOException{String rowkey= Bytes.toString(delete.getRow());try {solrManager.delete(rowkey);} catch (SolrServerException e1) {e1.printStackTrace();}}}
大体的写入流程我们已经完成了,接下来就是Solr的写入实现了。由于Solr需要使用Zookeeper等信息,我们可以直接通过HBase的conf中获取Zookeeper相关信息来构造所需要的SolrCloudServer。
另一方面,我们不能来了一条数据就马上写入,这样非常消耗资源。因此我们需要做一个缓存,将这些Solr数据暂时保存在里面,定时 + 定量的发送。代码如下
package com.bqjr.bigdata.HBaseObserver.entity;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.hbase.HBaseConfiguration;import org.apache.solr.client.solrj.SolrServerException;import org.apache.solr.client.solrj.impl.CloudSolrServer;import org.apache.solr.client.solrj.response.UpdateResponse;import org.apache.solr.common.SolrInputDocument;import java.io.IOException;import java.util.*;/*** Created by hp on 2017-02-15. */public class SolrServerManager {public static String ZKHost = "";public static String ZKPort = "";int zkClientTimeout = 1800000;// 心跳int zkConnectTimeout = 1800000;// 连接时间CloudSolrServer solrServer;private static String defaultCollection;int maxCache = 10000;public static List<SolrInputDocument> cache = new LinkedList<SolrInputDocument>();private static int maxCommitTime = 60; //最大提交时间,spublic SolrServerManager(String collection) {defaultCollection = collection;Configuration conf = HBaseConfiguration.create();ZKHost = conf.get("hbase.zookeeper.quorum", "bqdpm1,bqdpm2,bqdps2");ZKPort = conf.get("hbase.zookeeper.property.clientPort", "2181");String SolrUrl = ZKHost + ":" + ZKPort + "/" + "solr";solrServer = new CloudSolrServer(SolrUrl);solrServer.setDefaultCollection(defaultCollection);solrServer.setZkClientTimeout(zkClientTimeout);solrServer.setZkConnectTimeout(zkConnectTimeout);//启动定时任务,第一次延迟10执行,之后每隔指定时间执行一次Timer timer = new Timer();timer.schedule(new CommitTimer(), 10 * 1000L, maxCommitTime * 1000L);}public UpdateResponse put(SolrInputDocument doc) throws IOException, SolrServerException {solrServer.add(doc);return solrServer.commit(false, false);}public UpdateResponse put(List<SolrInputDocument> docs) throws IOException, SolrServerException {solrServer.add(docs);return solrServer.commit(false, false);}public void addDocToCache(SolrInputDocument doc) throws IOException, SolrServerException {synchronized (cache) {cache.add(doc);if (cache.size() >= maxCache) {this.put(cache);cache.clear();}}}public UpdateResponse delete(String rowkey) throws IOException, SolrServerException {solrServer.deleteById(rowkey);return solrServer.commit(false, false);}/*** 提交定时器 */ static class CommitTimer extends TimerTask {@Overridepublic void run() {synchronized (cache) {try {new SolrServerManager(defaultCollection).put(cache);cache.clear();} catch (IOException e) {e.printStackTrace();} catch (SolrServerException e) {e.printStackTrace();}cache.clear();}}}}
四、 添加协处理器

#先禁用这张表disable 'HBASE_OBSERVER_TEST'#为这张表添加协处理器,设置的参数具体为: jar文件路径|类名|优先级(SYSTEM或者USER)alter 'HBASE_OBSERVER_TEST','coprocessor'=>'hdfs://bqdpm1:8020/ext_lib/HBaseObserver-1.0.0.jar|com.bqjr.bigdata.HBaseObserver.server.HBaseIndexerToSolrObserver||'#启用这张表enable 'HBASE_OBSERVER_TEST'#删除某个协处理器,"$<bumber>"后面跟的ID号与desc里面的ID号相同alter 'HBASE_OBSERVER_TEST',METHOD=>'table_att_unset',NAME => 'coprocessor$1'
五、 测试
尝试插入一条数据put 'HBASE_OBSERVER_TEST','001','cf1:test_age','18'
结果Solr中一条数据都没有

然后查看了regionserver的日志发现,没有找到SolrJ的类

然后我们将所有的依赖加到Jar包里面之后,再次运行。就可以看到数据了。

测试Delete功能

测试进行到这里就完了吗?当然不是
我们尝试再插入一条put 'HBASE_OBSERVER_TEST','001','cf1:test_name','Bob'
理论上我们需要在Solr中看到 test_age = 18,test_name = Bob。
但是在Solr中只有一条数据

于是我们需要使用到Solr的原子更新功能。将postPut改成下面这样的代码即可
public void postPut(ObserverContext<RegionCoprocessorEnvironment> e,Put put, WALEdit edit, Durability durability) throws IOException {String rowkey= Bytes.toString(put.getRow());Long version = 1L;SolrInputDocument doc = new SolrInputDocument();for(String column : columns){if(put.has(Bytes.toBytes("cf1"),Bytes.toBytes(column))){Cell cell = put.get(Bytes.toBytes("cf1"),Bytes.toBytes(column)).get(0);Map<String, String > operation = new HashMap<String,String>();operation.put("set",Bytes.toString(CellUtil.cloneValue(cell)));doc.setField(column,operation);}}doc.addField("id",rowkey);// doc.addField("_version_",version);try {solrManager.addDocToCache(doc);} catch (SolrServerException e1) {e1.printStackTrace();}}
再次插入数据
查看Solr结果

六、 协处理器动态加载
hbase的官方文档指出动态级别的协处理器,可以做到不重启hbase,更新协处理,做法就是
禁用表,卸载协处理器,重新指定协处理器, 激活表,即可,但实际测试发现
动态加载无效,是hbase的一个bug,看这个链接:
https://issues.apache.org/jira/browse/HBASE-8445
因为协处理器,已经被JVM加载,即使删除jar也不能重新load的jar,因为cache里面的hdfs的jar路径,没有变化,所以动态更新无效
,除非重启JVM,那样就意味着,需要重启RegionServer,
里面的小伙伴们指出了两种办法,使协处理器加载生效:
(1)滚动重启regionserver,避免停掉所有的节点
(2)改变协处理器的jar的类名字或者hdfs加载路径,以方便有新的ClassLoad去加载它
但总体来看,第2种方法,比较安全,第一种风险太大,一般情况下没有人会随便滚动重启线上的服务器的,这只在hbase升级的时候使用
HBase协处理器同步二级索引到Solr的更多相关文章
- HBase协处理器同步二级索引到Solr(续)
一. 已知的问题和不足二.解决思路三.代码3.1 读取config文件内容3.2 封装SolrServer的获取方式3.3 编写提交数据到Solr的代码3.4 拦截HBase的Put和Delete操作 ...
- Hbase(三) hbase协处理器与二级索引
一.协处理器—Coprocessor 1. 起源Hbase 作为列族数据库最经常被人诟病的特性包括:无法轻易建立“二级索引”,难以执 行求和.计数.排序等操作.比如,在旧版本的(<0.92)Hb ...
- HBase 协处理器实现二级索引
HBase在0.92之后引入了coprocessors,提供了一系列的钩子,让我们能够轻易实现访问控制和二级索引的特性.下面简单介绍下两种coprocessors,第一种是Observers,它实际类 ...
- Lily HBase Indexer同步HBase二级索引到Solr丢失数据的问题分析
一.问题描述二.分析步骤2.1 查看日志2.2 修改Solr的硬提交2.3 寻求StackOverFlow帮助2.4 修改了read-row="never"后,丢失部分字段2.5 ...
- 通过phoenix在hbase上创建二级索引,Secondary Indexing
环境描述: 操作系统版本:CentOS release 6.5 (Final) 内核版本:2.6.32-431.el6.x86_64 phoenix版本:phoenix-4.10.0 hbase版本: ...
- CDH版本Hbase二级索引方案Solr key value index
概述 在Hbase中,表的RowKey 按照字典排序, Region按照RowKey设置split point进行shard,通过这种方式实现的全局.分布式索引. 成为了其成功的最大的砝码. 然而单一 ...
- HBase Region级别二级索引
我们会经常谈及二级索引,这是对全表数据进行另外一种方式的组织存储,是针对table级别的.如果要为HBase上的表实现一个强一致性的二级索引,那么就无法逃避分布式事务,而这一直是用户最期待的功能. 而 ...
- hbase基于solr配置二级索引
一.概述 Hbase适用于大表的存储,通过单一的RowKey查询虽然能快速查询,但是对于复杂查询,尤其分页.查询总数等,实现方案浪费计算资源,所以可以针对hbase数据创建二级索引(Hbase Sec ...
- HBase的二级索引
使用HBase存储中国好声音数据的案例,业务描述如下: 为了能高效的查询到我们需要的数据,我们在RowKey的设计上下了不少功夫,因为过滤RowKey或者根据RowKey查询数据的效率是最高的,我们的 ...
随机推荐
- iOS学习小结(一)
1.给类目添加属性需要使用runtime关联 #import <Foundation/Foundation.h> @interface NSURLRequest (AIFNetworkin ...
- 洛谷P2351 [SDOi2012]吊灯 【数学】
题目 Alice家里有一盏很大的吊灯.所谓吊灯,就是由很多个灯泡组成.只有一个灯泡是挂在天花板上的,剩下的灯泡都是挂在其他的灯泡上的.也就是说,整个吊灯实际上类似于[b]一棵树[/b].其中编号为 1 ...
- SPOJ GSS1 Can you answer these queries I ——线段树
[题目分析] 线段树裸题. 注意update的操作,写结构体里好方便. 嗯,没了. [代码] #include <cstdio> #include <cstring> #inc ...
- P3147 [USACO16OPEN]262144 (贪心)
题目描述 给定一个1*n的地图,在里面玩2048,每次可以合并相邻两个(数值范围1-262,144),问最大能合出多少.注意合并后的数值并非加倍而是+1,例如2与2合并后的数值为3. 这道题的思路: ...
- spring aop在mvc的controller中加入切面无效
spring aop在mvc的controller中加入切面无效 因为MVC的controller,aop默认使用jdk代理.要使用cglib代理. 在spring-mybatis.xml配置文件中加 ...
- jenkins执行自动化用例(详细、有用、mark 优先级高高高)
http://blog.sina.com.cn/s/blog_68f262210102vx8o.html 第七章 测试用例接入jenkins自动运行 ------Web自动化测试之Webdriver+ ...
- SGU104 二维dp
大致题意: n个东西放在(1.2.3...m)个容器中,先放的必需在后方的左边.a[i][j]表示i号物品放在j容器所得 的价值,求最大价值. 几乎是刚刚开始接触动态规划题,开始我这样想 每个东西一件 ...
- python--输出自己需要的字符串连接的的方式
python中有很多字符串连接方式,今天在写代码,顺便总结一下,从最原始的字符串连接方式到字符串列表连接,大家感受下: 最原始的字符串连接方式:str1 + str2 python 新字符串连接语法: ...
- Neo4j 第七篇:模式(Pattern)
模式和模式匹配是Cypher的核心,使用模式来描述所需数据的形状,该模式使用属性图的结构来描述,通常使用小括号()表示节点,-->表示关系,-[]->表示关系和关系的类型,箭头表示关系的方 ...
- 【WEB基础】HTML & CSS 基础入门(5)边框与背景
前面(HTML图片) 漂亮的网页肯定少不了边框与背景的修饰,本篇笔记就是说明如何为网页上的元素设置边框或者背景(背景颜色和背景图片). 之前,先了解一下HTML中的图片元素,因为图片标签的使用非常简单 ...