HBASE浅入浅出

907 阅读8分钟

前言

作为一名Java工程师,因为工作中使用到HBase,故阅读《HBase不睡觉》后整理的HBase简单使用手册。

初识HBase

为什么要用HBase

HBase复杂的存储结构和分布式的存储方式带来的代价就是:哪怕只是存储少量数据,它也不会
很快。HBase并不快,只是当数据量很大的时候它慢的不明显。

以下情况可以使用HBase

  • 单表数据量超千万,而且并发还挺高。
  • 数据分析需求较弱,或者不需要那么灵活或者实时。

HBase基本架构

image.png
HBase有Master服务器和RegionServer服务器,Master服务器只负责维护表结构信息,实际数据存储在RegionServer服务器上。ZooKeeper管理HBase所有的RegionServer信息,客户端先与ZooKeeper通信,然后再连接RegionServer。

Region是什么

Region是多个行的集合。Region不能跨服务器,一个RegionServer上有一个或者多个 Region。

RegionServer是什么

RegionServer是存放Region的容器。

Master是什么

master只负责协调工作,比如建表、删表、移动Region、合并等操作。

数据模型

image.png

  • Namespace(表命名空间)

表命名空间不是强制的,当想把多个表分到一个组去统一管理的时候才会用到表命名空间。这个概念之前没提到,因为初学者一般用不到,当数据库中没有那么多表的时候也用不到这个概念。

  • Table(表)

一个表由一个或者多个列族组成。数据属性,比 如超时时间(TTL),压缩算法等,都在列族的定义中定义。定义完列族后表是空的,只有添加了行,表才有数据。

  • Row(行)

一个行包含了多个列,这些列通过列族来分类。行中的数据所属列族只能从该表所定义的列族中选取,不能定义这个表中不存在的列族,否则你会得到一个NoSuchColumnFamilyException。由于HBase是一个列式数据库,
所以一个行中的数据可以分布在不同的服务器上。

  • Column Family(列族)

列族是多个列的集合。其实列式数据库只需要列就可以了,为什么还需要有列族呢?因为HBase会尽量把同一个列族的列放到同一个服务器上,这样可以提高存取性能,并且可以批量管理有关联的一堆列。所有的数据属性都是定义在列族上。在HBase中,建表定义的不是列,而是列族,列族可以说是HBase中最重要的概念。

  • Column Qualifier(列)

多个列组成一个行。列族和列经常用 Column Family: Column Qualifier来一起表示。列是可以随意定义的,一个行中的列不限名字、不限数量,只限定列族。

  • Cell(单元格)

一个列中可以存储多个版本的数据。而每个版 本就称为一个单元格(Cell),所以在HBase中的单元格跟传统关系型数据库的单元格概念不一样。HBase中的数据细粒度比传统数据结构更细一级,同一个位置的数据还细分成多个版本。Timestamp(时间戳/版本号):你既可以把它称为是时间戳,也可以称为是版本号,因为它是用来标定同一个列中多个单元格的版本号的。

  • Timestamp(时间戳/版本号)

你既可以把它称为是时间戳,也可以称为是版本号,因为它是用来标定同一个列中多个单元格的 版本号的。当你不指定版本号的时候,系统会自动采用当前的时间戳来作为版本号;而当你手动定义了一个数字来当作版本号的 时候,这个Timestamp只有版本号的意义了。


HBase是怎么存储数据的

RegionServer

image.png

image.png

  • WAL

预写日志,WAL是Write-Ahead Log的缩写。预先写入。当操作到达Region的时 候,HBase把操作写到WAL里面去。HBase会先把数据放到基于内存实现的Memstore里,等数据达到一定的数量时才刷写(flush)到最终存储的HFile内。而如果在这个过程中服务器宕机或者断电了,那么数据就丢失了。WAL是一个保险机制,数据在写到Memstore之前,先被写到WAL了。这样当故障恢复的时候可以从WAL中恢复数据。

  • Region

Region相当于一个数据分片。每一个Region都有起始rowkey和结束rowkey,代表了它所存储的row范围。
image.png

  • Store

每一个Region内都包含有多个Store实例。一个Store对应一个列族的数据,如果一个表有两个列族,那么在一 个Region里面就有两个Store。在最右边的单个Store的解剖图上,我们可以看到Store内部有MemStore和HFile这两个组成部分。
image.png

  • MemStore

每个Store中有一个MemStore实例。数据写入WAL之后就会被放入MemStore。MemStore是内存的存储对象,只有当 MemStore满了的时候才会将数据刷写(flush)到HFile中。

  • HFile

在Store中有多个HFile。当MemStore满了之后HBase就会在HDFS上生成一个新的HFile,然后把MemStore中的内容写到这个HFile中。HFile直接跟HDFS打交道,它是数据的存储实体。


HBase常用命令

list;
create 'table','cf';
describe 'table';
put 'table' ,'rowKey','cf:column','value';
scan 'table';
alter 'table',{NAME=>'cf',VERSIONS=>5};
scan 'table',{STARTROW=>'rowKey1',ENDROW=>'rowKey3',LIMIT=>2};
get 'table', 'rowKey', 'cf:name';
get 'table', 'rowKey',{COLUMN=>'cf:column ',VERSIONS=>5};
delete 'table', 'rowKey', 'cf:name';
delete 'table', 'rowKey', 'cf:name', 2; // 逻辑删除版本号之前的所有数据
scan 'table',{RAW=>true,VERSIONS=>5}; // 查询被逻辑删除的数据
deleteall 'table', "rowKey';
disable 'table';
drop 'table';
append 'table','rowKey', 'cf:name ', 'valueAppend',ATTRIBUTES =>{ 'kid'=>'yes'};
status;

HBase JAVA客户端API

创建表

// 创建连接
Connection connection = ConnectionFactory.createConnection(config);
// 获取admin对象
Admin admin = connection.getAdmin();
//定义表名
TableName tableName = TableName.valueOf("table");
//定义表
HTableDescriptor table = new HTableDescriptor(tableName) ;
//定义列族
HColumnDescriptor cf = new HColumnDescriptor("cf");
table.addFamily(new HColumnDescriptor(cf));
//执行创建表动作
admin.createTable(table);

插入数据

 Table table = connection.getTable(TableName.valueOf("table"));
 Put put = new Put(Bytes.toBytes("rowKey"));
 put.addColumn(Bytes.toBytes("cf"),Bytes.toBytes("column"), Bytes.toBytes("value"));
 table.put(put);

checkAndPut

CAS操作

Append

给value追加数据

Increment

原子性的增加方法

查询数据

// 只能用rowKey去查
Get get = new Get(Bytes.toBytes("rowKey"));
Result result = table.get(get);

Exists

// 判断数据是否存在
boolean exists (Get get);

删除数据

Delete delete=new Delete(Bytes.toBytes("rowKey")); // 删除该rowKey下的所有数据
delete.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("column")); // 增加列族与列,更细粒化的删除。
table.delete(delete);

批量操作

void batch(List<Row> actions,Object[] results);
void put(List<Put> puts);
Result[] get(List<Get> gets);
void delete(List<Delete> deletes);

HBase JAVA客户端高阶API

比较器

CompareFilter(比较关系)

  • LESS:小于
  • LESS_OR_EQUAL:小于等于
  • EQUAL:相等。
  • NOT_EQUAL:不相等
  • GREATER_OR_EQUAL:大于等于
  • GREATER:大于
  • NO_OP:无操作

SubstringComparator

判断目标字符串是否包含所指定的字符串,类似于MySQL中的like。

BinaryComparator

匹配比较器,与CompareFilter配合,类似于MySQL的等于大于小于。用数字比较时,要确保存储的是字节数组而不是字符串。

LongComparator

数字比较器

过滤器

image.png

值过滤器 ValueFilter

不需要指定列,查询所有列含有该值的行。

Filter filter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator("value"));

单列值过滤器 SingleColumnValueFilter

可以指定查询的列

// select * from tableName where `cf:column` like '%value%';
Filter filter = new SingleColumnValueFilter(Bytes.toBytes("cf"), Bytes.toBytes("column"), CompareFilter.CompareOp.EQUAL, new SubstringComparator("value"));

单值过滤器缺点

单列值过滤器在发现该行记录并没有你想要比较的列的时候,会把整行数据放入结果集。如果要安全地使用单列值过滤器,请务必保证你的每行记录都包含有将要比较的列。 如果无法保证每行记录中都包含有将要比较的列,可以用以下两种方案去处理

  1. 在遍历结果集的时候,再次判断结果中是否包含我们要比较的列,如果没有就不使用这条记录。
  2. 使用过滤器列表将列族过滤器(FamilyFilter)、列过滤器(QualifierFilter)和值过滤器放入过滤器列表,同时进行过滤。

过滤器列表 FamilyFilter

//创建过滤器列表
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);

//只有列族为cf的记录才放入结果集
Filter familyFilter = new FamilyFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("cf")));
filterList.addFilter(familyFilter);

//只有列为column的记录才放入结果集
Filter colFilter = new QualifierFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("column")));
filterList.addFilter(colFilter);

//只有值包含value的记录才放入结果集
Filter valueFilter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator("value"));

filterList.addFilter(valueFilter);

实现过滤器列表的AND与OR关系

使用Operator枚举

  • MUST_PASS_ALL 条件与
  • MUST_PASS_ONE 条件或
FilterList filterList = new FilterList(Operator operator,List<Filter> rowFilters);

分页过滤器 PageFilter

分页过滤器没有实现翻页功能,可以用业务代码实现分页。

// select * from tableName limit 5;
Filter filter = new PageFilter(5);

行过滤器 RowFilter

// select * from tableName where rowKey > rowKey;
Filter filter = new RowFilter(CompareFilter.CompareOp.GREATER, new BinaryComparator(Bytes.toBytes("rowKey")));

前缀过滤器 PrefixFilter

// select * from tableName where rowKey like 'rowKey%';
PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("rowKey"));

列族过滤器 FamilyFilter

过滤出等于该列族的数据

Filter filter = new FamilyFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("cf")));