5.1.1 、实现需求 1
为了能够统计张三在 2021 年 12 月份消费的总金额,我们需要用 scan 命令能够得到张
三在这个月消费的所有记录,之后在进行累加即可。Scan 需要填写 startRow 和 stopRow:
scan : startRow -> ^A^Azhangsan2021-12 endRow -> ^A^Azhangsan2021-12.
注意点:
(1)避免扫描数据混乱,解决字段长度不一致的问题,可以使用相同阿斯卡码值的符
号进行填充,框架底层填充使用的是阿斯卡码值为 1 的^A。
(2)最后的日期结尾处需要使用阿斯卡码略大于’-’的值
最终得到 rowKey 的设计为:
//注意 rowkey 相同的数据会视为相同数据覆盖掉之前的版本
rowKey: userdate(yyyy-MM-dd HH:mm:SS)
5.1.2 、实现需求 2
问题提出:按照需要 1 的 rowKey 设计,会发现对于需求 2,完全没有办法写 rowKey 的
扫描范围。此处能够看出 hbase 设计 rowKey 使用的特点为:
适用性强 泛用性差 能够完美实现一个需求 但是不能同时完美实现多个需要。
如果想要同时完成两个需求,需要对 rowKey 出现字段的顺序进行调整。
调整的原则为:可枚举的放在前面。其中时间是可以枚举的,用户名称无法枚举,所以
必须把时间放在前面。
最终满足 2 个需求的设计 可以穷举的写在前面即可 rowKey 设计格式 => date(yyyy-MM)^A^Auserdate(-dd hh:mm:ss ms) (1)统计张三在 2021 年 12 月份消费的总金额 scan: startRow => 2021-12^A^Azhangsan stopRow => 2021-12^A^Azhangsan. (2)统计所有人在 2021 年 12 月份消费的总金额 scan: startRow => 2021-12 stopRow => 2021-12.
5.1.3 、添加预分区优化
预分区的分区号同样需要遵守 rowKey 的 scan 原则。所有必须添加在 rowKey 的最前面,
前缀为最简单的数字。同时使用 hash 算法将用户名和月份拼接决定分区号。(单独使用用
户名会造成单一用户所有数据存储在一个分区)
添加预分区优化 startKey stopKey 001 001 002 002 003 ... 119 120 分区号=> hash(user+date(MM)) % 120 分区号填充 如果得到 1 => 001 rowKey 设计格式 => 分区号 date(yyyy-MM)^A^Auserdate(-dd hh:mm:ss ms)
缺点:实现需求 2 的时候,由于每个分区都有 12 月份的数据,需要扫描 120 个分区。
解决方法:提前将分区号和月份进行对应。
提前将月份和分区号对应一下 000 到 009 分区 存储的都是 1 月份数据 010 到 019 分区 存储的都是 2 月份数据 ... 110 到 119 分区 存储的都是 12 月份数据 是 9 月份的数据 分区号=> hash(user+date(MM)) % 10 + 80 分区号填充 如果得到 85 => 085 得到 12 月份所有人的数据 扫描 10 次 scan: startRow => 1102021-12 stopRow => 1102021-12. ... startRow => 1122021-12 stopRow => 1122021-12. .. startRow => 1192021-12 stopRow => 1192021-12.
5.2 、参数优化
1)Zookeeper 会话超时时间
hbase-site.xml
属性:zookeeper.session.timeout
解释:默认值为 90000 毫秒(90s)。当某个 RegionServer 挂掉,90s 之后 Master 才能察觉到。可适当减小此值,尽可能快地检测 regionserver 故障,可调整至 20-30s。
看你能有都能忍耐超时,同时可以调整重试时间和重试次数
hbase.client.pause(默认值 100ms)
hbase.client.retries.number(默认 15 次)
2)设置 RPC 监听数量
hbase-site.xml
属性:hbase.regionserver.handler.count
解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写
请求较多时,增加此值。
3)手动控制 Major Compaction
hbase-site.xml
属性:hbase.hregion.majorcompaction
解释:默认值:604800000 秒(7 天), Major Compaction 的周期,若关闭自动 Major
Compaction,可将其设为 0。如果关闭一定记得自己手动合并,因为大合并非常有意义
4)优化 HStore 文件大小
hbase-site.xml
属性:hbase.hregion.max.filesize
解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值,
因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间
过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两
个 Hfile。
5)优化 HBase 客户端缓存
hbase-site.xml
属性:hbase.client.write.buffer
解释:默认值 2097152bytes(2M)用于指定 HBase 客户端缓存,增大该值可以减少 RPC
调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到
减少 RPC 次数的目的。
6)指定 scan.next 扫描 HBase 所获取的行数
hbase-site.xml
属性:hbase.client.scanner.caching
解释:用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大。
7)BlockCache 占用 RegionServer 堆内存的比例
hbase-site.xml
属性:hfile.block.cache.size
解释:默认 0.4,读请求比较多的情况下,可适当调大
8)MemStore 占用 RegionServer 堆内存的比例
hbase-site.xml
属性:hbase.regionserver.global.memstore.size
解释:默认 0.4,写请求较多的情况下,可适当调大
Lars Hofhansl(拉斯·霍夫汉斯)大神推荐 Region 设置 20G,刷写大小设置 128M,其
它默认。
5.3 、JVM 调优
JVM 调优的思路有两部分:一是内存设置,二是垃圾回收器设置。
垃圾回收的修改是使用并发垃圾回收,默认 PO+PS 是并行垃圾回收,会有大量的暂停。理由是 HBsae 大量使用内存用于存储数据,容易遭遇数据洪峰造成 OOM,同时写缓存的数据是不能垃圾回收的,主要回收的就是读缓存,而读缓存垃圾回收不影响性能,所以最终设置的效果可以总结为:防患于未然,早洗早轻松。
1)设置使用 CMS 收集器:
-XX:+UseConcMarkSweepGC
2)保持新生代尽量小,同时尽早开启 GC,例如:
//在内存占用到 70%的时候开启 GC -XX:CMSInitiatingOccupancyFraction=70 //指定使用 70%,不让 JVM 动态调整 -XX:+UseCMSInitiatingOccupancyOnly //新生代内存设置为 512m -Xmn512m //并行执行新生代垃圾回收 -XX:+UseParNewGC // 设 置 scanner 扫 描 结 果 占 用 内 存 大 小 , 在 hbase-site.xml 中,设置 hbase.client.scanner.max.result.size(默认值为 2M)为 eden 空间的 1/8 (大概在 64M) // 设置多个与 max.result.size * handler.count 相乘的结果小于 Survivor Space(新生代经过垃圾回收之后存活的对象)
5.4 、HBase 使用经验法则
官方给出了权威的使用法则:
(1)Region 大小控制 10-50G
(2)cell 大小不超过 10M(性能对应小于 100K 的值有优化),如果使用 mob(Medium
sized Objects 一种特殊用法)则不超过 50M。
(3)1 张表有 1 到 3 个列族,不要设计太多。最好就 1 个,如果使用多个尽量保证不
会同时读取多个列族。
(4)1 到 2 个列族的表格,设计 50-100 个 Region。
(5)列族名称要尽量短,不要去模仿 RDBMS(关系型数据库)具有准确的名称和描述。
(6)如果 RowKey 设计时间在最前面,会导致有大量的旧数据存储在不活跃的 Region
中,使用的时候,仅仅会操作少数的活动 Region,此时建议增加更多的 Region 个数。
(7)如果只有一个列族用于写入数据,分配内存资源的时候可以做出调整,即写缓存
不会占用太多的内存。
第 6 章 整合 Phoenix
6.1 、Phoenix 简介
6.1.1 、Phoenix 定义
Phoenix 是 HBase 的开源 SQL 皮肤。可以使用标准 JDBC API 代替 HBase 客户端 API来创建表,插入数据和查询 HBase 数据。
6.1.2、 为什么使用 Phoenix
官方给的解释为:在 Client 和 HBase 之间放一个 Phoenix 中间层不会减慢速度,因为用户编写的数据处理代码和 Phoenix 编写的没有区别(更不用说你写的垃圾的多),不仅如此Phoenix 对于用户输入的 SQL 同样会有大量的优化手段(就像 hive 自带 sql 优化器一样)。之后使用执行计划查看效果Phoenix 在 5.0 版本默认提供有两种客户端使用(瘦客户端和胖客户端),在 5.1.2 版本安装包中删除了瘦客户端,本文也不再使用瘦客户端。而胖客户端和用户自己写 HBase 的API 代码读取数据之后进行数据处理是完全一样的。
6.2、 Phoenix 快速入门
6.2.1、 安装
1)官网地址
2)Phoenix 部署
(1)上传并解压 tar 包
[atguigu@hadoop102 software]$ tar -zxvf phoenix-hbase-2.4-5.1.2-bin.tar.gz -C /opt/module/ [atguigu@hadoop102 module]$ mv phoenix-hbase-2.4-5.1.2-bin/ phoenix
(2)复制 server 包并拷贝到各个节点的 hbase/lib
(3)配置环境变量
#phoenix export PHOENIX_HOME=/opt/module/phoenix export PHOENIX_CLASSPATH=$PHOENIX_HOME export PATH=$PATH:$PHOENIX_HOME/bin
(4)重启 HBase
[atguigu@hadoop102 ~]$ stop-hbase.sh [atguigu@hadoop102 ~]$ start-hbase.sh
(5)连接 Phoenix
1. [atguigu@hadoop101 phoenix]$ /opt/module/phoenix/bin/sqlline.py 2. hadoop102,hadoop103,hadoop104:2181
(6)错误解决
出现下面错误的原因是之前使用过 phoenix,建议删除之前的记录
解决方法:在/home/atguigu 目录下删除.sqlline 文件夹
[atguigu@hadoop102 ~]$ rm -rf .sqlline/
6.2.2、Phoenix Shell 操作
6.2.2.1 table
关于 Phoenix 的语法建议使用时直接查看官网:
1)显示所有表
!table 或 !tables
2)创建表
直接指定单个列作为 RowKey
CREATE TABLE IF NOT EXISTS student( id VARCHAR primary key, name VARCHAR, age BIGINT, addr VARCHAR);
在 phoenix 中,表名等会自动转换为大写,若要小写,使用双引号,如"us_population"。
骚戴理解:phoenix只是一个皮肤,实际上还是底层还是hbase,只是说可以通过操作phoenix来写sql操作hbase,所以这里必须要指定RowKey
指定多个列的联合作为 RowKey
CREATE TABLE IF NOT EXISTS student1 ( id VARCHAR NOT NULL, name VARCHAR NOT NULL, age BIGINT, addr VARCHAR CONSTRAINT my_pk PRIMARY KEY (id, name));
注:Phoenix 中建表,会在 HBase 中创建一张对应的表。为了减少数据对磁盘空间的占用,Phoenix 默认会对 HBase 中的列名做编码处理。具体规则可参考官网链接:Storage Formats | Apache Phoenix,若不想对列名编码,可在建表语句末尾加上 COLUMN_ENCODED_BYTES = 0;
3)插入数据
upsert into student values('1001','zhangsan', 10, 'beijing');
4)查询记录
select * from student;
select * from student where id='1001';
5)删除记录
delete from student where id='1001';
6)删除表
drop table student;
7)退出命令行
!quit
6.2.2.2 表的映射
1)表的关系
默认情况下,HBase 中已存在的表,通过 Phoenix 是不可见的。如果要在 Phoenix 中操作 HBase 中已存在的表,可以在 Phoenix 中进行表的映射。映射方式有两种:视图映射和表映射。
2)命令行中创建表 test
HBase 中 test 的表结构如下,两个列族 info1、info2。
(1)启动 HBase Shell
[atguigu@hadoop102 ~]$ /opt/module/hbase/bin/hbase shell
(2)创建 HBase 表 test
hbase(main):001:0> create 'test','info1','info2'
3)视图映射
Phoenix 创建的视图是只读的,所以只能用来做查询,无法通过视图对数据进行修改等操作。在 phoenix 中创建关联 test 表的视图
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> create view "test"( id varchar primary key, "info1"."name" varchar, "info2"."address" varchar);
删除视图
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> drop view "test";
4)表映射
在 Pheonix 创建表去映射 HBase 中已经存在的表,是可以修改删除 HBase 中已经存在的数据的。而且删除 Phoenix 中的表,那么 HBase 中被映射的表也会被删除。
注:进行表映射时,不能使用列名编码,需将 column_encoded_bytes 设为 0。
0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> create table "test"( id varchar primary key, "info1"."name" varchar, "info2"."address" varchar) column_encoded_bytes=0;
6.2.2.3 数字类型说明
HBase 中的数字,底层存储为补码,而 Phoenix 中的数字,底层存储为在补码的基础上,将符号位反转。故当在 Phoenix 中建表去映射 HBase 中已存在的表,当 HBase 中有数字类型的字段时,会出现解析错误的现象。
Hbase 演示:
create 'test_number','info' put 'test_number','1001','info:number',Bytes.toBytes(1000) scan 'test_number',{COLUMNS => 'info:number:toLong'}
phoenix 演示:
1. create view "test_number"(id varchar primary key,"info"."number" bigint); 2. select * from "test_number";
解决上述问题的方案有以下两种:
(1)Phoenix 种提供了 unsigned_int,unsigned_long 等无符号类型,其对数字的编码解
码方式和 HBase 是相同的,如果无需考虑负数,那在 Phoenix 中建表时采用无符号类型是最
合适的选择。
phoenix 演示:
drop view "test_number"; create view "test_number"( id varchar primary key, "info"."number" unsigned_long); select * from "test_number";
(2)如需考虑负数的情况,则可通过 Phoenix 自定义函数,将数字类型的最高位,即符号位反转即可,自定义函数可参考如下链接:User-defined functions(UDFs) | Apache Phoenix。
6.2.3 、Phoenix JDBC 操作
此处演示一个标准的 JDBC 连接操作,实际开发中会直接使用别的框架内嵌的 Phoenix 连接。
1)胖客户端
(1)maven 依赖
<dependencies> <dependency> <groupId>org.apache.phoenix</groupId> <artifactId>phoenix-client-hbase-2.4</artifactId> <version>5.1.2</version> </dependency> </dependencies>
(2)编写代码
package com.atguigu.phoenix; import java.sql.*; import java.util.Properties; public class PhoenixClient { public static void main(String[] args) throws SQLException { // 标准的 JDBC 代码 // 1.添加链接 String url = "jdbc:phoenix:hadoop102,hadoop103,hadoop104:2181"; // 2. 创建配置 // 没有需要添加的必要配置 因为 Phoenix 没有账号密码 Properties properties = new Properties(); // 3. 获取连接 Connection connection = DriverManager.getConnection(url, properties); // 5.编译 SQL 语句 PreparedStatement preparedStatement = connection.prepareStatement("select * from student"); // 6.执行语句 ResultSet resultSet = preparedStatement.executeQuery(); // 7.输出结果 while (resultSet.next()){ System.out.println(resultSet.getString(1) + ":" + resultSet.getString(2) + ":" + resultSet.getString(3)); } // 8.关闭资源 connection.close(); // 由于 Phoenix 框架内部需要获取一个 HBase 连接,所以会延迟关闭 // 不影响后续的代码执行 System.out.println("hello"); } }