断更只是为了更好的出现,这里带来最详细的hbase的笔记。这里我们详细的来看hbase的教学,今天这一章节主要对hbase做了基本的介绍和一些基本的使用。
hbase的简介
一、Hadoop和Hbase
- HBase是基于Hadoop集群之上来搭建的
- Hadoop有一些局限性的:
- 做一些批量的数据处理,吞吐量比较高,但是它对随机查询、实时操作性能是不行的
- HBase是NoSQL数据库的一种,它跟传统的RDBMS有很大的差别
- 不支持JOIN的,摒弃了关系型模型,而且在HBase中只有一种数据类型:byte[]
- HBase可以用来存储非常大的表,上亿行的数据、有超过百万列,而且它常用在实时数据处理中。因为它的读写是很快的。
二、HBase的应用场景
- HBase只要有海量数据存储,而且需要快速的写入以及快速的读取场景,就很适合HBase
- 但要记住NoSQL的特点:对事务的支持性较弱的
- 可以用来存储爬虫的数据、点赞/转发、银行转账订单…
三、Hbase对于RDBMS对比Hive
- RDBMS是关系型数据库支持join、ACID、有schema(创建表的时候必须要指定有哪些列、列是什么类型)…、支持二级索引
- HBase不支持join的、也不支持ACID、对事务支持有限,无schema(创建表的时候,无需去指定列、列类型)、原生就支持分布式存储的,所以可以用来存储海量数据,同时也兼顾了快速查询、写入的功能
对比Hive:
- Hive主要用于OLAP,HBase主要用于OLTP,HBase是可以直接接入到业务系统的
四、HBase的安装
安装的过程看我的另外一个
注意:
- HBase依赖于:ZooKeeper、HDFS,在启动HBase之前必须要启动ZK、HDFS,否则HBase无法启动
五、HBase的数据模型
- HBase中是有表的概念的
- 一个表中可以包含多个列族
- 一个列族可以包含很多的列
- 每一个列对应的单元格(值、timestamp)
Hbase的一些操作
创建表
- HBase是没有schema的,就是在创建表的时候不需要指定表中有哪些列,只需要指定有多少个列蔟
create "表名","列蔟1", "列蔟2"
删除表
删除表之前我们首先要禁用表,如果不禁用表这里会出现无法删除的情况
* 禁用表
disable “表名”
-
删除表
-
drop “删除表”
新增数据/更新数据/删除数据/查询数据
- 新增:put “表名”,“rowkey”,“列簇:列名”,“值”
- 新增一列
- 更新:和新增的 一样的
- 删除数据
- delete"表名",“rowkey”,“列簇:列名”
- delete也是删除一个列
*deleteall “表名”,“rowkey” - 删除一行
- delete也是删除一个列
- delete"表名",“rowkey”,“列簇:列名”
- 查询数据
- get"表名",“rowkey”
- get是查询一行的数据
- get"表名",“rowkey”
- 删除数据的时候,其实HBase不是真的直接把数据删除了,而是给某个列设置成一个标记,然后查询数据的时候,有这个标记的数据,就不显示出来。
- 什么时候真的删除数据呢?
*后台进程,专门来执行删除数据的操作。
执行delete的时候
1. 如果表中的某个列有对一个的几次修改,他会删除最近一次的修改
2. 默认是保存1个保存的时间戳
3. 有一个version的属性
计数器和简单的scan扫描操作
计算器
- count “表名”:hbase就会将这个表对应的所有数据都扫描一遍,得到最终的记录条数(慎用)
- 执行HBase提供的基于MR的RowCount的程序(用于做大批量的数据的查询)
- 启动yarn集群
- 启动mr-historyserver
scan扫描
- 全表扫描:scan"表名"(慎用,效率很低)
- 限定只显示多少条数据:scan " 表名",{LIMIT => XXX}
- 指查询某几个列:scan “表名”,{LIMIT => XX,COLUMNS => []}
- 根据ROWKEY来查询:scan"表名",{LIMIT => XXX,COLUMNS =>[],POWPREFIXFILTER => ‘ROWKEY’}
使用过滤器的重点
- 语法:
- 其实在hbase shell中,执行的ruby脚本,背后还是调用hbase提供的 javaAPI。
- 在HBase中会有很多的过滤器,语法格式看起来会比较复杂,所以重点理解这个语法是什么意思
- 过滤器在hbase shell中是使用一个表达式来描述,在java中new何种对象
scan "ORDER_INFO", {FILTER => "RowFilter(=,'binary:02602f66-adc7-
40d4-8485-76b5632b5b53')", COLUMNS => ['C1:STATUS', 'C1:PAYWAY'],
FORMATTER => 'toString'}
其中:
`"RowFilter(=,'binary:02602f66-adc7-40d4-8485-76b5632b5b53')"`这个就是一个表达式
语法解析
- RowFilter就是Java API中Filter的构造器名称
- 可以理解为RowFilter()就是创建一个过滤器对象
- =是JRuby一个特殊记号,表示是一个比较运算符,还可以是>、<、>=…
- binary:02602f66-adc7-40d4-8485-76b5632b5b53是一个比较器的表达式,为了方便大家理解,可以将比较器理解为配置值的地方,binary:xxxx表示直接和值进行毕节
使用HBase的计数器
- 要使用incr来去初始化一个列,一定不能使用put操作
- 可以使用get_couter的指令来获取计数器的操作,使用get是获取不到计数器的数据
- incr"表名",“rowkey”,“列簇:列”,xxx
Hbase一些管理命令
- status:常用命令,可以查看整个集群的运行的节点数
- whoami:查看当前的用户
- disable/enable:禁用/开启表
- drop:删除表
- exists:判断表是否存在
- truncate:清空表
HBase的建表操作
创建链接
- 建立Hbase链接
- 创建admin对象
Connection connection;
Admin admin;
@BeforeTest
public void beforeTest() throws Exception{
//1.创建Hbase配置,使用HBaseConfiguration.create();来进行创建
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "192.168.3.67:2181,192.168.3.68:2181,192.168.3.69:2181");
//2.使用 ConnectionFactory.createConnection创建Hbase链接
connection = ConnectionFactory.createConnection(conf);
//3.要创建表,需要基于Hbase链接获取admin管理对象
//要创建表、删除表需要和HMaster进行链接,所以需要有一个admin对象
admin = connection.getAdmin();
}
创建表
- 调用tableExists判断表是否存在
- 在HBase中,要去创建表,需要构建TableDescriptor(表描述器)、ColumnFamilyescriptor(列簇描述器),这两个对象不是直接new出来的,是通过builder来创建的
- 将列簇描述器添加到表描述器中
- 使用admin.creatTable创建表
@Test
public void CreateTable() throws Exception{
//创建表
//2.使用HtableDescriptor
HTableDescriptor testAPI = new HTableDescriptor(TableName.valueOf("testAPI"));
//创建列簇
HColumnDescriptor cf1 = new HcolumnDescriptor("info");
//对列簇进行配置
cf1.setMaxVersions(3);
//给testAPI表添加一个列簇
testAPI.addFamily(cf1);
//创建testAPI表
admin.createTable(testAPI);
}
在HBase中所有的书就都是以byte[]形式来存储的,所以需要将java的数据类型进行转换
// 经常会使用到一个工具类:Bytes(hbase包下的Bytes工具类)
// 这个工具类可以将字符串、long、double类型转换成byte[]数组
// 也可以将byte[]数组转换为指定类型
插入数据
- 首先要获取一个Table对象,这个对象是要和HRegionServer节点连接,所以将来HRegionServer负载是比较高的
- HBase的connection对象是一个重量级的对象,将来编写代码(Spark、Flink)的时候,避免频繁创建,使用一个对象就OK,因为它是线程安全的
Connection creation is a heavy-weight operation. Connection implementations are thread-safe
- Table这个对象是一个轻量级的,用完Table需要close,因为它是非线程安全的
Lightweight. Get as needed and just close when done.
-
需要构建Put对象,然后往Put对象中添加列蔟、列、值
-
当执行一些繁琐重复的操作用列标记:
- ctrl + shift + ←/→,可以按照单词选择,非常高效
获取数据(通过ROWKEY)
方式一
- 构建一个PUT对象,根据rowkey来查询一行数据
- 遍历单元格,使用Bytes.toString来进行类型转换
- 如何判断是getmian还是gettable呢,就看是否和数据有关系,如果和数据没有关系的话,就用getAdmin(),这里是获取数据,和数据明显的有关系,所以我们使用getTable()方法。
@Test
public void getTest() throws Exception {
//1.获取HTable
Table testAPI = connection.getTable(TableName.valueOf("testAPI"));
// 2. 使用rowkey构建Get对象
Get get = new Get("001".getBytes());
//3.执行get请求
Result rs = table.get(get)
//4.获取所有单元格,列出所有的单元格
这里有两种方法,第一种是打印所有的cell,第二种方法,下面贴上
这里打印所有的cell来获取数据
List<Cell> cellList = result.listCells();
//5、打印rowkey
byte[] rowkey = result.getRow();
System.out.println(Bytes.toString(rowkey));
//6.迭代单元格列表
for (Cell cell : cells) {
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(value);
}
}
方式二:
@Test
//get
public void GetDataOne() throws Exception{
Table testAPI = connection.getTable(TableName.valueOf("testAPI"));
Get get = new Get("001".getBytes());
get.setMaxVersions(10);
Result rs = testAPI.get(get);
//获取rowkey
byte[] row = rs.getRow();
byte[] name = rs.getValue("cf1".getBytes(), "name".getBytes());
byte[] age = rs.getValue("cf1".getBytes(), "age".getBytes());
byte[] clazz = rs.getValue("cf1".getBytes(), "clazz".getBytes());
System.out.println(Bytes.toString(row)+","+ Bytes.toString(name)+","+Bytes.toString(age)+","+Bytes.toString(clazz));
}
查看表结构
- 如何判断是getmian还是gettable呢,就看是否和数据有关系,如果和数据没有关系的话,就用getAdmin()
@Test
//查看表结构,使用desc方法进行查看
public void descList() throws Exception{
//如何判断是getmian还是gettable呢,就看是否和数据有关系。
// 如果和数据没有关系的话,就用getAdmin()
Admin admin = connection.getAdmin();
HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("testAPI"));
//添加表结构是addFamily。查询表结构是getFamily
//获取方法1:getFamilies返回的是集合
Collection<HColumnDescriptor> families = tableDescriptor.getFamilies();
//获取方法2:getColumnFamilies(),这里返回的是一个数组
HColumnDescriptor[] columnFamilies = tableDescriptor.getColumnFamilies();
//如果是数组的话可以进行遍历数组进行
for (HColumnDescriptor columnFamily : columnFamilies) {
System.out.println(columnFamily.getNameAsString());
System.out.println(columnFamily.getMaxVersions());
System.out.println(columnFamily.getTimeToLive());
}
}
HBase的Java API scan + filer过滤操作
- ResultScanner需要手动关闭,这个操作是比较消耗资源的,用完了 就应该关掉,不能一直开着
- 扫描使用的是Scan对象
- SingleColumnValueFilter ----过滤单列值的过滤器
- FilterList——是可以用来组合多个过滤器
@Test
public void scanFilterTest() throws IOException {
// 1. 获取表
Table table = connection.getTable(TABLE_NAME);
// 2. 构建scan请求对象
Scan scan = new Scan();
// 3. 构建两个过滤器
// a) 构建两个日期范围过滤器(注意此处请使用RECORD_DATE——抄表日期比较
SingleColumnValueFilter startFilter = new SingleColumnValueFilter(Bytes.toBytes("C1")
, Bytes.toBytes("RECORD_DATE")
, CompareOperator.GREATER_OR_EQUAL
, new BinaryComparator(Bytes.toBytes("2020-06-01")));
SingleColumnValueFilter endFilter = new SingleColumnValueFilter(Bytes.toBytes("C1")
, Bytes.toBytes("RECORD_DATE")
, CompareOperator.LESS_OR_EQUAL
, new BinaryComparator(Bytes.toBytes("2020-06-30")));
// b) 构建过滤器列表
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL, startFilter, endFilter);
// 4. 执行scan扫描请求
scan.setFilter(filterList);
ResultScanner resultScanner = table.getScanner(scan);
Iterator<Result> iterator = resultScanner.iterator();
// 5. 迭代打印result
while(iterator.hasNext()) {
Result result = iterator.next();
// 列出所有的单元格
List<Cell> cellList = result.listCells();
// 5. 打印rowkey
byte[] rowkey = result.getRow();
System.out.println(Bytes.toString(rowkey));
// 6. 迭代单元格列表
for (Cell cell : cellList) {
// 将字节数组转换为字符串
// 获取列蔟的名称
String cf = Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
// 获取列的名称
String columnName = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
String value = "";
// 解决乱码问题:
// 思路:
// 如果某个列是以下列中的其中一个,调用toDouble将它认为是一个数值来转换
//1. NUM_CURRENT
//2. NUM_PREVIOUS
//3. NUM_USAGE
//4. TOTAL_MONEY
if(columnName.equals("NUM_CURRENT")
|| columnName.equals("NUM_PREVIOUS")
|| columnName.equals("NUM_USAGE")
|| columnName.equals("TOTAL_MONEY")) {
value = Bytes.toDouble(cell.getValueArray()) + "";
}
else {
// 获取值
value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
}
System.out.println(cf + ":" + columnName + " -> " + value);
}
}
// 7. 关闭ResultScanner(这玩意把转换成一个个的类似get的操作,注意要关闭释放资源)
resultScanner.close();
// 8. 关闭表
table.close();
}
HBase的存储架构
- 进程角色
- client:客户端,写的Java程序、hbase shell都是客户端(Flink、MapReduce、Spark)
- HMaster:主要是负责表的管理操作(创建表、删除表、Region分配),不负责具体的数据操作
- HRegionServer:负责数据的管理、数据的操作(增删改查)、负责接收客户端的请求来操作数据
- HBase架构的一些关键概念
- Region:一个表由多个Region组成,每个Region保存一定的rowkey范围的数据,Region中的数据一定是有序的,是按照rowkey的字典序来排列的
- Store:存储的是表中每一个列蔟的数据
- MemStore:所有的数据都是先写入到MemStore中,可以让读写操作更快,当MemStore快满的时候,需要有一个线程定期的将数据Flush到磁盘中
- HFile:在HDFS上保存的数据,是HBase独有的一种数据格式(丰富的结构、索引、DataBlock、BloomFilter布隆过滤器…)
- WAL:WAL预写日志,当客户端连接RegionServer写数据的时候,会先写WAL预写日志,put/delete/incr命令写入到WAL,有点类似于之前Redis中的AOF,当某一个RegionServer出现故障时,还可以通过WAL来恢复数据,恢复的就是MemStore的数据。
HBase的存储架构这里单独来开一个笔记进行讲解,这里会通过xmind的形式进行总结,尽情期待下一篇博客