一、[endif]概念&原理梳理
1. 最基本的单位是列;
2. 一列或多列形成一行,由唯一的行键确定存储;
3. 一行由若干列组成,若干列构成一个列族;
4. 一个列族的所有列存储在同一个底层的存储文件里,此文件为HFile;
5. 列族在表创建时必须定义好,不能太频繁修改,数量不能太多;
6. 列的数量没有限制,一个列族里可以有数百万个列;
7. 每一列的值或单元格的值都有时间戳,默认系统指定,也可显示设定;
8. HBase是一个稀疏的、分布式的、持久化的、多维的映射,由行键、列键和时间戳索引;
9. 行数据的存取操作是原子的;
10. HBase扩展和负载均衡的基本单元为region,region是以行键排序的连续存储的区间;region太大则自动拆分,反之自动合并;
11. 如果超过限制,在中间键处将此region拆分成两个大致相等的region;
12. 每一个region只能由一台region服务器加载,一台region服务器可以同时加载多个region;
13. 每个region的最佳大小时1G-2G;
14. HFile文件中存储的是经过排序的键值映射结构,文件内部由连续的块组成,块的索引信息存储在文件的尾部;每个块的默认大小为64KB;
15. 存储文件保存在HDFS(部署在完全分布式的集群上)中,HDFS作为HBase存储层;
16. 每次更新数据时,都先将数据记录在提交日志中(预写日志),之后将数据写入内存的memstore中。一旦内存中的数据大于给定的最大值,系统会将这些数据移出内存,作为HFile文件刷写到磁盘中;
17. 内存memstore中的数据不断写入磁盘,会产生越来越多的HFile,会自动合并成一个较大文件;
18. HBase3个重要组件:客户端库、一台主服务器、多台region服务器。可动态增加和删除region服务器;
19. 每台region服务器在Zookeeper中注册一个自己的临时节点,主服务器会利用这些临时节点来发现可用服务器;
20. HBase通过Zookeeper确保只有一个主服务器在运行;
21. Master服务器提供跨region服务器的全局region负载均衡,将繁忙服务器中的region移到负载低的服务器中;
22. Master服务器不实际存储数据或检索路径,它仅提供负载均衡和集群管理,不为region服务器或客户端提供任何数据服务,属于轻量级服务器;
23. Master服务器不需要大存储空间,不需要挂载过多磁盘;
24. 所有修改数据的操作都只保证行级别的原子性;尽量使用批量处理更新来减少单独操作同一行数据的次数;
25. 最好只创建一次HTable实例(一般在应用程序初始化时创建),而且是每个线程创建一个,然后在应用的生存周期内复用此对象;
26. 如果需要创建多个HTable实例,可以使用HTablePool类;
27. HBase中每行数据都有唯一的行键(rowkey),通常行键的含义与真实场景有关;
28. 行键(rowkey)+列族(family)+列限定符(qualifier)+时间戳指向一个单元格的数值;
29. HBase能为一个单元格(一个特定列的值)存储多个版本的数据。通过每个版本使用一个时间戳,并按降序存储实现;
30. 默认情况下会保存3个版本的数据;scan和get操作只会返回一个最新版本数据,在调用中加入最大版本参数或使用时间范围参数才可以获得多个版本的数据;
二、问题汇总
[if !supportLists]1. [endif]填坑
从http://mvnrepository.com/artifact/org.springframework.data/spring-data-hadoop-hbase上面下载hbase-client 2.1.0版本和spring-data-hadoop 2.5.0版本的jar包并上传到maven仓库。Springboot工程中引入上述两个jar包后,pom文件报错,原因是缺少jdk1.8 tools.jar。将如下配置加入pom解决报错问题:
jdk.tools
jdk.tools
1.8
system
${JAVA_HOME}/lib/tools.jar
[if !supportLists]2. [endif]插入数据之前,必须保证table已创建且列族Colum Family已创建,否则插入数据时报如下错误:
三、配置
SpringBoot工程,配置如下:
#Hbase
hbase.zookeeper.quorum=10.120.240.210,10.120.240.211,10.120.240.212
hbase.zookeeper.port=2181
hbase.master=10.120.240.210:6000
其中10.120.240.210和10.120.240.211都为master节点,主备关系
配置类:
四、JAVA API操作
/**
* AnalysisEngine——HbaseServiceTest
*
* @author ZhangChi
* @date 2018年8月10日---上午11:17:51
* @version 1.0
*/
@Service("hbaseServiceTest")
public class HbaseServiceTest {
@Autowired
private Connection connection;
private HBaseAdmin hBaseAdmin;
public void createTable(String tableName, String columnFamily) {
try {
hBaseAdmin = (HBaseAdmin) connection.getAdmin();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
if (hBaseAdmin.tableExists(tableName)) {
LogConstant.runLog.info(tableName + " exists!");
} else {
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
tableDesc.addFamily(new HColumnDescriptor(columnFamily));
hBaseAdmin.createTable(tableDesc);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void findTable(String tableName) {
try {
hBaseAdmin = (HBaseAdmin) connection.getAdmin();
LogConstant.runLog.info("is table exist:" + hBaseAdmin.tableExists(tableName));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void deleteTable(String tableName) {
try {
hBaseAdmin = (HBaseAdmin) connection.getAdmin();
// 通过tableName创建表名
TableName tbName = TableName.valueOf(tableName);
// 判断表是否存在,若存在就删除,不存在就退出
if (hBaseAdmin.tableExists(tbName)) {
// 首先将表解除占用,否则无法删除
hBaseAdmin.disableTable(tbName);
// 调用delete方法
hBaseAdmin.deleteTable(tbName);
LogConstant.runLog.info("table: " + tableName + " is deleted!");
} else {
LogConstant.runLog.info("table: " + tableName + " is not exist!");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void putData(String tableName) {
try {
Random random = new Random();
hBaseAdmin = (HBaseAdmin) connection.getAdmin();
// 通过表名获取tbName
TableName tbname = TableName.valueOf(tableName);
// 通过connection获取相应的表
Table table = connection.getTable(tbname);
// hbase支持批量写入数据,创建Put集合来存放批量的数据
List batput = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 实例化put对象,传入行键
Put put = new Put(Bytes.toBytes("rowkey_" + i));
// 调用addcolum方法,向i簇中添加字段
/* byte [] family, byte [] qualifier, byte [] value */
put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("username"), Bytes.toBytes("un_" + i));
put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("age"), Bytes.toBytes(random.nextInt(50) + 1));
put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("birthday"), Bytes.toBytes("2017" + i));
put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("phone"), Bytes.toBytes("phone:" + i));
put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("e-mail"), Bytes.toBytes("e-mail:" + i));
batput.add(put);
}
table.put(batput);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void getAllData(String tableName) {
TableName tbname = TableName.valueOf(tableName);
// 通过connection获取相应的表
try {
Table table = connection.getTable(tbname);
// 创建Get的集合以承接查询的条件
List gets = new ArrayList<>();
for (int i = 0; i < 5; i++) {
// 就将查询条件放入get对象中
Get get = new Get(Bytes.toBytes("rowkey_" + i));
// 将get对象放入聚合
gets.add(get);
}
Result[] results = table.get(gets);
// 遍历结果数组,利用CellScanner配合cellUtil获得对应的数据
for (Result result : results) {
// 调用result.cellscanner创建scanner对象
CellScanner cellScanner = result.cellScanner();
// 遍历结果集,取出查询结果
// 如果存在下一个cell则advandce方法返回true,且current方法会返回一个有效的cell,可以当作循环条件
while (cellScanner.advance()) {
// current方法返回一个有效的cell
Cell cell = cellScanner.current();
// 使用CellUtil调用相应的方法获取想用的数据,并利用Bytes.toString方法将结果转换为string输出
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String qualify = Bytes.toString(CellUtil.cloneQualifier(cell));
String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));
LogConstant.runLog.info(family + "_" + qualify + "_" + rowkey + "_" + value);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void getAccurateData(String tableName, String rowkeyIn) {
TableName tbname = TableName.valueOf(tableName);
try {
Table table = connection.getTable(tbname);
Get get = new Get(Bytes.toBytes(rowkeyIn));
Result result = table.get(get);
// 嵌套遍历result获取cell
for (Cell cell : result.listCells()) {
// 使用CellUtil工具类直接获取cell中的数据
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String qualify = Bytes.toString(CellUtil.cloneQualifier(cell));
String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));
LogConstant.runLog.info("[accurate data]:" + family + "_" + qualify + "_" + rowkey + "_" + value);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}