超详细梳理HBase核心知识点(上)建议收藏
Posted 叹了口丶气
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了超详细梳理HBase核心知识点(上)建议收藏相关的知识,希望对你有一定的参考价值。
之前我的公众号名字叫做:’‘Java不睡觉’’,原因就是当时看了一本书,名字是《HBase不睡觉书》。这本书正如其名字一样,是一本让人读起来根本不会发困的书,very奈斯。本文就是整理了这本书上的知识点而形成的文章,准备分为上下两篇文章系统梳理HBase核心知识点,如果你想了解HBase,那么这篇文章不会让你失望的,同时推荐阅读一下原书。让我们开始吧。
文章目录
前言
HBase 是一个开源的、面向列的非关系型分布式数据库,目前是Hadoop体系中非常关键的一部分。
在最初,HBase是基于谷歌的 BigTable 原型实现的,许多技术来自于Fay Chang在2006年所撰写的Google论文"BigTable"。与 BigTable基于Google文件系统(File System)一样,HBase则是基于HDFS(Hadoop的分布式文件系统)之上而开发的。
HBase 采用 Java 语言实现,在其内部实现了BigTable论文提到的一些压缩算法、内存操作和布隆过滤器等,这些能力使得HBase 在海量数据存储、高性能读写场景中得到了大量应用,如 Facebook 在 2010年11 月开始便一直选用 HBase来作为消息平台的存储层技术。
HBase 以 Apache License Version 2.0开源,这是一种对商业应用友好的协议,同时该项目当前也是Apache软件基金会的顶级项目之一。
有什么特性?
- 基于列式存储模型,对于数据实现了高度压缩,节省存储成本
- 采用 LSM 机制而不是B(+)树,这使得HBase非常适合海量数据实时写入的场景
- 高可靠,一个数据会包含多个副本(默认是3副本),这得益于HDFS的复制能力,由RegionServer提供自动故障转移的功能
- 高扩展,支持分片扩展能力(基于Region),可实现自动、数据均衡
- 强一致性读写,数据的读写都针对主Region上进行,属于CP型的系统
- 易操作,HBase提供了Java API、RestAPI/Thrift API等接口
- 查询优化,采用Block Cache 和 布隆过滤器来支持海量数据的快速查找
一、整体架构
整个HBase 集群主要由 Zookeeper、HBase Master、HBase RegionServer和HDFS构成。HBase集群架构图如下:
其中 Master 节点是允许存在多个的,当多个 Master 节点共存时,只有一个 Master 是提供服务的,这种主备角色的"仲裁"由 ZooKeeper 实现。
RegionServer是直接负责存储数据的服务器,RegionServer保存的表数据直接存储在Hadoop的HDFS上。RegionServer非常依赖ZooKeeper服务。ZooKeeper管理了HBase中所有的RegionServer的信息,包括具体的数据段存放在哪个 RegionServer上。 客户端每次与HBase连接,其实都是先与ZooKeeper通信,查询出具体需要连接哪个RegionServer,然后再连接到RegionServer。
1.1 Region
Region就是一段数据的集合。HBase中的表一般拥有一个到多个Region。Region具有以下特性:
- Region不能跨服务器,一个RegionServer上有一个或者多个 Region。
- 数据量小的时候,一个Region足以存储所有数据;但是,当数据 量大的时候,HBase会拆分Region。
- 当HBase在进行负载均衡的时候,也有可能会从一台 RegionServer上把Region移动到另一台RegionServer上。
- Region是基于HDFS的,它的所有数据存取操作都是调用了HDFS的 客户端接口来实现的。
1.2 RegionServer
RegionServer就是存放Region的容器,直观上说就是服务器上的一 个服务。当客户端从ZooKeeper获取RegionServer的地址后,它会直接从 RegionServer获取数据。
1.3 Master
在1.2小节中提到过,客户端从 ZooKeeper获取了RegionServer的地址后,会直接从RegionServer获取数据。其实不光是获取数据,包括插入、删除等所有的数据操作都是直接操作RegionServer,而不需要经过Master。
不像Hadoop等其他分布式系统,在HBase中,Master更像是一个打杂的。Master只负责各种协调工作,比如建表、删表、 移动Region、合并等操作。它们的共性就是需要跨RegionServer,这些 操作由哪个RegionServer来执行都不合适,所以HBase就将这些操作放 到了Master上了。
这种结构的好处是大大降低了集群对Master的依赖。Master节点一般只有一个到两个,一旦宕机,如果集群对Master的依赖度很大,那么就会产生单点故障问题。在HBase中,即使Master宕机了,集群依然 可以正常地运行,依然可以存储和删除数据。
1.4 Zookeeper
Zookeeper 对于 HBase的作用是至关重要的。
- Zookeeper 提供了 HBase Master 的高可用实现,并保证同一时刻有且仅有一个主 Master 可用。
- Zookeeper 保存了 Region 和 Region Server 的关联信息(提供寻址入口),并保存了集群的元数据(Schema/Table)。
- Zookeeper 实时监控Region server的上线和下线信息,并实时通知Master。
除了 HBase之外,有许多分布式大数据相关的开源框架,都依赖于 Zookeeper 实现 HA。
1.5 微观架构
HBase是一个分布式列式数据库,最基本的存储单位是列(column),一个列或者多个列形成一行(row)。在HBase中,这一行有三个列a、b、 c,下一个行也许是有4个列a、e、f、g。行跟行的列可以完全不一样,这个行的数据跟另外一个行的数据也可以存储在不同的机器上,甚至同一行内的列也可以存储在完全不同的机器上!
每个行(row)都拥有唯一的行键(row key)来标定这个行的唯一 性。每个列都有多个版本,多个版本的值存储在单元格(cell)中。
综上,HBase的存储结构可以表示成下图所示的结构:
二、常用Shell命令与API
2.1 常用Shell命令
①进入hbase命令行:
$HBASE_HOME/bin/hbase shell
②create命令建表
在hbase shell下执行:
create 'zhb_test', 'cf'
创建一个名为’zhb_test’的表,并且带有一个名为’cf’的列族。
补充一下列族的知识点:
前面提到过,HBase是一个分布式列式数据库。
HBase的表都是由列族(Column Family)组成的;
没有列族的表是没有意义的;
列并不是依附于表上,而是依附于列族上;如下图所示:
通过刚才的命令,我们现在建立的表有一个列族,叫cf,但是我们没有指定这个列族里面有什么列。向表TableA中插入数据时,你只是向HBase中插入了一个单元格(Cell),而这个单元格是由表:列族:行:列来定位的,而别的行有没有此列HBase并不知道。
HBase的所有数据属性都是定义在列族上的。同一个表的不同列族可以定义完全不同的两套属性,所以从这个意义上来说,列族更像是传统关系数据库中的表,而表本身反倒变成只是存放列族的空壳了。
③ list命令看到整个库中有哪些表
可以看到库里共有15个表,包括我们刚才创建的’zhb_test’
④ describe命令来查看表属性
输入命令alter ‘zhb_test’, 'cf2’再添加一个列族,然后再describe看一下:
可以看到现在describe输出的是两个元素,分别对应cf和cf2两 个列族。也印证了我们之前说的数据属性是存在于列族上的。
补充:
在执行alter命令之前,最好先停用(disable)这个表 。因为对列族的所有操作都会同步到所有拥有这个表的RegionServer上,你在执行命令的时候可以看到总共 有多少个RegionServer,当前执行了几个RegionServer。当有很多客户端都在连着的时候,直接新增一个列族对性能的影响较大。
⑤ put命令来插入数据
在HBase中,如果你的一行有10列,那存储一行的数据得写10行的 语句。这是因为HBase中行的每一个列都存储在不同的位置,你必须指 定你要存储在哪个单元格;而单元格需要根据表、行、列这几个维度来 定位。
执行命令:
put ‘zhb_test’, ‘rowkey1’, ‘cf:name’, ‘zhb’
表示:
往’zhb_test’表插入一个单元格。这个单元格的rowkey为’rowkey1’,也就是说它是属于’rowkey1’这个行中的 一个列,该单元格的列族为’cf’,该单元格的列名为’name’。数据值为’zhb’。
之后我们用scan命令扫描一下表,就可以看到我们刚才插入的数据了:
scan ‘zhb_test’
看这条记录的时候,你会看到时间戳属性。每一个单元格都可 以存储多个版本(version)的值。HBase的单元格并没有version这个 属性,它用timestamp来存储该条记录的时间戳,这个时间戳就用来当 版本号使用。如果你在写put语句的时候不指定时间戳,系统就会自动用当前时 间帮你指定它。有意思的是,这个timestamp虽然说是时间的标定,其 实你可以输入任意的数字,比如1、2、3都可以存储进去。当你用scan命令的时候HBase会显示拥有最大(最新)的timestamp的数据版本。可以指定列族中保存的Cell版本数。
⑥ get命令获取单元格数据
过get只能查询一个单元格的记录,在表的数据很大的时候,get查询 的速度远远高于scan。
get ‘zhb_test’, ‘rowkey1’, ‘cf:name’
2.2 Java API
Hbase API 文档:https://hbase.apache.org/apidocs/index.html
创建一个Maven项目,在pom.xml中添加如下依赖:
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.0.0</version>
</dependency>
HbaseClient.java
package javaa.sg.bigo;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HbaseClient {
// config zookeeper
static private org.apache.hadoop.conf.Configuration configuration = null;
static private Connection connection = null;
private static Logger logger = LoggerFactory.getLogger(HbaseClient.class);
static private Lock lock = new ReentrantLock();
static Connection getConnectionInstance() {
if (null == connection) {
lock.lock();
try {
if (null == connection) {
configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum", "zk1:2182,zk2:2182,zk3:2182");
configuration.set("hbase.client.keyvalue.maxsize", "100000000");
connection = ConnectionFactory.createConnection(configuration);
}
} catch (IOException e) {
logger.error("create hbase error ", e);
} finally {
lock.unlock();
}
}
return connection;
}
}
使用demo
// 懒加载单例模式
static private Connection connection = HbaseClient.getConnectionInstance();
/**
* 创建表
*
* @param tableName
*/
public static void createTable(String tableStr, String[] familyNames) {
System.out.println("start create table ......");
try {
Admin admin = connection.getAdmin();
TableName tableName = TableName.valueOf(tableStr);
if (admin.tableExists(tableName)) {// 如果存在要创建的表,那么先删除,再创建
admin.disableTable(tableName);
admin.deleteTable(tableName);
System.out.println(tableName + " is exist,detele....");
}
HTableDescriptor tableDescriptor = new HTableDescriptor(tableName);
// 添加表列信息
if (familyNames != null && familyNames.length > 0) {
for (String familyName : familyNames) {
tableDescriptor.addFamily(new HColumnDescriptor(familyName));
}
}
admin.createTable(tableDescriptor);
} catch (MasterNotRunningException e) {
e.printStackTrace();
} catch (ZooKeeperConnectionException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("end create table ......");
}
/**
* 添加行列数据数据
*
* @param tableName
* @throws Exception
*/
public static void insertData(String tableName, String rowId, String familyName,String qualifier, String value) throws Exception {
System.out.println("start insert data ......");
Table table = connection.getTable(TableName.valueOf(tableName));
Put put = new Put(rowId.getBytes());// 一个PUT代表一行数据,再NEW一个PUT表示第二行数据,每行一个唯一的ROWKEY,此处rowkey为put构造方法中传入的值
put.addColumn(familyName.getBytes(), qualifier.getBytes(), value.getBytes());// 本行数据的第一列
try {
table.put(put);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("end insert data ......");
}
/**
* 添加行列数据数据
*
* @param tableName
* @throws Exception
*/
public static void batchInsertData(String tableName, String rowId, List<String> familyNames,
String qualifier, List<String> values) throws Exception {
if (null == qualifier) qualifier = "tmp";
Table table = connection.getTable(TableName.valueOf(tableName));
Put put = new Put(rowId.getBytes());// 一个PUT代表一行数据,再NEW一个PUT表示第二行数据,每行一个唯一的ROWKEY,此处rowkey为put构造方法中传入的值
for (int i = 0; i < familyNames.size(); ++i) {
put.addColumn(familyNames.get(i).getBytes(),
qualifier.getBytes(), values.get(i).getBytes());// 本行数据的第一列
}
try {
table.put(put);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除行
*
* @param tablename
* @param rowkey
*/
public static void deleteRow(String tablename, String rowkey) {
try {
Table table = connection.getTable(TableName.valueOf(tablename));
Delete d1 = new Delete(rowkey.getBytes());
table.delete(d1);//d1.addColumn(family, qualifier);d1.addFamily(family);
System.out.println("删除行成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 查询所有数据
*
* @param tableName
* @throws Exception
*/
public static void queryAll(String tableName) throws Exception {
Table table = connection.getTable(TableName.valueOf(tableName));
try {
ResultScanner rs = table.getScanner(new Scan());
for (Result r : rs) {
System.out.println("获得到rowkey:" + new String(r.getRow()));
for (Cell keyValue : r.rawCells()) {
System.out.println("列:" + new String(CellUtil.cloneFamily(keyValue))+":"+
new String(CellUtil.cloneQualifier(keyValue)) + "====值:" + new String(CellUtil.cloneValue(keyValue)));
}
}
rs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 根据rowId查询
*
* @param tableName
* @throws Exception
*/
public static void queryByRowId(String tableName, String rowId) throws Exception {
Table table = connection.getTable(TableName.valueOf(tableName));
try {
Get scan = new Get(rowId.getBytes());// 根据rowkey查询
Result r = table.get(scan);
System.out.println("获得到rowkey:" + new String(r.getRow()));
for (Cell keyValue : r.rawCells()) {
System.out.println("列:" + new String(CellUtil.cloneFamily(keyValue))+":"+
new String(CellUtil.cloneQualifier(keyValue)) + "====值:" + new String(CellUtil.cloneValue(keyValue)));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 根据列条件查询
*
* @param tableName
*/
public static void queryByCondition(String tableName, String familyName,String qualifier,String value) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Filter filter = new SingleColumnValueFilter(Bytes.toBytes(familyName),
Bytes.toBytes(qualifier), CompareOp.EQUAL, Bytes.toBytes(value)); // 当列familyName的值为value时进行查询
Scan s = new Scan();
s.setFilter(filter);
ResultScanner rs = table.getScanner(s);
for (Result r : rs) {
System.out.println("获得到rowkey:" + new String(r.getRow()));
for (Cell keyValue : r.rawCells()) {
System.out.println("列:" + new String(CellUtil.cloneFamily(keyValue))+":"+
new String(CellUtil.cloneQualifier(keyValue)) + "====值:" + new String(CellUtil.cloneValue(keyValue)));
}
}
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 多条件查询
*
* @param tableName
*/
public static void queryByConditions(String tableName, String[] familyNames, 以上是关于超详细梳理HBase核心知识点(上)建议收藏的主要内容,如果未能解决你的问题,请参考以下文章
C语言入门基础必备知识串讲-详细梳理!(新人疑惑解决,建议收藏)
C语言入门基础必备知识串讲-详细梳理!(新人疑惑解决,建议收藏)
C语言入门基础必备知识串讲-详细梳理!(新人疑惑解决,建议收藏)