本文记录 hbase
安装
hadoop
d:\Dev\hadoop\hadoop-common-2.2.0-bin
Hbase
d:\Dev\hbase\hbase-1.1.6
hbase-site.xml
<configuration>
<property>
<name>hbase.rootdir</name>
<value>file:///d:\Dev\hbase\hbase-1.1.6\root</value>
</property>
<property>
<name>hbase.tmp.dir</name>
<value>d:\Dev\hbase\hbase-1.1.6\tmp</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>127.0.0.1</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>d:\Dev\hbase\hbase-1.1.6\zoo</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>false</value>
</property>
<property>
<name>hbase.master.dns.nameserver</name>
<value>127.0.0.1</value>
</property>
<property>
<name>hbase.regionserver.dns.nameserver</name>
<value>127.0.0.1</value>
</property>
</configuration>
运行
start-hbase.cmd
hive
hbase shell
http
http://hbase.local:16010/master-status
使用
- HBase连接耗时,频繁连接会导致读写效率差,改为hbase提供的api来代替spring的hbasetemplate,效果明显
- HBase查询第一次查询相对慢,为了提供更高效的服务,可在写入的时候,主动读取一次,HBase会自动存入缓存,后期的查询则会更高效
- HBase的rowkey热点问题,大部分的请求都在新订单上,导致了最后一个region server访问量超级高,所有请求都在这个分区上,为了能将rowkey打散,我们采取的方案是将rowkey的第一个字段订单id反转来分散请求
- HBase集群变更,切换新集群,内存从64升级到256ssd内存
- 迁移hbase以后,淘宝调度worker总是莫名其妙的将进程杀掉导致数据没有被执行,经过查看jvm以及看淘宝调度的源码以后发现了如下这个问题, 淘宝调度worker会启动多个线程来处理任务,启动线程后的run方法如下
public void run() { long startTime = 0; long sequence = 0; Object executeTask = null; while (true) { try { if (this.isStopSchedule == true) { // 停止队列调度 this.threadList.remove(Thread.currentThread()); if(this.threadList.size()==0){ this.scheduleManager.unRegisterScheduleServer(); } return; } // 加载调度任务 if (this.isMutilTask == false) { executeTask = this.getScheduleTaskId(); } else { executeTask = this.getScheduleTaskIdMulti(); } if (executeTask == null ) { this.loadScheduleData(); continue; } try { // 运行相关的程序 this.runningTaskList.add(executeTask); startTime = ScheduleUtil.getCurrentTimeMillis(); sequence = sequence + 1; if (this.isMutilTask == false) { if (((IScheduleTaskDealSingle<Object>) this.taskDealProcessor).execute(executeTask,scheduleManager.getTaskTypeRunningInfo().getOwnSign()) == true) { addSuccessNum(1, ScheduleUtil.getCurrentTimeMillis() - startTime, "com.taobao.pamirs.schedule.TBScheduleProcessorNotSleep.run"); } else { addFailNum(1, ScheduleUtil.getCurrentTimeMillis() - startTime, "com.taobao.pamirs.schedule.TBScheduleProcessorNotSleep.run"); } } else { if (((IScheduleTaskDealMulti<Object>) this.taskDealProcessor) .execute((Object[]) executeTask,scheduleManager.getTaskTypeRunningInfo().getOwnSign()) == true) { addSuccessNum(((Object[]) executeTask).length, ScheduleUtil .getCurrentTimeMillis() - startTime, "com.taobao.pamirs.schedule.TBScheduleProcessorNotSleep.run"); } else { addFailNum(((Object[]) executeTask).length, ScheduleUtil .getCurrentTimeMillis() - startTime, "com.taobao.pamirs.schedule.TBScheduleProcessorNotSleep.run"); } } } catch (Throwable ex) { if (this.isMutilTask == false) { addFailNum(1, ScheduleUtil.getCurrentTimeMillis() - startTime, "TBScheduleProcessor.run"); } else { addFailNum(((Object[]) executeTask).length, ScheduleUtil .getCurrentTimeMillis() - startTime, "TBScheduleProcessor.run"); } logger.error("Task :" + executeTask + " 处理失败", ex); } finally { this.runningTaskList.remove(executeTask); } } catch (Throwable e) { throw new RuntimeException(e); //log.error(e.getMessage(), e); } } }
从上面的代码中,我们可以看到最后的catch没有捕获异常,而是抛出了异常,这样就会导致线程中断,所以导致线程中断的方法肯定会在 这里面 经过查看,我们再调用方法getScheduleTaskIdMulti中调用了getScheduleTaskId方法,这个getScheduleTaskId调用了isDealing,这里面调用了我们重载的compare方法。(代码如下)
@SuppressWarnings("unchecked")
protected boolean isDealing(T aTask) {
if (this.maybeRepeatTaskList.size() == 0) {
return false;
}
T[] tmpList = (T[]) this.maybeRepeatTaskList.toArray();
for (int i = 0; i < tmpList.length; i++) {
if(this.taskComparator.compare(aTask, tmpList[i]) == 0){
this.maybeRepeatTaskList.remove(tmpList[i]);
return true;
}
}
return false;
}
/**
* 获取单个任务,注意lock是必须,
* 否则在maybeRepeatTaskList的数据处理上会出现冲突
* @return
*/
public T getScheduleTaskId() {
lockFetchID.lock();
try {
T result = null;
while (true) {
if (this.taskList.size() > 0) {
result = this.taskList.remove(0); // 按正序处理
} else {
return null;
}
if (this.isDealing(result) == false) {
return result;
}
}
} finally {
lockFetchID.unlock();
}
}
/**
* 获取单个任务,注意lock是必须,
* 否则在maybeRepeatTaskList的数据处理上会出现冲突
* @return
*/
@SuppressWarnings("unchecked")
public T[] getScheduleTaskIdMulti() {
lockFetchMutilID.lock();
try {
if (this.taskList.size() == 0) {
return null;
}
int size = taskList.size() > taskTypeInfo.getExecuteNumber() ? taskTypeInfo
.getExecuteNumber() : taskList.size();
List<T> result = new ArrayList<T>();
int point = 0;
T tmpObject = null;
while (point < size
&& ((tmpObject = this.getScheduleTaskId()) != null)) {
result.add(tmpObject);
point = point + 1;
}
if (result.size() == 0) {
return null;
} else {
return (T[]) result.toArray();
}
} finally {
lockFetchMutilID.unlock();
}
}
最后,我们查看了下我们重载的compare方法,发现如果compara方法总有异常就会出现错误 问题就是如果o1.getId()为空就会抛出异常,就会出现线程中断的情况。最后我们修改了getComparator方法,问题解决。
HBase Shell常用的HBase Shell命令
名称 | 命令表达式 |
---|---|
创建表 | create ‘表名称’, ‘列名称1’,’列名称2’,’列名称N’ |
添加记录 | put ‘表名称’, ‘行名称’, ‘列名称:’, ‘值’ |
查看记录 | get ‘表名称’, ‘行名称’ |
查看表中的记录总数 | count ‘表名称’ |
删除记录 | delete ‘表名’ ,’行名称’ , ‘列名称’ |
删除一张表
先要屏蔽该表,才能对该表进行删除,第一步 disable ‘表名称’ 第二步 drop ‘表名称’
查看所有记录
scan "表名称"
查看某个表某个列中所有数据
scan "表名称" , ['列名称:']
更新记录
就是重写一遍进行覆盖
一、一般操作
1.查询服务器状态
hbase(main):024:0>status
3 servers, 0 dead,1.0000 average load
2.查询hive版本
hbase(main):025:0>version
0.90.4, r1150278,Sun Jul 24 15:53:29 PDT 2011
二、DDL操作
1.创建一个表
hbase(main):011:0>create 'member','member_id','address','info'
0 row(s) in 1.2210seconds
2.获得表的描述
hbase(main):012:0>list
TABLE
member
1 row(s) in 0.0160seconds
hbase(main):006:0>describe 'member'
DESCRIPTION ENABLED
{NAME => 'member', FAMILIES => [{NAME=> 'address', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', true
VERSIONS => '3', COMPRESSION => 'NONE',TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'fa
lse', BLOCKCACHE => 'true'}, {NAME =>'info', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSI
ONS => '3', COMPRESSION => 'NONE', TTL=> '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false',
BLOCKCACHE => 'true'}]}
1 row(s) in 0.0230seconds
3.删除一个列族,alter,disable,enable 我们之前建了3个列族,但是发现member_id这个列族是多余的,因为他就是主键,所以我们要将其删除。
hbase(main):003:0>alter 'member',{NAME=>'member_id',METHOD=>'delete'}
ERROR: Table memberis enabled. Disable it first before altering.
报错,删除列族的时候必须先将表给disable掉。
hbase(main):004:0>disable 'member'
0 row(s) in 2.0390seconds
hbase(main):005:0>alter'member',NAME=>'member_id',METHOD=>'delete'
0 row(s) in 0.0560seconds
hbase(main):006:0>describe 'member'
DESCRIPTION ENABLED
{NAME => 'member', FAMILIES => [{NAME=> 'address', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0',false
VERSIONS => '3', COMPRESSION => 'NONE',TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'fa
lse', BLOCKCACHE => 'true'}, {NAME =>'info', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSI
ONS => '3', COMPRESSION => 'NONE', TTL=> '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false',
BLOCKCACHE => 'true'}]}
1 row(s) in 0.0230seconds
该列族已经删除,我们继续将表enable
hbase(main):008:0> enable 'member'
0 row(s) in 2.0420seconds
4.列出所有的表
hbase(main):028:0>list
TABLE
member
temp_table
2 row(s) in 0.0150seconds
5.drop一个表
hbase(main):029:0>disable 'temp_table'
0 row(s) in 2.0590seconds
hbase(main):030:0>drop 'temp_table'
0 row(s) in 1.1070seconds
6.查询表是否存在
hbase(main):021:0>exists 'member'
Table member doesexist
0 row(s) in 0.1610seconds
7.判断表是否enable
hbase(main):034:0>is_enabled 'member'
true
0 row(s) in 0.0110seconds
8.判断表是否disable
hbase(main):032:0>is_disabled 'member'
false
0 row(s) in 0.0110seconds
三、DML操作
1.插入几条记录
put'member','scutshuxue','info:age','24'
put'member','scutshuxue','info:birthday','1987-06-17'
put'member','scutshuxue','info:company','alibaba'
put'member','scutshuxue','address:contry','china'
put'member','scutshuxue','address:province','zhejiang'
put'member','scutshuxue','address:city','hangzhou'
put'member','xiaofeng','info:birthday','1987-4-17'
put'member','xiaofeng','info:favorite','movie'
put'member','xiaofeng','info:company','alibaba'
put'member','xiaofeng','address:contry','china'
put'member','xiaofeng','address:province','guangdong'
put'member','xiaofeng','address:city','jieyang'
put'member','xiaofeng','address:town','xianqiao'
2.获取一条数据
获取一个id的所有数据
hbase(main):001:0>get 'member','scutshuxue'
COLUMN CELL
address:city timestamp=1321586240244, value=hangzhou
address:contry timestamp=1321586239126, value=china
address:province timestamp=1321586239197, value=zhejiang
info:age timestamp=1321586238965, value=24
info:birthday timestamp=1321586239015, value=1987-06-17
info:company timestamp=1321586239071, value=alibaba
6 row(s) in 0.4720seconds
获取一个id,一个列族的所有数据
hbase(main):002:0>get 'member','scutshuxue','info'
COLUMN CELL
info:age timestamp=1321586238965, value=24
info:birthday timestamp=1321586239015, value=1987-06-17
info:company timestamp=1321586239071, value=alibaba
3 row(s) in 0.0210seconds
获取一个id,一个列族中一个列的所有数据
hbase(main):002:0>get 'member','scutshuxue','info:age'
COLUMN CELL
info:age timestamp=1321586238965, value=24
1 row(s) in 0.0320seconds
6.更新一条记录
将scutshuxue的年龄改成99
hbase(main):004:0>put 'member','scutshuxue','info:age' ,'99'
0 row(s) in 0.0210seconds
hbase(main):005:0>get 'member','scutshuxue','info:age'
COLUMN CELL
info:age timestamp=1321586571843, value=99
1 row(s) in 0.0180seconds
3.通过timestamp来获取两个版本的数据
hbase(main):010:0>get 'member','scutshuxue',{COLUMN=>'info:age',TIMESTAMP=>1321586238965}
COLUMN CELL
info:age timestamp=1321586238965, value=24
1 row(s) in 0.0140seconds
hbase(main):011:0>get 'member','scutshuxue',{COLUMN=>'info:age',TIMESTAMP=>1321586571843}
COLUMN CELL
info:age timestamp=1321586571843, value=99
1 row(s) in 0.0180seconds
4.全表扫描:
hbase(main):013:0>scan 'member'
ROW COLUMN+CELL
scutshuxue column=address:city, timestamp=1321586240244, value=hangzhou
scutshuxue column=address:contry, timestamp=1321586239126, value=china
scutshuxue column=address:province, timestamp=1321586239197, value=zhejiang
scutshuxue column=info:age,timestamp=1321586571843, value=99
scutshuxue column=info:birthday, timestamp=1321586239015, value=1987-06-17
scutshuxue column=info:company, timestamp=1321586239071, value=alibaba
temp column=info:age, timestamp=1321589609775, value=59
xiaofeng column=address:city, timestamp=1321586248400, value=jieyang
xiaofeng column=address:contry, timestamp=1321586248316, value=china
xiaofeng column=address:province, timestamp=1321586248355, value=guangdong
xiaofeng column=address:town, timestamp=1321586249564, value=xianqiao
xiaofeng column=info:birthday, timestamp=1321586248202, value=1987-4-17
xiaofeng column=info:company, timestamp=1321586248277, value=alibaba
xiaofeng column=info:favorite, timestamp=1321586248241, value=movie
3 row(s) in 0.0570seconds
5.删除id为temp的值的‘info:age’字段
hbase(main):016:0>delete 'member','temp','info:age'
0 row(s) in 0.0150seconds
hbase(main):018:0>get 'member','temp'
COLUMN CELL
0 row(s) in 0.0150seconds
6.删除整行
hbase(main):001:0>deleteall 'member','xiaofeng'
0 row(s) in 0.3990seconds
7.查询表中有多少行:
hbase(main):019:0>count 'member'
2 row(s) in 0.0160seconds
8.给‘xiaofeng’这个id增加’info:age’字段,并使用counter实现递增
hbase(main):057:0*incr 'member','xiaofeng','info:age'
COUNTER VALUE = 1
hbase(main):058:0>get 'member','xiaofeng','info:age'
COLUMN CELL
info:age timestamp=1321590997648, value=\x00\x00\x00\x00\x00\x00\x00\x01
1 row(s) in 0.0140seconds
hbase(main):059:0>incr 'member','xiaofeng','info:age'
COUNTER VALUE = 2
hbase(main):060:0>get 'member','xiaofeng','info:age'
COLUMN CELL
info:age timestamp=1321591025110, value=\x00\x00\x00\x00\x00\x00\x00\x02
1 row(s) in 0.0160seconds
获取当前count的值
hbase(main):069:0>get_counter 'member','xiaofeng','info:age'
COUNTER VALUE = 2
9.将整张表清空:
hbase(main):035:0>truncate 'member'
Truncating 'member'table (it may take a while):
- Disabling table...
- Dropping table...
- Creating table...
0 row(s) in 4.3430seconds
可以看出,Hbase是先将掉disable掉,然后drop掉后重建表来实现truncate的功能的。
Rowkey 设计原则
1. 写入的时候能分散到多台服务器上;
大量写入的时候,单台节点压力是很大的,因此将写入分散到多台机器上能有效提升 TPS,并且避免单节点宕机;
2. 读取的时候都尽量批量顺序访问;
读取的时候批量获取结果比单条结果的获取效率要高一到两个数量级以上
3. 尽量短;(按 byte 数组越短越优)
对于 HBase 的存储,Rowkey 是唯一标示数据的关键字,对于每一个 Value 都会重复存储,因此越短越好。从存储空间利用率讲,也应该是在业务可以接受的情况下,越短越好。
4. Rowkey 在 HBase 中只插入不更新(不做update操作)
在 HBase 中所有操作都是“流式”操作,不会更改原来存储过的数据,只会进行 append,并通过加上新的版本标识表示是最新数据。
HBase 使用场景
HBase 采用 LSM 架构,适合写多读少场景
1. HBase 适用场景
支持海量数据,需要 TB/PB 级别的在线服务(亿行以上) 拥有良好扩展性,数据量增长速度快,对水平扩展能力有需求 高性能读写,简单 kv 读写,响应延迟低 写入很频繁,吞吐量大 满足强一致性要求 批量读取数据需求 schema 灵活多变 无跨行跨表事务要求
2. HBase 的限制
只有主索引(Rowkey),如果需要二级索引的麻烦联系我们 不支持表联接、聚合、order by 等高级查询
3. 案例分析
A. 统一日志案例
场景:写多读少,写入量巨大(日写入>37 亿),读取随机且访问较少。
设计:将 Rowkey 用 UUID 类似方式生成随机字段,转换成 Byte 数组,经过优化,截取为 4 个 Byte。既 随机,又很短,能满足 40 年存储。通过 HBase 组针对性的预分区,将数据区域划分到所有节点上,写入均 衡到每个节点,因此压力平均。读取的时候,因为随机,设置了 BLOOMFILTER,所以随机读性能提高。因 为读取少,所以对于读取的优化就没有写那么重要了。
B. 一次导入,多次读取
场景:晚上导入大量数据,白天提供用户访问,每次查询是带有客户 ID 的随机数据,数据量可以估算,每 条数据小于100B。
设计:晚上导入大量数据,如果只有一台服务器提供服务,TPS 上不去,且容易导致节点宕机,因此需要写 入是均衡分布的。另外一方面,用户访问的时间和方式是比较随机的,数据又是随机的,因此将 Rowkey 随机分布有助于大量请求比较均衡在多个节点上。因为用户的访问更重要,因此将客户 ID 和相关业务类型 作为 Rowkey。如果写入数据是对用户顺序处理的,就会出现压力集中在某个节点上,导致宕机。于是将导 入 worker 设计为先在本地存储,然后分 10 个线程,将用户 Hash 成 10 组。每个线程读取一个用户的 300 条数据,批量写入 HBase,然后写下一个用户的 300 条数据,继续直到第 10 个用户,再返回写本组第一个 用户,直到所有数据写完。最终集群的表现比较平稳,且 TPS 很高。