随着公司业务规模的扩大,需求的不断提升,数据量级也在不停的增长,公司其他兄弟部门也需要越来越多的数据需求。为此,我们部门有如下任务需要完成:
基于以上5点,现需要对hbase进行调研和集成,并形成初步的api操作文档
首先,阅读了一本书《Hbase企业应用开发实战》,大致系统的了解hbase的基础知识与实战案例。我以前有过Nosql数据库,以及Hbase的开发经验,该书籍讲的还是比较浅显易懂,容易上手的,可以将这本书当工具书,然后就是用到的话,翻一下了解关键细节概念。
在这里大致介绍下Hbase的大致知识点:
以前Google存储大量的网页信息,如何存储,如何计算,如何快速查询就成为了一个问题,后来在2003年Google发表了3篇论文提供了解决思路,分别是GFS、MAPREDUCE、BigTable ,但是没有讲源码开源出来,后来这几篇论文被doung cutting看见了,由于Doung Cutting是做搜索引擎lucence,遇到和Google同样的问题,在03年看到google发表的论文后,就使用java语言实现了三篇论文,与之对应的就是:
GFS— HDFS 分布式存储
MAPREDUCE—MAPREDUCE 分布式计算
bigtable — hbase 分布式数据库,海量数据随机近实时查询
HBase是BigTable的开源(源码使用Java编写)版本。是 Apache Hadoop的数据库,目前是apache基金会的顶级项目,是建立在 HDFS之上,被设计用来提供高可靠性、高性能、列存储、可伸缩、多版本的 NoSQL 的分布式数据存储系统,实现对大型数据的实时、随机的读写访问。
HBase 依赖于 HDFS 做底层的数据存储,BigTable 依赖 Google GFS 做数据存储;
HBase依赖于 MapReduce做数据计算,BigTable 依赖 Google MapReduce 做数据计算
HBase 依赖于 ZooKeeper 做服务协调,BigTable 依赖 Google Chubby 做服务协调
备注:nosql的概念
NoSQL = NO SQL或者NoSQL = Not Only SQL:会有一些把 NoSQL 数据的原生查询语句封装成 SQL ,
比如HBase 就有 Phoenix 工具
①它介于 NoSQL 和 RDBMS 之间,仅能通过主键(rowkey)和主键的 range 来检索数据
②HBase查询数据功能很简单,不支持 join 等复杂操作
③不支持复杂的事务,只支持行级事务(可通过 hive 支持来实现多表 join等复杂操作)。
④HBase中支持的数据类型:byte
⑤主要用来存储结构化和半结构化的松散数据。
①大:一个表可以有上十亿行,上百万列
②面向列:面向列(族)的存储和权限控制,列(簇)独立检索。相比行是存储,可以少查询很多字段,能大大减少读取的数据量,大大降低I/O,列式存储,数据即是索引,且数据类型一致,数据特征类似,可以高效的压缩和解压。
③稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。
④无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一 张表中不同的行可以有截然不同的列
①扩展性:通过增加机器就可以动态扩展。
②高可靠性:首先HBase有WAL和Replication机制,可以保证数据不会因为集群异常导致数据写入丢失;后者保证集群异常不会导致数据损坏。但是考虑到底层hdfs的block副本机制已经保证了HBase的可靠性。
③高性能:底层的LMS数据结构和Rowkey有序排列等架构的独特设计(LSM,Rowkey的定长、散列、唯一,按照字典排序由低到高存储),使得HBase具有非常高的写入性能。
这里拓展一下LSM数据结构。
Hbase采用的是LSM树的结构,这种结构的关键是,
这样,Hbase就把效率低下的文件中的插入、移动操作转变成了单纯的文件输出、 合并操作。
①半结构化或非结构化数据
对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用 HBase。而 且 HBase 是面向列的,HBase支持动态增加字段
②记录非常稀疏
RDBMS 的行有多少列是固定的,为 null 的列浪费了存储空间。而 HBase 为 null 的 Column 是不会被存储的,这样既节省了空间又提高了读性能。
③多版本数据
对于需要存储变动历史记录的数据,使用 HBase 就再合适不过了。HBase 根据 Row key 和 Column key 定位到的 Value 可以有任意数量的版本值。
④超大数据量
当数据量越来越大,RDBMS 数据库撑不住了,就出现了读写分离策略,通过一个Master专门负责写操作,多个Slave负责读操作,服务器成本倍增。随着压力增加,Master 撑不住了, 这时就要分库了,把关联不大的数据分开部署,一些 join 查询不能用了,需要借助中间层。 随着数据量的进一步增加,一个表的记录越来越大,查询就变得很慢,于是又得搞分表,比 如按 ID 取模分成多个表以减少单个表的记录数。经历过这些事的人都知道过程是多么的折腾。采用 HBase 就简单了,只需要加机器即可,HBase 会自动水平切分扩展,跟 Hadoop 的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce)。
bigtable即大表,能存储十亿行百万列,底层数据存储在hdfs上,对行键创建索引(二次索引(二级)),查询的时候,先查询高级索引表,再由高级索引表跳到初始索引表,再由初始索引表确定数据的存储位置,最终定位数据真实存储 ,总结下来hdfs的设计思想就是:跳表结构 + 布隆过滤器
与 NoSQL 数据库们一样,rowkey 是用来检索记录的主键。访问HBase Table中的行,只有三种方式:
通过单个 row key 访问
通过 row key 的 range
全表扫描
rowkey 行键可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),最好是16。在 HBase内部,rowkey保存为字节数组。HBase 会对表中的数据按照 rowkey 排序 (字典顺序)存储时,数据按照 rowkey 的字典序(byte order)排序存储。设计 key 时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
注意:
字典序对 int 排序的结果是 1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,„,9,91,92,93,94,95,96,97,98,99。 要保持整形的自然序,行键必须用 0 作为左填充。 行的一次读写是原子操作(不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。
HBase 表中的每个列,都归属与某个列簇。列簇是表的Schema
的一部分(而列不是),必须在使用表之前定义好,而且定义好了之后就不能更改。
列名都以列簇作为前缀。例如 courses:history,courses:math 都属于 courses 这个列簇。
访问控制、磁盘和内存的使用统计等都是在列簇层面进行的。 列簇越多,在取一行数据时所要参与
IO、搜寻的文件就越多,所以,如果没有必要,不要设置太多的列簇(最好就一个列簇)
每一个列都属于一个列簇,列是属于表中数据,是插入的时候指定的
HBase 中通过 rowkey 和 columns 确定的为一个存储单元称为 cell。
每个 cell 都保存着同一份数据的多个版本,版本通过时间戳来索引。时间戳的类型是 64 位整型。时间戳可以由hbase(在数据写入时自动)赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。 每个cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase
提供了两种数据版 本回收方式: 保存数据的最后 n 个版本 保存最近一段时间内的版本(设置数据的生命周期 TTL)。用户可以针对每个列簇进行设置。
由rowkey、列族、列名、时间戳 唯一确定的单元。 Cell中的数据是没有类型的,全部是字节码形式存贮。
可以为当前hbase开启rest访问操作,可以通过hbase-daemon.sh start rest 开启后台进程,通过restful接口形式,查询数据
可以通过当前的http方式操作当前hbase
Hbase是目前比较火的列存储数据库,由于Hbase是用Java写的,因此它原生地提供了Java接口,对非Java程序人员,怎么办呢?幸好它提供了thrift接口服务器,因此也可以采用其他语言来编写Hbase的客户端,例如Hbase python接口。
了解一下hbase的shell操作。在hbase的节点,hbase shell进入
ddl、dml、namespace比较重要
创建
help “create_namespace”
create_namespace “name”
查看namespace列表
list_namespace
查看详细描述信息
describe_namespace “name”
显示当前namespace下的所有表
list_namespace_tables "name"
删除 namespace
drop_namespace "name"
很具体的hbase shell操作,可以参看工具书,这里只是大概介绍一下
建表
create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
create 'test','cf
'查表
list
查看表的详细信息
help ‘describe’
hbase> describe ‘t1’
表是否禁用
hbase中表有两种状态:
启用 enable ——可以执行操作
禁用 disable ——不可以执行操作
is_disabled "namespace:t1"
hbase(main):034:0> is_disabled "namespace:t1"
ERROR: Unknown table namespace:t1!
Is named table disabled? For example:
hbase> is_disabled 't1'
hbase> is_disabled 'ns1:t1'
hbase(main):035:0>
禁用表和启用表的相关操作
disable “namespace:t1”
enable “namespace:t1”
例子:
disable “test1”
enable “test1”
修改表alter
表中至少有一个列族 如果表中只剩一个列族 不允许删除
删除表
先禁用表,再删除表
drop “namespace:表名”
disable "t1"
drop "t1"
drop_all 删除指定的所有表
hbase> drop_all 'namespace:.*|t.*'
首先创建一个新表,刚才不小心被删了
create 'test','cf'
list 'table'
put 'test','row1','cf:a','value1'
put 'test','row2','cf:b','value2'
put 'test','row3','cf:c','value3'
scan "test"
get 'test','row1'
disable "test"
drop "test"
主要 Hbase API 类和数据模型之间的对应关系:
备注如下:
HbaseConfiguration——hbase的配置文件管理对象,加载hbase的配置
加载配置文件的时候重点加载zookeeper的位置配置信息
zookeeper中保存的是hbase中表的结构schema信息——hbase的寻址路径(索引表的存储位置)
HBaseAdmin | admin hbase的管理对象
hbase中的namespace或table创建/删除操作,可以理解为ddl的句柄操作
HTable | Table 表操作对象,对表的数据进行操作
HTableDescriptor 表描述器对象,描述表的信息,表名/列簇 HColumnDescriptor
列簇描述器,描述表中的列簇信息的
在做hbaseapi代码demo之前呢,先不谈hbase计数器 原子操作等,先讨论一个重要的概念。
连接查询
Hbase是否支持连接查询,即join查询,是一个常见的问题。简单来说是不支持的。hbase读取数据只有两种Get和Scan两种。
但是不表示Join查询不能实现,只是需要用户自己实现。一般来说,实现的方法有两种:
用哪种方式,依赖于做什么,所以没有一个准确的答案适合所有的情况。
package housekeeperHbase.hbase.develop;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.HBaseAdmin;
/**
* Document:HBase DDL 操作
* User: Shouzhuang.li
* Date: 2020/07/16 19:43
*/
public class HBaseDDL {
static Configuration conf = null;
static HBaseAdmin admin = null;
static Connection conn = null;
/**
* 创建namespace
* @throws IOException
*/
public static void create_namespace(String nameSpace) throws IOException {
if (nameSpace.isEmpty()){
return;
}
NamespaceDescriptor ns = NamespaceDescriptor.create(nameSpace).build();
// 参数namespacedescriptor
admin.createNamespace(ns);
}
/**
* 展示namespace列表
* @throws IOException
*/
public static void list_namespace() throws IOException {
NamespaceDescriptor[] nsDescriptors = admin.listNamespaceDescriptors();
for (NamespaceDescriptor ns : nsDescriptors) {
System.out.println(ns.getName());
}
}
/**
* 命名空间删除
* @param name
* @throws IOException
*/
public static void drop_namespace(String name) throws IOException{
if(!name.isEmpty()){
admin.deleteNamespace(name);
System.out.println("命名空间删除完成");
}else{
return;
}
}
/**
* 创建表
* @param name
* @param familys
* @throws IOException
*/
public static void create_table(String name,String...familys) throws IOException {
//判断是否存在
if (admin.tableExists(name)) {
System.out.println(name+"表已存在,请换个表名");
} else {
TableName tn = TableName.valueOf(name);
//参数 tableName对象,表名描述器
HTableDescriptor table = new HTableDescriptor(tn);
//一个表至少有一个列簇
//封装列簇描述器
for (String f : familys) {
HColumnDescriptor family = new HColumnDescriptor(f);
table.addFamily(family);
}
admin.createTable(table);
}
System.out.println("建表成功");
}
/**
* 查看表列表
* @throws IOException
*/
public static void list_tables() throws IOException {
TableName[] tNames = admin.listTableNames();
for (TableName t : tNames) {
System.out.println(t.getNameAsString());
}
}
/**
* 删除表
* @param name
* @throws IOException
*/
public static void delete_table(String name) throws IOException {
if (admin.tableExists(name)) {
//hbase删除表,必须禁用
admin.disableTable(name);
admin.deleteTable(name);
System.out.println("删除成功!!!");
} else {
System.out.println(name+"不存在啊,请检查后再操作");
}
}
public static void main(String[] args) throws IOException {
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "pro-app-175");
conf.set("hbase.zookeeper.property.clientPort", "2181");
conn = ConnectionFactory.createConnection(conf);
// 获取ddl句柄
admin = (HBaseAdmin) conn.getAdmin();
create_namespace("test_api_1901");
list_namespace();
create_table("test_api_1901:table1", "f1","f2");
list_tables();
//如果要删除表,这里一定主要先disable禁用
delete_table("test_api_1901:table1");
drop_namespace("test_api_1901");
}
}
引入pom依赖。
org.apache.hbase
hbase-client
1.2.0
上面代码main方法可以看到,我们就是建了一个命名空间,然后创建了一个表。然后直接删除了。
可以看到服务器上面什么也没有发生。
hbase(main):034:0*
hbase(main):035:0* list_namespace
NAMESPACE
default
hbase
2 row(s) in 0.0140 seconds
hbase(main):036:0>
hbase(main):037:0*
hbase(main):038:0*
UI监控的话,hbase的master 主节点 端口号:
http://ipaddress:60010/tablesDetailed.jsp
关于调试/接口/开发 会在后续文档记录