Hbase

简介

HBase是一个分布式的、面向列的开源数据库,源于google的一篇论文《bigtable:一个结构化数据的分布式存储系统》。HBase是Google Bigtable的开源实现,它利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为协同服务。

HBase的表结构

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

1
2
3
4
5
| Row Key | column-family1 | column-family2 | column-family3 |
| column1 | column2 | column1 | column2 | column3 | column1 |
| key1 |
| key2 |
| key3 |

如上图所示,key1,key2,key3是三条记录的唯一的row key值,column-family1,column-family2,column-family3是三个列族,每个列族下又包括几列。比如column-family1这个列族下包括两列,名字是column1和column2,t1:abc,t2:gdxdf是由row key1和column-family1-column1唯一确定的一个单元cell。这个cell中有两个数据,abc和gdxdf。两个值的时间戳不一样,分别是t1,t2, hbase会返回最新时间的值给请求者。

这些名词的具体含义如下:

Row Key

  • 与nosql数据库们一样,row key是用来检索记录的主键。访问hbase table中的行,只有三种方式:
    • 通过单个row key访问
    • 通过row key的range
    • 全表扫描
  • 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作左填充。
  • 行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

列族 column family

  • hbase表中的每个列,都归属与某个列族。列族是表的chema的一部分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如courses:historycourses:math 都属于 courses这个列族。
  • 访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)。

单元 Cell

HBase中通过row和columns确定的为一个存贮单元称为cell。由{row key, column( =<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存储。

时间戳 timestamp

  • 每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
  • 为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。

列存储

列存储不同于传统的关系型数据库,其数据在表中是按行存储的,列方式所带来的重要好处之一就是,由于查询中的选择规则是通过列来定义的,因 此整个数据库是自动索引化的。按列存储每个字段的数据聚集存储,在查询只需要少数几个字段的时候,能大大减少读取的数据量,一个字段的数据聚集存储,那就 更容易为这种聚集存储设计更好的压缩/解压算法。这张图讲述了传统的行存储和列存储的区别:

安装及使用

安装

从 Apache的HBase的镜像网站上下载一个稳定版本的HBase http://mirrors.devlib.org/apache/hbase/stable/hbase-1.1.2-bin.tar.gz, 下载完成后,对其进行解压缩。确定你的机器中已经正确的安装了Java SDK、SSH,否则将无法正常运行。

  • 进入到hbase的安装目录编辑 conf/hbase-env.sh 文件,将JAVA_HOME修改为你的JDK安装目录export JAVA_HOME=/JDK_PATH
  • 编辑regionservers,里面改为你的Hbase所有服务器名,ip地址或者是代理名,如果是单机版的则是只需要localhost即可。
  • 然后编辑hbase-site.xml文件,在里面加入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//指定hbase在HDFS上存储的路劲:hdfs://root1/hbase
//也可以是本地路劲
<property>
<name>hbase.rootdir</name>
<value>/home/bingbing/hbase-1.1.2</value>
</property>
//指定Hbase是分布式的
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
//指定ZK的地址,多个用","分割,hbase必须依赖zookeeper,自带zookeeper,但是自带的不太好用一般用外部的
//当然要想让外部的zookeeper生效,还需要在hbase-env.sh中加入一个配置:export HBASE_MANAGES_ZK=false,表示hbase的zk就不管了
<property>
<name>hbase.zookeeper.quorum</name>
<value>node01:2181,node02:2181</value>
</property>
  • 此外还可以输入一些配置:
    • hbase.master.portHBase的Master的端口.默认: 60000
    • hbase.cluster.distributedHBase的运行模式。false是单机模式,true是分布式模式。若为false,HBase和Zookeeper会运行在同一个JVM里面。默认: false.
    • hbase.master.info.portHBase Master web 界面端口. 设置为-1 意味着你不想让他运行。0.98 版本以后默认:16010以前是60010.
    • hbase.zookeeper.quorumZookeeper集群的地址列表,用逗号分割。例如:”host1.mydomain.com,host2.mydomain.com,host3.mydomain.com”.默认是localhost,是给伪分布式用的。要修改才能在完全分布式的情况下使用。如果在hbase-env.sh设置了HBASE_MANAGES_ZK,这些ZooKeeper节点就会和HBase一起启动。默认: localhost.
    • hbase.zookeeper.property.clientPortZooKeeper的zoo.conf中的配置.客户端连接的端口,默认: 2181.

使用

  • 启动:进入到bin目录,然后./start-hbase.sh即可启动hbase.
    • 可以通过web的方式查看hbase的运行状态:ip:61010
  • 停止:./stop-hbase.sh.

使用shell语言

进入bin目录下运行hbase shell命令可以HQL指模式:

基本命令:

名称 命令表达式
查看所有表 list
创建表 create ‘表名称’, ‘列名称1’,’列名称2’,’列名称N’ 或者规定版本有多少 create ‘表名称’,{NAME=>’表名称’,VERSIONS=>3}…
添加记录 put ‘表名称’, ‘行名称’, ‘列名称:*’, ‘值’
查看记录 get ‘表名称’, ‘行名称’
查看表中的记录总数 count ‘表名称’
删除记录 delete ‘表名’ ,’行名称’ , ‘列名称’
删除一张表 先要屏蔽该表,才能对该表进行删除,第一步 disable ‘表名称’ 第二步 drop ‘表名称’
查看所有记录 scan “表名称”
查看某个表某个列中所有数据 scan “表名称” , [‘列名称:*’]
更新记录 就是重写一遍进行覆盖
  • version 查看hbase版本号
  • describe ‘表名’ 查看表结构
  • hbase更新之后,以前的数据不丢失,分版本存起来,时间戳为版本的区分,老的版本存一个小时
  • hbase中存储的都是byte数组,即使你写字符串进去也会转换成byte数组

java操作hbase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
public class HbaseDao {
@Test
//插入数据
public void insertTest() throws Exception{
Configuration conf = HBaseConfiguration.create();
//可以不加配置文件然后在这里直接指定zk即可,只要指定zk然后zk就会找到root表就能操作hbase了
conf.set("hbase.zookeeper.quorum", "weekend05:2181,weekend06:2181,weekend07:2181");
//new一个表
HTable nvshen = new HTable(conf, "nvshen");
//hbase提供的一个转换工具
Put name = new Put(Bytes.toBytes("rk0001"));
name.add(Bytes.toBytes("base_info"), Bytes.toBytes("name"), Bytes.toBytes("angelababy"));
Put age = new Put(Bytes.toBytes("rk0001"));
age.add(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes(18));
ArrayList<Put> puts = new ArrayList<>();
puts.add(name);
puts.add(age);
nvshen.put(puts);
}
//创建一个表
public static void main(String[] args) throws Exception {
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "weekend05:2181,weekend06:2181,weekend07:2181");
HBaseAdmin admin = new HBaseAdmin(conf);
//new 一个表名,hbase中存储的都是byte数组,即使你写字符串进去也会转换成byte数组
TableName name = TableName.valueOf("nvshen");
HTableDescriptor desc = new HTableDescriptor(name);
//new两个列簇,然后指定版本号
HColumnDescriptor base_info = new HColumnDescriptor("base_info");
HColumnDescriptor extra_info = new HColumnDescriptor("extra_info");
base_info.setMaxVersions(5);
desc.addFamily(base_info);
desc.addFamily(extra_info);
//创建表
admin.createTable(desc);
}
}
  • 更多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
public class HbaseDemo {
private Configuration conf = null;
@Before
public void init(){
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "weekend05,weekend06,weekend07");
}
@Test
public void testDrop() throws Exception{
HBaseAdmin admin = new HBaseAdmin(conf);
admin.disableTable("account");
admin.deleteTable("account");
admin.close();
}
@Test
public void testPut() throws Exception{
HTable table = new HTable(conf, "person_info");
Put p = new Put(Bytes.toBytes("person_rk_bj_zhang_000002"));
p.add("base_info".getBytes(), "name".getBytes(), "zhangwuji".getBytes());
table.put(p);
table.close();
}
@Test
public void testGet() throws Exception{
HTable table = new HTable(conf, "person_info");
Get get = new Get(Bytes.toBytes("person_rk_bj_zhang_000001"));
get.setMaxVersions(5);
Result result = table.get(get);
List<Cell> cells = result.listCells();
// result.getValue(family, qualifier); 可以从result中直接取出一个特定的value
//遍历出result中所有的键值对
for(KeyValue kv : result.list()){
String family = new String(kv.getFamily());
System.out.println(family);
String qualifier = new String(kv.getQualifier());
System.out.println(qualifier);
System.out.println(new String(kv.getValue()));
}
table.close();
}
/**
* 多种过滤条件的使用方法
* @throws Exception
*/
@Test
public void testScan() throws Exception{
HTable table = new HTable(conf, "person_info".getBytes());
Scan scan = new Scan(Bytes.toBytes("person_rk_bj_zhang_000001"), Bytes.toBytes("person_rk_bj_zhang_000002"));
//前缀过滤器----针对行键
Filter filter = new PrefixFilter(Bytes.toBytes("rk"));
//行过滤器
ByteArrayComparable rowComparator = new BinaryComparator(Bytes.toBytes("person_rk_bj_zhang_000001"));
RowFilter rf = new RowFilter(CompareOp.LESS_OR_EQUAL, rowComparator);
/**
* 假设rowkey格式为:创建日期_发布日期_ID_TITLE
* 目标:查找 发布日期 为 2014-12-21 的数据
*/
rf = new RowFilter(CompareOp.EQUAL , new SubstringComparator("_2014-12-21_"));
//单值过滤器 1 完整匹配字节数组
new SingleColumnValueFilter("base_info".getBytes(), "name".getBytes(), CompareOp.EQUAL, "zhangsan".getBytes());
//单值过滤器2 匹配正则表达式
ByteArrayComparable comparator = new RegexStringComparator("zhang.");
new SingleColumnValueFilter("info".getBytes(), "NAME".getBytes(), CompareOp.EQUAL, comparator);
//单值过滤器2 匹配是否包含子串,大小写不敏感
comparator = new SubstringComparator("wu");
new SingleColumnValueFilter("info".getBytes(), "NAME".getBytes(), CompareOp.EQUAL, comparator);
//键值对元数据过滤-----family过滤----字节数组完整匹配
FamilyFilter ff = new FamilyFilter(
CompareOp.EQUAL ,
new BinaryComparator(Bytes.toBytes("base_info")) //表中不存在inf列族,过滤结果为空
);
//键值对元数据过滤-----family过滤----字节数组前缀匹配
ff = new FamilyFilter(
CompareOp.EQUAL ,
new BinaryPrefixComparator(Bytes.toBytes("inf")) //表中存在以inf打头的列族info,过滤结果为该列族所有行
);
//键值对元数据过滤-----qualifier过滤----字节数组完整匹配
filter = new QualifierFilter(
CompareOp.EQUAL ,
new BinaryComparator(Bytes.toBytes("na")) //表中不存在na列,过滤结果为空
);
filter = new QualifierFilter(
CompareOp.EQUAL ,
new BinaryPrefixComparator(Bytes.toBytes("na")) //表中存在以na打头的列name,过滤结果为所有行的该列数据
);
//基于列名(即Qualifier)前缀过滤数据的ColumnPrefixFilter
filter = new ColumnPrefixFilter("na".getBytes());
//基于列名(即Qualifier)多个前缀过滤数据的MultipleColumnPrefixFilter
byte[][] prefixes = new byte[][] {Bytes.toBytes("na"), Bytes.toBytes("me")};
filter = new MultipleColumnPrefixFilter(prefixes);
//为查询设置过滤条件
scan.setFilter(filter);
scan.addFamily(Bytes.toBytes("base_info"));
ResultScanner scanner = table.getScanner(scan);
for(Result r : scanner){
/**
for(KeyValue kv : r.list()){
String family = new String(kv.getFamily());
System.out.println(family);
String qualifier = new String(kv.getQualifier());
System.out.println(qualifier);
System.out.println(new String(kv.getValue()));
}
*/
//直接从result中取到某个特定的value
byte[] value = r.getValue(Bytes.toBytes("base_info"), Bytes.toBytes("name"));
System.out.println(new String(value));
}
table.close();
}
@Test
public void testDel() throws Exception{
HTable table = new HTable(conf, "user");
Delete del = new Delete(Bytes.toBytes("rk0001"));
del.deleteColumn(Bytes.toBytes("data"), Bytes.toBytes("pic"));
table.delete(del);
table.close();
}
public static void main(String[] args) throws Exception {
Configuration conf = HBaseConfiguration.create();
// conf.set("hbase.zookeeper.quorum", "weekend05:2181,weekend06:2181,weekend07:2181");
HBaseAdmin admin = new HBaseAdmin(conf);
TableName tableName = TableName.valueOf("person_info");
HTableDescriptor td = new HTableDescriptor(tableName);
HColumnDescriptor cd = new HColumnDescriptor("base_info");
cd.setMaxVersions(10);
td.addFamily(cd);
admin.createTable(td);
admin.close();
}
}

hbase的集群架构和表存储机制

  • 对事务的支持性很弱
  • 支持简单的查询,但是对数据的支持很大
  • hbase表结构:建表时,不需要限定表中的字段,只需要指定若干个列族
    • 插入数据时,列族中可以存储任意多个列(KV,列名,列值)
  • 一个value可以有多个版本,通过版本来区分(时间戳)
  • 要查询某一个具体字段的值,需要指定的坐标:表名—–>行键—–>列族(ColumnFamily):列名(Qualifier)—–>版本

集群架构

  • 存储的时候会把表分片,然后存在Region Server中,通常跟DataNode在一台机器上,然后Region Server存储在Hbase上。
  • 还有一个协调者叫做,HMaster也叫做主节点,不负责存储表数据,负责:
    • 管理RegionServer的状态
    • 负责RegionServer的负载均衡
  • 在查找数据的时候,会有一个专门记录信息在哪台Region的表叫做META表,然后这张表还会在分片放在Region中还有一张记录这张表的存储信息的表叫做ROOT表。

  • Region中其实也是很丰富的,里面有HLogH,还有store,一个store存储一个region中的列族,store里面还有一块内存叫做Hfile表示访问频率多的数据。
张冲 wechat
欢迎扫一扫上面的微信关注我,一起交流!
坚持原创技术分享,您的支持将鼓励我继续创,点击打赏!