您的位置:首页 > 其它

hbase学习笔记

2016-06-17 11:22 471 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。

文章实例下载文件:http://download.csdn.net/detail/ruishenh/9551930

原文地址:http://blog.csdn.net/ruishenh/article/details/51694730

hbase 是什么

官方说明:Use Apache HBase™ when youneed random, realtime read/write access to your Big Data. This project's goalis the hosting of very large tables -- billions of rows X millions of columns-- atop clusters of commodity hardware. Apache HBase is an open-source,distributed,
versioned, non-relational database modeled after Google's Bigtable: A DistributedStorage System for Structured Data by
Chang et al. Just as Bigtableleverages the distributed data storage provided by the Google File System,Apache HBase provides Bigtable-like capabilities on top of Hadoop and
HDFS.

0.非关系型数据库

1.随机、实时读写高并发

2.低廉的商用机器上部署(方便扩增)

3.高可用的分布式系统(一般依赖于HDFS,但不仅限于)

4.key-Value

hbase可以做什么

非固定模式设计的表,日志存储,常用访问记录性数据存储。

1.当你的数据增长呈几何倍增的时候(很短的时间里增长到千万,亿级的数据,实时读写高并发)

2.当你的查询相对来讲固定的时候

3.当你后期面临着N多的数据分析的时候

4.当数据模式非结构化的时候

等等

hbase 逻辑结构

和关系型数据库的简单比较



HBase以表的形式存储数据。表有行和列组成。列划分为若干个列族(row family)



Row Key

与nosql数据库们一样,row key是用来检索记录的主键。访问hbase table中的行,只有三种方式:

1 通过单个rowkey访问 (get)

2 通过rowkey的range (scan)

3 全表扫描

Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。

存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)

注意:

字典序对int排序的结果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。要保持整形的自然序,行键必须用0作左填充。

行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

列族

hbase表中的每个列,都归属与某个列族。列族是表的schema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如courses:history , courses:math 都属于 courses 这个列族。

访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能 帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因 为隐私的原因不能浏览所有数据)。

时间戳

HBase中通过row和columns确定的为一个存贮单元称为cell。每个 cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。

为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。maxversion=3 verson=1

Cell

由{rowkey, column( =<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。

hbase 物理结构

1、Table中所有行都按照row key的字典序排列;

2、Table在行的方向上分割为多个Region;

3、Region按大小分割的,每个表开始只有一个region,随着数据增多,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region,之后会有越来越多的region;

4、Region是Hbase中分布式存储和负载均衡的最小单元,不同Region分布到不同RegionServer上。



5、Region虽然是分布式存储的最小单元,但并不是存储的最小单元。Region由一个或者多个Store组成,每个store保存一个 columnsfamily;每个Strore又由一个memStore和0至多个StoreFile组成,StoreFile就是对HFile的轻量级封装;memStore存储在 内存中,StoreFile存储在HDFS上。



6.HFile, HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际StoreFile就是对HFile做了轻量级包装,即StoreFile底层就是HFile

HFile的存储格式



首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。正如图中所示的,

Trailer中有指针指向其他数 据块的起始点。

File Info中记录了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN,LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。

Data Index和Meta Index块记录了每个Data块和Meta块的起始点。

Meta Block 段 (可选的)–保存用户自定义的kv对,可以被压缩。

Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于LRU的Block Cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定,大号的Block有利于顺序Scan,小号Block利于随机查询。 每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成, Magic内容就是一些随机数字,目的是防止数据损坏。后面会详细介绍每个KeyValue对的内部构造。

HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构

keyValue格式



开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是 RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。

查看下hbase 的内容:用hfile命令(hbase hfile 能看到对应的参数解释)

[hcr@localhost 桌面]$ hbase hfile -bmpf/hbase/data/default/hcr_test/57df02bcef30c53874263ad8d72626ff/cf/f308df775b6b470eaa006df6623b5f2a

K:20150123000000001111/cf:skuID/1465113764558/Put/vlen=4/mvcc=0 V: 1234

K:20150123000000001315/cf:url/1465113632832/Put/vlen=13/mvcc=0 V: www.baidu.com

K:20150123000000002111/cf:url/1465113632920/Put/vlen=13/mvcc=0 V: www.baidu.com

K:20150123000000002214/cf:url/1465113632958/Put/vlen=13/mvcc=0 V: www.baidu.com

K:20150123000000002315/cf:url/1465113634419/Put/vlen=13/mvcc=0 V: www.baidu.com

Block indexsize as per heapsize: 408

reader=/hbase/data/default/hcr_test/57df02bcef30c53874263ad8d72626ff/cf/f308df775b6b470eaa006df6623b5f2a,

compression=none,

cacheConf=CacheConfig:enabled[cacheDataOnRead=true] [cacheDataOnWrite=false] [cacheIndexesOnWrite=false][cacheBloomsOnWrite=false] [cacheEvictOnClose=false] [cacheCompressed=false],

firstKey=20150123000000001111/cf:skuID/1465113764558/Put,

lastKey=20150123000000002315/cf:url/1465113634419/Put,

avgKeyLen=37,

avgValueLen=11,

entries=5,

length=1344

Trailer:

fileinfoOffset=496,

loadOnOpenDataOffset=370,

dataIndexCount=1,

metaIndexCount=0,

totalUncomressedBytes=1235,

entryCount=5,

compressionCodec=NONE,

uncompressedDataIndexSize=52,

numDataIndexLevels=1,

firstDataBlockOffset=0,

lastDataBlockOffset=0,

comparatorClassName=org.apache.hadoop.hbase.KeyValue$KeyComparator,

majorVersion=2,

minorVersion=3

Fileinfo:

BLOOM_FILTER_TYPE = ROW

DATA_BLOCK_ENCODING = NONE

DELETE_FAMILY_COUNT =\x00\x00\x00\x00\x00\x00\x00\x00

EARLIEST_PUT_TS =\x00\x00\x01U\x1F\x93\xE0@

KEY_VALUE_VERSION = \x00\x00\x00\x01

LAST_BLOOM_KEY = 20150123000000002315

MAJOR_COMPACTION_KEY = \x00

MAX_MEMSTORE_TS_KEY =\x00\x00\x00\x00\x00\x00\x00\x00

MAX_SEQ_ID_KEY = 14

TIMERANGE = 1465113632832....1465113764558

hfile.AVG_KEY_LEN = 37

hfile.AVG_VALUE_LEN = 11

hfile.LASTKEY =\x00\x1420150123000000002315\x02cfurl\x00\x00\x01U\x1F\x93\xE6s\x04

Mid-key:\x00\x1420150123000000001111\x02cfskuID\x00\x00\x01U\x1F\x95\xE2\xCE\x04

Bloom filter:

BloomSize: 8

No of Keys in bloom: 5

Max Keys for bloom: 6

Percentage filled: 83%

Number of chunks: 1

Comparator: RawBytesComparator

Delete FamilyBloom filter:

Not present

Block Index:

size=1

key=20150123000000001111/cf:skuID/1465113764558/Put

offset=0, dataSize=325

Scanned kvcount -> 5

hbase 架构

基本组件

  HBase的服务器体系结构遵循简单的主从服务器架构,它由HRegion服务器(HRegion Server)群和HBase Master服务器(HBaseMaster
Server)构成。HBase Master服务器负责管理所有的HRegion服务器,而HBase中所有的服务器都是通过ZooKeeper来进行协调,并处理HBase服务器运行 期间可能遇到的错误。HBase Master Server本身不存储HBase中的任何数据,HBase逻辑上的表可能会被划分为多个HRegion,然后存储到HRegion Server群中,HBase Master Server中存储的是从数据到HRegionServer中的映射。



Client

操作HBase的接口,并维护cache来加快对HBase的读或者写操作

Zookeeper

维护master的单点问题

存贮所有Region的寻址入口

实时监控Region server的上线和下线信息。并实时通知给Master

存储HBase的schema和table元数据 指针

WAL监控 等等(split,roller);

可能新版本还会有其他的监控等等吧

Master

为Region server分配region

负责Region server的负载均衡

发现失效的Region server并重新分配其上的region

管理用户对table的增删改查操作

HregionServer

HRegionServer主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。

所有的数据库数据一般是保存在Hadoop分布式文件系统上面的,用户通过一系列HRegion服务器来获取这些数据,一台机器上面一般只运行一个HRegionServier,且每一个区段的HRegion也只会被一个HRegionServier维护。

当用户需要更新数据的时候,他会被分配到对应的HRegionServier上提交修改,这些修改先是被写到memStore(内存中的缓存,保存最 近更新的数据)缓存和服务器的Hlog(磁盘上面的记录文件,他记录着所有的更新操作)文件里面。在操作写入Hlog之后,commit()调用才会将其 返回给客户端。

在读取数据的时候,HRegionServier会先访问memStore缓存,如果缓存里没有改数据,才会回到Store磁盘上面寻找,每一个列族都会有一个Store集合,每一个Store集合包含很多storeFile(封装了Hfile)文件



存储原理

LSM树(log-structured merge-tree)则按另一种方式组织数据。输入数据首先被存储在日志文件,这些文件内的数据完全有序。当有日志文件被修改时,对应的更新会被先保存在内存中来加速查询。

当系统经历过许多次数据修改,且内存空间被逐渐被占满后,LSM树会把有序的“键-记录”对写到磁盘中,同时创建一个新的数据存储文件。此时,因为最近的修改都被持久化了,内存中保存的最近更新就可以被丢弃了。

存储文件的组织与B树相似,不过其为磁盘顺序读取做了优化,所有节点都是满的并按页存储。修改数据文件的操作通过滚动合并完成,也就是说,系统将现有的页与内存刷写数据混合在一起进行管理,直到数据块达到它的容量



在内存中多个块存储归并到磁盘的过程,合并写入会产生一个新的结果块,最终多个块被合并为更大块。

多次数据刷写之后会创建许多数据存储文件,后台线程就会自动将小文件聚合成大文件,这样磁盘查找就会 被限制在少数几个数据存储文件中。磁盘上的树结构也可以拆分成独立的小单元,这样更新就可以被分散到多个数据存储文件中。所有的数据存储文件都按键排序, 所以没有必要在存储文件中为新的键预留位置。

查询时先查找内存中的存储,然后再查找磁盘上的文件。这样在客户端看来数据存储文件的位置是透明的。

删除是一种特殊的更改,当删除标记被存储之后,查找会跳过这些删除过的键。当页被重写时,有删除标记的键会被丢弃。

此外,后台运维过程可以处理预先设定的删除请求。这些请求由TTL(time-to-live)触发,例如,当TTL设为20天后,合并进程会检查这些预设的时间戳,同时在重写数据块时丢弃过期的记录。

B树和LSM树最主要的区别在于它们的结构如何利用硬件,特别是磁盘。

WAL(write-ahead-log)

region服务器会将数据保存到内存中,直到积攒足够多的数据再将其刷写到硬盘上,这样可以避免创建很多小文件。存储在内存中的数据是不稳定的,例如,在服务器断电的情况下数据就可能会丢失。一个比较常见的解决这个问题的方法是预写日志(WAL)每次更新都会写入日志,只有写入成功才会通知客户端操作成功,然后服务器可以按需自由地批量处理或聚合内存中的数据。

当灾难发生的时候,WAL就是所需的生命线。类似于MySQL的binary log,WAL存储了对数据的所有更改。这在主存储器出现意外的情况下非常重要。如果服务器崩溃,它可以有效地回放日志,使得服务器恢复到服务器崩溃以
前。这也就意味着如果将记录写入到WAL失败时,整个操作也会被认为是失败的。



处理过程如下:首先客户端启动一个操作来修改数据。例如,可以对put()、delete()和increment()进行调用。每一个修改都封装到一个KeyValue对象实例中,并通过RPC调用发送出去。这些调用(理想情况下)成批地发送给含有匹配region的HRegionServer。

一旦KeyValue实例到达,它们会被发送到管理相应行的HRegion实例。数据被写入到WAL,然后被放入到实际拥有记录的存储文件的MemStore中。实质上,这就是HBase大体的写路径。

最后,当memstore达到一定的大小或是经历一个特定的时间之后,数据就会异步地连续写入到文件 系统中。在写入的过程中,数据以一种不稳定的状态存放在内存中,即使在服务器完全崩溃的情况下,WAL也能够保证数据不会丢失,因为实际的日志存储在 HDFS上。其他服务器可以打开日志文件然后回放这些修改—恢复操作并不在这些崩溃的物理服务器上进行。

当然WAL可以关闭比如当执行大规模的Mapreduce运算的时候一般都是关闭的。

region查找

client->zookeeper->.ROOT->.META-> 用户数据表

zookeeper记录了.ROOT的路径信息(root只有一个region),.ROOT里记录了.META的region信息, (.META可能有多个region),.META里面记录了region的信息



(在0.96+以上)ROOT表已经改名为hbase:namespace,META则是hbase:meta .

client->zookeeper->meta-region-server->regionServer上meta数据查找具体Regioin

hbase使用

1.安装

因官方和各大论坛都存在很多的安装介绍,此处就不再熬述。

2.客户端

1.客户端分类

交互客户端

hbase的客户端访问有很多种比较常用的就是
JAVA 原生语言

比如创建表:

HBaseAdmin admin = new HBaseAdmin(conf);

HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));

for (inti = 0; i < family.length; i++) {

desc.addFamily(new HColumnDescriptor(family[i]));

}

if (admin.tableExists(tableName)) {

System.out.println("table Exists!");

} else {

admin.createTable(desc);

System.out.println("create table Success!");

}

shell 基于ruby语言实现

[hcr@localhost hbase-0.96.1.1-cdh5.0.6]$bin/hbase shell

2016-05-22 20:14:17,696 INFO [main] Configuration.deprecation:hadoop.native.lib is deprecated. Instead, use io.native.lib.available

HBase Shell; enter 'help<RETURN>'for list of supported commands.

Type "exit<RETURN>" toleave the HBase Shell

Version 0.96.1.1-cdh5.0.6, rUnknown, MonApr 6 13:14:38 PDT 2015

hbase(main):001:0>

COMMANDGROUPS:

Groupname: general

Commands:status, table_help, version, whoami

Groupname: ddl

Commands:alter, alter_async, alter_status, create, describe, disable, disable_all, drop,drop_all, enable, enable_all, exists, get_table, is_disabled, is_enabled, list,show_filters
http://www.cnblogs.com/johnnyflute/p/3654426.html?utm_source=tuicool&utm_medium=referral
Groupname: namespace

Commands:alter_namespace, create_namespace, describe_namespace, drop_namespace,list_namespace, list_namespace_tables

Groupname: dml

Commands:count, delete, deleteall, get, get_counter, incr, put, scan, truncate,truncate_preserve

Groupname: tools

Commands:assign, balance_switch, balancer, catalogjanitor_enabled, catalogjanitor_run, catalogjanitor_switch,close_region, compact, flush, hlog_roll, major_compact, merge_region, move,split, trace, unassign, zk_dump

Groupname: replication

Commands:add_peer, disable_peer, enable_peer, list_peers, list_replicated_tables,remove_peer

Groupname: snapshot

Commands:clone_snapshot, delete_snapshot, list_snapshots, rename_snapshot,restore_snapshot, snapshot

Groupname: security

Commands:grant, revoke, user_permission

rest

启动

hbaserest start

[hcr@localhost ~]$ curl -X gethttp://localhost:8080/

hcr_test

t1
http://localhost:8080/version
http://localhost:8080/version/cluster
http://localhost:8080/status/cluster http://localhost:8080/
http://localhost:8080/t1/schema

参考:

http://kane-xie.iteye.com/blog/2228967

http://wiki.apache.org/hadoop/Hbase/HbaseRest

thrift 各种语言版本的接口



官方文档:http://wiki.apache.org/hadoop/Hbase/ThriftApi

avro

hadoop生态圈的一种类是与thrfit的协议和序列化框架

除了以上客户端还有一些批处理客户端(pig,hive,mapreduce,)和其他小使用范围语言的client没有列入。

3.Example

Maven 依赖

<dependency>

<groupId>org.apache.hbase</groupId>

<artifactId>hbase-client</artifactId>

<version>0.96.1.1-hadoop2</version>

</dependency>

java API 调用

package com.ruishenh.hbase.client.Java;

import com.google.common.io.Closeables;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.hbase.*;

import org.apache.hadoop.hbase.client.*;

import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

public class HbaseClient {

// 声明静态配置

static Configuration conf = null;

static {

conf = HBaseConfiguration.create();

conf.set("hbase.zookeeper.quorum","localhost");

}

/*

* 创建表

*

* @tableName表名

*

* @family 列族列表

*/

public static voidcreatTable(String tableName, String[] family)

throws Exception {

HBaseAdmin admin = new HBaseAdmin(conf);

HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));

for (inti = 0; i < family.length; i++) {

desc.addFamily(new HColumnDescriptor(family[i]).setMaxVersions(10));

}

if (admin.tableExists(tableName)) {

System.out.println("table Exists!");

} else {

admin.createTable(desc);

System.out.println("create table Success!");

}

Closeables.close(admin,false);

}

/**

* 获取表等于list命令

* @throwsException

*/

public static voidgetTables()

throws Exception {

HBaseAdmin admin = new HBaseAdmin(conf);

TableName[] tableNames = admin.listTableNames();

for (TableName tableName : tableNames) {

HTableDescriptor tableDescriptor = admin.getTableDescriptor(tableName);

System.out.println(tableName+"--"+tableDescriptor);

}

Closeables.close(admin,false);

}

/*

* 根据rwokey查询

*

* @rowKey rowKey

*

* @tableName表名

*/

public staticResult getResult(String tableName, String rowKey)

throws IOException {

Get get = new Get(Bytes.toBytes(rowKey));

HTable table = new HTable(conf, Bytes.toBytes(tableName));//获取表

Result result = table.get(get);

for (KeyValue kv : result.list()) {

System.out.println("family:"+ Bytes.toString(kv.getFamily()));

System.out

.println("qualifier:"+ Bytes.toString(kv.getQualifier()));

System.out.println("value:"+ Bytes.toString(kv.getValue()));

System.out.println("Timestamp:"+ kv.getTimestamp());

System.out.println("-------------------------------------------");

}

Closeables.close(table,false);

return result;

}

/*

* 遍历查询hbase表

*

* @tableName表名

*/

public static voidgetResultScann(String tableName) throwsIOException {

Scan scan = new Scan();

ResultScanner rs = null;

HTable table = new HTable(conf, Bytes.toBytes(tableName));

try {

rs = table.getScanner(scan);

for (Result r : rs) {

for (KeyValue kv : r.list()) {

System.out.println("row:"+ Bytes.toString(kv.getRow()));

System.out.println("family:"

+ Bytes.toString(kv.getFamily()));

System.out.println("qualifier:"

+ Bytes.toString(kv.getQualifier()));

System.out

.println("value:"+ Bytes.toString(kv.getValue()));

System.out.println("timestamp:"+ kv.getTimestamp());

System.out

.println("-------------------------------------------");

}

}

} finally {

rs.close();

}

}

/*

* 遍历查询hbase表

*

* @tableName表名

*/

public static voidgetResultScann(String tableName, String start_rowkey,

String stop_rowkey) throwsIOException {

Scan scan = new Scan();

scan.setStartRow(Bytes.toBytes(start_rowkey));

scan.setStopRow(Bytes.toBytes(stop_rowkey));

ResultScanner rs = null;

HTable table = new HTable(conf, Bytes.toBytes(tableName));

try {

rs = table.getScanner(scan);

for (Result r : rs) {

for (KeyValue kv : r.list()) {

System.out.println("row:"+ Bytes.toString(kv.getRow()));

System.out.println("family:"

+ Bytes.toString(kv.getFamily()));

System.out.println("qualifier:"

+ Bytes.toString(kv.getQualifier()));

System.out

.println("value:"+ Bytes.toString(kv.getValue()));

System.out.println("timestamp:"+ kv.getTimestamp());

System.out

.println("-------------------------------------------");

}

}

} finally {

Closeables.close(rs,false);

}

}

/*

* 查询表中的某一列

*

* @tableName表名

*

* @rowKey rowKey

*/

public static voidgetResultByColumn(String tableName, String rowKey,

String familyName, String columnName)throws IOException {

HTable table = new HTable(conf, Bytes.toBytes(tableName));

Get get = new Get(Bytes.toBytes(rowKey));

get.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName));// 获取指定列族和列修饰符对应的列

Result result = table.get(get);

System.out.println("查询表中的某一列:");

for (KeyValue kv : result.list()) {

System.out.println("family:"+ Bytes.toString(kv.getFamily()));

System.out

.println("qualifier:"+ Bytes.toString(kv.getQualifier()));

System.out.println("value:"+ Bytes.toString(kv.getValue()));

System.out.println("Timestamp:"+ kv.getTimestamp());

System.out.println("---------------");

}

System.out.println("-------------------------------------------");

Closeables.close(table,false);

}

/*

* 更新表中的某一列

*

* @tableName表名

*

* @rowKey rowKey

*

* @familyName列族名

*

* @columnName列名

*

* @value 更新后的值

*/

public static voidupdateTable(String tableName, String rowKey,

String familyName, String columnName, String value)

throws IOException {

HTable table = new HTable(conf, Bytes.toBytes(tableName));

Put put = new Put(Bytes.toBytes(rowKey));

put.add(Bytes.toBytes(familyName), Bytes.toBytes(columnName),

Bytes.toBytes(value));

table.put(put);

System.out.println("update table Success!");

Closeables.close(table,false);

}

/*

* 查询某列数据的多个版本

*

* @tableName表名

*

* @rowKey rowKey

*

* @familyName列族名

*

* @columnName列名

*/

public static voidgetResultByVersion(String tableName, String rowKey,

String familyName, String columnName)throws IOException {

HTable table = new HTable(conf, Bytes.toBytes(tableName));

Get get = new Get(Bytes.toBytes(rowKey));

get.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName));

get.setMaxVersions(5);

Result result = table.get(get);

System.out.println("查询某列数据的多个版本");

for (KeyValue kv : result.list()) {

System.out.println("family:"+ Bytes.toString(kv.getFamily()));

System.out

.println("qualifier:"+ Bytes.toString(kv.getQualifier()));

System.out.println("value:"+ Bytes.toString(kv.getValue()));

System.out.println("Timestamp:"+ kv.getTimestamp());

System.out.println("--------------------");

}

System.out.println("-------------------------------------------");

Closeables.close(table,false);

}

/*

* 查询某列数据的时间磋范围

*

* @tableName 表名

*

* @rowKey rowKey

*

* @familyName 列族名

*

* @columnName 列名

*/

public static voidgetResultByTimeRange(String tableName, String rowKey,

String familyName, String columnName,longstart,long end) throws IOException
{

HTable table = new HTable(conf, Bytes.toBytes(tableName));

Get get = new Get(Bytes.toBytes(rowKey));

get.setTimeRange(start, end);

get.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName));

Result result = table.get(get);

System.out.println("查询某列数据TimeRange");

System.out.println("start:"+start);

System.out.println("start:"+end);

for (KeyValue kv : result.list()) {

System.out.println("family:"+ Bytes.toString(kv.getFamily()));

System.out

.println("qualifier:"+ Bytes.toString(kv.getQualifier()));

System.out.println("value:"+ Bytes.toString(kv.getValue()));

System.out.println("Timestamp:"+ kv.getTimestamp());

System.out.println("--------------------");

}

System.out.println("-------------------------------------------");

Closeables.close(table,false);

}

/*

* 删除指定的列

*

* @tableName表名

*

* @rowKey rowKey

*

* @familyName列族名

*

* @columnName列名

*/

public static voiddeleteColumn(String tableName, String rowKey,

String falilyName, String columnName) throws IOException {

HTable table = new HTable(conf, Bytes.toBytes(tableName));

Delete deleteColumn = new Delete(Bytes.toBytes(rowKey));

deleteColumn.deleteColumns(Bytes.toBytes(falilyName),

Bytes.toBytes(columnName));

table.delete(deleteColumn);

System.out.println(falilyName +":" + columnName + "is deleted!");

Closeables.close(table,false);

}

/*

* 删除指定的列

*

* @tableName表名

*

* @rowKey rowKey

*/

public static voiddeleteAllColumn(String tableName, String rowKey)

throws IOException {

HTable table = new HTable(conf, Bytes.toBytes(tableName));

Delete deleteAll = new Delete(Bytes.toBytes(rowKey));

table.delete(deleteAll);

System.out.println("all columns are deleted!");

Closeables.close(table,false);

}

/*

* 删除表

*

* @tableName表名

*/

public static voiddeleteTable(String tableName) throwsIOException {

HBaseAdmin admin = new HBaseAdmin(conf);

admin.disableTable(tableName);

admin.deleteTable(tableName);

System.out.println(tableName +"is deleted!");

Closeables.close(admin,false);

}

public static void main(String[] args)throws Exception {

String tableName = "hcr_tb";

String[] family = { "dcf","cfc" };

//创建表

creatTable(tableName, family);

//查看表

getTables();

//删除表

// deleteTable(tableName);

// 为表添加数据

String[] column1 = { "name","age", "tag"};

String[] value1 = {"侯哥","20", "hadoop"};

String[] column2 = { "modifycount","searchcount" };

String[] value2 = { "3","5" };

long a2=System.currentTimeMillis();

addData("rowkey1",tableName,family[0], column1, value1);

addData("rowkey1", tableName, family[1],column2, value2);

addData("rowkey2", tableName, family[0], column1, value1);

addData("rowkey2", tableName, family[1], column2, value2);

// 遍历查询

getResultScann(tableName, "rowkey1", "rowkey2");

// 根据row key范围遍历查询

getResultScann(tableName, "rowkey4", "rowkey5");

// 查询

getResult(tableName, "rowkey1");

// 查询某一列的值

getResultByColumn(tableName, "rowkey1", "dcf","name");

long a=System.currentTimeMillis();

// 更新列

updateTable(tableName, "rowkey1", "dcf","name", "大师兄");

// 查询某一列的值

getResultByColumn(tableName, "rowkey1", "dcf","name");

// 查询某列的多版本

getResultByVersion(tableName,"rowkey1", "dcf","name");

//时间错范围查询

getResultByTimeRange(tableName,"rowkey1", "dcf","name",a2,a);

// 删除一列

deleteColumn(tableName, "rowkey1", "dcf","name");

// 删除所有列

deleteAllColumn(tableName, "rowkey1");

// 删除表

deleteTable(tableName);

}

private static void addData(String rowKey, String tableName, String s, String[] column1, String[] value1)throws IOException {

Put put = new Put(Bytes.toBytes(rowKey));//设置rowkey

HTable table = newHTable(conf, Bytes.toBytes(tableName));// HTabel负责跟记录相关的操作如增删改查等//

for (int j = 0; j < column1.length;
j++) {

put.add(Bytes.toBytes(s),

Bytes.toBytes(column1[j]), Bytes.toBytes(value1[j]));

}

table.put(put);

Closeables.close(table,false);

System.out.println("add data Success!");

}

}

集成spring-data

增加 maven 依赖

<!--spring-data-->

<dependency>

<groupId>org.springframework.data</groupId>

<artifactId>spring-data-hadoop</artifactId>

<version>1.0.1.RELEASE</version>

</dependency>

增加spring 配置文件 hbase.xml内容如下
<?xml version="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:hdp="http://www.springframework.org/schema/hadoop"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/hadoop http://www.springframework.org/schema/hadoop/spring-hadoop-2.0.xsd">
<hdp:configurationresources="classpath:/hbase-site.xml"/>

<hdp:hbase-configurationconfiguration-ref="hadoopConfiguration"/>

<beanid="hbaseTemplate"class="org.springframework.data.hadoop.hbase.HbaseTemplate"p:configuration-ref="hbaseConfiguration"/>

</beans>

增加对应的hbase-site.xml 配置文件
<?xml version="1.0"encoding="UTF-8"?>

<configuration>

<property>

<name>hbase.zookeeper.quorum</name>

<value>localhost</value>

</property>

<property>

<name>hbase.zookeeper.property.clientPort</name>

<value>2181</value>

</property>

</configuration>
使用示例
ClassPathXmlApplicationContext classPathXmlApplicationContext = newClassPathXmlApplicationContext("hbase.xml");

HbaseTemplate bean = classPathXmlApplicationContext.getBean(HbaseTemplate.class);

bean.execute("t1", newTableCallback<Object>() {

@Override

public Object doInTable(HTableInterface table) throwsThrowable {

HTableDescriptor tableDescriptor = table.getTableDescriptor();

System.out.println(tableDescriptor);

return null;

}

});

bean.get("t1", "r1",new RowMapper<Object>() {

@Override

public Object mapRow(Result result, int rowNum) throws Exception {

for (KeyValue kv : result.list()) {

System.out.println("family:"+ Bytes.toString(kv.getFamily()));

System.out.println("qualifier:"+ Bytes.toString(kv.getQualifier()));

System.out.println("value:"+ Bytes.toString(kv.getValue()));

System.out.println("Timestamp:"+ kv.getTimestamp());

System.out.println("-------------------------------------------");

}

return null;

}

});

4.hbase高级用法

rowkey设计

1.rowkey长度

Rowkey是一个二进制码流,Rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节

2.散列原则

如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段, 这样将提高数据均衡分布在每个Regionserver实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。

3.预分区

有些比较有范围固定的数据可以通过预分区来把region 提前分好。比如全国每个城市的数据分割或者对应有枚举的数据。

4.查询时索引过滤性能如下图



5.部分扫瞄

利用查询条件的特定开始键和结束键来固定一个查询纬度。

过滤器

所有的过滤器都在服务端生效,叫做谓语下推(predicate push down),这样可以保证被过滤掉的数据不会被传送到客户端,节省流量。

hbase中主要读取数据的函数有get 和sacn。

可以通过setFilter(Filter filter)传给对应的get和scan 对象。

过滤器原理:



过滤器结构

filter接口

//为每一个新行重置

void reset() throws IOException;

//检查行键,rowkey不符合直接跳过整行

boolean filterRowKey(byte[] buffer, intoffset, int length) throws IOException;

//check 是否还有剩余,用于结束整个扫瞄操作

boolean filterAllRemaining() throwsIOException;

//check KV, 根据返回值做处理

ReturnCode filterKeyValue(final Cell v) throwsIOException;

//过滤cell

void filterRowCells(List<Cell> kvs)throws IOException;

//处理行过滤和列过滤后的数据过滤

void filterRow(List<KeyValue> kvs)throws IOException;

boolean filterRow() throws IOException;//用户判断是否继续执行

执行流程:



内置过滤器:

参考: http://blog.csdn.net/cnweike/article/details/42920547#comments
http://blog.csdn.net/u010967382/article/details/37653177
过滤器之间的兼容:



计数器

hbase有类似于redis 的incr的功能。

实用于 一些点击量,访问量等实时统计的功能

使用方式:单列计数器和多列计数器 方式。

版本控制

默认我们插入的每一个cell都是如果不带有时间戳,系统默认用当前regionserver的时间。

可使用于:查看某一些特殊场景的历史数据查看

用户获取的时候可以通过setMaxVersions(5)获取更多的版本数据

不同版本的默认version可能不同,我用的0.96的cdh版本 默认目前是version是1,用户可以通过对列族的setMaxVersions来设置最大值。

mapreduce集成

使用mapreduce 可以做一些,大数据分析,报表离线生成,离线job,批量数据导出,批量数据导入等等.

Mapreduce 基础就不说了。google...

package com.ruishenh.hbase.mr;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.hbase.HBaseConfiguration;

import org.apache.hadoop.hbase.client.Put;

import org.apache.hadoop.hbase.client.Result;

import org.apache.hadoop.hbase.client.Scan;

import org.apache.hadoop.hbase.io.ImmutableBytesWritable;

import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;

import org.apache.hadoop.hbase.mapreduce.TableMapper;

import org.apache.hadoop.hbase.mapreduce.TableReducer;

import org.apache.hadoop.hbase.util.Bytes;

import org.apache.hadoop.io.IntWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapreduce.Job;

import java.io.IOException;

import java.util.StringTokenizer;

/**

* Created by hcr on 16-6-11.

*/

public class MapReduceTest {

public static void main(String[] args)throws IOException, ClassNotFoundException, InterruptedException {

Configuration conf = HBaseConfiguration.create();

conf.addResource("yarn-site.xml");

conf.addResource("mapred-site.xml");

conf.set("tmpjars","file:/app/project/myself/hbasetTest/target/hbaseTest-1.0-SNAPSHOT.jar");

Job job =Job.getInstance(conf);

job.setJobName("hcr-job name");

job.setJarByClass(MapReduceTest.class);

Scan scan = new Scan();

TableMapReduceUtil.initTableMapperJob("hcr_test", scan, WordCountHbaseMapper.class, Text.class,
IntWritable.class, job);

TableMapReduceUtil.initTableReducerJob("hcr_test2", WordCountHbaseReducer.class, job);

boolean b = job.waitForCompletion(true);

System.out.println("run:"+b);

}

public static class WordCountHbaseMapperextends

TableMapper<Text, IntWritable> {

private final static IntWritableone = newIntWritable(1);

private Text word = newText();

@Override

protected voidmap(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException
{

StringTokenizer itr = new StringTokenizer(value.toString());

System.out.println("key:"+newString(key.get()));

System.out.println("value:"+value);

while (itr.hasMoreTokens()) {

word.set(itr.nextToken());

context.write(word,one);//输出<key,value>为<word,one>

}

}

}

public static class WordCountHbaseReducerextends

TableReducer<Text, IntWritable, ImmutableBytesWritable> {

@Override

protected voidreduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException
{

int sum = 0;

for (IntWritable val : values) {//遍历求和

sum += val.get();

}

Put put = new Put(key.getBytes());//put实例化,每一个词存一行

//列族为content,列修饰符为count,列值为数目

put.add(Bytes.toBytes("cf"), Bytes.toBytes("count"), Bytes.toBytes(String.valueOf(sum)));

context.write(new ImmutableBytesWritable(key.getBytes()), put);//输出求和后的<key,value>

}

}

}

map中的打印:



协处理器

HBase在0.92之后引入了协处理器(coprocessors),实现一些激动人心的新特性:能够轻易建立二次索引、复杂过滤器(谓词下推)以及访问控制等.

Coprocessor分两种

第一种是Observers,它实际类似于触发器,允许集群在正常的客户端操作过程中可以有不同的行为表现。

第二种是Endpoint,它类似与存储过程,允许扩增集群的能力,对客户端开放新的功能,客户端调用操控集群运算。

Observer

· RegionObserver:提供客户端的数据操纵事件钩子:Get、Put、Delete、Scan等。

· WALObserver:提供WAL相关操作钩子。

· MasterObserver:提供DDL-类型的操作钩子。如创建、删除、修改数据表等。

以RegionObserver执行的原理



在idea 中查看调用类的地方



简单测试自定义协处理器生成二级索引

package com.ruishenh.hbase.cp;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.hbase.Cell;

import org.apache.hadoop.hbase.client.Durability;

import org.apache.hadoop.hbase.client.HTable;

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 java.io.IOException;

import java.util.Iterator;

import java.util.List;

/**

* Created by hcr on 16-6-13.

*/

public class MyTestCoprocessorextends BaseRegionObserver {

@Override

public voidprePut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, Durability durability)throws IOException
{

Configuration conf = new Configuration();

conf.set("hbase.zookeeper.quorum","localhost");

String colName = "url";

HTable table = new HTable(conf,"idx_tb");

List<Cell> kv = put.get("cf".getBytes(), colName.getBytes());

Iterator<Cell> kvItor = kv.iterator();

while (kvItor.hasNext()) {

Cell tmp = kvItor.next();

Put indexPut = new Put(tmp.getValue());

indexPut.add("cf".getBytes(),colName.getBytes(),

tmp.getRow());

table.put(indexPut);

}

table.close();

}

}

可以把打包成 jar的文件上传到hdfs上,或者放在各个regionserver本地(最好是第一种)

//指定表增加表级别协处理器

alter'hcr_test',METHOD=>'table_att','coprocessor'=>'hdfs://nameNode:9000/test.jar|com.ruishenh.hbase.cp.MyTestCoprocessor|1001'

//删除指定的表级别协处理器

alter 'hcr_test', METHOD => 'table_att_unset',NAME => 'coprocessor$1'

hbase(main):007:0> put'hcr_test','N20016012002301','cf:url','www.baidu.com'

0 row(s) in 0.3470 seconds

hbase(main):008:0> scan'hcr_test'

ROW COLUMN+CELL

//其他数据省

N20016012002301 column=cf:url, timestamp=1465910380162,value=www.baidu.co

m

//其他数据省

hbase(main):009:0> scan 'idx_tb'

ROW COLUMN+CELL

www.baidu.com column=cf:url,timestamp=1465910380033, value=N20016012002301

1 row(s) in 0.0310 seconds

hbase(main):010:0>

Endpoint

endpoint允许动态RPC插件的接口,它的实现代码被安装在服务器端,从而能够通过HBase RPC唤醒。客户端类库提供了非常方便的方法来调用这些动态接口,它们可以在任意时候调用一个终端,它们的实现代码会被目标region远程执行,结果会 返回到终端。用户可以结合使用这些强大的插件接口,为HBase添加全新的特性。

0.94.x之前使用EndPoint需要实现CoprocessorProtocol接口,而0.96.x的EndPoint改为用protobufs作为RPC的协议

(没安装protobuf的可以自己安装下)

原理:



简单示例:

定一个 t.proto
option java_package = "com.ruishenh.hbase.cp.proto";

option java_outer_classname = "CounterService";

option java_generic_services = true;

option java_generate_equals_and_hash = true;

option optimize_for = SPEED;

message CountReq {

required string condition = 1;

}

message CountRes {

required int64 ret = 1;

}

service Counter{

rpc count(CountReq)

returns (CountRes);

}
//执行编译

[hcr@localhost protobuf-2.5.0]$bin/protoc t.proto --java_out=.

当前目录生存的com文件夹放到工程里

编写实现类
package com.ruishenh.hbase.cp;

import com.google.common.io.Closeables;

import com.google.protobuf.RpcCallback;

import com.google.protobuf.RpcController;

import com.google.protobuf.Service;

import com.ruishenh.hbase.cp.proto.CounterService;

import org.apache.hadoop.hbase.Cell;

import org.apache.hadoop.hbase.Coprocessor;

import org.apache.hadoop.hbase.CoprocessorEnvironment;

import org.apache.hadoop.hbase.client.Scan;

import org.apache.hadoop.hbase.coprocessor.CoprocessorException;

import org.apache.hadoop.hbase.coprocessor.CoprocessorService;

import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;

import org.apache.hadoop.hbase.regionserver.RegionScanner;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

/**

* Created by hcr on 16-6-14.

*/

public class MyTestCoprocessorEndPointextends CounterService.Counter

implements Coprocessor, CoprocessorService {

Logger LOGGER =LoggerFactory.getLogger(MyTestCoprocessorEndPoint.class);

private RegionCoprocessorEnvironmentenv;

@Override

public voidstart(CoprocessorEnvironment coprocessorEnvironment) throwsIOException {

if (coprocessorEnvironmentinstanceof RegionCoprocessorEnvironment)

this.env= (RegionCoprocessorEnvironment) coprocessorEnvironment;

else throw new CoprocessorException("Must be loaded on a table region!!");

}

@Override

public voidstop(CoprocessorEnvironment env) throwsIOException {

}

@Override

public Service getService() {

return this;

}

@Override

public voidcount(RpcController controller, CounterService.CountReq request, RpcCallback<CounterService.CountRes> done) {

RegionScanner scanner = null;

CounterService.CountRes.Builder respBuilder = CounterService.CountRes.newBuilder();

long count = 0;

try {

Scan scan = new Scan();

scan.setMaxVersions(1);

scanner = env.getRegion().getScanner(scan);

List<Cell> list = new ArrayList<Cell>();

while (scanner.next(list))

count += 1;

respBuilder.setRet(count);

} catch (IOException e) {

LOGGER.error("io error",e);

} finally {

try {

Closeables.close(scanner,false);

} catch (IOException e) {

LOGGER.error("close io error",e);

}

}

done.run(respBuilder.build());

}

}
可以放到hbase 的lib或和classpath下,或者上传到 hdfs上

[hcr@localhost lib]$ cp/app/project/myself/hbasetTest/target/hbaseTest-1.0-SNAPSHOT.jar .

[hcr@localhost lib]$ pwd

/app/local/hbase-0.96.1.1-cdh5.0.6/lib

[hcr@localhosthbase-0.96.1.1-cdh5.0.6]$ hadoop fs -put/app/project/myself/hbasetTest/target/hbaseTest-1.0-SNAPSHOT.jar /test2.jar

如果是hdfs就这样

alter'hcr_test',METHOD=>'table_att','coprocessor'=>'hdfs://nameNode:9000/test2.jar|com.ruishenh.hbase.cp.MyTestCoprocessorEndPoint|1001'

如果是放到了hbase的lib下

可以直接

alter'hcr_test',METHOD=>'table_att','coprocessor'=>'|com.ruishenh.hbase.cp.MyTestCoprocessorEndPoint|1001'

指定了表的协处理器后。

客户端代码:

package com.ruishenh.hbase.cp.proto;

import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.hbase.HBaseConfiguration;

import org.apache.hadoop.hbase.client.HTable;

import org.apache.hadoop.hbase.client.coprocessor.Batch;

import org.apache.hadoop.hbase.ipc.BlockingRpcCallback;

import org.apache.hadoop.hbase.ipc.ServerRpcController;

import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

import java.util.concurrent.atomic.AtomicLong;

/**

* Created by hcr on 16-6-14.

*/

public class EndPointTest {

public static void main(String[] args)throws Throwable {

Configuration conf = HBaseConfiguration.create();

conf.set("hbase.zookeeper.quorum","localhost");

HTable table = new HTable(conf,"hcr_test");

final CounterService.CountReq req = CounterService.CountReq.newBuilder().setCondition("xxxx").build();

final AtomicLong ret =new AtomicLong();

table.coprocessorService(CounterService.Counter.class,null, null,new Batch.Call<CounterService.Counter,
Long>() {

@Override

publicLong call(CounterService.Counter instance) throwsIOException {

ServerRpcController controller = newServerRpcController();

BlockingRpcCallback<CounterService.CountRes> rpc = new BlockingRpcCallback<CounterService.CountRes>();

instance.count(controller, req, rpc);

CounterService.CountRes resp = rpc.get();

return resp.getRet();

}

}, new Batch.Callback<Long>() {

@Override

public voidupdate(byte[] region,byte[] row, Long result) {

ret.getAndAdd(result);

System.out.println(Bytes.toString(row)+": "+result);

}

});

System.out.println("lines: "+ ret.get());

}

}
运行结果



简单的avg,sum在高版本中有默认的AggregateImplementation

参考:http://www.aboutyun.com/thread-7840-1-1.html

优化

rowkey的设计尽量范围或者单个查询,和避免单region热点

预分区

压缩开启

memstore调优

IN_MEMORY 有些配置表或者经常需要用的join表可以开启此功能

TTL 自动过期数据

bloom filter

此过滤器默认是ROW级别的,开启ROWCOL 可过滤一些

Compact & Split 运维级操作

读取的时候开启cacheBlock块,和batch值

scan.setCacheBlocks();

scan.setBatch();

WAL 关闭(此性能会提高很多,同时面临着丢数据的风险)

jvm 调优

hbase 参数配置

比如:hfile的大小,hregion的大小,日志是否(同步写,异步写),hregionserver的并发数等等

运维

常用命令

监控工具

云测工具

文档整理参考文献(可能有部分文献可能没有列入,罪过罪过)

http://www.cnblogs.com/NicholasLee/archive/2012/09/13/2683223.html

http://blog.csdn.net/woshiwanxin102213/article/details/17584043

http://www.epubit.com.cn/book/onlinechapter/26106

http://www.cnblogs.com/johnnyflute/p/3654426.html?utm_source=tuicool&utm_medium=referral

http://blog.sae.sina.com.cn/archives/3727

http://www.csdn.net/article/2014-01-15/2818147-hbase-in-2013

http://www.open-open.com/lib/view/open1417612091323.html

实例下载文件:http://download.csdn.net/detail/ruishenh/9551930

原文作者:http://blog.csdn.net/ruishenh/article/details/51694730
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  hbase