前言
HBase表结构设计(中),介绍ROWKEY设计原则、项目初始化。
4.5 ROWKEY设计原则
4.5.1 HBase官方的设计原则
避免使用递增行键/时序数据
如果ROWKEY设计的都是按照顺序递增(例如:时间戳),这样会有很多的数据写入时,负载都在一台机器上。我们尽量应当将写入大压力均衡到各个RegionServer
避免ROWKEY和列的长度过大
- 在HBase中,要访问一个Cell(单元格),需要有ROWKEY、列蔟、列名,如果ROWKEY、列名太大,就会占用较大内存空间。所以ROWKEY和列的长度应该尽量短小
- ROWKEY的最大长度是64KB,建议越短越好
使用long等类型比String类型更省空间
long类型为8个字节,8个字节可以保存非常大的无符号整数,例如:18446744073709551615。如果是字符串,是按照一个字节一个字符方式保存,需要快3倍的字节数存储。
ROWKEY唯一性
- 设计ROWKEY时,必须保证RowKey的唯一性
- 由于在HBase中数据存储是Key-Value形式,若向HBase中同一张表插入相同RowKey的数据,则原先存在的数据会被新的数据覆盖。
4.5.2 避免数据热点
- 热点是指大量的客户端(client)直接访问集群的一个或者几个节点(可能是读、也可能是写)
- 大量地访问量可能会使得某个服务器节点超出承受能力,导致整个RegionServer的性能下降,其他的Region也会受影响
预分区
- 默认情况,一个HBase的表只有一个Region,被托管在一个RegionServer中
- 每个Region有两个重要的属性:Start Key、End Key,表示这个Region维护的ROWKEY范围
- 如果只有一个Region,那么Start Key、End Key都是空的,没有边界。所有的数据都会放在这个Region中,但当数据越来越大时,会将Region分裂,取一个Mid Key来分裂成两个Region
- 预分区个数 = 节点的倍数。默认Region的大小为10G,假设我们预估1年下来的大小为10T,则10000G / 10G = 1000个Region,所以,我们可以预设为1000个Region,这样,1000个Region将均衡地分布在各个节点上
ROWKEY避免热点设计
- 反转策略
- 如果设计出的ROWKEY在数据分布上不均匀,但ROWKEY尾部的数据却呈现出了良好的随机性,可以考虑将ROWKEY的翻转,或者直接将尾部的bytes提前到ROWKEY的开头。
- 反转策略可以使ROWKEY随机分布,但是牺牲了ROWKEY的有序性
- 缺点:利于Get操作,但不利于Scan操作,因为数据在原ROWKEY上的自然顺序已经被打乱
- 加盐策略
- Salting(加盐)的原理是在原ROWKEY的前面添加固定长度的随机数,也就是给ROWKEY分配一个随机前缀使它和之间的ROWKEY的开头不同
- 随机数能保障数据在所有Regions间的负载均衡
- 缺点:因为添加的是随机数,基于原ROWKEY查询时无法知道随机数是什么,那样在查询的时候就需要去各个可能的Regions中查找,加盐对比读取是无力的
- 哈希策略
- 基于 ROWKEY的完整或部分数据进行 Hash,而后将Hashing后的值完整替换或部分替换原ROWKEY的前缀部分
- 这里说的 hash 包含 MD5、sha1、sha256 或 sha512 等算法
- 缺点:Hashing 也不利于 Scan,因为打乱了原RowKey的自然顺序
4.5.3 陌陌打招呼数据预分区
预分区
在HBase中,可以通过指定start key、end key来进行分区,还可以直接指定Region的数量,指定分区的策略。
- 指定 start key、end key来分区
hbase> create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40']
hbase> create 't1', 'f1', SPLITS => ['10', '20', '30', '40']
hbase> create 't1', 'f1', SPLITS_FILE => 'splits.txt', OWNER => 'johndoe'
- 指定分区数量、分区策略
hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
分区策略
- HexStringSplit: ROWKEY是十六进制的字符串作为前缀的
- DecimalStringSplit: ROWKEY是10进制数字字符串作为前缀的
- UniformSplit: ROWKEY前缀完全随机
Region的数量可以按照数据量来预估。本次案例,因为受限于虚拟机,所以我们设计为6个Region。因为ROWKEY我们是使用多个字段拼接,而且前缀不是完全随机的,所以需要使用HexStringSplit。
ROWKEY设计
- 为了确保数据均匀分布在每个Region,需要以MD5Hash作为前缀
- ROWKEY = MD5Hash_发件人账号_收件人账号_时间戳
业务分区脚本create 'MOMO_CHAT:MSG', {NAME => "C1", COMPRESSION => "GZ"}, { NUMREGIONS => 6, SPLITALGO => 'HexStringSplit'}
执行完命令后,我们发现该表已经分为6个分区。这样将来数据就可以均匀地分布到不同的分区中了
注意:勾选ShowDetailName&Start/EndKey,点击Recorder
HDFS中,也有对应的6个文件夹。
URL:/hbase/data/MOMO_CHAT/MSG
4.6 项目初始化
4.6.1 导入POM依赖
<repositories><!-- 代码库 -->
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- HBase客户端 -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Xml操作相关 -->
<dependency>
<groupId>com.github.cloudecho</groupId>
<artifactId>xmlbean</artifactId>
<version>1.5.5</version>
</dependency>
<!-- 操作Office库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 操作Office库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 操作Office库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 操作JSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- phoenix core -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
<!-- phoenix 客户端 -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-queryserver-client</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
</configuration>
</plugin>
</plugins>
</build>
4.6.2 拷贝配置文件
将配套资料中的以下几个文件拷贝到resources目录。
- core-site.xml
- hbase-site.xml
- log4j.properties
4.6.3 创建包结构
cn.itcast.momo_chat.service | 用于存放数据服务接口相关代码,例如:查询的API代码 |
cn.itcast.momo_chat.service.impl | 用于存放数据服务接口实现类相关代码,例如:查询的API代码 |
cn.itcast.momo_chat.tool | 工具类 |
cn.itcast.momo_chat.entity | 存放实体类 |
4.6.4 导入ExcelReader工具类
在资料包中有一个ExcelReader.java文件,ExcelReader工具类可以读取Excel中的数据称为HashMap这样,方便我们快速生成数据。
ExcelReader工具类主要有两个方法:
1.readXlsx——用于将指定路径的Excel文件中的工作簿读取为Map结构
2.randomColumn——随机生成某一列中的数据。
将ExcelReader添加到cn.itcast.momo_chat.tool包中。
4.6.5 创建实体类
在cn.itcast.momo_chat.entity包中创建一个名为Msg的实体类,使用Java代码描述陌陌消息。
字段名 | 说明 |
msg_time | 消息时间 |
sender_nickyname | 发件人昵称 |
sender_account | 发件人账号 |
sender_sex | 发件人性别 |
sender_ip | 发件人IP |
sender_os | 发件人系统 |
sender_phone_type | 发件人手机型号 |
sender_network | 发件人网络制式 |
sender_gps | 发件人GPS |
receiver_nickyname | 收件人昵称 |
receiver_ip | 收件人IP |
receiver_account | 收件人账号 |
receiver_os | 收件人系统 |
receiver_phone_type | 收件人手机型号 |
receiver_network | 收件人网络制式 |
receiver_gps | 收件人GPS |
receiver_sex | 收件人性别 |
msg_type | 消息类型 |
distance | 双方距离 |
message | 消息 |
操作步骤:
1.使用列编辑,快速复制讲义中上述的表格字段给实体类添加成员变量
2.使用IDEA快捷键 Alt + Insert 键快速生成 getter/setter 方法,并重写toString方法
参考代码:
public class Msg {
private String msg_time;
private String sender_nickyname;
private String sender_account;
private String sender_sex;
private String sender_ip;
private String sender_os;
private String sender_phone_type;
private String sender_network;
private String sender_gps;
private String receiver_nickyname;
private String receiver_ip;
private String receiver_account;
private String receiver_os;
private String receiver_phone_type;
private String receiver_network;
private String receiver_gps;
private String receiver_sex;
private String msg_type;
private String distance;
private String message;
public String getMsg_time() {
return msg_time;
}
public void setMsg_time(String msg_time) {
this.msg_time = msg_time;
}
public String getSender_nickyname() {
return sender_nickyname;
}
public void setSender_nickyname(String sender_nickyname) {
this.sender_nickyname = sender_nickyname;
}
public String getSender_account() {
return sender_account;
}
public void setSender_account(String sender_account) {
this.sender_account = sender_account;
}
public String getSender_sex() {
return sender_sex;
}
public void setSender_sex(String sender_sex) {
this.sender_sex = sender_sex;
}
public String getSender_ip() {
return sender_ip;
}
public void setSender_ip(String sender_ip) {
this.sender_ip = sender_ip;
}
public String getSender_os() {
return sender_os;
}
public void setSender_os(String sender_os) {
this.sender_os = sender_os;
}
public String getSender_phone_type() {
return sender_phone_type;
}
public void setSender_phone_type(String sender_phone_type) {
this.sender_phone_type = sender_phone_type;
}
public String getSender_network() {
return sender_network;
}
public void setSender_network(String sender_network) {
this.sender_network = sender_network;
}
public String getSender_gps() {
return sender_gps;
}
public void setSender_gps(String sender_gps) {
this.sender_gps = sender_gps;
}
public String getReceiver_nickyname() {
return receiver_nickyname;
}
public void setReceiver_nickyname(String receiver_nickyname) {
this.receiver_nickyname = receiver_nickyname;
}
public String getReceiver_ip() {
return receiver_ip;
}
public void setReceiver_ip(String receiver_ip) {
this.receiver_ip = receiver_ip;
}
public String getReceiver_account() {
return receiver_account;
}
public void setReceiver_account(String receiver_account) {
this.receiver_account = receiver_account;
}
public String getReceiver_os() {
return receiver_os;
}
public void setReceiver_os(String receiver_os) {
this.receiver_os = receiver_os;
}
public String getReceiver_phone_type() {
return receiver_phone_type;
}
public void setReceiver_phone_type(String receiver_phone_type) {
this.receiver_phone_type = receiver_phone_type;
}
public String getReceiver_network() {
return receiver_network;
}
public void setReceiver_network(String receiver_network) {
this.receiver_network = receiver_network;
}
public String getReceiver_gps() {
return receiver_gps;
}
public void setReceiver_gps(String receiver_gps) {
this.receiver_gps = receiver_gps;
}
public String getReceiver_sex() {
return receiver_sex;
}
public void setReceiver_sex(String receiver_sex) {
this.receiver_sex = receiver_sex;
}
public String getMsg_type() {
return msg_type;
}
public void setMsg_type(String msg_type) {
this.msg_type = msg_type;
}
public String getDistance() {
return distance;
}
public void setDistance(String distance) {
this.distance = distance;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}