漫画学习HBase—-最易懂的Hbase架构原理解析

HBase基于Google的BigTable论文,是建立的==HDFS==之上,提供高可靠性高性能列存储可伸缩实时读写的分布式数据库系统。

在需要==实时读写随机访问==超大规模数据集时,可以使用HBase。

点击跳转

数据模型

  • rowkey行键

    • table的主键,table中的记录按照rowkey 的字典序进行排序
    • Row key行键可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes)
  • Column Family列族

    • 列族或列簇
    • HBase表中的每个列,都归属与某个列族
    • 列族是表的schema的一部分(而列不是),即建表时至少指定一个列族
    • 比如创建一张表,名为user,有两个列族,分别是infodata,建表语句create 'user', 'info', 'data'
  • Column列

    • 列肯定是表的某一列族下的一个列,用列族名:列名表示,如info列族下的name列,表示为info:name
    • 属于某一个ColumnFamily,类似于我们mysql当中创建的具体的列
  • cell单元格

    • 指定row key行键、列族、列,可以确定的一个cell单元格

    • cell中的数据是没有类型的,全部是以字节数组进行存储

  • Timestamp时间戳
    • 可以对表中的Cell多次赋值,每次赋值操作时的时间戳timestamp,可看成Cell值的版本号version number
    • 即一个Cell可以有多个版本的值

整体架构

  • Client客户端

    • Client是操作HBase集群的入口
      • 对于管理类的操作,如表的增、删、改操纵,Client通过RPC与HMaster通信完成
      • 对于表数据的读写操作,Client通过RPC与RegionServer交互,读写数据
    • Client类型:
      • HBase shell / Java编程接口 /Thrift、Avro、Rest等等
  • ZooKeeper集群

    • 实现了HMaster的高可用,多HMaster间进行主备选举

    • 保存了HBase的元数据信息meta表,提供了HBase表中region的寻址入口的线索数据

    • 对HMaster和HRegionServer实现了监控

  • HMaster

    • HBase集群也是主从架构,HMaster是主的角色,是老大
    • 主要负责Table表和Region的相关管理工作:
    • 关于Table
      • 管理Client对Table的增删改的操作
      • 关于Region
        • 在Region分裂后,负责新Region分配到指定的HRegionServer上
        • 管理HRegionServer间的负载均衡,迁移region分布
        • 当HRegionServer宕机后,负责其上的region的迁移
  • HRegionServer

    • HBase集群中从的角色,是小弟

      • 响应客户端的读写数据请求
      • 负责管理一系列的Region
      • 切分在运行过程中变大的region
  • Region

    • HBase集群中分布式存储的最小单元
    • 一个Region对应一个Table表的部分数据

HBase使用,主要有两种形式:①命令;②Java编程

HBase Client

HBase Shell

cd /kfly/install/hbase-1.2.0-cdh5.14.2/
bin/HBase shell
# list table
list

# create table
create 'user', 'info', 'data'
create 'user', {NAME => 'info', VERSIONS => '3'},{NAME => 'data'}
# 向user表中插入信息,row key为rk0001,列族info中添加名为age 、 name的列,值为zhangsan
put 'user', 'rk0001', 'info:age', 'zhangsan'
put 'user', 'rk0001', 'info:name', 'female'

# 获取user表中row key为rk0001的所有信息(即所有cell的数据)
get 'user', 'rk0001'

# 获取user表中row key为rk0001,info列族的所有信息
get 'user', 'rk0001', 'info'

# 获取user表中row key为rk0001,info列族的name、age列的信息
get 'user', 'rk0001', 'info:name', 'info:age'

# 获取多个列镞的信息
get 'user', 'rk0001', 'info', 'data'
get 'user', 'rk0001', {COLUMN => ['info', 'data']}
get 'user', 'rk0001', {COLUMN => ['info:name', 'data:pic']}

# 指定rowkey与列值过滤器查询
get 'user', 'rk0001', {FILTER => "ValueFilter(=, 'binary:zhangsan')"}
# 获取user表中row key为rk0001,列标示符中含有a的信息
get 'user', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}

get 'user', 'rk0002', {FILTER => "ValueFilter(=, 'binary:中国')"}

# 查询user表中的所有信息
scan 'user'
scan 'user', {COLUMNS => 'info'}
scan 'user', {COLUMNS => 'info', RAW => true, VERSIONS => 5}
scan 'user', {COLUMNS => ['info', 'data']}
scan 'user', {COLUMNS => 'info:name'}

# 指定多个列族与按照数据值模糊查询
scan 'user', {COLUMNS => ['info', 'data'], FILTER => "(QualifierFilter(=,'substring:a'))"}

#  指定rowkey的范围查询
scan 'user', {COLUMNS => 'info', STARTROW => 'rk0001', ENDROW => 'rk0003'}

#  指定rowkey模糊查询
scan 'user',{FILTER=>"PrefixFilter('rk')"}

# 指定数据版本的范围查询
scan 'user', {TIMERANGE => [1392368783980, 1392380169184]}
# 更新数据值 将user表的f1列族版本数改为5
alter 'user', NAME => 'info', VERSIONS => 5

# 指定rowkey以及列名进行删除
delete 'user', 'rk0001', 'info:name'

#  指定rowkey,列名以及版本号进行删除
delete 'user', 'rk0001', 'info:name', 1392383705316

# 删除一个列镞
alter 'user', NAME => 'info', METHOD => 'delete' 
或 alter 'user', 'delete' => 'info'

# 清空表数据
truncate 'user'

# 删除表
disable 'user'
drop 'user'

# 统计有多少数据
count 'user' 

高级shell管理命令

# 显示服务器状态
status 'node01'

# 显示HBase当前用户,例如:
whoami

#  显示当前所有的表
HBase >  list

#  统计指定表的记录数,例如:
HBase> count 'user' 

#  展示表结构信息
HBase> describe 'user'

#  检查表是否存在,适用于表量特别多的情况
HBase> exists 'user'

#  检查表是否启用或禁用
HBase> is_enabled 'user'
HBase> is_disabled 'user'

#  该命令可以改变表和列族的模式,例如:
#  **为当前表增加列族:**
HBase> alter 'user', NAME => 'CF2', VERSIONS => 2
#  **为当前表删除列族:**
HBase(main):002:0>  alter 'user', 'delete' => 'CF2'

#  禁用一张表/启用一张表
HBase> disable 'user'
HBase> enable 'user'

JavaAPI

  • HBase是一个分布式的NoSql数据库,在实际工作当中,我们一般都可以通过JavaAPI来进行各种数据的操作,包括创建表,以及数据的增删改查等等

  • 讲如下内容作为maven工程中pom.xml的repositories的内容

  <dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>2.6.0-mr1-cdh5.14.2</version>
</dependency>
<dependency>
  <groupId>org.apache.HBase</groupId>
  <artifactId>hbase-client</artifactId>
  <version>1.2.0-cdh5.14.2</version>
</dependency>
<dependency>
  <groupId>org.apache.HBase</groupId>
  <artifactId>hbase-server</artifactId>
  <version>1.2.0-cdh5.14.2</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.testng</groupId>
  <artifactId>testng</artifactId>
  <version>6.14.3</version>
  <scope>test</scope>
</dependency>

基础Api

// 1. 连接HBase
Configuration configuration = HBaseConfiguration.create();
//连接HBase集群不需要指定HBase主节点的ip地址和端口号
configuration.set("HBase.zookeeper.quorum","node01:2181,node02:2181,node03:2181");
//创建连接对象
Connection connection = ConnectionFactory.createConnection(configuration);    


//2. 获取管理员对象,来对手数据库进行DDL的操作
Admin admin = connection.getAdmin();

// 3. 建表
HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("myuser"));
//指定列族
HColumnDescriptor f1 = new HColumnDescriptor("f1");
hTableDescriptor.addFamily(f1);
admin.createTable(hTableDescriptor);

// 4. 添加数据
Table table = connection.getTable(TableName.valueOf(TABLE_NAME));
Put put = new Put("0001".getBytes());//创建put对象,并指定rowkey值
put.addColumn("f1".getBytes(),"name".getBytes(),"zhangsan".getBytes());
table.put(put);

// 5. 查询数据
//通过get对象,指定rowkey
Get get = new Get(Bytes.toBytes("0003"));
get.addFamily("f1".getBytes());//限制只查询f1列族下面所有列的值
//查询f2列族 phone这个字段
get.addColumn("f2".getBytes(),"phone".getBytes());
//通过get查询,返回一个result对象,所有的字段的数据都是封装在result里面了
Result result = table.get(get);

// 6. 遍历结果
List<Cell> cells = result.listCells();  //获取一条数据所有的cell,所有数据值都是在cell里面 的
for (Cell cell : cells) {
  byte[] family_name = CellUtil.cloneFamily(cell);//获取列族名
  byte[] column_name = CellUtil.cloneQualifier(cell);//获取列名
  byte[] rowkey = CellUtil.cloneRow(cell);//获取rowkey
  byte[] cell_value = CellUtil.cloneValue(cell);//获取cell值
  // 需要判断字段的数据类型,使用对应的转换的方法,才能够获取到值
  if("age".equals(Bytes.toString(column_name))  || "id".equals(Bytes.toString(column_name))){}else{}
}

// 7. scan查询
Scan scan = new Scan();//没有指定startRow以及stopRow  全表扫描
//扫描f1列族
scan.addFamily("f1".getBytes());
//扫描 f2列族 phone  这个字段
scan.addColumn("f2".getBytes(),"phone".getBytes());
scan.setStartRow("0003".getBytes());
scan.setStopRow("0007".getBytes());
//通过getScanner查询获取到了表里面所有的数据,是多条数据
ResultScanner scanner = table.getScanner(scan);

//遍历ResultScanner 得到每一条数据,每一条数据都是封装在result对象里面了
for (Result result : scanner) {
  List<Cell> cells = result.listCells();
  for (Cell cell : cells) {
    byte[] family_name = CellUtil.cloneFamily(cell);
    byte[] qualifier_name = CellUtil.cloneQualifier(cell);
    byte[] rowkey = CellUtil.cloneRow(cell);
    byte[] value = CellUtil.cloneValue(cell);
  }
}

过滤器

  • 过滤器的作用是在服务端判断数据是否满足条件,然后只将满足条件的数据返回给客户端

  • 过滤器的类型很多,但是可以分为两大类

    • ==比较过滤器==
    • ==专用过滤器==
  • HBase过滤器的比较运算符

LESS  <
LESS_OR_EQUAL <=
EQUAL =
NOT_EQUAL <>
GREATER_OR_EQUAL >=
GREATER >
NO_OP 排除所有
  • Hbase过滤器的比较器(指定比较机制):
BinaryComparator  按字节索引顺序比较指定字节数组,采用Bytes.compareTo(byte[])
BinaryPrefixComparator 跟前面相同,只是比较左端的数据是否相同
NullComparator 判断给定的是否为空
BitComparator 按位比较
RegexStringComparator 提供一个正则的比较器,仅支持 EQUAL 和非EQUAL
SubstringComparator 判断提供的子串是否出现在中。
// 比rowKey  0003小的所有值出来,获取我们比较对象
BinaryComparator binaryComparator = new BinaryComparator("0003".getBytes());
RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.GREATER, binaryComparator);

// 列镞名包含f2的数据
SubstringComparator substringComparator = new SubstringComparator("f2");
FamilyFilter familyFilter = new FamilyFilter(CompareFilter.CompareOp.EQUAL, substringComparator);

// 定义列名过滤器,只查询列名包含name的列
SubstringComparator substringComparator = new SubstringComparator("name");
QualifierFilter qualifierFilter = new QualifierFilter(CompareFilter.CompareOp.EQUAL, substringComparator);

// 列值过滤器,过滤列值当中包含数字8的所有的列
SubstringComparator substringComparator = new SubstringComparator("8");
ValueFilter valueFilter = new ValueFilter(CompareFilter.CompareOp.EQUAL, substringComparator);

// 单列值过滤器,过滤  f1 列族  name  列  值为刘备的数据
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter("f1".getBytes(), "name".getBytes(), CompareFilter.CompareOp.EQUAL, "刘备".getBytes());

// 列值排除过滤器SingleColumnValueExcludeFilter,与SingleColumnValueFilter相反,会排除掉指定的列,其他的列全部返回

// 前缀过滤器,过滤rowkey以  00开头的数据
PrefixFilter prefixFilter = new PrefixFilter("00".getBytes());


scan.setFilter(filter);
PageFilter
  • 通过pageFilter实现分页过滤器
// 1. 获取第一页的数据
scan.setMaxResultSize(pageSize);
scan.setStartRow("".getBytes()); // 第一页的开始row为 “”空字符串
//使用分页过滤器来实现数据的分页
PageFilter filter = new PageFilter(pageSize);
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);

// 2. 获取第n(n > 1)页的数据
String  startRow = "";
// (扫描跳过前面的数据)扫描数据的调试 扫描(pageNum - 1) * pageSize + 1条数据
int scanDatas = (pageNum - 1) * pageSize + 1;
scan.setMaxResultSize(scanDatas);//设置一步往前扫描多少条数据
PageFilter filter = new PageFilter(scanDatas);
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
// 到现在已经扫过目标值前面数据,最后一条作为目标页的startRowKey
for (Result result : scanner) {
  byte[] row = result.getRow();//获取rowkey
  //最后一次startRow的值就是目标值
  startRow= Bytes.toString(row);//循环遍历我们多有获取到的数据的rowkey
}
// 现在开始获取目标页数据
scan.setStartRow(startRow.getBytes());
scan.setMaxResultSize(pageSize);//设置我们扫描多少条数据
PageFilter filter1 = new PageFilter(pageSize);
scan.setFilter(filter1);
ResultScanner scanner1 = table.getScanner(scan);
FilterList
Scan scan = new Scan();
FilterList filterList = new FilterList();
filterList.addFilter(singleColumnValueFilter);
filterList.addFilter(prefixFilter);
scan.setFilter(filterList);
ResultScanner scanner = table.getScanner(scan);

删除操作

// 根据rowkey删除数据
Delete delete = new Delete("0003".getBytes());
table.delete(delete);

//删除一张表
admin.disableTable(TableName.valueOf(TABLE_NAME));
admin.deleteTable(TableName.valueOf(TABLE_NAME));