HBase 初探,架构 ,原理 ,对比,实践。

因为工作需要使用 HBase,因此调研了 HBase 相关的内容。本文的写作目的不仅仅是对前期工作的总结,也希望能帮助到工作繁忙但又想了解 HBase 的同学。在本文写作过程中,将穿插 MySQL 相关内容,希望能帮助理解 HBase 。

本文主要讨论以下几个问题,所述内容仅为个人思考,见解有限,有误之处还望批评指正。

· HBase 是什么?其架构是怎样的?
· HBase 如何管理数据?
· HBase 是分布式数据库,那数据怎么路由?
· HBase 的适用场景?
· HBase 和 MySQL 的主要区别?
· HBase 怎么用?如何实现 CREATE、INSERT、SELECT、
UPDATE、DELETE、LIKE 操作?

1 HBase

1.1 HBase 架构
HBase 是什么?其架构是怎样的?

HBase(Hadoop DataBase),是一种非关系型分布式数据库(NoSQL),支持海量数据存储(官方:单表支持百亿行百万列)。HBase 采用经典的主从架构,底层依赖于 HDFS,并借助 ZooKeeper 作为协同服务,其架构大致如下:在这里插入图片描述
其中,

· Master:HBase 管理节点。管理 Region Server,分配 Region 到 Region Server,提供负载均衡能力;执行创建表等 DDL 操作。
· Region Server:HBase 数据节点。管理 Region,一个 Region Server 可包含多个 Region,Region 相当于表的分区。客户端可直接与 Region Server 通信,实现数据增删改查等 DML 操作。
· ZooKeeper:协调中心。负责 Master 选举,节点协调,存储 hbase:meta 等元数据。
· HDFS:底层存储系统。负责存储数据,Region 中的数据通过 HDFS 存储。
对 HBase 全局有了基本理解后,我认为有几个比较重要的点值得关注:HBase 数据模型、Region 的概念、数据路由。

1.2 HBase 数据模型
HBase 如何管理数据?(逻辑层)

HBase 的数据模型和 MySQL 等关系型数据库有比较大的区别,其是一种 ”Schema-Flexiable“ 的理念。

在表的维度,其包含若干行,每一行以 RowKey 来区分。
在行的维度,其包含若干列族,列族类似列的归类,但不只是逻辑概念,底层物理存储也是以列族来区分的(一个列族对应不同 Region 中的一个 Store)。
在列族的维度,其包含若干列,列是动态的。与其说是列,不如说是一个个键值对,Key 是列名,Value 是列值。
HBase 的表结构如下:在这里插入图片描述
在这里插入图片描述
1.3 Region
HBase 如何管理数据?(物理层)

Region 是 HBase 中的概念,类似 RDBMS 中的分区。

1.Region 是表的横向切割,一个表由一个或多个 Region 组成,Region 被分配到各个 Region Server;
2.·一个 Region 根据列族分为多个 Store,每个 Store 由 MemStore 和 StoreFile 组成;数据写入 MemStore,MemStore 类似输入缓冲区,持久化后为 StoreFile;数据写入的同时会更新日志 WAL,WAL 用于发生故障后的恢复,保障数据读写安全;
一个 StoreFile 对应一个 HFile,HFile 存储在 HDFS 。
下面是我梳理的大致模型:在这里插入图片描述
1Region 是一个 RowKey Range

每个 Region 实际上是一个 RowKey Range,比如 Region A 存放的 RowKey 区间为 [aaa,bbb),Region B 存放的 RowKey 区间为 [bbb,ccc) ,以此类推。Region 在 Region Server 中存储也是有序的,Region A 必定在 Region B 前面。

注:这里将 RowKey 设计为 aaa,而不是 1001 这样的数字,是为了强调 RowKey 并非只能是数字,只要能进行字典排序的字符都是可以的,如:abc-123456 。在这里插入图片描述

2数据被路由到各个 Region

表由一个或多个 Region 组成(逻辑),Region Server 包含一个或多个 Region(物理)。数据的路由首先要定位数据存储在哪张表的哪个 Region,表的定位直接根据表名,Region 的定位则根据 RowKey(因为每个 Region 都是一个 RowKey Range,因此根据 RowKey 很容易知道其对应的 Region)。

注:Master 默认采用 DefaultLoadBalancer 策略分配 Region 给 Region Server,类似轮询方式,可保证每个 Region Server 拥有相同数量的 Region(这里只是 Region 的数量相同,但还是有可能出现热点聚集在某个 Region,从而导致热点聚集在某个 Region Server 的情况)。
在这里插入图片描述

3.当一个表太大时,Region 将自动分裂

自动分裂
0.94 版本之前,Region 分裂策略为
ConstantSizeRegionSplitPolicy ,根据一个固定值触发分裂。

0.94 版本之后,分裂策略默认为
IncreasingToUpperBoundRegionSplitPolicy,该策略会根据 Region 数量和 StoreFile 的最大值决策。当 Region 的数量小于 9 且 StoreFile 的最大值小于某个值时,分裂 Region;当Region数量大于9 时,采用
ConstantSizeRegionSplitPolicy 。

手动分裂

ConstantSizeRegionSplitPolicy 下,通过设置
hbase.hregion.max.filesize 控制 Region 分裂。

在这里插入图片描述

1.4 数据路由 hbase:meta

HBase 是分布式数据库,那数据怎么路由?

数据路由借助 hbase:meta 表完成,hbase:meta 记录的是所有 Region 的元数据信息,hbase:meta 的位置记录在 ZooKeeper 。

注:一些比较老的文章可能会提到 .root 和 .meta 两个表。事实上, .root 和 .meta 两个表是 HBase 0.96 版本之前的设计。在 0.96 版本后,.root 表已经被移除,.meta 表更名为 hbase:meta。

hbase:meta 表的格式如下:在这里插入图片描述
其中,
在这里插入图片描述一条数据的写入流程:

数据写入时需要指定表名、Rowkey、数据内容。

1.HBase 客户端访问 ZooKeeper,获取 hbase:meta 的地址,并缓存该地址;
2.访问相应 Region Server 的 hbase:meta;
3.从 hbase:meta 表获取 RowKey 对应的 Region Server 地址,并缓存该地址;
4.HBase 客户端根据地址直接请求 Region Server 完成数据读写。
注 1:数据路由并不涉及Master,也就是说 DML 操作不需要 Master 参与。借助 hbase:meta,客户端直接与 Region Server 通信,完成数据路由、读写。

注 2:客户端获取 hbase:meta 地址后,会缓存该地址信息,以此减少对 ZooKeeper 的访问。同时,客户端根据 RowKey 查找 hbase:meta,获取对应的 Region Server 地址后,也会缓存该地址,以此减少对 hbase:meta 的访问。因为 hbase:meta 是存放在 Region Server 的一张表,其大小可能很大,因此不会缓存 hbase:meta 的完整内容。在这里插入图片描述

1.5 HBase 适用场景

1.不需要复杂查询的应用。HBase 原生只支持基于 RowKey 的索引,对于某些复杂查询(如模糊查询,多字段查询),HBase 可能需要全表扫描来获取结果。
2.写密集应用。HBase 是一个写快读慢(慢是相对的)的系统。HBase 是根据 Google 的 BigTable 设计的,典型应用就是不断插入新数据(如 Google 的网页信息)。
3.对事务要求不高的应用。HBase 只支持基于 RowKey 的事务。
4.对性能、可靠性要求高的应用。HBase 不存在单点故障,可用性高。
5.数据量特别大的应用。HBase 支持百亿行百万列的数据量,单个 Region 过大将自动触发分裂,具备较好的伸缩能力。

2 HBase 与 MySQL 的区别?

HBase 和 MySQL 的主要区别?

2.1 MySQL

MySQL 表结构规整,每一行有固定的列。

创建表时,需要指定表名,预设字段(列)个数以及数据类型,Schema 是固定的。
插入数据时,只需根据表的 Schema 填充每个列的值即可。如果 Schema 没有该列,则无法插入。在这里插入图片描述

2.2 HBase

HBase 支持动态列,不同行可拥有不同数量的列,可动态增加新的列。HBase 的表结构看起来杂乱无章,但却有利于存储稀疏数据。

创建表时,需指定表名、列族,无需指定列的个数、数据类型,Schema 是灵活的。
插入数据时,需要指定表名、列族、RowKey、若干个列(列名和列值),这里列的个数可以是一个或多个。在这里插入图片描述2.3 对比
进一步,假设 ct_account_info_demo 表中只有一条记录(account_id = 1,account_owner = Owner1,account_amount = 23.0,is_deleted = n),分别通过 MySQL 和 HBase 查找该记录。

MySQL 返回的结果在这里插入图片描述HBase 返回的结果:在这里插入图片描述上述结果都表示一行数据,MySQL 的返回结果比较直观,容易理解。

HBase 返回的结果其实是多个键值对,ROW 表示数据的 RowKey,COLUMN+CELL 表示该 RowKey 对应的内容。

COLUMN+CELL 中又是多个键值对,如:在这里插入图片描述表示列族 CT 的列 account_amount 的值为 23.0,时间戳为 1532502487735 。

注:ROW 为 1 是因为这里 RowKey = {account_id},CT 是提前定义的列族(HBase 在插入数据时需要指定 RowKey、Column Family)。

总的来说,

1.HBase 比 MySQL 多了 RowKey 和 Column Family 的概念,这里的 RowKey 类似 MySQL 中的主键,Column Family 相当于多个列的“归类”。
2.列族只有一个的情况下,HBase 的 Schema 和 MySQL 可以保持一致,但 HBase 允许某些字段为空或动态增加某个列,而 MySQL 只可根据 Schema 填充相应的列,不能动态增减列。
3.因为 HBase 的 Schema 是不固定的,所以每次插入、查找数据不像 MySQL 那么简洁,HBase 需要指定行键、列族、列等信息。
更为详细的对比如下表(引自:HBase 深入浅出):

RDBMS HBase 硬件架构 传统的多核系统,硬件成本昂贵 类似于 Hadoop 的分布式集群,硬件成本低廉 容错性 一般需要额外硬件设备实现 HA 机制 由软件架构实现,因为多节点,所以不担心单点故障 数据库大小 GB、TB PB 数据排布 以行和列组织 稀疏的、分布的、多维的 Map 数据类型 丰富的数据类型 Bytes 事务支持 全面的 ACID 支持,支持 Row 和表 ACID 只支持单个 Row 级别 查询语言 SQL 只支持 Java API (除非与其他框架一起使用,如 Phoenix、Hive) 索引 支持 只支持 Row-key(除非与其他技术一起应用,如 Phoenix、Hive) 吞吐量 数千查询/每秒 百万查询/每秒

3 HBase 相关操作(CRUD


HBase 怎么用?如何实现 CREATE、INSERT、SELECT、UPDATE、DELETE、LIKE 操作?

为便于理解,结合 MySQL 说明 HBase 的 DML 操作,演示如何使用 HBase 来实现 MySQL 的 CREATE、 INSERT、SELECT、UPDATE、DELETE、LIKE 操作。

为方便代码复用,这里提前封装获取 HBase 连接的代码:在这里插入图片描述3.0 CREATE在这里插入图片描述3.1 INSERT
MySQL:在这里插入图片描述HBase 实现上述 SQL 语句的功能:在这里插入图片描述在这里插入图片描述3.2 SELECT
MySQL:
在这里插入图片描述HBase 实现上述 SQL 语句的功能:在这里插入图片描述在这里插入图片描述在这里插入图片描述3.3 UPDATE
MySQL:在这里插入图片描述HBase 实现上述 SQL 语句的功能:在这里插入图片描述3.4 DELETE
MySQL:在这里插入图片描述通过 HBase 实现上述 SQL 语句的功能:在这里插入图片描述在这里插入图片描述3.5 LIKE
MySQL:``在这里插入图片描述HBase 实现上述 SQL 语句的功能:


```java
// 模糊查询
public Account getAccountInfoByKeyWord(Long accountId, String keyWord) {
    Account account = new Account();
    // 表名
    String tableName = "ct_account_info_demo";		
    // 起始行键(闭区间)
    String startRow = String.valueOf(accountId);	
    // 终止行键(开区间,结果不包含stopRow)
    String stopRow = String.valueOf(accountId);	
    // 列族
    String familyName = "account_info";
    // 设置需要模糊查询的列
    String targetColumn = "account_owner";
    // 设置需要返回哪些列
    List<String> columns = new ArrayList<>();
    columns.add("account_id");
    columns.add("account_owner");
    columns.add("account_amount");
    columns.add("is_deleted");
    // 模糊查询
    HashMap<String,String> accountRecord = singleColumnFilter(tableName, familyName, startRow, stopRow, targetColumn, keyWord, columns);
    if (accountRecord.size()==0) {
    	return null;
    }
    // 根据查询结果,封装账户信息
    account.setId( Long.valueOf(accountRecord.get("account_id")));
    account.setOwner(accountRecord.get("account_owner"));
    account.setBalance(new BigDecimal(accountRecord.get("account_amount")));
    account.setDeleted(accountRecord.get("isDeleted"));
    return account;
}

private HashMap<String,String> singleColumnFilter(String tableName, String familyColumn, String startRowKey, String stopRowKey, String targetColumn, String keyWord, List<String> columns) {
    if (hbase == null) {
    	throw new NullPointerException("HBaseConfig");
    }
    if (familyColumn == null || columns.size() == 0) {
    	return null;
    }
    HashMap<String,String> accountRecord = new HashMap<>(8);
    try {
        // 获取HBase连接
        Connection hbaseConnect = hbase.getHBaseConnect();
        // 获取相应的表
        Table table = hbaseConnect.getTable(TableName.valueOf(tableName));
        // 封装Scan
        Scan scan = new Scan();
        scan.setStartRow(Bytes.toBytes(startRowKey));
        scan.setStopRow(Bytes.toBytes(stopRowKey));
        // 设置查询的列
        for (String column:columns) {
            scan.addColumn(Bytes.toBytes(familyColumn), Bytes.toBytes(column));
        }
        // 定义过滤器:某一列的值是否包含关键字
        SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(Bytes.toBytes(familyColumn),Bytes.toBytes(targetColumn),CompareFilter.CompareOp.EQUAL,new SubstringComparator(keyWord));
        //ValueFilter filter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator(keyWord));
        FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ONE,singleColumnValueFilter);
        // Scan添加过滤器
        scan.setFilter(list);
        // 获取数据
        ResultScanner resultScanner = table.getScanner(scan);
        for(Result result = resultScanner.next();result!=null;result = resultScanner.next()){
        	if (result.listCells() != null) {
            	for (Cell cell : result.listCells()) {
                    // 将结果存放在map中
                    String k = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
                    String v = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                    accountRecord.put(k,v);
    			}
    		}
    	}
    	table.close();
    } catch (IOException e) {
    	e.printStackTrace();
    }
    // 返回所有行数据
    return accountRecord;
}
后话:
在学校时,我参与过 Elasticsearch 相关的项目。在理解 HBase 时,发现 HBase 的设计其实和 Elasticsearch 十分相似,如 HBase 的 Flush&Compact 机制等设计与 Elasticsearch 如出一辙,因此理解起来比较顺利。

从本质上来说,HBase 的定位是分布式存储系统,Elasticsearch 是分布式搜索引擎,两者并不等同,但两者是互补的。HBase 的搜索能力有限,只支持基于 RowKey 的索引,其它二级索引等高级特性需要自己开发。因此,有一些案例是结合 HBase 和 Elasticsearch 实现存储 + 搜索的能力。通过 HBase 弥补 Elasticsearch 存储能力的不足,通过 Elasticsearch 弥补 HBase 搜索能力的不足。

其实,不只是 HBase 和 Elasticsearch。任何一种分布式框架或系统,它们都有一定的共性,不同之处在于各自的关注点不同。我的感受是,在学习分布式中间件时,应先弄清其核心关注点,再对比其它中间件,提取共性和特性,进一步加深理解。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值