用户可以针对每个列族进行设置
<property>
<name>hbase.rootdirname>
<value>file:///home/software/hbase/tmpvalue>
property>
启动完成之后可以通过jps命令查看是否有HMaster进程
1. 安装JDK
2. 安装Hadoop的伪分布式或者完全分布式集群
3. 上传或者下载HBASE的安装包
4. 解压安装包:tar -xvf hbase-0.98.17-hadoop2-bin.tar.gz
5. 进入HBASE安装目录下的子目录conf:cd hbase-0.98.17-hadoop2/conf
6. 修改conf/hbase-env.sh:vim hbase-env.sh
7. 添加JAVA_HOME:export JAVA_HOME=JDK的实际安装路径
8. 重新生效:source hbase-env.sh
9. 修改配置文件hbase-site.xml:vim hbase-site.xml
10. 配置使用hdfs:
hbase.rootdir
hdfs://hadoop01:9000/hbase
dfs.replication
1
11. 启动Hadoop。如果是使用的Hadoop完全分布式集群,则还需要启动Zookeeper
12. 进入HBASE的安装目录的子目录bin下:cd ../bin
13. 启动服务器端,执行sh start-hbase.sh
启动完成之后可以通过jps命令查看是否有HMaster进程
14. 启动客户端 ./hbase shell
1. 安装和配置:Hadoop+JDK+Zookeeper
2. 安装Hbase
3. 修改conf/hbase-env.sh
#修改JAVA_HOME:export JAVA_HOME=xxxx
#修改Zookeeper和Hbase的协调模式,hbase默认使用自带的zookeeper,如果需要使用外部zookeeper,需要先关闭:export HBASE_MANAGES_ZK=false
4. 修改hbase-site.xml,配置开启完全分布式模式
hbase.rootdir
hdfs://hadoop01:9000/hbase
hbase.cluster.distributed
true
hbase.zookeeper.quorum
hadoop01:2181,hadoop02:2181,hadoop03:2181
5. 配置region服务器,修改conf/regionservers文件,每个主机名独占一行,hbase启动或关闭时会按照该配置顺序启动或关闭主机中的hbase:
hadoop01
hadoop02
hadoop03
6. 将01节点配置好的hbase通过远程复制拷贝到其他节点上
7. 启动Zookeeper服务
8. 启动Hadoop
9. 启动Hbase
10. 查看各节点的java进程是否正确,或者通过浏览器访问http://xxxxx:60010来访问web界面,通过web见面管理hbase
11. 关闭Hmaster,进入到hbase安装目录下的bin目录,执行:stop-hbase.sh
12. 关闭regionserver,进入到hbase安装目录下的bin目录,执行:sh hbase-daemon.sh stop regionserver
指令 |
说明 |
示例 |
create |
创建表,t1指表名,c1,c2 列族名 |
create 'tab1','colfamily1','colfamily2' |
list |
查看一共有哪些表 |
list |
put |
t1指表名,r1指行键名,c1指列名,value指单元格值。ts1指时间戳,一般都省略掉了。注意,行键名在一张表里要全局唯一 |
put 'tab1','row-1','colfamily1:co11','aaa' put 'tab1','row-1','colfamily1:co12','bbb' put 'tab1','row-1','colfamily2:co11','ccc' put 'tab1','row-1','colfamily2:co12','ddd'
|
get |
根据表名和行键查询 |
get 'tab1','row-1' get 'tab1','row-1','colfamily1' get 'tab1','row-1','colfamily1','colfamily2' get 'tab1','row-1','colfamily1:co11'
|
scan |
扫描所有数据,也可以跟指定条件
|
scan 'tab1' #扫描整表数据,会查询出所有的行数据
scan 'tab1',{COLUMNS=>['colfamily1']} scan 'tab1',{COLUMNS=>['cf1:name']} scan 'tab1',{COLUMNS=>['cf1:name','cf2:salary']} scan 'tab1',{COLUMNS=>['colfamily1','colfamily2']}
scan 'tab1',{RAW=>true,VERSIONS=>3} 可以在查询时加上RAW=>true来开启对历史版本数据的查询,VERSIONS=>3指定查询最新的几个版本的数据
|
deleteall |
根据表名、行键删除整行数据 |
deleteall 'tab1','row-1' |
drop |
删除表,前提是先禁用表 |
drop 'tab1' |
disable |
禁用表 |
disable 'tab1' |
create 指令补充 |
建表时可以指定VERSIONS,配置的是当前列族在持久化到文件系统中时,要保留几个最新的版本数据,这并不影响内存中的历史数据版本 |
create 'tab1',{NAME=>'c1',VERSIONS=>3},{NAME=>'c2',VERSIONS=>3} |
exit |
推出shell客户端 |
|
enable |
启用表 |
enable 'tab1' |
创建表
// 创建表
@Test
public void create() throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取连接
HBaseAdmin admin = new HBaseAdmin(conf);
// 指定表名
HTableDescriptor table = new HTableDescriptor(TableName.valueOf("student"));
// 指定列族名
HColumnDescriptor basic = new HColumnDescriptor("basic");
HColumnDescriptor info = new HColumnDescriptor("info");
// 指定历史版本上限
basic.setMaxVersions(3);
info.setMaxVersions(5);
// 将列族添加到表中
table.addFamily(basic);
table.addFamily(info);
// 创建表
admin.createTable(table);
// 关闭连接
admin.close();
}
添加数据
/*
* 添加数据
*/
@Test
public void put() throws MasterNotRunningException, ZooKeeperConnectionException, IOException {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
// 添加行键
Put put = new Put("1".getBytes());
// 向指定的列族中添加列值
put.add("basic".getBytes(), "name".getBytes(), "Sam".getBytes());
put.add("info".getBytes(), "address".getBytes(), "info".getBytes());
// 将数据添加到table中
table.put(put);
// 关闭流
table.close();
}
百万条数据写入
/*
* 测试百万条数据添加
*/
@Test
public void putmillion() throws IOException {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
long begin = System.currentTimeMillis();
List list = new ArrayList<>();
for (int i = 1; i <= 100000; i++) {
Put put = new Put(("" + i).getBytes());
put.add("basic".getBytes(), "id".getBytes(), ("id-" + i).getBytes());
list.add(put);
if (i % 10000 == 0) {
table.put(list);
list = new ArrayList<>();
}
}
long end = System.currentTimeMillis();
System.out.println(end - begin);
table.close();
}
获取数据
/*
* 获取数据
*/
@Test
public void get() throws IOException {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
// 获取指定行键的数据
Get get = new Get("100".getBytes());
// 获取结果
Result r = table.get(get);
byte[] bs = r.getValue("basic".getBytes(), "id".getBytes());
System.out.println(new String(bs));
// 关流
table.close();
}
获取结果集
/*
* 获取结果集
*/
@Test
public void scan() throws IOException {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
// 获取迭代器
Scan s = new Scan("99950".getBytes());
ResultScanner rs = table.getScanner(s);
Iterator it = rs.iterator();
// 迭代遍历
while (it.hasNext()) {
Result result = (Result) it.next();
byte[] bs = result.getValue("basic".getBytes(), "id".getBytes());
System.out.println(new String(bs));
}
// 关流
table.close();
}
删除数据
/*
* 删除数据
*/
@Test
public void delete() throws IOException {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
// 进行删除
Delete del = new Delete("2".getBytes());
table.delete(del);
// 关流
table.close();
}
删除表
/*
* 删除表
*/
@Test
public void deleteTable() throws IOException{
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取管理权限
HBaseAdmin admin = new HBaseAdmin(conf);
// 禁用表
admin.disableTable("student");
// 删除表
admin.deleteTable("student");
// 关流
admin.close();
}
正则过滤器
/*
* 正则过滤器
*/
@Test
public void scanData() throws Exception {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
// 获取迭代器
Scan scan = new Scan();
// 正则过滤器,匹配行键含3的行数据
Filter filter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator(".*3.*"));
// 加入过滤器
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
// 获取结果的迭代器
Iterator it = scanner.iterator();
while (it.hasNext()) {
Result result = it.next();
// 通过result对象获取指定列族的列的数据
byte[] value = result.getValue("basic".getBytes(), "id".getBytes());
System.out.println(new String(value));
}
scanner.close();
table.close();
}
行键过滤器
@Test
public void rowKeyCompare() throws Exception {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
Scan scan = new Scan();
// 行键比较过滤器,下例是匹配小于或等于指定行键的行数据
Filter filter = new RowFilter(CompareOp.LESS_OR_EQUAL, new BinaryComparator("100".getBytes()));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
Iterator it = scanner.iterator();
while (it.hasNext()) {
Result result = it.next();
byte[] value = result.getValue("basic".getBytes(), "id".getBytes());
System.out.println(new String(value));
}
scanner.close();
table.close();
}
行键前缀过滤器
@Test
public void prefix() throws Exception {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
Scan scan = new Scan();
// 行键前缀过滤器
Filter filter = new PrefixFilter("3".getBytes());
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
Iterator it = scanner.iterator();
while (it.hasNext()) {
Result result = it.next();
byte[] value = result.getValue("basic".getBytes(), "id".getBytes());
System.out.println(new String(value));
}
scanner.close();
table.close();
}
列值过滤器
@Test
public void colScan() throws Exception {
// 获取配置
Configuration conf = HBaseConfiguration.create();
// 设置zookeeper的地址
conf.set("hbase.zookeeper.quorum", "192.168.232.129:2181,192.168.232.130:2181,192.168.232.131:2181");
// 获取表
HTable table = new HTable(conf, "student");
Scan scan = new Scan();
// --列值过滤器
Filter filter = new SingleColumnValueFilter("basic".getBytes(), "name".getBytes(), CompareOp.EQUAL,
"rose".getBytes());
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
// --获取结果的迭代器
Iterator it = scanner.iterator();
while (it.hasNext()) {
Result result = it.next();
byte[] name = result.getValue("basic".getBytes(), "name".getBytes());
byte[] age = result.getValue("basic".getBytes(), "age".getBytes());
System.out.println(new String(name) + ":" + new String(age));
}
scanner.close();
table.close();
}
一张Hbase表,可能有多个HRegion,每个HRegion达到一定大小(默认是10GB)时,进行分裂
分裂
另外,HMaster通过监听ZooKeeper中的Ephemeral节点(默认:/hbase/rs/*)来监控HRegionServer的加入和宕机。在第一个HMaster连接到ZooKeeper时会创建Ephemeral节点(默认:/hbasae/master)来表示Active的HMaster,其后加进来的HMaster则监听该Ephemeral节点,如果当前Active的HMaster宕机,则该节点消失,因而其他HMaster得到通知,而将自身转换成Active的HMaster,在变为Active的HMaster之前,它会创建在/hbase/back-masters/下创建自己的Ephemeral节点。
HBase的第一次读写
可是即使客户端有缓存,在初始阶段需要三次请求才能获取到用户自定义的Table真正所在的位置
开始是两个固定长度的数值,分别表示Key的长度和Value的长度
对HFileV2格式具体分析,它是一个多层的类B+树索引,采用这种设计,可以实现查找不需要读取整个文件:
Data Block中的Cell都是升序排列,每个block都有它自己的Leaf-Index,每个Block的最后一个Key被放入Intermediate-Index中,Root-Index指向Intermediate-Index。在HFile的末尾还有Bloom Filter(布隆过滤)用于快速定位那么没有在某个Data Block中的Row;TimeRange信息用于给那些使用时间查询的参考。在HFile打开时,这些索引信息都被加载并保存在内存中,以增加以后的读取性能。
这种简单的Hash Table存在一定的问题,就是Hash冲突的问题。假设 Hash 函数是良好的,如果位阵列长度为 m 个点,那么如果想将冲突率降低到例如 1%, 这个散列表就只能容纳 m * 1% 个元素。显然这就不叫空间有效了(Space-efficient)。
Bloom Filter概述
// minor compact
admin.compact("tab2".getBytes());
// major compact
admin.majorCompact("tab2".getBytes());
compact('tab2') # minor compact
major_compact('tab2') # major compact
原生HBase只支持从小到大的排序,但是现在有个需求想展现影片热度排行榜,这就要求实现从大到小排列,针对这种情况可以采用Rowkey=Integer.MAX_VALUE-Rowkey的方式将Rowkey进行转换,最大的变最小,最小的变最大,在应用层再转回来即可完成排序需求
最重要的是要保证散列,这样就会保证所有的数据都不是在一个Region上,从而避免读写的时候负载会集中在个别Region上。比如ROWKEY_Random
如果Rowkey太长,第一存储开销会增加,影响存储效率;第二内存中Rowkey字段过长,会导致内存的利用率降低,进而降低索引命中率
Rowkey是一个二进制码流,Rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节
原因如下:
虽然行键在HBase中是以byte[]字节数组的形式存储的,但是建议在系统开发过程中将其数据类型设置为String类型,保证通用性。
常用的行键字符串有以下几种:
RowKey的主要作用是为了进行数据记录的唯一性标示,但是唯一性并不是其全部,具有明确意义的行键对于应用开发、数据检索等都具有特殊意义,譬如数字字符串:9559820140512,其实际意义是这样:95598(电网客服电话)+20140512(日期)
行键往往由多个值组合而成,而各个值的位置顺序将影响到数据存储和检索效率,所以在设计行键时,需要对日后的业务应用开发有比较深入的了解和前瞻性预测,才能设计出可尽量高效率检索的行键
行键具有有序性的基础便是定长,譬如20140512080500、20140512083000,这两个日期时间形式的字符串是递增的,不管后面的秒数是多少,我们都将其设置为14位数字形式,如果我们把后面的0去除了,那么201405120805将大于20140512083,其有序性发生了变更。所以行键一定要设计成定长的
此外,目前操作系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操作系统的最佳特性
HBase对于内存的消耗是非常大的,主要是其LSM树状结构、缓存机制和日志记录机制决定的,所以物理内存当然是越大越好
在互联网领域,服务器内存方面的主流配置已经是64GB,所以一定要根据实际的需求和预算配备服务器内存。如果资源很紧张,推荐内存最小在32GB,如果再小会严重影响HBase集群性能
HBase给使用者的印象可能更偏向于“内存型”NoSQL数据库,从而忽略了CPU方面的需求,其实HBase在某些应用上对CPU的消耗非常大,例如频繁使用过滤器,因为在过滤器中包含很多匹配、搜索和过滤的操作;多条件组合扫描的场景也是CPU密集型的;压缩操作很频繁等。如果服务器CPU不够强悍,会导致整个集群的负载非常高,很多线程都在阻塞状态(非网络阻塞和死锁的情况)。
建议每台物理节点至少使用双路四核CPU(2×4),主流是2~8路,一般单颗CPU至少四核。对于CPU密集型的集群,当然是越多越好。
对于运行HBase相关进程JVM的垃圾回收器,不仅仅关注吞吐量,还关注停顿时间,而且两者之间停顿时间更为重要,因为HBase设计的初衷就是解决大规模数据集下实时访问的问题。那么按照首位是停顿时间短,从这个方面CMS和G1有着非常大的优势
而CMS作为JDK1.5已经出现的垃圾收集器,已经成熟应用在互联网等各个行业。所以,选用CMS作为老年代的垃圾回收器。与CMS搭配的新生代收集器有Serial和ParNew,而对比这两个收集器,明显ParNew具有更好的性能,所以新生代选用ParNew作为垃圾收集器。那么,最终选用的垃圾收集器搭配组合是CMS+ParNew。而且很多成熟应用已经验证了这种组合搭配的优势
与CMS收集器相关的几个重要参数的具体含义、默认值和相关说明详见表
配置方式:需要添加到hbase-env.sh文件中
export HBASE_OPTS="-XX:+UseConcMarkSweepGC" -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSCompactAtFullCollection
堆内存大小参数hbase-env.sh文件中设置:export HBASE_HEAPSIZE=16384,单位是MB,即默认是16GB。当然,这个值需要根据节点实际的物理内存来决定。一般不超过实际物理内存的1/2。服务器内存的分配,比如服务器内存64GB,为操作系统预留出8G~16GB。此外给Yarn留出8G~16GB,如果没有其他框架,把剩余的留给HBase
HBase调优
1. 调节数据块(data block)的大小
HFile数据块大小可以在列族层次设置。这个数据块不同于之前谈到的HDFS数据块,其默认值是65536字节,或64KB。数据块索引存储每个HFile数据块的起始键。数据块大小的设置影响数据块索引的大小。数据块越小,索引越大,从而占用更大内存空间。同时加载进内存的数据块越小,随机查找性能更好。但是,如果需要更好的序列扫描性能,那么一次能够加载更多HFile数据进入内存更为合理,这意味着应该将数据块设置为更大的值。相应地,索引变小,将在随机读性能上付出更多的代价
可以在表实例化时设置数据块大小:hbase(main):002:0> create 'mytable',{NAME => 'colfam1', BLOCKSIZE => '65536'}
2. 适当时机关闭数据块缓存
把数据放进读缓存,并不是一定能够提升性能。如果一个表或表的列族只被顺序化扫描访问或很少被访问,则Get或Scan操作花费时间长一点是可以接受的。在这种情况下,可以选择关闭列族的缓存
关闭缓存的原因在于:如果只是执行很多顺序化扫描,会多次使用缓存,并且可能会滥用缓存,从而把应该放进缓存获得性能提升的数据给排挤出去,所以如果关闭缓存,不仅可以避免上述情况发生,而且可以让出更多缓存给其他表和同一表的其他列族使用。数据块缓存默认是打开的
可以在新建表或更改表时关闭数据块缓存属性:hbase(main):002:0> create 'mytable', {NAME => 'colfam1', BLOCKCACHE => 'false'}
3. 开启布隆过滤器
布隆过滤器(Bloom Filter)允许对存储在每个数据块的数据做一个反向测验。当查询某行时,先检查布隆过滤器,看看该行是否不在这个数据块。布隆过滤器要么确定回答该行不在,要么回答不知道。因此称之为反向测验。布隆过滤器也可以应用到行内的单元格上,当访问某列标识符时先使用同样的反向测验
使用布隆过滤器也不是没有代价,相反,存储这个额外的索引层次占用额外的空间。布隆过滤器的占用空间大小随着它们的索引对象数据增长而增长,所以行级布隆过滤器比列标识符级布隆过滤器占用空间要少。当空间不是问题时,它们可以压榨整个系统的性能潜力
可以在列族上打开布隆过滤器: create 'mytable', {NAME => 'colfam1', BLOOMFILTER => 'ROWCOL'}
布隆过滤器参数的默认值是NONE。另外,还有两个值:ROW表示行级布隆过滤器;ROWCOL表示列标识符级布隆过滤器。行级布隆过滤器在数据块中检查特定行键是否不存在,列标识符级布隆过滤器检查行和列标识符联合体是否不存在。ROWCOL布隆过滤器的空间开销高于ROW布隆过滤器。
4. 开启数据压缩
HFile可以被压缩并存放在HDFS上,这有助于节省硬盘I/O,但是读写数据时压缩和解压缩会抬高CPU利用率。压缩是表定义的一部分,可以在建表或模式改变时设定。除非确定压缩不会提升系统的性能,否则推荐打开表的压缩。只有在数据不能被压缩,或者因为某些原因服务器的CPU利用率有限制要求的情况下,有可能需要关闭压缩特性
HBase可以使用多种压缩编码,包括LZO、SNAPPY和GZIP,LZO和SNAPPY是其中最流行的两种
当建表时可以在列族上打开压缩:create 'mytable', {NAME => 'colfam1', COMPRESSION => 'SNAPPY'}
注意,数据只在硬盘上是压缩的,在内存中(MemStore或BlockCache)或在网络传输时是没有压缩的
5. 设置Scan缓存
HBase的Scan查询中可以设置缓存,定义一次交互从服务器端传输到客户端的行数,设置方法是使用Scan类中setCaching()方法,这样能有效地减少服务器端和客户端的交互,更好地提升扫描查询的性能
HTable table = new HTable(config, Bytes.toBytes(tableName));
Scan scanner = new Scan();
/* batch and caching */
scanner.setBatch(0);
scanner.setCaching(10000);
ResultScanner rsScanner = table.getScanner(scanner);
for (Result res : rsScanner) {
final List list = res.list();
String rk = null;
StringBuilder sb = new StringBuilder();
for (final KeyValue kv : list) {
sb.append(Bytes.toStringBinary(kv.getValue()) + ",");
rk = getRealRowKey(kv);
}
if (sb.toString().length() > 0)
sb.setLength(sb.toString().length() - 1);
System.out.println(rk + "\t" + sb.toString());
}
rsScanner.close();
6. 显式地指定列
当使用Scan或Get来处理大量的行时,最好确定一下所需要的列。因为服务器端处理完的结果,需要通过网络传输到客户端,而且此时,传输的数据量成为瓶颈,如果能有效地过滤部分数据,使用更精确的需求,能够很大程度上减少网络I/O的花费,否则会造成很大的资源浪费。如果在查询中指定某列或者某几列,能够有效地减少网络传输量,在一定程度上提升查询性能。下面代码是使用Scan类中指定列的addColumn()方法
HTable table = new HTable(config, Bytes.toBytes(tableName));
Scan scanner = new Scan();
/* 指定列 */
scanner.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column));
ResultScanner rsScanner = table.getScanner(scanner);
for (Result res : rsScanner) {
final List list = res.list();
String rk = null;
StringBuilder sb = new StringBuilder();
for (final KeyValue kv : list) {
sb.append(Bytes.toStringBinary(kv.getValue()) + ",");
rk = getRealRowKey(kv);
}
if (sb.toString().length() > 0)
sb.setLength(sb.toString().length() - 1);
System.out.println(rk + "\t" + sb.toString());
}
rsScanner.close();
7. 关闭ResultScanner
ResultScanner类用于存储服务端扫描的最终结果,可以通过遍历该类获取查询结果。但是,如果不关闭该类,可能会出现服务端在一段时间内一直保存连接,资源无法释放,从而导致服务器端某些资源的不可用,还有可能引发RegionServer的其他问题。所以在使用完该类之后,需要执行关闭操作。这一点与JDBC操作MySQL类似,需要关闭连接。代码的最后一行rsScanner.close()就是执行关闭ResultScanner。
8. 使用批量读
通过调用HTable.get(Get)方法可以根据一个指定的行键获取HBase表中的一行记录。同样HBase提供了另一个方法,通过调用HTable.get(List)方法可以根据一个指定的行键列表,批量获取多行记录。使用该方法可以在服务器端执行完批量查询后返回结果,降低网络传输的速度,节省网络I/O开销,对于数据实时性要求高且网络传输RTT高的场景,能带来明显的性能提升。
9. 使用批量写
通过调用HTable.put(Put)方法可以将一个指定的行键记录写入HBase,同样HBase提供了另一个方法,通过调用HTable.put(List)方法可以将指定的多个行键批量写入。这样做的好处是批量执行,减少网络I/O开销。
10. 关闭写WAL日志
在默认情况下,为了保证系统的高可用性,写WAL日志是开启状态。写WAL开启或者关闭,在一定程度上确实会对系统性能产生很大影响,根据HBase内部设计,WAL是规避数据丢失风险的一种补偿机制,如果应用可以容忍一定的数据丢失的风险,可以尝试在更新数据时,关闭写WAL。该方法存在的风险是,当RegionServer宕机时,可能写入的数据会出现丢失的情况,且无法恢复。关闭写WAL操作通过Put类中的writeToWAL()设置。可以通过在代码中添加:put.setWriteToWAL(false);
11. 设置AutoFlush
HTable有一个属性是AutoFlush,该属性用于支持客户端的批量更新。该属性默认值是true,即客户端每收到一条数据,立刻发送到服务端。如果将该属性设置为false,当客户端提交Put请求时,将该请求在客户端缓存,直到数据达到某个阈值的容量时(该容量由参数hbase.client.write.buffer决定)或执行hbase.flushcommits()时,才向RegionServer提交请求。这种方式避免了每次跟服务端交互,采用批量提交的方式,所以更高效。
但是,如果还没有达到该缓存而客户端崩溃,该部分数据将由于未发送到RegionServer而丢失。这对于有些零容忍的在线服务是不可接受的。所以,设置该参数的时候要慎重。
可以在代码中添加:table.setAutoFlush(false);table.setWriteBufferSize(12*1024*1024);
12. 预创建Region
在HBase中创建表时,该表开始只有一个Region,插入该表的所有数据会保存在该Region中。随着数据量不断增加,当该Region大小达到一定阈值时,就会发生分裂(Region Splitting)操作。并且在这个表创建后相当长的一段时间内,针对该表的所有写操作总是集中在某一台或者少数几台机器上,这不仅仅造成局部磁盘和网络资源紧张,同时也是对整个集群资源的浪费。这个问题在初始化表,即批量导入原始数据的时候,特别明显。为了解决这个问题,可以使用预创建Region的方法
Hbase内部提供了RegionSplitter工具:${HBASE_HOME}/bin/hbase org.apache.hadoop.hbase.util.RegionSplitter test2 HexStringSplit -c 10 -f cf1
其中,test2是表名,HexStringSplit表示划分的算法,参数-c 10表示预创建10个Region,-f cf1表示创建一个名字为cf1的列族。
13. 调整ZooKeeper Session的有效时长
参数zookeeper.session.timeout用于定义连接ZooKeeper的Session的有效时长,这个默认值是180秒。这意味着一旦某个RegionServer宕机,HMaster至少需要180秒才能察觉到宕机,然后开始恢复。或者客户端读写过程中,如果服务端不能提供服务,客户端直到180秒后才能觉察到。在某些场景中,这样的时长可能对生产线业务来讲不能容忍,需要调整这个值
此参数在HBase-site.xml中,通过
插入100亿条数据,每条数据大约100kb,比如每次更新1%的数据,如果用B-tree,用时100天,如果用LSM-TREE,用时1天。
MemStore
MemStore是HBase中C0的实现,向HBase中写数据的时候,首先会写到内存中的MemStore,当达到一定阀值之后,flush(顺序写)到磁盘,形成新的StoreFile(HFile),最后多个StoreFile(HFile)又会进行Compact。
memstore内部维护了一个数据结构:ConcurrentSkipListMap,数据存储是按照RowKey排好序的跳跃列表。跳跃列表的算法有同平衡树一样的渐进的预期时间边界,并且更简单、更快速和使用更少的空间。
HFile:HFlile是lsm tree中C1的实现
1. 上传/下载Phoenix安装包到linux服务器并解压,这台linux服务器最好是Hbase Master节点。所以,如果是Hbase集群,则不需要在全部的服务节点上来安装Phoenix,只需要在HMaster节点上安装即可
2. 将Phoneix安装目录下的两个jar包,拷贝到Hbase安装目录下的lib目录
cp phoenix-4.8.1-HBase-0.98-server.jar /home/software/hbase/lib
cp phoenix-4.8.1-HBase-0.98-client.jar /home/software/hbase/lib
3. 在 etc/profile文件中配置Hbase的目录路径
export HBASE_HOME=/home/software/hbase
export PATH=$PATH:$HBASE_HOME/bin
4. 重新生效,source /etc/profile
5. 启动Hadoop、ZK、HBase集群
6. 进入Phoenix安装目录的bin目录
7. 执行:./sqlline.py hadoop01,hadoop02,hadoop03:2181
如上图所以,证明Phoenix安装成功
此外,此时进入hbase,执行list查看,会发现多出如下的表:
8. 如果需要kill掉Phoenix进程,则执行: pstree -p
查看 py进程,杀掉Sqlline的父进程
1. 创建表:
create table tab1(id integer primary key,name varchar);
注:
①Phoenix建表必须有声明主键,否则报错
②Phoenix建表的表名,在hbase里的表名是大写的,此外,列名也是大写的。
③这条建表语句,并未声明表的列族,则默认就一个列族,且列族的名字为:0。
④在列族0中,除主键列外,其余的列都属于0列族里的列
2. 查看所有表:
tables
3. 插入数据:
upsert into tab1 values(1,'hello');
注:字符串类型用 ' '包起来,不要用“ ”,否则报错。
4. 查询数据:
select * from tab1;
5. 删除数据:
delete from tab1 where id=2;
6. 删除表:
drop table tab1;
7. 创建小写的表名:
create table "tab2" (id integer primary key,name varchar);
create table "tab2" ("id" integer primary key,"name" varchar);
select * from "tab2";
注:CRUD都以 "tab2"为表名来操作
8. 自定义列族名
create table tab3 (id integer primary key,info.name varchar,info.age integer);
upsert into tab3 values(1,'tom',23);
然后在hbase里查看会发现:describe 'TAB3'
scan 'TAB3'