day01
一、大纲形式
##一、Linux
1. 操作系统
2. 开源
3. 免费
4. 多用户
5. 多进程
6. 多线程
7. 性能稳定
8. 安全(权限的管理)
##二、Hadoop的核心模块之一HDFS
解决了大数据集如何存储的问题
##三、Hadoop的核心模块之一Mapreduce
解决了分布式系统上的大数据集如何快速,高效的分析与计算的问题,是一个运行在hdfs上的并发的计算与分析框架
##四、Hadoop的核心模块之一yarn
是一个资源(内存,cpu,磁盘)管理框架
##五、Zookeeper
是一个为分布式应用程序提供协调服务的分布式框架。
##六、hive
是一款数据仓库的工具,可以将HDFS上的具有结构化的文件映射(描述信息,也叫元数据,存储在MYSQL)成一张表。可以使用类sql语言进行管理。其实底层就是MR、SPARK、TEZ.
1、Linux
1. 概述
2. 常用指令
3. VI/VIM编辑工具
4. 网络配置(静态IP的设置)(重点)
5. 用户和权限管理(重点)
6. 软件包的安装
7. 虚拟机的克隆(学习期间 一定要会)
8. scp命令
9. 免密登录认证(原理,重点)
10. 时间同步(重点)
11. shell脚本(重点)
2、HDFS
1. 概述
--4V(重点)
--google的三篇论文:《GFS》《MapReduce》《Bigtable》
2. 安装:
-- 单节点:
--1)本地模式
--2) 伪分布式:使用分布式文件系统,守护进程都是独立的(每一个进程都有自己的jvm)
-- 多节点(重点)
-- 完全分布式:使用分布式文件系统,守护进程是分布到各个节点的。也都是独立的进程。
3. 块的概念(重点)
-- 合理的块的设计,都是为了解决稀缺的网络带宽和负载均衡问题
-- 块是hdfs的存储单元
-- 块的大小是相同的(除了最后一个块)
-- 块的大小选择(参考的是寻址时间与传输时间的比例,认为在1:100的这个比例是最优)
4. hdfs的体系结构(重点)
5. hdfs的工作机制
-- 开机启动过程
-- 安全模式
-- 检查点机制 (重点)
-- 心跳反馈(10分30秒)(重点)
-- 网络拓扑
-- 机架感知(2.8.2是一个分水岭, 版本之前是 前两个副本一个机架, 版本之后是后两个副本在同一个机架)(重点)
-- 动态的上下线
6. 读写流程(重中之重)
7. API
-- 关心1: 此方法是静态的还是非静态的
-- 关心2: 此方法的形参类型
-- 关心3: 此方法的返回值类型
3、Mapreduce
1. 概述
-- 框架的思想(重点)
移动计算而非移动数据,分而治之,然后进行汇总处理。
数据的扭转: fetch
原始数据--><K1,V1>-->map函数--<K2,V2>---><K2,List<V2>>--->reduce函数---><K3,V3>
2. 入门案例
-- 分片机制(重点)
inputSplit--->FileSplit(path,start,length,hosts)
-- 分片的特点
3. 序列化机制
4. MapTask的流程(重点)
5. ReduceTask的流程(重点)
6. shuffle流程(重中之重)
7. yarn的job提交流程(重中之重)
8. 经典案例:
--topN案例
--共同好友案例
--自定义输入格式案例
--Map-join
--Reduce-join
4、Zookeeper
1. 简介与特点
--本身是一个分布式集群框架,一个leader,多个follower
--适合安装在奇数台机器上,能正常服务的条件是,(n+1)/2台机器正常运行
允许(n-1)/2台机器宕机
-- 每个服务节点的数据一致(重点理解)
2. 安装:
集群模式(重点)
3. 数据模型:(重点)
类似Linux/Unix的文件系统的多层次的数据结构。每个节点称之为znode. znode可以记录子节点的名字和个数(相当于文件系统的目录),还可以存储1M以内的数据(相当于文件系统的文件).znode通过路径进行唯一标识。
每一个服务节点上都有这个数据模型,因此客户端连接上任务一个服务节点,数据都是一致的。
4. 选举制度
权重: epoch > zxid >serverid
5. 监听和通知机制(重点理解)
6. 应用场景(重点):
-- 集群的管理,比如HA
-- 配置文件的管理
-- 服务器的动态上下线感知
-- 分布式锁
-- 分布式队列
5、HIVE
1. 概念和体系结构,工作流程
-- 体系结构(重点)
-- 工作流程(重点)
2. hive的安装
-- 内嵌模式: derby,只支持单session
-- 本地模式: hive客户端指令会内置开启metastore服务项,自己连接自己,与mysql在哪一台机器上无关。
-- 远程模式: hive的服务项(hiveserver2或metastore)是单独开启的,然后供客户端指令去连接。
重点理解本地模式和远程模式, 实际生产环境中本地和远程用的一样多。
3. hive的库和表操作
-- 本地上,库和表都是hdfs上的一个目录
4. hive的表类型
-- 内部表
-- 外部表
5. hive的基本查询子句
-- left semi join(与exists的原理相同)
-- map-side-join
6. 函数
-- 日期函数
-- 字符串函数
-- 数学函数
-- 开窗函数(重点)
--1) 排名函数
--2) 聚合函数
--3) 序列函数
-- 自定义函数
7. serde
8. 分区与分桶(重点)
二、Hbase的讲解
1、Hbase的简介
1. hadoop base 缩写成Hbase
-- 开源的,基于hdfs的分布式的,可扩展的,面向列式存储,非关系型(nosql->not only sql)的数据库
-- 数据可以有多个版本(历史版本)
-- 提供了高可靠性
-- 本身是基于内存的(高性能,近似实时的访问)
-- 起源于google的《bigtable》论文
2. 与hive的区别
-- hive是用于OLAP,提供类sql语言的分析与计算的框架,底层就是MR。
-- hbase是用于存储的,设计目录是想存储数十亿行X数百万列的大数据集
2、Hbase的表模型
2.1 关系型数据库的表模型(扩展)
关系型数据库的表模型:面向行式存储。在定义表结构时,需要提前定义好列名,列类型,列数目
缺点:
1. 一旦数据表中存储数据后,修改表结构变得特别困难。
2. 如果我们想扩展字段时,会对表结构产生影响。
3. 即使某一行中的某个字段没有赋值,也要使用null填充
4. 一旦涉及到多张表,因为数据表存在着复杂的关系,管理非常不方便。
5. 一旦面对海量数据的处理时,读写性能特别差,尤其在高并发这一块。
2.2 Hbase的表模型
1)Cell
hbase是面向列式存储的表模型,列指的是KV对,这个key就是column,v就是column对应的值。KV对被称之单元格,也就是cell。单元格有属于自己的版本号,其实就是一个时间戳。
2)rowkey
为了表示某些单元格是同一个事物的,所以引入了rowkey的概念。因此rowkey是不可以重复的,否则会出现覆盖情况。
3)column family(列族)
为了更好的方便管理单元格,以及有相同意义的单元格尽可能的汇聚到一起,所以引入了列族的概念。
4)region
region是hbase的物理存储模型,是整张表(数据量比较小的时候),或者是表的一部分(数据量比较大时,有多个region)
5)排序机制
由于hbase是优先基于内存存储的,因此内存中的数据进行排序,排序规则是字典排序(ascii升序)
排序方式:
-- 先按照rowkey进行排序
-- 然后按照列族进行排序
-- 再按照key进行排序
-- 如果是多个版本,会按照时间戳进行降序排序
6)数据类型
hbase是不支持其他类型的维护的,底层就是byte[]类型
3、Hbase的体系结构
4、Hbase的安装
4.1 单机模式
步骤1)上传并解压,更名
[root@qianfeng01 ~]# tar -zxvf hbase-1.2.1-bin.tar.gz -C /usr/local/
[root@qianfeng01 ~]# cd /usr/local/
[root@qianfeng01 local]# mv hbase-1.2.1/ hbase
步骤2)配置环境变量并重新引导
[root@qianfeng01 local]# vim /etc/profile
.........省略........
# hbase environment
export HBASE_HOME=/usr/local/hbase
export PATH=$HBASE_HOME/bin:$PATH
[root@qianfeng01 local]# source /etc/profile
步骤3)修改hbase的环境脚本(hbase-env.sh)
[hadoop@qianfeng01 local]$ vim $HBASE_HOME/conf/hbase-env.sh
#找到下面内容,解开注释,添加具体路径
# The java implementation to use. Java 1.7+ required.
export JAVA_HOME=/usr/local/jdk
# Tell HBase whether it should manage it's own instance of Zookeeper or not.
# hbase内置zookeeper开启
export HBASE_MANAGES_ZK=true
步骤4)修改hbase的site.xml(自定义配置文件)
<configuration>
<!-- 属性hbase.rootdir用于指定hbase产生的数据的存储位置 -->
<property>
<name>hbase.rootdir</name>
<value>file:///usr/local/hbase/data</value>
</property>
<!-- hbase依赖于zookeeper,需要指定内置zookeeper的数据存储位置 -->
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/usr/local/hbase/zkdata</value>
</property>
</configuration>
步骤5)可以开心的玩了
start-hbase.sh
hbase shell
stop-hbase.sh
4.2 伪分布式模式
4.2.1 情况说明
hbase的伪分布式,其实就是指相关的守护进程都有,并且运行在同一个机器上,是独立的进程(每一个守护进程都有自己的JVM)
-- hbase的伪分布式的数据的存储位置,可以是hdfs,也可以是本地文件系统。
-- hbase的伪分布式如果选择hdfs,针对于zookeeper来说,可以选择内置的,也可以我们自行安装的。
下面的演示是自行安装的zookeeper
步骤1)上传并解压,更名
[root@qianfeng01 ~]# tar -zxvf hbase-1.2.1-bin.tar.gz -C /usr/local/
[root@qianfeng01 ~]# cd /usr/local/
[root@qianfeng01 local]# mv hbase-1.2.1/ hbase
步骤2)配置环境变量并重新引导
[root@qianfeng01 local]# vim /etc/profile
.........省略........
# hbase environment
export HBASE_HOME=/usr/local/hbase
export PATH=$HBASE_HOME/bin:$PATH
[root@qianfeng01 local]# source /etc/profile
步骤3)修改hbase的环境脚本(hbase-env.sh)
[hadoop@qianfeng01 local]$ vim $HBASE_HOME/conf/hbase-env.sh
#找到下面内容,解开注释,添加具体路径
# The java implementation to use. Java 1.7+ required.
export JAVA_HOME=/usr/local/jdk
# Tell HBase whether it should manage it's own instance of Zookeeper or not.
# 由于要使用我们自行安装的zookeeper,所以要把内置的关闭掉
export HBASE_MANAGES_ZK=false
步骤4)修改hbase的site.xml(自定义配置文件)
<configuration>
<!-- 属性hbase.rootdir用于指定hbase产生的数据的存储位置 -->
<property>
<name>hbase.rootdir</name>
<value>hdfs://qianfeng01/hbase</value>
</property>
<!-- 开启用hbase集群模式 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- 指定hbase使用的zookeeper集群 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>qianfeng01:2181,qianfeng02:2181,qianfeng03:2181</value>
</property>
<!--将属性hbase.unsafe.stream.capability.enforce 改为true -->
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>true</value>
</property>
</configuration>
步骤5)可以开心的玩了
由于使用自行安装的zookeeper,所以再启动hbase服务进程时,应该提前开启zookeeper集群
zkServer.sh start <---三台一起开
start-hbase.sh
hbase shell
stop-hbase.sh
4.3 完全分布式模式
4.3.1 情况说明和布局安排
4.3.2 安装步骤
步骤1)上传并解压,更名
[root@qianfeng01 ~]# tar -zxvf hbase-1.2.1-bin.tar.gz -C /usr/local/
[root@qianfeng01 ~]# cd /usr/local/
[root@qianfeng01 local]# mv hbase-1.2.1/ hbase
步骤2)配置环境变量并重新引导
[root@qianfeng01 local]# vim /etc/profile
.........省略........
# hbase environment
export HBASE_HOME=/usr/local/hbase
export PATH=$HBASE_HOME/bin:$PATH
[root@qianfeng01 local]# source /etc/profile
步骤3)修改hbase的环境脚本(hbase-env.sh)
[hadoop@qianfeng01 local]$ vim $HBASE_HOME/conf/hbase-env.sh
#找到下面内容,解开注释,添加具体路径
# The java implementation to use. Java 1.7+ required.
export JAVA_HOME=/usr/local/jdk
# Tell HBase whether it should manage it's own instance of Zookeeper or not.
# 由于要使用我们自行安装的zookeeper,所以要把内置的关闭掉
export HBASE_MANAGES_ZK=false
步骤4)修改hbase的site.xml(自定义配置文件)
<configuration>
<!-- 属性hbase.rootdir用于指定hbase产生的数据的存储位置 -->
<property>
<name>hbase.rootdir</name>
<value>hdfs://qianfeng01/hbase</value>
</property>
<!-- 开启用hbase集群模式 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- 指定hbase使用的zookeeper集群 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>qianfeng01:2181,qianfeng02:2181,qianfeng03:2181</value>
</property>
<!--将属性hbase.unsafe.stream.capability.enforce 改为true -->
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>true</value>
</property>
</configuration>
步骤5)三台机器的免密登录认证要做好
步骤6)三台机器的时间一定要同步,不能超过30秒
步骤7)配置regionserver的布局
[root@qianfeng01 hbase]# vim conf/regionservers
qianfeng01
qianfeng02
qianfeng03
步骤8) 配置备份的hmaster
[root@qianfeng01 hbase]# echo "qianfeng02" > ./conf/backup-masters
步骤9)将hadoop的core-site.xml和hdfs-site.xml拷贝到hbase的conf目录下
[root@qianfeng01 hbase]# cp ${HADOOP_HOME}/etc/hadoop/{core-site.xml,hdfs-site.xml} ./conf/
步骤10)分发到其他两台机器上
[root@qianfeng01 local]# scp -r hbase qianfeng02:/usr/local/
[root@qianfeng01 local]# scp -r hbase qianfeng03:/usr/local/
[root@qianfeng01 local]# scp /etc/profile qianfeng02:/etc/
[root@qianfeng01 local]# scp /etc/profile qianfeng03:/etc/
最好去另外两台机器上,重新加载一下环境变量的文件
步骤11)启动hbase
hdfs和zookeeper一定要先启动,
然后再启动hbase的服务进程
启动后,要去webui界面查看一下,ip:16010
如果启动失败,应该去相关进程所在的机器上的hbase的家里的logs目录下查看响应的日志文件
day02
一 Regionserver的动态上下线(了解)
1.1 动态上线
1.1.1 原理
当我们启动一个新的regionserver机器时,会主动向zookeeper的某一个znode(/hbase/rs/)下创建一个临时节点,名字是代表自己的唯一标识。zookeeper会通知master,master收到信息后会进行维护新的regionserver,比如分region等等。
1.1.2 环境准备
准备1)搭建一台新机器:包括jdk,hadoop的环境,hdfs的动态上线。
hdfs的动态上线的步骤:
##1. 在hdfs-site.xml里添加属性
<property>
<name>dfs.hosts</name>
<value>/usr/local/hadoop/etc/hadoop/include</value>
</property>
##2. 创建include文件,添加所有的datanode节点的主机名,包含要上线的
##3. 使用管理命令,刷新节点
hdfs dfsadmin -refreshNodes
##4. 启动新机器上的datanode
[root@qianfeng04 local]# hadoop-daemon.sh start datanode
准备2)准备hbase的环境
可以scp, 并且将/etc/profile也拷贝过去, 保证新机器上的hbase的配置要与hmaster上的一致。
1.1.3 上线步骤
##1. 启动新机器上的regionserver
[root@qianfeng04 local]# hbase-daemon.sh start regionserver
##2. 检查webui上是否多了一个regionserver
##3. 为了下次启动时,带上新机器,因此要将新机器的主机名添加到regionservers文件中,要记得分发哦。
1.2 动态下线
1.2.1 原理
regionserver与zookeeper维护一个会话,在/hbase/rs下有一个代表自己的唯一标识znode。如果regionserver动态下线,那么这个会话会断开,zookeeper会删除此唯一标识znode。master也会与zookeeper时刻保证一个会话,也就是监听/hbase/rs下的znode的增删变量,当有动态下线的机器时,重新分配下线的regionserver上的region,
最后删除regionserver,停止服务。
1.2.2 下线步骤
##1.使用下线命令,下线一台机器,比如qianfeng04
[root@qianfeng01 local]# graceful_stop.sh qianfeng04
##@ 为了下次启动时,不带上已经动态下线的机器,因此要将主机名从regionservers文件中删除,要记得分发哦。
二、hbase shell操作(熟悉)
1.namespace的DDL
##1. list_namespace
##2. create_namespace
##3. describe_namespace
##4. alter_namespace: 可以add/modify 也可以delete属性
##5. list_namespace_tables
##6. drop_namespace: 不能删除非空的namespace
2.table的DDL
##1. create
##2. desc|describe
##3. alter: 可以增删列族,还可以修改列族的属性
##4. list: 不能列出系统表
##5. disable
##6. drop
##7. disable_all,enable_all,drop_all
3.table的CRUD(DML+DQL)
## 插入数据使用put
## 修改数据使用put
## 查询表数据scan: 浏览整个表,或者rowkey范围的所有行记录
## 查询表数据get: 指定行进行查询
## delete :删除单元格
## deleteall: 删除指定行
## count
## truncate
## exists
三、Hbase的API操作(重点)
0.准备工作
1. 创建maven项目:sz2002_hbase
2. 导入jar包(坐标)
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
3. 导入log4j.properties
注意事项: 因为要访问zookeeper,必须使用域名(ip无效),因此要在windows里的hosts文件里配置映射关系
C:/windows/system32/drivers/etc/hosts
1.namespace的DDL
createNamespace()
describeNamespace()
alterNamespaceAddProperties()
alterNamespaceDropProperties()
listAllNamespace()
listTablesOfSpecifiedNamespace()
listAllTable()
listTablesOfALLNamespace()
deleteNamespace()
package com.qf.hbase.api;
import com.qf.hbase.util.HbaseUtil;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
/**
* 使用Hbase的客户端API接口来操纵namespace的DDL操作
*/
public class NamespaceDDL {
private Admin admin;
@Before
public void getAdmin() throws IOException {
admin = HbaseUtil.getAdmin();
}
@After
public void closeAdmin(){
HbaseUtil.closeAdmin(admin);
}
/**
* create_namespace 'myns2'
* @throws IOException
*/
@Test
public void createNamespace() throws IOException {
/*//获取一个配置对象,构造器中的逻辑会读取默认的配置文件中的所有属性
Configuration conf = new Configuration();
// 将zookeeper的集群配置设置到配置对象上
conf.set("hbase.zookeeper.quorum","qianfeng01,qianfeng02,qianfeng03");
//使用连接工厂工具类的静态方法createConnection(Configuration conf)来获取连接对象
Connection connection = ConnectionFactory.createConnection(conf);
//打印连接对象的地址
System.out.println(connection);*/
//获取一个命名空间描述器对象
NamespaceDescriptor.Builder builder = NamespaceDescriptor.create("myns2");
NamespaceDescriptor namespace = builder.build();
/* //获取可以执行DDL操作的客户端API----->Admin对象
Admin admin = HbaseUtil.getAdmin()*/
//调用admin的createNamespace方法将命名空间描述器对象发送到hbase中。其实就是提交操作
admin.createNamespace(namespace);
//关闭操作
/* admin.close();
connection.close();*/
}
/**
* describe_namespace 'myns2'
*/
@Test
public void describeNamespace() throws IOException {
//使用admin客户端API 向hbase发送请求获取一个已经存在的命名空间的描述器对象
NamespaceDescriptor myns2 = admin.getNamespaceDescriptor("myns2");
//获取命名空间的属性信息,属性信息会封装到map集合中
Map<String, String> map = myns2.getConfiguration();
//遍历map集合
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key+"="+map.get(key));
}
}
/**
* alter_namespace 'myns2',{METHOD=>'set','authoer'=>'michael'}
*/
@Test
public void alterNamespaceAddProperties() throws IOException {
//使用admin客户端API 向hbase发送请求获取一个已经存在的命名空间的描述器对象
NamespaceDescriptor myns2 = admin.getNamespaceDescriptor("myns2");
//调用setConfiguration(String key,String value)设置额外的属性
myns2.setConfiguration("author","michael");
myns2.setConfiguration("time","2020-09-01");
myns2.setConfiguration("company","qf");
//将赋有新属性的命名空间描述器对象提交到hbase中
admin.modifyNamespace(myns2);
}
/**
* alter_namespace 'myns2',{METHOD=>'unset',NAME=>'time'}
*/
@Test
public void alterNamespaceDropProperties() throws IOException {
//使用admin客户端API 向hbase发送请求获取一个已经存在的命名空间的描述器对象
NamespaceDescriptor myns2 = admin.getNamespaceDescriptor("myns2");
//调用removeConfiguration(String key)移除已经存在的属性
myns2.removeConfiguration("time");
//将描述器对象提交到hbase中
admin.modifyNamespace(myns2);
}
/**
* list_namespace
* @throws IOException
*/
@Test
public void listAllNamespace() throws IOException {
NamespaceDescriptor[] namespaceDescriptors = admin.listNamespaceDescriptors();
for (NamespaceDescriptor namespaceDescriptor : namespaceDescriptors) {
System.out.println(namespaceDescriptor.getName());
}
}
/**
* list_namespace 'myns1'
*/
@Test
public void listTablesOfSpecifiedNamespace() throws IOException {
TableName[] tables = admin.listTableNamesByNamespace("myns1");
for (TableName tableName : tables) {
System.out.println(tableName);
}
}
/**
* list
*/
@Test
public void listAllTable() throws IOException {
TableName[] tables = admin.listTableNames();
for (TableName tableName : tables) {
System.out.println(tableName);
}
}
/**
* 列出所有的命名空间下的所有的表
* @throws IOException
*/
@Test
public void listTablesOfALLNamespace() throws IOException {
NamespaceDescriptor[] namespaceDescriptors = admin.listNamespaceDescriptors();
for (NamespaceDescriptor namespaceDescriptor : namespaceDescriptors) {
TableName[] tableNames = admin.listTableNamesByNamespace(namespaceDescriptor.getName());
for (TableName tableName : tableNames) {
System.out.println(tableName.getNameAsString());
}
}
}
/**
* drop_namespace 'ns11'
*/
@Test
public void deleteNamespace() throws IOException {
admin.deleteNamespace("ns11");
}
}
2.table的DDL
createTable()
describeTable()
alterTableProperties()
alterTableAddFamily()
alterTableDropFamily()
dropTable()
package com.qf.hbase.api;
import com.qf.hbase.util.HbaseUtil;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.regionserver.BloomType;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Map;
public class TableDDL {
private Admin admin;
@Before
public void getAdmin() throws IOException {
admin = HbaseUtil.getAdmin();
}
@After
public void closeAdmin(){
HbaseUtil.closeAdmin(admin);
}
/**
* create 'myns2:student','base_info'
*/
@Test
public void createTable() throws IOException {
//使用TableName来描述一个表名
TableName tableName = TableName.valueOf("myns2:student");
//获取一个表描述器对象
HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
//获取一个列族描述器对象
HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("base_info".getBytes());
//设置块大小为128KB
hColumnDescriptor.setBlocksize(1024*64*2);
//设置版本数为3
hColumnDescriptor.setVersions(1,3);
//设置一个单元格的存活时间7天
hColumnDescriptor.setTimeToLive(3600*24*7);
//将列族绑定到表上
hTableDescriptor.addFamily(hColumnDescriptor);
//使用Admin将表提交到Hbase中
admin.createTable(hTableDescriptor);
}
/**
* desc 'myns2:student'
*/
@Test
public void describeTable() throws IOException {
//使用Admin向hbase发送请求,获取已经存在的表的描述器对象
HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("myns2:student"));
//获取表描述器有哪些列族描述器对象
HColumnDescriptor[] columnFamilies = tableDescriptor.getColumnFamilies();
for (HColumnDescriptor columnFamily : columnFamilies) {
System.out.print("列族名:"+columnFamily.getNameAsString()+"\t");
System.out.print("块大小:"+columnFamily.getBlocksize()+"\t");
System.out.print("布隆过滤器:"+columnFamily.getBloomFilterType()+"\t");
System.out.print("版本数:"+columnFamily.getMaxVersions()+"\t");
System.out.println("存活时间:"+columnFamily.getTimeToLive());
}
}
/**
* alter 'myns:student',{NAME=>'base_info',VERSIONS=>5}
*/
@Test
public void alterTableProperties() throws IOException {
//使用Admin向hbase发送请求,获取已经存在的表的描述器对象
HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("myns2:student"));
//获取指定的列族描述器,通过getFamily(byte[] name);
HColumnDescriptor base_info = tableDescriptor.getFamily(Bytes.toBytes("base_info"));
//重新设置存活时间
base_info.setTimeToLive(3600*24*5);
//重新设置版本数量
base_info.setVersions(1,5);
//将修改过属性的列族描述器和某一个表名关联,并提交到hbase中
admin.modifyColumn(TableName.valueOf("myns2:student"),base_info);
}
/**
* alter 'myns:student','f2','f3','f4'
*/
@Test
public void alterTableAddFamily() throws IOException {
//使用TableName来描述一个表名对象
TableName tableName = TableName.valueOf("myns2:student");
//新创建三个列族描述器
HColumnDescriptor hColumnDescriptor1 = new HColumnDescriptor("f1");
hColumnDescriptor1.setVersions(1,3);
HColumnDescriptor hColumnDescriptor2 = new HColumnDescriptor("f2");
hColumnDescriptor2.setTimeToLive(3600*3);
HColumnDescriptor hColumnDescriptor3 = new HColumnDescriptor("f3");
hColumnDescriptor3.setBloomFilterType(BloomType.ROWCOL);
//将新的列族描述器绑定到表描述器上
admin.addColumn(tableName,hColumnDescriptor1);
admin.addColumn(tableName,hColumnDescriptor2);
admin.addColumn(tableName,hColumnDescriptor3);
}
/**
* alter 'myns2:student',{NAME=>'f3',METHOD=>'delete'}
*/
@Test
public void alterTableDropFamily() throws IOException {
//使用TableName来描述一个表名对象
TableName tableName = TableName.valueOf("myns2:student");
//描述一个列族名
byte[] columnName = "f3".getBytes();
admin.deleteColumn(tableName,columnName);
}
/**
* drop 'myns2:student'
*/
@Test
public void dropTable() throws IOException {
//描述一个表名对象
TableName tableName = TableName.valueOf("myns2:student");
//使用Admin向hbase发送请求,判断表是否存在
if(admin.tableExists(tableName)){
//判断表是否已经禁用
if(!admin.isTableDisabled(tableName)){
//禁用表
admin.disableTable(tableName);
}
//删除表
admin.deleteTable(tableName);
}
}
}
3.table的CRUD(DML+DQL)
putOneRowData()
putBatchData()
getOneRowData()
-------------------------------------
getMultiRowData()
scanMultiRowData()
deletOneRowData()
deleteMultiRowData()
deleteOneCell()
package com.qf.hbase.api;
import com.qf.hbase.util.HbaseUtil;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 练习Hbase的表的增删改查操作
* <p>
* 要使用的API是Table对象
*/
public class TableCRUD {
private Table table;
@Before
public void getTable() throws IOException {
table = HbaseUtil.getTable("myns2:student");
}
@After
public void closeTable() {
HbaseUtil.closeTable(table);
}
/**
* put 'myns2:student','rk00001','base_info:name','zhangsan'
*/
@Test
public void putOneRowData() throws IOException {
//获取put对象,同时指定rowkey
Put put = new Put("rk02004".getBytes());
/*
addColumn(byte[] family,byte[] columnName,byte[] value)
family: 列族名
columnName: 列名也就是key的名字
value:key对应的值
*/
put.addColumn("base_info".getBytes(), Bytes.toBytes("name"), "hanxin".getBytes());
put.addColumn("f1".getBytes(), Bytes.toBytes("province"), "广东".getBytes());
//使用Table对象将put对象提交到hbase中
table.put(put);
}
@Test
public void putBatchData() throws IOException {
//获取一个泛型为Put的线性表对象
List<Put> puts = new ArrayList<Put>();
for (int i = 2; i < 1010; i++) {
String rowkey = "";
if (i < 10) {
rowkey = "rk0000" + i;
} else if (i < 100) {
rowkey = "rk000" + i;
} else if (i < 1000) {
rowkey = "rk00" + i;
} else {
rowkey = "rk0" + i;
}
Put put = new Put(rowkey.getBytes());
put.addColumn("base_info".getBytes(), "name".getBytes(), Bytes.toBytes("zhaoyun" + i));
put.addColumn("base_info".getBytes(), "age".getBytes(), Bytes.toBytes((int) (Math.random() * 90) + 10 + ""));
int num = (int) (Math.random() * 2);
String gender = num == 0 ? "f" : "m";
put.addColumn("base_info".getBytes(), "gender".getBytes(), Bytes.toBytes(gender));
//将put添加到集合中
puts.add(put);
//每300行一提交
if (i % 300 == 0) {
table.put(puts);
//提交后需要清空集合,否则会出现重复提交
puts.clear();
}
}
table.put(puts);
}
/**
* 获取指定一行的所有的单元格
*/
@Test
public void getOneRowData() throws IOException {
//获取Get对象,指定行号
Get get = new Get("rk00009".getBytes());
//使用table对象的get方法,获取指定行的数据,返回的数据 被封装到Result对象里
Result result = table.get(get); //强调:Result里是这一行的所有的单元格
// advance():相当于迭代器的hasNext方法:问
while(result.advance()){
//current():相当于迭代器的next方法,取
Cell cell = result.current();
//CellUtils工具类,提供了克隆方法,将Cell中的每一个部分单独克隆出来
System.out.print(new String(CellUtil.cloneRow(cell))+"\t");
System.out.print(new String(CellUtil.cloneFamily(cell))+"\t");
System.out.print(new String(CellUtil.cloneQualifier(cell))+"\t");
System.out.println(new String(CellUtil.cloneValue(cell)));
}
}
/**
* 获取多行的所有的单元格
*/
@Test
public void getMultiRowData() throws IOException {
//获取一个泛型为Get的集合对象
List<Get> list = new ArrayList<Get>();
//获取多个Get对象
Get g1= new Get(Bytes.toBytes("rk00001"));
Get g2= new Get(Bytes.toBytes("rk00002"));
Get g3= new Get(Bytes.toBytes("rk00003"));
Get g4= new Get(Bytes.toBytes("rk00004"));
list.add(g1);
list.add(g2);
list.add(g3);
list.add(g4);
//调用table的get(list<Get> list),向Hbase发送查询请求
Result[] results = table.get(list);
for (Result result : results) {
HbaseUtil.printResult(result);
}
}
/**
* 使用Scan对象封装一个行范围,进行查询数据
*
*
* 如果rowkey的长度不统一,那么在scan查询时,可能会查询出来我们不想要的数据。
* 如何避免这种情况呢?
*
* 小技巧:
* 因为排序时是按照ascii码进行字典升序排序
* rk00010
* rk0001010
* rk00011
*
* 如果不想要rk0001010 那么必须要指定一个rowkey是小于等于它的rowkey,
* 反过来说,如果要想rk00010,那么只需要指定一个稍微比之大一点点的rowkey即可。
* rk00010-->rk00010 只需要在其后拼接一个ascii中最小的字符即可 \000
*
*
*
* 从上面的情况可知,在企业中,rowkey的长度要统一。
*
*/
@Test
public void scanMultiRowData() throws IOException {
/*
调用构造器Scan(byte[] startrow,byte[] stoprow)
需要指定开始rowkey,和结束rowkey. 但是左闭右开的情况
*/
//查询rk00001 到rk00010的10行数据的内容
Scan scan = new Scan("rk00001".getBytes(),("rk00020"+"\000").getBytes());
//调用table的getScanner(Scan scan)方法获取所有的行数据,返回的是ResultScanner对象,本质依然是一个迭代器
ResultScanner scanner = table.getScanner(scan);
//获取ResultScanner的迭代器形式
Iterator<Result> iterator = scanner.iterator();
//问:有没有下一个元素,如果返回的是true,指针会主动向下移动
while(iterator.hasNext()){
//取出当前的元素
Result result = iterator.next();
HbaseUtil.printResult(result);
}
}
/**
* 删除一行的所有的单元格
*/
@Test
public void deletOneRowData() throws IOException {
//使用Delete类型的实例封装一个要删除的行号
Delete d1 = new Delete(Bytes.toBytes("rk0001010"));
// 调用table的delete(Delete delete)
table.delete(d1);
}
/**
* 删除多行的数据
*/
@Test
public void deleteMultiRowData() throws IOException {
//获取一个泛型是Delete的集合对象
List<Delete> list = new ArrayList<Delete>();
Delete d1 = new Delete(Bytes.toBytes("rk00010"));
Delete d2 = new Delete(Bytes.toBytes("rk00011"));
Delete d3 = new Delete(Bytes.toBytes("rk00012"));
Delete d4 = new Delete(Bytes.toBytes("rk00013"));
list.add(d1);
list.add(d2);
list.add(d3);
list.add(d4);
//调用table的delete(List<Delete> list)
table.delete(list);
}
/**
* 删除指定行中的某一个单元格
*/
@Test
public void deleteOneCell() throws IOException {
//使用Delete类型的实例封装一个要删除的行号
Delete d1 = new Delete(Bytes.toBytes("rk00009"));
//指定这一行中的gender单元格
d1.addColumn("base_info".getBytes(),"gender".getBytes());
//删除操作
table.delete(d1);
}
}
day03
一、API(续)
1.table的CRUD(DML+DQL)(续)
putOneRowData()
putBatchData()
getOneRowData()
-------------------------------------续讲
getMultiRowData()
scanMultiRowData()
deletOneRowData()
deleteMultiRowData()
deleteOneCell()
2.hbase的过滤器(重点)
2.1 为什么要学习过滤器
因为之前学习的查询有局限性,只能查询指定行号的或者行范围的,或者是查询column叫什么名字的这些情况
不能查询单元格的值是什么,比如name=zhangsan or age = 23的这种需求。
过滤器是可以帮助我们实现上述需求。
2.2过滤器的分类
1. 列值(ColumnValue)过滤器
-- SingleColumnValueFilter : select *
2. 结构(Structural)过滤器
-- FilterList: 可以绑定多个过滤器(条件)
3. keyvalue元数据(metadata)过滤器 : 查询的都是符合指定条件的单元格数据,不是select *
-- FamilyFilter
-- QualifierFilter
-- ColumnPrefixFilter
-- MultipleColumnPrefixFilter
-- ColumnRangeFilter
4. 行键(RowKey)过滤器
-- RowFilter : 查询效果是select *
5. 实用(utility)过滤器
-- FirstKeyOnlyFilter : 查询效果是每一行中的第一个单元格
6. 分页过滤器
-- PageFilter
package com.qf.hbase.api;
import com.qf.hbase.util.HbaseUtil;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.filter.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.yarn.webapp.view.HtmlPage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Iterator;
/**
* 演示所有的过滤器的练习
*/
public class FilterDemo {
private Table table;
@Before
public void getTable() throws IOException {
table = HbaseUtil.getTable("myns2:student");
}
@After
public void closeTable() {
HbaseUtil.closeTable(table);
}
/**
* 测试单列值过滤器的用法
*
* 值得注意的地方:
* 默认情况下,如果要查询的单元格不存在,那么认为是满足条件的,所以会返回。
* 但是在实际需求中,是不应该出现这种情况,
* 问:那么如何避免这种情况的发生??????
*
* 答:需要通过设置一个参数,将不存在此单元格的行数据过滤器
*
* 过滤器会提供setFilterIfMissing(boolean flag)方法
* true:表示过滤掉,不返回
* false: 不过滤掉,返回,认为满足条件,默认就是false
*
*
*/
@Test
public void testSingleColumnValueFilter() throws IOException {
/**
* SingleColumnValueFilter(final byte [] family, final byte [] qualifier,final CompareOp compareOp, final byte[] value) {
*
* 第一个参数:要指定一个查询的列族名
* 第二个参数:要指定一个列族下的单元格的key
* 第三个参数:要传入一个比较符号的常量,比如>,>=,=,<,<=,<>
* CompareFilter.CompareOp.NOT_EQUAL
* CompareFilter.CompareOp.EQUAL
* CompareFilter.CompareOp.GREATER
* CompareFilter.CompareOp.GREATER_OR_EQUAL
* CompareFilter.CompareOp.LESS
* CompareFilter.CompareOp.LESS_OR_EQUAL
* 第四个参数:要指定一个列族下的单元格的value
*/
//指定要查询的是所有的age是23的行记录 : select * from student where age = 23;
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
"base_info".getBytes(),"age".getBytes(), CompareFilter.CompareOp.EQUAL,"23".getBytes());
//设置过滤字段的方法
singleColumnValueFilter.setFilterIfMissing(true);
//因为不知道有多少行,应该使用Scan去查询整张表的数据
Scan scan = new Scan();
//绑定过滤器条件
scan.setFilter(singleColumnValueFilter);
ResultScanner scanner = table.getScanner(scan);
Iterator<Result> iterator = scanner.iterator();
while (iterator.hasNext()){
Result next = iterator.next();
HbaseUtil.printResult(next);
}
}
/**
* 查询 age>89 并且 gender = m的需求
*/
@Test
public void testFilterList() throws IOException {
//使用单列值过滤器指定 age>89
SingleColumnValueFilter ageFilter = new SingleColumnValueFilter(
"base_info".getBytes(), Bytes.toBytes("age"), CompareFilter.CompareOp.GREATER,"89".getBytes()
);
//使用单列值过滤器指定 gender = m
SingleColumnValueFilter genderFilter = new SingleColumnValueFilter(
"base_info".getBytes(), Bytes.toBytes("gender"), CompareFilter.CompareOp.EQUAL,"m".getBytes()
);
//缺失字段的设置
ageFilter.setFilterIfMissing(true);
genderFilter.setFilterIfMissing(true);
/**
* FilterList.Operator.MUST_PASS_ALL 相当于 and
* FilterList.Operator.MUST_PASS_ONE 相当于 or
*/
//获取一个结构过滤器对象
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
//将单列值过滤器绑定到结构过滤器上
filterList.addFilter(ageFilter);
filterList.addFilter(genderFilter);
HbaseUtil.printScan(filterList,table);
}
/**
* 四种比较器的使用
*/
@Test
public void testComparator() throws IOException {
//写一个正则表达式,匹配字符串中有en字符的
// 第一种 "^en" : 匹配en开头的字符串
// 第二种 "en$" : 匹配en结尾的字符串
// 第三种 "en" :匹配字符串中有en字符的
// 第四种 "^en$" : 全匹配
//RegexStringComparator comparator = new RegexStringComparator("^en");
//SubstringComparator comparator = new SubstringComparator("en"); //与正则比较器的第三种情况一致。
//BinaryComparator comparator = new BinaryComparator("zhenji".getBytes()); // 与正则比较器的第四种一致
BinaryPrefixComparator comparator = new BinaryPrefixComparator("zhen".getBytes());//与正则比较器的第一种一致。
//创建一个单列值过滤器,查询 name like '%en%'
SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(
"base_info".getBytes(),"name".getBytes(), CompareFilter.CompareOp.EQUAL,comparator
);
//设置缺失字段
nameFilter.setFilterIfMissing(true);
//查询
HbaseUtil.printScan(nameFilter,table);
}
/**
* 列族过滤器:FamilyFilter
* 构造器中需要传入比较器对象
* 返回值是要查询的列族下的所有的单元格, select score.math,score.english,score.chinese from ..... where 列族=名字
* @throws IOException
*/
@Test
public void testFamilyFilter() throws IOException {
//使用正则比较器
RegexStringComparator comparator = new RegexStringComparator("^f1");
FamilyFilter filter = new FamilyFilter(CompareFilter.CompareOp.EQUAL,comparator);
HbaseUtil.printScan(filter,table);
}
/**
* QualifierFilter: 列名过滤器
* 返回的结果是所有匹配到的列名的所有行的单元格数据
* @throws IOException
*/
@Test
public void testQualifierFilter() throws IOException {
//使用正则比较器
RegexStringComparator comparator = new RegexStringComparator("ender");
QualifierFilter filter = new QualifierFilter(CompareFilter.CompareOp.EQUAL,comparator);
HbaseUtil.printScan(filter,table);
}
/**
* ColumnPrefixFilter:列名前缀过滤器
* 返回的结果是所有匹配到的列名的所有行的单元格数据
* @throws IOException
*/
@Test
public void testColumnPrefixFilter() throws IOException {
//列名前缀过滤器
ColumnPrefixFilter filter = new ColumnPrefixFilter("gen".getBytes());
HbaseUtil.printScan(filter,table);
}
/**
* MultipleColumnPrefixFilter:多列名前缀比较器
* 返回的是每行中所有的匹配到的单元格
* @throws IOException
*/
@Test
public void testMultipleColumnPrefixFilter() throws IOException {
byte[][] columns = new byte[][]{"gen".getBytes(),"name".getBytes()};
//获取一个多列名前缀比较器对象
MultipleColumnPrefixFilter filter = new MultipleColumnPrefixFilter(columns);
HbaseUtil.printScan(filter,table);
}
/**
* ColumnRangeFilter(final byte[] minColumn, boolean minColumnInclusive,final byte[] maxColumn, boolean maxColumnInclusive)
* 列名范围过滤器: 两个列名(age和name)之间的范围. 由于底层使用的ascii码进行比较,所有age---->name是有一定范围的,
* 比如 gender 就在age和name范围内
* minColumn: 指定两个列名中较小的列名
* minColumnInclusive: 返回的结果是否要包含最小的列名,true表示包含,false表示不包含
* maxColumn: 指定两个列名中较大的列名
* maxColumnInclusive: 返回的结果是否要包含最大的列名,true表示包含,false表示不包含
*/
@Test
public void testColumnRangeFilter() throws IOException {
ColumnRangeFilter filter = new ColumnRangeFilter("age".getBytes(),true,"name".getBytes(),false);
HbaseUtil.printScan(filter,table);
}
/**
* RowFilter:行键过滤器
* 返回的是匹配到的所有的行里的单元格数据
* @throws IOException
*/
@Test
public void testRowFilter() throws IOException {
//使用二进制比较器
BinaryPrefixComparator comparator = new BinaryPrefixComparator("rk0001".getBytes());
RowFilter filter = new RowFilter(CompareFilter.CompareOp.EQUAL,comparator);
HbaseUtil.printScan(filter,table);
}
/**
* FirstKeyOnlyFilter: 第一个单元格过滤器
* 返回的是所有行的第一个单元格的数据
* @throws IOException
*/
@Test
public void testFirstKeyOnlyFilter() throws IOException {
FirstKeyOnlyFilter filter = new FirstKeyOnlyFilter();
HbaseUtil.printScan(filter,table);
}
@Test
public void testPageFilter() throws IOException {
// 每页10行,构造器中要指定10
PageFilter filter = new PageFilter(10);
Scan scan = new Scan();
scan.setFilter(filter);
String maxRow = "";
//定义一个计数器,用于统计当前的页号
int currentPage = 0;
while(true){
//定义一个计数器
int count = 0;
ResultScanner scanner = table.getScanner(scan);
Iterator<Result> iterator = scanner.iterator();
System.out.println("--------------currentPage"+ (++currentPage) +"-----------------");
while(iterator.hasNext()){
Result result = iterator.next();
HbaseUtil.printResult(result);
//取出当前行的行键
maxRow=new String(result.getRow());
//每打印一行就自动+1
count++;
}
System.out.println("");
//判断是否是最后一页
if(count<10){
break;
}
//为查询下一页做准备
scan.setStartRow((maxRow+"\000").getBytes());
}
}
}
3.hbase的比较器
--1. RegexStringComparator: 正则字符串比较器
--2. SubstringComparator: 子串比较器
--3. BinaryComparator : 二进制比较器
--4. BinaryPrefixComparator: 二进制前缀比较器
二、工作机制
2.1 寻址过程
客户端会发送访问请求,可能是一个查询操作,也可能是插入操作
查询操作:get 'myns2:student','rk01000';
插入操作:put 't1','25','base_info:name','wangzhaojun'
从上述的操作中,可以看出需要找到具体表的具体的某一个region,如果是插入,就向对应的store的memstore里插入,如果是查询,就从需要查询的store里查询数据(memstore-写缓存,storefile,blockCache-读缓存)
访问过程分3步:
第1步:Client请求ZooKeeper获取hbase:meta表所在的RegionServer的地址。
第2步:Client 请求hbase:meta表所在的RegionServer获取访问的具体的表的region的RegionServer地址,Client会将hbase:meta表的相关信息cache下来,以便下一次快速访问。
第3步:Client请求数据所在的 RegionServer,获取所需要的数据。
2.2 存储机制
2.2.1 存储模型region
2.2.2 flush、compact、split
参考文档
day04
一、复习
一、API(重点)
1. table的crud
--. get对象: 只能指定单行查询,但是可以使用List<Get>查询多行
查询效果: select * 的形式
--. scan对象:
默认情况下是全表查询: select * 的形式
也可以指定行范围进行查询: select * 的形式
也可以指定过滤器进行查询: 可能是select *的形式
也可能是select colName1,colName2.....where 的形式
2. 过滤器
--. 单列值过滤器: select *
--. 结构过滤器: 可以绑定多个其他过滤器进行查询
--. keyvalue元数据过滤器: 返回指定要查询的单元格
--. 行键过滤器: 查询指定行的所有单元格
--. 实用过滤器(FirstKeyOnlyFilter): 查询的每行的第一个单元格
--. 分页过滤器(PageFilter): 可以进行一页固定多少条记录进行查询
3. 比较器
--. RegexStringComparator: 可以指定一个正则表达式的字符串进行匹配想要的数据
--. SubStringComparator: 可以指定一个字符串,查询包含此字符串的数据
--. BinaryComparator: 可以指定一个字符串的字节数组,查询必须是此字符串的数据
--. BinaryPrefixComparator: 可以指定一个字符串的字节数组,查询必须是以此字符串开头的数据
二、工作机制
1. 寻址流程
--. 客户端连接zookeeper,获取hbase:meta表的region的位置(regionserver的ip地址)
--. 紧接着,客户端请求该位置,查询hbase:meta表的数据,获取要操作的表的region及其地址。
--. 然后,客户端就会访问该region的regionserver,然后处理(存取)数据。
2. 存储机制(region)
--. region是hbase中的表或者是表的一部分,也就是基本的存储结构(存储模型,是内存中的java对象)
--. region管理的数据除了在内存中有一部分外,剩下的全都是以文件(storefile)的形式存储在hdfs上。
--. region所要管理的数据可能由于单元格过多,或者是意义不同,分为列族进行管理,一个列族对应一个store(也是内存中的对象)
--. 一个store就管理着相同意义的单元格,这些单元格在对应的memstore中存储,但是由于阈值,最终会flush成storefile。
--. storefile的个数越来越多,store(对象)管理着这些文件的索引信息。
--. storefile的个数太多的话,也不好管理,因此会合并机制,合并成一个文件
--. 如果一个storefile过大,region和regionserver的负载不均衡,因此会有切分机制,细节上是切分文件(从某一个rowkey开始切分,文件大小尽可能均分,也可能造成其他的文件的切分),宏观上是region的切分,因为region有自己的rowkey范围。
3. flush、compact、split、merge_region
-- flush: 指的是memstore存储单元格时,达到自己的阈值(比如是128m,或者是1个小时,或者是整个regionserver的内存的40%),会进行flush,刷成storefile
-- compact: 指的是storefile的数量达到阈值(3)时,会进行合并成一个storefile
-- split: 宏观上指的就是region的切分
二、工作机制
2.1 region
为了更方便的管理表的region以及负载均衡,避免热点问题,所以region一般情况下会预切分。
就是在建表期间提前划分好每一个region的rowkey的范围
create 'ns1:student','base_info',SPLITS=>['RK250000','RK500000','RK750000']
上述就是一种预切分的手段,需要预先估计要存储的真实数据的情况。
2.2 写流程(重点)
(1) Client通过Zookeeper的调度,向RegionServer发出写数据请求,在Region中写数据。
(2) 数据被写入Region的MemStore,直到MemStore达到预设阈值。
(3) MemStore中的数据被Flush成一个StoreFile。
(4) 随着StoreFile文件的不断增多,当其数量增长到一定阈值后,触发Compact合并操作,将多个StoreFile合并成一个StoreFile,同时进行版本合并和数据删除。
(5) StoreFiles通过不断的Compact合并操作,逐步形成越来越大的StoreFile。
(6) 单个StoreFile大小超过一定阈值后,触发Split操作,把当前Region Split成2个新的Region。父Region会下线,新Split出的2个子Region会被HMaster分配到相应的RegionServer上,使得原先1个Region的压力得以分流到2个Region上。
2.3 读流程(重点)
(1) Client 通过寻址流程,找到具体要查询的region。
(2) 由于Regionserver的内存分为MemStore(写存储)和BlockCache(读缓存)两部分。所以,读操作会先读取BlockCache里的数据,然后再读取MemStore里的数据,最后再通过索引找到可能有数据的storefile进行读取,从storefile中读到的数据会暂时缓冲到BlockCache中,为了下次的快速读取操作。
三、Hbase与hive的整合(了解)
3.1 说明
hbase是hadoop数据库,用于存储数据,虽然能提供近似实时的读写操作,但是依然不能从事OLTP的工作,OLAP也不合适。
HIVE底层是MR(MR是一个离线的分析计算框架),也决定了HIVE适合OLAP的工作。
因此Hbase与HIVE整合到一起,就可以满足存储和分析的工作了。
整合的目的:hbase中的表数据在hive中能看到,hive中的表数据在hbase中也能看到
3.2 Hive-to-Hbase
在hive中创建的表,一定要让hbase可以看到
create table if not exists student (
uid int,
uname string,
age int,
sex string,
province string,
city string
)
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties(
"hbase.columns.mapping"=":key,base_info:name,base_info:age,base_info:gender,address_info:province,address_info:city"
)
tblproperties(
"hbase.table.name"="student1"
);
向hive的student表中插入数据
insert into student values (1001,'zs',23,'f','广东','广州');
insert into student (uid,uname,city) values (1002,'lisi','杭州');
向hbase的student1表中插入数据
put 'student1','1003','base_info:name','wangwu'
put 'student1','1003','address_info:city','changchun'
然后在hive中查询,在hbase中查询
3.3 Hbase-to-Hive
- 先在hbase中维护一张表
hbase> create_namespace 'ns1'
hbase> create 'ns1:t1','f1','f2'
put 'ns1:t1','rk00001','f1:name','zhaoyun'
put 'ns1:t1','rk00001','f1:age',23
put 'ns1:t1','rk00001','f1:gender','m'
put 'ns1:t1','rk00001','f2:math','100'
put 'ns1:t1','rk00001','f2:english','10'
put 'ns1:t1','rk00001','f2:chinese','90'
put 'ns1:t1','rk00002','f1:name','zhenji'
put 'ns1:t1','rk00002','f1:age',24
put 'ns1:t1','rk00002','f1:gender','f'
put 'ns1:t1','rk00003','f1:name','貂蝉'
put 'ns1:t1','rk00003','f1:age',24
put 'ns1:t1','rk00003','f1:gender','f'
2)在hive中创建一张表与之进行映射
create external table if not exists score_info (
uid string,
uname string,
age int,
sex string,
math string,
chinese string,
english string
)
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties(
"hbase.columns.mapping"="f1:name,f1:age,f1:gender,f2:math,f2:chinese,f2:english"
)
tblproperties(
"hbase.table.name"="ns1:t1"
);
- 在hive中查询一下数据
3.4 注意事项
1. hive和hbase在字段的映射关系上,是按照建表的字段顺序进行映射的,不是根据名称来的。
2. :key 是要映射成hbase的rowkey的。可以写出来,也可以不写,不写的话是默认提供的。
3. hbase先有表数据时,然后映射到hive中,那么hive的表必须是外部表(external)
四、布隆过滤器的原理(熟悉)
4.1 布隆过滤器的由来
1970年由Howard Bloom提出的一个二进制向量存储结构。用于判断一个元素是否在集合中,如果是“否”,那么一定不在集合中,如果是“是”,那么可能在集合中,牺牲了正确率来节省存储空间
4.2 布隆过滤器的应用场景
在爬虫爬取网页数据时,是根据网页链接来计算这个链接是否爬取过,使用布隆过滤器的存储结构,来判断这个网页链接是否没爬过,如果存储结构中经过计算,发现没有这个链接,那么这个链接一定没有爬过,那就爬。
如果经过计算,这个链接可能在存储结构中,那就意味着这个链接可能爬过,也可能没爬过,那么就不爬(大不了不要此网页的数据了)。
4.3 原理
布隆过滤器内部维护了一个64k大小的位数组,以及n个hashcode函数(每个hashcode函数的逻辑不同)
在初始化时,位数组里全都是0,假设有三个hashcode函数
hashCode1() int值-----是位数组的一个下标-----下标对应的元素存为1.
hashCode2() int值-----是位数组的另一个下标-----下标对应的元素存为1.
hashCode3() int值-----是位数组的另一个下标-----下标对应的元素存为1.
判断一个元素是否在一个集合中,
针对于这个元素分别调用三个函数,来计算出三个下标,如果三个下标上的位数组里的元素只要有一个是0,那么这个元素一定不存在集合中,如果三个下标的位数组的上的元素都是1,那么要判断的元素不一定在集合中。
4.4 在Hbase中的应用
当我们随机读get数据时,如果采用hbase的块索引机制,hbase会加载很多块文件。如果采用布隆过滤器后,它能够准确判断该HFile的所有数据块中,是否含有我们查询的数据,从而大大减少不必要的块加载,从而增加hbase集群的吞吐率。这里有几点细节:
1. 布隆过滤器的存储在哪?
对于hbase而言,当我们选择采用布隆过滤器之后,HBase会在生成StoreFile(HFile)时包含一份布隆过滤器结构的数据,称其为MetaBlock;MetaBlock与DataBlock(真实的KeyValue数据)一起由LRUBlockCache维护。所以,开启bloomfilter会有一定的存储及内存cache开销。但是在大多数情况下,这些负担相对于布隆过滤器带来的好处是可以接受的。
2. 采用布隆过滤器后,hbase如何get数据?
在读取数据时,hbase会首先在布隆过滤器中查询,根据布隆过滤器的结果,再在MemStore中查询,最后再在对应的HFile中查询。
3. 采用ROW还是ROWCOL布隆过滤器?
这取决于用户的使用模式。
如果用户只做行扫描,使用更加细粒度的行加列布隆过滤器不会有任何的帮助,这种场景就应该使用行级布隆过滤器。
当用户不能批量更新特定的一行,并且最后的使用存储文件都含有该行的一部分时,行加列级的布隆过滤器更加有用。
例如:ROW 使用场景假设有2个Hfile文件hf1和hf2, hf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v) hf2包含kv3(r3 cf:q1 v)、kv4(r4 cf:q1 v) 如果设置了CF属性中的bloomfilter(布隆过滤器)为ROW,那么get(r1)时就会过滤hf2,get(r3)就会过滤hf1 。
ROWCOL使用场景假设有2个Hfile文件hf1和hf2, hf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v) hf2包含kv3(r1 cf:q2 v)、kv4(r2 cf:q2 v) 如果设置了CF属性中的bloomfilter为ROW,无论get(r1,q1)还是get(r1,q2),都会读取hf1+hf2;而如果设置了CF属性中的bloomfilter为ROWCOL,那么get(r1,q1)就会过滤hf2,get(r1,q2)就会过滤hf1。
tip:
ROW和ROWCOL只是名字上有联系,但是ROWCOL并不是ROW的扩展,也不能取代ROW
五、Rowkey的设计原则及其案例(重点)
5.1 热点问题
当大量的客户端要访问的数据集中在一个regionserver或者region里,那么这个regionserver或者这个region一定会不堪重负,造成性能上的影响,也可能影响到其他的region的访问。这种情况就是热点问题。因此在存储数据时,应该尽量散列存储行数据。
5.2 rowkey的重要性
因为hbase的存储机制,以及查询方式(通常是按照rowkey来查询,这样的性能非常高,而不是全表遍历),所以rowkey的设计显得格外重要。
比如在建表之处,我们就要考虑好,我们未来可能发生的查询操作,比如按照部门号查询,按照时间、地域性查询。那么就可以将这些信息设计到rowkey中,来达到更高的查询性能。
5.3 设计原则
--1. 唯一性原则
rowkey一定要遵守唯一性原则,如果不唯一,某一些单元格可能会被不小心覆盖掉
--2. 统一长度原则
rowkey的长度如果不统一,那么在查询时,返回的结果可能与想要的结果不一致。
如果rowkey的长度不统一,假如现在有5个Rowkey,分别是:"012", "0", "123", "234", "3"。因此底层是字典排序,所以排序的结果是:
"0"
"012"
"123"
"234"
"3"
而我们想要的结果是如下的:
"0"
"3"
"012"
"123"
"234"
由于是字典排序,做不到这样的效果。如果想要这样的效果,只能长度统一
"000"
"003"
"012"
"123"
"234"
建议在10个字节到100个字节以内,最好是16个字节,也就是8的倍数个字节。原则是能短绝对不要长。
--3. 散列原则
目的:为了尽量避开热点问题,应该使数据散列到不同的region中。
方式1: rowkey的reverse反转
130xxxx6666
130xxxx6789
131xxxx1234
131xxxx2234
159xxxx3234
188xxxx1111
手机号反转:
1234xxxx031
9876xxxx031
4321xxxx131
4322xxxx131
4323xxxx951
1111xxxx881
方式2:salting(加盐)
abc001
abc002
abc003
abc004
.....
abc019
.....
在设计存储时,随机a-g中的任意一个字符,作为前缀拼接到rowkey上
d-abc001 [,b][b,d][d,f][f,]
a-abc002
g-abc003
f-abc004
d-abc005
........
方式3:hashcode散列/取模(也是加前缀)
abc001
abc002
abc003
abc004
.....
abc019
--- 可以对这些rowkey调用hashcode函数或者是其他算法,比如md5等得到一个固定的字符串。
如abc001-->md5算法--->9bf049097142c168c38a94c626eddf3d-->取前四位作为rowkey的前缀
9bf0-abc001
7006-abc002
95e6-abc003
1ba5-abc010
如果是纯数字的rowkey
100001
100002
100003
100004
如果不想连续,而是散列存储,可以使用原始的rowkey对某一个数字比如4进行取模,结果作为前缀
1-100001
1-100005
2-100002
3-100003
0-100004
5.4 案例:多条件的rowkey设计
案例需求:
List<XXXX> find(starttime,endtime,filename,filetype,userid)
-1. 文件创建时间区间 (比如从20120901到20120914期间创建的文件)
-2. 文件名(“中国好声音”),
-3. 分类(“综艺”),
-4. 所有者(“浙江卫视”)。
设计rowkey时,可以是userid+createtime+fileid
rowKey(userID 6 + time 8 + fileID 6) name category ….
00000120120902000001
00000120120904000002
00000120120906000003
00000120120908000004
00000120120910000005
00000120120914000007
00000220120912000006
00000220120916000008
00000320120918000009
00000420120920000010
真正应用时:传入一下参数
find(20120901,20120914,“中国好声音”,“综艺”,1){
将形参拼接成想要的rowkey,传入到scan里
scan.setRowKeyRange(starttime,endtime)
}
六、二级索引表的概念(熟悉)
rk0001 f1:name=zhangsan f1:age=23 的f2:province的值
rk0002 f1:name=lisi f1:age=23
rk0003 f1:name=wangwu f1:age=23
rk0004 f1:age=23
rk0005 f1:name=zhaoliu f1:gender=xxx
...........................
rk0010 f1:name=zhangsan f1:gender=xxx f2:province
rk0011 f1:name=zhangsan f1:gender=xxx
创建一张表: 存储 f1:name===xxxxx所有的行号
rowkey
f1:name=zhangsan f1:rk1 rk0001
f1:rk2 rk0010
f1:rk3 rk0011
f1:name=lisi f1:rk rk0002
二级索引表的概念:
二级索引表存储的就是一些单元格与rowkey的映射关系。在查询原表时,为了不遍历整张表,可以通过二级索引表查询出要查询的行号,然后再去原表中通过行号查询对应的数据,这样就避免了全表扫描,提高了查询效率
七、协处理器(重要)
在hbase的低版本中,想要维护一张二级索引表是非常困难的。后来在高版本中,引入了协处理器的API。可以很轻松的完成二级索引表的建立。
还有就是求和,计数,排序等操作,低版本无法轻易完成,而引入的协处理器可以在server端进行并发运算,从而提高性能。
7.1 协处理器的分类
一、Observer: 相当于关系型数据库支持的触发器(重点)
-- RegionObserver:针对Region的观察者,可以监听关于Region的操作
-- RegionServerObserver:针对RegionServer的观察者,可以监听关于RegionServer的操作
-- WALObserver:针对WAL的观察者,可以监听关于WAL的操作
-- MasterObserver:针对Master的观察者,可以监听关于Master的操作
二、EndPoint: 相当于关系型数据库支持的存储过程,提供的就是一个接口API。
7.2 两种类型的区别
- Observer 允许集群在正常的客户端操作过程中可以有不同的行为表现
- Observer 类似于 RDBMS 中的触发器,主要在服务端工作
- Observer 可以实现权限管理、优先级设置、监控、ddl 控制、二级索引等功能(重点)
- Endpoint 允许扩展集群的能力,对客户端应用开放新的运算命令
- Endpoint 类似于 RDBMS 中的存储过程,主要在服务端工作
- Endpoint 可以实现 min、max、avg、sum、distinct、group by 等功能(重点)
7.3 协处理器的应用
需求:帮助"关注表"创建一个二级索引表"粉丝表"
表名:guanzhu
rowkey cell cell
liushuai-canglaoshi f1:from liushuai f1:to canglaoshi
liushuai-bolaoshi f1:from liushuai f1:to bolaoshi
...............
jiashuai-longzelaoshi f1:from jiashuai f1:to longzelaoshi
......
二级索引表:fensi
rowkey
canglaoshi-liushuai f1:from liushuai f1:to canglaoshi
canglaoshi-jiashuai f1:from jiashuai f1:to canglaoshi
..........................
需要提前创建好两张表
hbase(main):027:0> create 'guanzhu','f1'
hbase(main):028:0> create 'fensi','f1'
步骤1:
1. 自定义类型,继承BaseRegionObserver 重写prePut方法,拦截put对象
2. 打包程序,上传到HDFS上
3. 将此协处理器挂载到关注表上
alter 'guanzhu',METHOD =>'table_att','coprocessor'=>'hdfs://qianfeng01:8020/jar/mycoprocessor.jar|com.qf.hbase.coprocessor.FensiObserver|1001|'
4. 进行测试
注意:在加载协处理器的时候,如果报 set hbase.table.sanity.checks to false等提示,需要在hbase-site.xml里配置以下属性,并分发到其他机器上,然后重启hbase服务
<property>
<name>hbase.table.sanity.checks</name>
<value>false</value>
</property>
加载方式的介绍
1) 静态加载
通过修改 hbase-site.xml 这个文件来实现,启动全局 aggregation,能过操纵所有的表上 的数据。只需要添加如下代码:
<property>
<name>hbase.coprocessor.user.region.classes</name>
<value>类全名</value>
</property>
可以用”,”分割加载多个 class
2) 动态加载
只对特定的表生效。通过 HBase Shell 来实现。
1. 停用表 disable 'mytable'
2. 添加协处理器 alter 't_guanzhu',METHOD => 'table_att','coprocessor'=>'hdfs://supercluster/jar/mycoprocessor.jar|com.qf.hbase.coprocessor.MyIndexCoprocessor|1001|'
3. 启用表 enable 'mytable'
longzelaoshi
…
二级索引表:fensi
rowkey
canglaoshi-liushuai f1:from liushuai f1:to canglaoshi
canglaoshi-jiashuai f1:from jiashuai f1:to canglaoshi
…
需要提前创建好两张表
hbase(main):027:0> create ‘guanzhu’,‘f1’
hbase(main):028:0> create ‘fensi’,‘f1’
步骤1:
- 自定义类型,继承BaseRegionObserver 重写prePut方法,拦截put对象
- 打包程序,上传到HDFS上
- 将此协处理器挂载到关注表上
alter ‘guanzhu’,METHOD =>‘table_att’,‘coprocessor’=>‘hdfs://qianfeng01:8020/jar/mycoprocessor.jar|com.qf.hbase.coprocessor.FensiObserver|1001|’ - 进行测试
注意:在加载协处理器的时候,如果报 set hbase.table.sanity.checks to false等提示,需要在hbase-site.xml里配置以下属性,并分发到其他机器上,然后重启hbase服务
hbase.table.sanity.checks
false
加载方式的介绍
- 静态加载
通过修改 hbase-site.xml 这个文件来实现,启动全局 aggregation,能过操纵所有的表上 的数据。只需要添加如下代码:
hbase.coprocessor.user.region.classes
类全名
可以用”,”分割加载多个 class
- 动态加载
只对特定的表生效。通过 HBase Shell 来实现。
- 停用表 disable ‘mytable’
- 添加协处理器 alter ‘t_guanzhu’,METHOD => ‘table_att’,‘coprocessor’=>‘hdfs://supercluster/jar/mycoprocessor.jar|com.qf.hbase.coprocessor.MyIndexCoprocessor|1001|’
- 启用表 enable ‘mytable’
## 八、优化参数的讲解