HBase核心原理基础入门
HBaseHBase 核心概念介绍什么是hbase?HBase是一个开源的非关系型分布式数据库(NoSQL),它参考了谷歌的BigTable建模,实现的编程语言为 Java。它是Apache软件基金会的Hadoop项目的一部分,运行于HDFS文件系统之上,为 Hadoop 提供类似于BigTable 规模的服务。因此,它可以对稀疏文件提供极高的容错率。HBase在列上实现了BigTable论...
HBase基础
HBase 核心原理
什么是 Hbase?
HBase是一个开源的非关系型分布式数据库(NoSQL),它参考了谷歌的BigTable建模,实现的编程语言为 Java。它是Apache软件基金会的Hadoop项目的一部分,运行于HDFS文件系统之上,为 Hadoop 提供类似于BigTable 规模的服务。因此,它可以对稀疏文件提供极高的容错率。HBase在列上实现了BigTable论文提到的压缩算法、内存操作和布隆过滤器。HBase的表能够作为MapReduce任务的输入和输出,可以通过JavaAPI来访问数据,也可以通过REST、Avro或者Thrift的API来访问。
虽然最近性能有了显著的提升,HBase 还不能直接取代SQL数据库。如今,它已经应用于多个数据驱动型网站,包括 Facebook的消息平台。在 Eric Brewer的CAP理论中,HBase属于CP类型的系统。
HBase 设计模型
HBase中的每一张表就是所谓的BigTable。BigTable会存储一系列的行记录,行记录有三个基本类型的定义:
1、RowKey
是行在BigTable中的唯一标识。
2、TimeStamp:
是每一次数据操作对应关联的时间戳,可以看作SVN的版本。
3、Column:
定义为:
Hbase 逻辑存储模型
HBase 是以表的形式存储数据,表由行和列组成。列划分为若干个列簇,列簇中存储多个 key:value。
RowKey
与NoSQL数据库一样,rowkey是用来检索记录的主键。访问HBase Table中的行,只有三种方式:
1.通过单个rowkey访问
2.通过rowkey的range
3.全表扫描
rowkey行键可以任意字符串(最大长度64KB,实际应用中长度一般为10-100bytes),在HBase内部RowKey保存为字节数组。
存储时,数据按照RowKey的字典序(byte order)排序存储,设计key时,要充分了解这个特性,将经常一起读取的行存放在一起。
需要注意的是:行的一次读写是原子操作(不论一次读写多少列)
列簇
HBase表中的每个列,都归属于某个列簇,列簇是表的 schema 的一部分(而列不是),必须在使用表之前定义。列名都以列簇作为前缀。例如:
user:name, user:age 都属于 user 这个列簇。
实际应用中,列簇上的控制权限能帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、
一些应用可以读取基本数据并创建继承的列簇、一些应用则只允许浏览数据(设置可能因为隐私的原因不能浏览所有数据)。
时间戳
HBase中通过row和columns确定的为一个存储单元称为cell。每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引。
时间戳的类型是64位整型。时间戳可以由HBase在写入时自动赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显示赋值。
如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个cell中在不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多的版本造成的管理负担,HBase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本
Cell
由{row key, column(=+), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存储。
Hbase 物理存储模型
Table 在行的方向上分割为多个 HRegion,每个 HRegion 分散在不同的 RegionServer 中。
每个HRegion由多个Store构成,每个Store由一个MemStore和零或多个StoreFile组成,每个Store保存一个Columns Family,StoreFile以HFile格式存储在HDFS中。
HBase 存储架构
HBase 核心组件
1、Master
-
管理HRegionServer,实现其负载均衡。
-
管理和分配HRegion,比如在HRegion split时分配新的HRegion;在HRegionServer退出时迁移其负责的HRegion到其他HRegionServer上。
-
Admin职能创建、删除、修改Table的定义。实现DDL操作(namespace和table的增删改,column familiy的增删改等)。
-
管理namespace和table的元数据(实际存储在HDFS上)。
-
权限控制(ACL)。
-
监控集群中所有HRegionServer的状态(通过Heartbeat和监听ZooKeeper中的状态)。
2、RegionServer
-
管理自己所负责的region数据的读写。
-
读写HDFS,管理Table中的数据。
-
Client直接通过HRegionServer读写数据(从HMaster中获取元数据,找到RowKey所在的HRegion/HRegionServer后)。
Zookeeper集群所起作用
-
存放整个HBase集群的元数据以及集群的状态信息。
-
实现HMaster主从节点的failover。
注: HMaster通过监听ZooKeeper中的Ephemeral节点(默认:/hbase/rs/*)来监控HRegionServer的加入和宕机。
在第一个HMaster连接到ZooKeeper时会创建Ephemeral节点(默认:/hbasae/master)来表示Active的HMaster,其后加进来的HMaster则监听该Ephemeral节点
如果当前Active的HMaster宕机,则该节点消失,因而其他HMaster得到通知,而将自身转换成Active的HMaster,在变为Active的HMaster之前,它会在/hbase/masters/下创建自己的Ephemeral节点。
HBase读写数据流程
1、在HBase 0.96以前,HBase有两个特殊的Table:-ROOT-和.META. 用来记录用户表的rowkey范围所在的的regionserver服务器;因而客户端读写数据时需要通过3次寻址请求来对数据所在的regionserver进行定位,效率低下;
2、而在HBase 0.96以后去掉了-ROOT- Table,只剩下这个特殊的目录表叫做Meta Table(hbase:meta),它存储了集群中所有用户HRegion的位置信息,而ZooKeeper的节点中(/hbase/meta-region-server)存储的则直接是这个Meta Table的位置,并且这个Meta Table如以前的-ROOT- Table一样是不可split的。这样,客户端在第一次访问用户Table的流程就变成了:
A.从ZooKeeper(/hbase/meta-region-server)中获取hbase:meta的位置(HRegionServer的位置),缓存该位置信息。
B.从HRegionServer中查询用户Table对应请求的RowKey所在的HRegionServer,缓存该位置信息。
C.从查询到HRegionServer中读取Row。
注:客户会缓存这些位置信息,然而第二步它只是缓存当前RowKey对应的HRegion的位置,因而如果下一个要查的RowKey不在同一个HRegion中,则需要继续查询hbase:meta所在的HRegion,然而随着时间的推移,客户端缓存的位置信息越来越多,以至于不需要再次查找hbase:meta Table的信息,除非某个HRegion因为宕机或Split被移动,此时需要重新查询并且更新缓存。
-
hbase:meta表存储了所有用户HRegion的位置信息:
-
Rowkey:tableName,regionStartKey,regionId,replicaId等;
-
info列族:这个列族包含三个列,他们分别是:
-
info:regioninfo列:
-
regionId,tableName,startKey,endKey,offline,split,replicaId;
-
info:server列:HRegionServer对应的server:port;
-
info:serverstartcode列:HRegionServer的启动时间戳。
-
RegionServer 内部机制
-
WAL即Write Ahead Log,在早期版本中称为HLog,它是HDFS上的一个文件,如其名字所表示的,所有写操作都会先保证将数据写入这个Log文件后,才会真正更新MemStore,最后写入HFile中。WAL文件存储在/hbase/WALs/${HRegionServer_Name}的目录中
-
BlockCache是一个读缓存,即“引用局部性”原理(也应用于CPU,分空间局部性和时间局部性,空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能。
-
HRegion是一个Table中的一个Region在一个HRegionServer中的表达。一个Table可以有一个或多个Region,他们可以在一个相同的HRegionServer上,也可以分布在不同的HRegionServer上,一个HRegionServer可以有多个HRegion,他们分别属于不同的Table。HRegion由多个Store(HStore)构成,每个HStore对应了一个Table在这个HRegion中的一个Column Family,即每个Column Family就是一个集中的存储单元,因而最好将具有相近IO特性的Column存储在一个Column Family,以实现高效读取(数据局部性原理,可以提高缓存的命中率)。HStore是HBase中存储的核心,它实现了读写HDFS功能,一个HStore由一个MemStore 和0个或多个StoreFile组成。
-
MemStore是一个写缓存(In Memory Sorted Buffer),所有数据的写在完成WAL日志写后,会 写入MemStore中,由MemStore根据一定的算法将数据Flush到地层HDFS文件中(HFile),通常每个HRegion中的每个 Column Family有一个自己的MemStore。
-
HFile(StoreFile) 用于存储HBase的数据(Cell/KeyValue)。在HFile中的数据是按RowKey、Column Family、Column排序,对相同的Cell(即这三个值都一样),则按timestamp倒序排列。
-
FLUSH详述
-
每一次Put/Delete请求都是先写入到MemStore中,当MemStore满后会Flush成一个新的StoreFile(底层实现是HFile),即一个HStore(Column Family)可以有0个或多个StoreFile(HFile)。
-
当一个HRegion中的所有MemStore的大小总和超过了hbase.hregion.memstore.flush.size的大小,默认128MB。此时当前的HRegion中所有的MemStore会Flush到HDFS中。
-
当全局MemStore的大小超过了hbase.regionserver.global.memstore.upperLimit的大小,默认40%的内存使用量。此时当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中,Flush顺序是MemStore大小的倒序(一个HRegion中所有MemStore总和作为该HRegion的MemStore的大小还是选取最大的MemStore作为参考?有待考证),直到总体的MemStore使用量低于hbase.regionserver.global.memstore.lowerLimit,默认38%的内存使用量。
-
当前HRegionServer中WAL的大小超过了
hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs
的数量,当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中,
Flush使用时间顺序,最早的MemStore先Flush直到WAL的数量少于
hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs
这里说这两个相乘的默认大小是2GB,查代码,hbase.regionserver.max.logs默认值是32,而hbase.regionserver.hlog.blocksize默认是32MB。但不管怎么样,因为这个大小超过限制引起的Flush不是一件好事,可能引起长时间的延迟
-
HBase 安装和使用
Hbase集群搭建
HBASE是一个分布式系统,其中有一个管理角色: HMaster(一般2台,一台active,一台backup)
其他的数据节点角色:HRegionServer(很多台,看数据容量)
首先,要有一个HDFS集群,并正常运行; regionserver应该跟hdfs中的datanode在一起
其次,还需要一个zookeeper集群,并正常运行
# 1、下载
wget http://archive.apache.org/dist/hbase/hbase-1.2.9/hbase-1.2.9-bin.tar.gz
# 2、解压
tar -zxvf hbase-1.2.9-bin.tar.gz -C /xxx/xxx
# 3、修改$HBASE_HOME/bin/hbase-env.sh文件修改配置如下
export JAVA_HOME=/usr/local/jdk1.8.0_192
export HBASE_MANAGES_ZK=false
# 4、修改或新建$HBASE_HOME/bin/hbase-site.xml文件
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<!-- 指定hbase在HDFS上存储的路径 -->
<property>
<name>hbase.rootdir</name>
<value>hdfs://node1:9000/hbase</value>
</property>
<!-- 指定hbase是分布式的 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- 指定zk的地址,多个用“,”分割 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>node2,node3,node4</value>
</property>
</configuration>
# 5、修改 $HBASE_HOME/bin/regionservers文件(配置的是regionserver节点)
node2
node3
node4
# 6、启动集群
$HBASE_HOME/bin/start-hbase.sh
# 7、启动完后,还可以在集群中找任意一台机器启动一个备用的master,新启的这个master会处于backup状态
$HBASE_HOME/bin/hbase-daemon.sh start master
# 8、启动hbase的命令行客户端
$HBASE_HOME/bin/hbase shell
Hbase> list // 查看表
Hbase> status // 查看集群状态
Hbase> version // 查看集群版本
常用shell命令
command | 操作 | 描述 |
---|---|---|
create | create ‘t_user’,‘family1’,‘family2’ | 创建表(包含两个列簇) |
alter | alter ‘t_user’,‘family3’ | 修改列族,t_user添加一个列簇 |
put | put ‘t_user’, ‘row1’, ‘family1:name’, ‘james’ | 向指向的表单元添数据(put 表名, rowKye, 列簇:key, value) |
get | get ‘t_user’,‘row’ | 获取行或单元(cell)的值 |
count | count ‘t_user’ | 统计表中行的数量 |
describe | describe ‘t_user’ | 显示表相关的详细信息 |
delete | delete ‘t_user’,‘row1’,‘family1:name’ | 删除指定对象的值(delete 表名, rowKey, 列簇:key) |
deleteall | deleteall ‘t_user’,‘row1’ | 删除指定行(rowKey)的所有数据 |
disable | disable ‘t_user’ | 使表无效 |
drop | drop ‘t_user’ | 删除表(需要先禁用表) |
truncate | truncate ‘t_user’ | 清空表数据 |
enable | enable ‘t_user’ | 使表有效 |
exists | exists ‘t_user’ | 测试表是否存在 |
tools | tools | 列出hbase所支持的工具 |
scan | scan ‘t_user’ | 通过对表的扫描来获取对用的值 |
status | status | 返回hbase集群的状态信息 |
shutdown | shutdown | 关闭hbase集群(与exit不同) |
version | version | 返回hbase版本信息 |
exit | exit | 退出hbase shell |
注意:hbase命令操作的时候记得加上单引号。
Hbase Java 客户端
- HBASE依赖
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.2.9</version>
</dependency>
- Hbase Java 客户端操作
package com.leone.bigdata.hbase.client;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.regionserver.BloomType;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* <p> HBase 客户端操作
*
* @author leone
* @since 2018-12-16
**/
public class HBaseClientTest {
private static final Logger LOG = LoggerFactory.getLogger(HBaseClientTest.class);
// 表名
private static final String tableName = "user";
// 列簇
private static final String family1 = "family1", family2 = "family2", family3 = "family3";
// 连接对象
private Connection conn;
private Admin admin;
// 配置对象
private Configuration conf;
@Before
public void init() throws Exception {
// 创建连接对象,会自动加载HBase配置文件 zookeeper集群的URL配置信息
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "host:2181");
conn = ConnectionFactory.createConnection(conf);
// 创建ddl描述对象
admin = conn.getAdmin();
}
/**
* 创建表
* create 'user', 'family1', 'family2'
*
* @throws Exception
*/
@Test
public void createTable() throws Exception {
// 创建表描述对象
HTableDescriptor table = new HTableDescriptor(TableName.valueOf(tableName));
// 创建列簇描述对象
HColumnDescriptor column1 = new HColumnDescriptor(family1);
// 设置保存数据的最大半本数量是3
column1.setMaxVersions(3);
HColumnDescriptor column2 = new HColumnDescriptor(family2);
table.addFamily(column1);
table.addFamily(column2);
admin.createTable(table);
}
/**
* 删除表
* disable 'user'
* drop 'user'
*
* @throws Exception
*/
@Test
public void dropTable() throws Exception {
// 先停用表
admin.disableTable(TableName.valueOf(tableName));
// 再删除表
admin.deleteTable(TableName.valueOf(tableName));
}
/**
* 修改表,添加列簇
* alter 'user', 'family3'
*
* @throws Exception
*/
@Test
public void alterTable() throws Exception {
// 取出旧的的表的描述信息
HTableDescriptor table = admin.getTableDescriptor(TableName.valueOf(tableName));
HColumnDescriptor column = new HColumnDescriptor(family3);
// 设置布隆过滤器
column.setBloomFilterType(BloomType.ROWCOL);
table.addFamily(column);
admin.modifyTable(TableName.valueOf(tableName), table);
}
/**
* 查看表定义信息
* desc 'user'
*
* @throws Exception
*/
@Test
public void descTable() throws Exception {
HTableDescriptor table = admin.getTableDescriptor(TableName.valueOf(tableName));
HColumnDescriptor[] columnFamilies = table.getColumnFamilies();
LOG.info(table.getNameAsString());
for (HColumnDescriptor hcd : columnFamilies) {
LOG.info("HColumn: {}", Bytes.toString(hcd.getName()));
}
}
/**
* DML 操作,向 HBase 插入数据
* put 'user', 'row1', 'family1:name', 'james'
*
* @throws Exception
*/
@Test
public void put() throws Exception {
// 获取指定表对象,进行dml操作
Table table = conn.getTable(TableName.valueOf(tableName));
List<Put> rows = new ArrayList<>();
/*
* 0 代表前面补充0
* 4 代表长度为4
* d 代表参数为正数型
*/
for (int i = 1; i <= 10; i++) {
Put row = new Put(Bytes.toBytes(String.format("%04d", i)));
row.addColumn(Bytes.toBytes(family1), Bytes.toBytes("name"), Bytes.toBytes("james" + i));
row.addColumn(Bytes.toBytes(family1), Bytes.toBytes("age"), Bytes.toBytes(i + 20));
row.addColumn(Bytes.toBytes(family2), Bytes.toBytes("address"), Bytes.toBytes("address" + i));
rows.add(row);
}
LOG.info("Size:{}", rows.size());
table.put(rows);
}
/**
* DML 操作,删除数据 HBase 中数据
* delete 'user', '0001', 'family1:name'
*
* @throws Exception
*/
@Test
public void delete() throws Exception {
// 获取指定表对象,进行dml操作
Table table = conn.getTable(TableName.valueOf(tableName));
Delete delete1 = new Delete(Bytes.toBytes(String.format("%04d", 1)));
Delete delete2 = new Delete(Bytes.toBytes(String.format("%04d", 2)));
delete2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("name"));
List<Delete> deleteList = new ArrayList<>();
deleteList.add(delete1);
deleteList.add(delete2);
table.delete(deleteList);
}
/**
* DML 操作,删除数据 HBase 中数据
* deleteall 'user', '0003'
*
* @throws Exception
*/
@Test
public void deleteAll() throws Exception {
Table table = conn.getTable(TableName.valueOf(tableName));
Delete delete = new Delete(Bytes.toBytes(String.format("%04d", 3)));
table.delete(delete);
}
/**
* DML 操作,修改数据 HBase 中数据
*
* @throws Exception
*/
@Test
public void update() throws Exception {
// 获取指定表对象,进行dml操作
Table table = conn.getTable(TableName.valueOf(tableName));
}
/**
* DML 操作 HBase 查询数据
* get 'user', '0004'
*
* @throws Exception
*/
@Test
public void get() throws Exception {
Table table = conn.getTable(TableName.valueOf(tableName));
List<Get> gets = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Get get = new Get(Bytes.toBytes(String.valueOf(String.format("%04d", i))));
gets.add(get);
}
Result[] results = table.get(gets);
for (Result result : results) {
CellScanner cellScanner = result.cellScanner();
while (cellScanner.advance()) {
Cell cell = cellScanner.current();
// 行键的字节数组
byte[] rowArray = cell.getRowArray();
// 列簇的字节数组
byte[] familyArray = cell.getFamilyArray();
// 列名的字节数组
byte[] qualifierArray = cell.getQualifierArray();
// value的字节数组
byte[] valueArray = cell.getValueArray();
LOG.info("行键:{} \t 列簇:{} \t key:{} \t value:{}", new String(rowArray, cell.getRowOffset(), cell.getRowLength()), new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()), new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()), new String(valueArray, cell.getValueOffset(), cell.getValueLength()));
}
}
conn.close();
}
/**
* DML 操作 HBase 查询数据
* scan 'user'
*
* @throws Exception
*/
@Test
public void scan() throws Exception {
// 获取指定表对象,进行dml操作
Table table = conn.getTable(TableName.valueOf(tableName));
// 可以指定开始行键和结束行键
Scan scan = new Scan(Bytes.toBytes(String.format("%04d", 2)), Bytes.toBytes(String.format("%04d", 5)));
ResultScanner result = table.getScanner(scan);
for (Result rs : result) {
CellScanner cellScanner = rs.cellScanner();
while (cellScanner.advance()) {
Cell cell = cellScanner.current();
byte[] rowArray = cell.getRowArray();
// 列簇的字节数组
byte[] familyArray = cell.getFamilyArray();
// 列名的字节数组
byte[] qualifierArray = cell.getQualifierArray();
// value的字节数组
byte[] valueArray = cell.getValueArray();
LOG.info("行键:{} \t 列簇:{} \t key:{} \t value:{}", new String(rowArray, cell.getRowOffset(), cell.getRowLength()), new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()), new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()), new String(valueArray, cell.getValueOffset(), cell.getValueLength()));
}
}
}
/**
* 查询某列数据的某个版本
*
* @throws Exception
*/
@Test
public void getVersion() throws Exception {
HTable htable = new HTable(conf, tableName);
Get get = new Get(Bytes.toBytes("0004"));
get.addColumn(Bytes.toBytes(family1), Bytes.toBytes("name"));
get.setMaxVersions(2);
Result result = htable.get(get);
for (KeyValue cell : result.list()) {
byte[] rowArray = cell.getRowArray();
// 列簇的字节数组
byte[] familyArray = cell.getFamilyArray();
// 列名的字节数组
byte[] qualifierArray = cell.getQualifierArray();
// value的字节数组
byte[] valueArray = cell.getValueArray();
LOG.info("行键:{} \t 列簇:{} \t key:{} \t value:{} \ttimestamp:{}", new String(rowArray, cell.getRowOffset(), cell.getRowLength()), new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()), new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()), new String(valueArray, cell.getValueOffset(), cell.getValueLength()), cell.getTimestamp());
}
}
@After
public void close() throws IOException {
admin.close();
conn.close();
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)