(零)前言
这篇接着上一篇:《从零开始学习大数据平台(Episode 1)》。
(一)为什么要用大数据平台
用大数据平台和自己开发分布式应用有啥区别呢?比如下图是一个自己开发的分布式稽核系统的测试。
我们有自己的Windows,RHEL下的调度平台,我们可以将数据分块分布式处理。那么为什么我们还需要学习和使用“通用”的大数据平台呢?
(1.1)开发/学习成本
一个普通程序员可以很快的熟练的掌握Spark开发,简短的代码就可以实现一种/多种业务场景。
同样开发水平的,自己开发一套,则需要考虑多线程,通信,容错,效率各种方面。开发周期很长bug也会很多,如果水平不够基本上是不可能成功完成的。
(1.2)分布式存储
分布式应用相对还算简单的话,开发一套分布式存储系统对于中小企业基本是不可能了。
考虑到人员流动,新开发人员学习它的成本也会很高。
没有分布式存储,数据处理量大而计算简单的业务场景,越分布式越慢。。。
(1.3)通用平台vs单一业务场景
使用大数据平台可以快速的开发出适合不同业务场景的业务模块。
自己开发的系统由于相对简单,实现的功能也比较单一。
(1.4)效率,灵活,扩展
自己开发的分布式应用因为没有分布式文件系统支撑,效率难以保证。
也无法像Spark,Mesos集群等灵活的指定分配资源。
自己开发的分布式节点难以做到实时的接入与下线(也是开发成本/难度的问题)。
最后由于自己开发不够强大,难以充分利用全部的CPU,内存,等资源。
(Spark集群/16core/7.7分钟)
(自己的平台,单线程,30分钟+)
只使用一台计算机上的全部虚拟机。
保证其它资源一致且不受网络条件影响。
通过调整分区参数,观察执行效率。
可以看到即便是使用同一台计算机上的全部虚拟机,效率也远高于自己开发的程序。
(二)准备测试环境
(2.1)CPU内存资源
正式生产环境可能是分配好的Linux服务器,可能是已有的集群。
但是我们测试只能用PC机,那么就需要注意资源分配的时候不要超过了物理极限。
- 全部虚拟机的内存+物理PC机保留的内存得留有余量,不要内存不够导致使用交换文件。
- 虚拟机内部比如Spark集群的Worker,Excutor,Driver分配也要留有余量,别OOM或者大量GC。
- 注意CPU内核总数,不要用超线程的线程数来计算内核数。
(2.2)磁盘同样重要
正式生产环境通常是磁盘阵列,或者划分好的单独存储。
测试环境中我们的多个虚拟机最好是能放在不同的物理磁盘上,有条件的尽量用SSD。如果几个虚拟机放在同一个HDD上,那么瓶颈就卡在磁盘上,效率怎么都高不了。
(因为其它工作,这里停了几个月没写,都快全忘了T_T)
(2.3)虚拟机管理
如果我们用VMWare,并且有好几台实体的服务器,那么一定要用远程管理(共享虚拟机)。
就我实际情况来说,实体的服务器资源不够,当别的项目需要用到服务器的时候,需要关闭全部虚拟机。那么在一个界面开关全部虚拟机是最方便的。
同时远程管理虚拟机,也避免了SSH出现问题连不上,远程桌面需要和别人抢着用的尴尬情况。
同样用关闭虚拟机当然也是批量用脚本最方便:
#关机脚本文件
ssh -t shionlnx "echo \"密码\" | sudo -S shutdown -h now"
ssh -t shion1 "echo \"密码\" | sudo -S shutdown -h now"
ssh -t ac1 "echo \"密码\" | sudo -S shutdown -h now"
ssh -t ac2 "echo \"密码\" | sudo -S shutdown -h now"
ssh -t ad1 "echo \"密码\" | sudo -S shutdown -h now"
ssh -t ad2 "echo \"密码\" | sudo -S shutdown -h now"
(2.4)网络
用千兆有线局域网,不要用WIFI!!!
用千兆有线局域网,不要用WIFI!!!
用千兆有线局域网,不要用WIFI!!!
(2.5)主机时间
如果多台主机的时间相差较大,则HDFS和HBase会有问题。
我们可以用VMware Tool设置虚拟机时间与物理计算机同步(物理机用windows自动时间同步)。
也可以用ntp同步时间(或设置成启动就运行的服务)
$Shion@ad2 ~> sudo yum install ntp
$Shion@ad2 ~> sudo ntpdate ntp1.aliyun.com
或者临时手动设置一下时间,并和BIOS同步。
$Shion@ad2 ~> date -s 时间
$Shion@ad2 ~> sudo hwclock -w
(三)HBase
HBase™ :是基于Hadoop的数据库, 一个分布式可扩展的大数据存储。
以前我们把文件存储在磁盘上,需要管理和查询的数据存储在Oracle数据库中。
当数据达到一定规模的时候文件我们改放入HDFS,而数据库则需要使用非关系型数据库。
当然,如果传统关系型数据库比如Oracle,在容量和性能各方面要求都OK情况下,那就用Oracle吧。
(3.1)HBase安装/配置/启动
【1】下载安装包
从官网下载HBase 1.4的最新安装包。
【2】解压到你想放置的目录
……略……
【3】配置环境
先配置HADOOP_HOME:
$ sudo vim /etc/profile ——加入:
#HBase
export HBASE_HOME=/home/Shion/hbase
export PATH=$HBASE_HOME/bin:$PATH
进入{HBASE_HOME}/conf,配置hbase-site.xml:
<configuration>
<property>
<name>hbase.rootdir</name> <!-- hbase存放数据目录 -->
<value>hdfs://vm201:9000/opt/hbase/hbase_db</value>
</property>
<property>
<name>hbase.cluster.distributed</name> <!-- 是否分布式部署 -->
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>vm201,vm202</value>
</property>
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/Shion/hbase/zookeeper</value>
</property>
<property>
<name>hbase.replication</name>
<value>false</value>
</property>
<property>
<name>hbase.backup.enable</name>
<value>false</value>
</property>
</configuration>
以及配置regionservers,backup-masters,两个文件。
【4】从Master服务器整体拷贝hbase目录到各个RegionServer服务器
……略……
【5】在指定hbase.zookeeper.quorum的服务器上配置ZK
1)默认情况下HBASE使用内置的zookeeper,所以需要如下配置:
新建 {HBASE_HOME}/zookeeper/myid 文件。
文件内容,第一台服务器填写0,第二台填写1,如果有更多则依次填写不同的ID。
2)如果不想用内置的zookeeper则需要配置hbase-env.sh:
export HBASE_MANAGES_ZK=false
【6】启动/停止HBase
之前忘写了,还是需要先启动再使用的,呵呵哒……
#启动
$Shion@shionlnx ~> start-hbase.sh
#停止
$Shion@shionlnx ~> stop-hbase.sh
(3.2)HBase Shell
进入HBase Shell
$Shion@shionlnx ~> hbase shell
HBase Shell
Use "help" to get list of supported commands.
Use "exit" to quit this interactive shell.
For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell
Version 2.1.4, r5b7722f8551bca783adb36a920ca77e417ca99d1, Tue Mar 19 19:05:06 UTC 2019
Took 0.0020 seconds
hbase(main):001:0>
列举表:list
hbase(main):001:0> list
TABLE
ACTest
zg_crm
zk_cm
3 row(s)
Took 0.4271 seconds
=> ["ACTest", "zg_crm", "zk_cm"]
创建表:create
#默认列簇属性
hbase(main):001:0> create 'ACTest', 'baseInfo', 'extrInfo'
Created table ACTest
Took 4.6956 seconds
=> Hbase::Table - ACTest
#指定列簇属性
hbase(main):001:0> create 'ACTest', { NAME => 'baseInfo', COMPRESSION => 'GZ' }, { NAME => 'extrInfo', COMPRESSION => 'GZ'}
Created table ACTest
Took 4.4826 seconds
=> Hbase::Table - ACTest
修改表:alter
#增加列簇
hbase(main):001:0> alter 'ACTest','moreInfo'
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 4.6656 seconds
#修改列簇
hbase(main):001:0> alter 'ACTest',{NAME=>'moreInfo',COMPRESSION => 'GZ'}
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 3.8519 seconds
#删除列簇
hbase(main):001:0> alter 'ACTest',{NAME=>'moreInfo',METHOD => 'delete'}
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 3.6241 seconds
查看表定义:describe
hbase(main):001:0> describe 'zg_crm'
Table zg_crm is ENABLED
zg_crm
COLUMN FAMILIES DESCRIPTION
{NAME => 'Info', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NO
NE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_
BLOCKS_ON_OPEN => 'false', COMPRESSION => 'GZ', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
{NAME => 'Region', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => '
NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETC
H_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'GZ', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
2 row(s)
Took 1.2797 seconds
启用表:enable(判断是否启用is_enabled)
hbase(main):001:0> enable 'ACTest'
Took 1.7209 seconds
hbase(main):002:0> is_disabled 'ACTest'
false
Took 0.0471 seconds
=> 1
hbase(main):003:0> is_enabled 'ACTest'
true
Took 0.0239 seconds
=> true
禁用表:disable(判断是否启用is_disabled)
hbase(main):001:0> disable 'ACTest'
Took 4.9442 seconds
hbase(main):028:0> is_disabled 'ACTest'
true
Took 0.1571 seconds
=> 1
hbase(main):029:0> is_enabled 'ACTest'
false
Took 1.5190 seconds
=> false
删除表:drop(删除表之前需要禁用该表)
hbase(main):001:0> drop 'ACTest'
Took 0.4823 seconds
表数据操作
#插入数据=put
hbase(main):001:0> put 'ACTest','rowkey000001','baseInfo:name','JimStone'
Took 0.6365 seconds
hbase(main):002:0> put 'ACTest','rowkey000001','baseInfo:age','42'
Took 0.0221 seconds
#取出数据=get
hbase(main):037:0> get 'ACTest','rowkey000001'
COLUMN CELL
baseInfo:age timestamp=1567581473419, value=42
baseInfo:name timestamp=1567581335735, value=JimStone
1 row(s)
Took 0.2473 seconds
#扫描数据=scan
hbase(main):006:0> scan 'ACTest'
ROW COLUMN+CELL
rowkey000001 column=baseInfo:age, timestamp=1567581473419, value=42
rowkey000001 column=baseInfo:name, timestamp=1567670052357, value=JimStone
rowkey000002 column=baseInfo:age, timestamp=1567670102703, value=18
rowkey000002 column=baseInfo:name, timestamp=1567670091778, value=FDragon
2 row(s)
Took 0.0139 seconds
#删除数据=delete
#清空数据=truncate
具体指令的各种参数关于还是通过HBase Shell里的help指令来了解吧。
查看快照:list_snapshots
hbase(main):001:0> list_snapshots
SNAPSHOT TABLE + CREATION TIME
0 row(s)
Took 0.0349 seconds
=> []
退出HBase Shell
hbase(main):005:0> quit
$Shion@shionlnx ~>
(3.4)HBase on HDFS
查看HBase在HDFS中占用的空间:
$Shion@shionlnx ~> hdfs dfs -du -h /opt/hbase/hbase_db/
0 0 /opt/hbase/hbase_db/.hbck
0 0 /opt/hbase/hbase_db/.tmp
0 0 /opt/hbase/hbase_db/MasterProcWALs
0 2.3 G /opt/hbase/hbase_db/WALs
22.1 K 66.4 K /opt/hbase/hbase_db/archive
0 0 /opt/hbase/hbase_db/corrupt
24.2 K 70.3 K /opt/hbase/hbase_db/data
0 0 /opt/hbase/hbase_db/default
0 0 /opt/hbase/hbase_db/hbase
42 84 /opt/hbase/hbase_db/hbase.id
7 14 /opt/hbase/hbase_db/hbase.version
0 0 /opt/hbase/hbase_db/mobdir
90.8 K 181.6 K /opt/hbase/hbase_db/oldWALs
0 0 /opt/hbase/hbase_db/staging
查看HBase在HDFS中的机架节点块信息:
$Shion@shionlnx ~> hdfs fsck /opt/hbase/hbase_db/
Connecting to namenode via http://shionlnx:9870/fsck?ugi=Shion&path=%2Fopt%2Fhbase%2Fhbase_db
FSCK started by Shion (auth:SIMPLE) from /192.168.168.14 for path /opt/hbase/hbase_db at Tue Sep 17 15:09:12 CST 2019
Status: HEALTHY
Number of data-nodes: 6
Number of racks: 1
Total dirs: 347
Total symlinks: 0
Replicated Blocks:
Total size: 9320539815 B (Total open files size: 664 B)
Total files: 171 (Files currently being written: 9)
Total blocks (validated): 158 (avg. block size 58990758 B) (Total open file blocks (not validated): 8)
Minimally replicated blocks: 158 (100.0 %)
Over-replicated blocks: 0 (0.0 %)
Under-replicated blocks: 0 (0.0 %)
Mis-replicated blocks: 0 (0.0 %)
Default replication factor: 2
Average block replication: 2.0
Missing blocks: 0
Corrupt blocks: 0
Missing replicas: 0 (0.0 %)
Erasure Coded Block Groups:
Total size: 0 B
Total files: 0
Total block groups (validated): 0
Minimally erasure-coded block groups: 0
Over-erasure-coded block groups: 0
Under-erasure-coded block groups: 0
Unsatisfactory placement block groups: 0
Average block group size: 0.0
Missing block groups: 0
Corrupt block groups: 0
Missing internal blocks: 0
FSCK ended at Tue Sep 17 15:09:12 CST 2019 in 7 milliseconds
The filesystem under path '/opt/hbase/hbase_db' is HEALTHY
设置HBase在HDFS中的文件副本数量:
$Shion@shionlnx ~> hdfs dfs -setrep 2 /opt/hbase/hbase_db
Replication 2 set: /opt/hbase/hbase_db/MasterProcWALs/state-00000000000000000038.log
Replication 2 set: /opt/hbase/hbase_db/MasterProcWALs/state-00000000000000000039.log
Replication 2 set: /opt/hbase/hbase_db/WALs/ac1,16020,1568703613120/ac1%2C16020%2C1568703613120.1568703622640
Replication 2 set: /opt/hbase/hbase_db/WALs/ac1,16020,1568703613120/ac1%2C16020%2C1568703613120.meta.1568703621350.meta
Replication 2 set: /opt/hbase/hbase_db/WALs/ac2,16020,1568703612854/ac2%2C16020%2C1568703612854.1568703622746
Replication 2 set: /opt/hbase/hbase_db/WALs/ad1,16020,1568703609296/ad1%2C16020%2C1568703609296.1568703615447
Replication 2 set: /opt/hbase/hbase_db/WALs/ad1,16020,1568703609296/ad1%2C16020%2C1568703609296.meta.1568703925128.meta
Replication 2 set: /opt/hbase/hbase_db/WALs/ad2,16020,1568703609298/ad2%2C16020%2C1568703609298.1568703615526
Replication 2 set: /opt/hbase/hbase_db/WALs/shion1,16020,1568703611795/shion1%2C16020%2C1568703611795.1568703621964
Replication 2 set: /opt/hbase/hbase_db/WALs/shionlnx,16020,1568703612514/shionlnx%2C16020%2C1568703612514.1568703619021
Replication 2 set: /opt/hbase/hbase_db/archive/data/hbase/meta/1588230740/info/0d342d0b49454a4085c7c15a8116f78d
Replication 2 set: /opt/hbase/hbase_db/archive/data/hbase/meta/1588230740/info/3e6fdd587fe5417dbe020714088d4a30
Replication 2 set: /opt/hbase/hbase_db/archive/data/hbase/meta/1588230740/info/4af401e58a284d4f9e58113933a28816
Replication 2 set: /opt/hbase/hbase_db/data/default/SYSTEM.CATALOG/.tabledesc/.tableinfo.0000000001
......
......
......
(3.4)HBase with Java
首先我们用上一篇文章的数据:
JX_BOSS/CRM数据稽核,
文件格式为custID|custName|regNbr|regionCode,
双方记录共270,000,000条,
按照这个格式,准备将数据导入HBase。
rowKey就是custID,建立两个列簇,Info中存放custName,而Region中存放regNbr|regionCode。
然后可以通过custID,对数据进行批量和单个用户的双表查询,并对比两张表用户是否一致。
入库时间比较长:
一个表大概1亿3千多万条,50多分钟才入完(回退到HBase1.4后居然只用了37分钟)。
单个查询示例:
批量查询示例:
这里如果一次查询1000条,速度也是毫秒级别的。
Java代码如下,首先按照这个格式定义一个User类:
//User.java
package com.ac;
public class User {
private String CustID;
private String CustName;
private String RegionNBR;
private String RegionCode;
User(String CustID, String CustName, String RegionNBR, String RegionCode) {
this.CustID = CustID;
this.CustName = CustName;
this.RegionNBR = RegionNBR;
this.RegionCode = RegionCode;
}
User(){}
String getCustID() {
return CustID;
}
void setCustID(String CustID) {
this.CustID = CustID;
}
String getCustName() {
return CustName;
}
void setCustName(String CustName) {
this.CustName = CustName;
}
String getRegionNBR() {
return RegionNBR;
}
void setRegionNBR(String RegionNBR) {
this.RegionNBR = RegionNBR;
}
String getRegionCode() {
return RegionCode;
}
void setRegionCode(String RegionCode) {this.RegionCode = RegionCode;
}
@Override
public String toString() {
return "Cust{" +
"CustID='" + CustID + '\'' +
", CustName='" + CustName + '\'' +
", RegionNBR='" + RegionNBR + '\'' +
", RegionCode='" + RegionCode + '\'' +
'}';
}
}
然后定义一个HBase的操作类,由于开始用的HBase2.1.6,后来用1.4.10所以代码有些变化注释掉了。
//HBaseCustAC.java
package com.ac;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
class HBaseCustAC {
private Admin admin;
private Connection conn;
HBaseCustAC(String HBMaster,String quorum,String ZKPort) throws Exception {
conn=initHbase(HBMaster,quorum,ZKPort);
}
//连接集群
private Connection initHbase(String HBMaster,String quorum,String ZKPort) throws IOException {
Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.property.clientPort", ZKPort);
configuration.set("hbase.zookeeper.quorum", quorum);
configuration.set("hbase.master", HBMaster);
return ConnectionFactory.createConnection(configuration);
}
//创建表
void createTable(String tableNmae, String[] cols) throws IOException {
TableName tableName = TableName.valueOf(tableNmae);
admin = conn.getAdmin();
if (admin.tableExists(tableName)) {
System.out.println("表已存在!");
} else {
//TableName tName = TableName.valueOf(tableNmae);//定义表名
//TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(tName);
HTableDescriptor tableDescriptor = new HTableDescriptor(tableName);
for (String col : cols) {
/*ColumnFamilyDescriptor family = ColumnFamilyDescriptorBuilder
.newBuilder(Bytes.toBytes(col))
.setCompressionType(Compression.Algorithm.GZ)//设置压缩格式
.build();//构建列族对象
tableDescriptor.setColumnFamily(family);//设置列族*/
HColumnDescriptor ColumnDescriptor = new HColumnDescriptor(col);
ColumnDescriptor.setCompressionType(Compression.Algorithm.GZ);
tableDescriptor.addFamily(ColumnDescriptor);
}
admin.createTable(tableDescriptor);//创建表
}
}
//删除表
void deleteTable(String tableName){
try {
TableName tablename = TableName.valueOf(tableName);
admin = conn.getAdmin();
admin.disableTable(tablename);
admin.deleteTable(tablename);
} catch (IOException e) {
e.printStackTrace();
}
}
//判断表存在
boolean isTableExists(String tableName) throws IOException {
TableName tablename = TableName.valueOf(tableName);
admin = conn.getAdmin();
return admin.tableExists(tablename);
}
//插入数据(批量)
void insertData(String tableName, List<User> users) throws IOException {
TableName atablename = TableName.valueOf(tableName);
Table table = conn.getTable(atablename);
List<Put> puts= new ArrayList<>();
for(User user: users) {
Put put = new Put((user.getCustID()).getBytes());
put.addColumn("INFO".getBytes(), "CUSTNAME".getBytes(), user.getCustName().getBytes());
put.addColumn("REGION".getBytes(), "REGIONNBR".getBytes(), user.getRegionNBR().getBytes());
put.addColumn("REGION".getBytes(), "REGIONCODE".getBytes(), user.getRegionCode().getBytes());
puts.add(put);
}
table.put(puts);
}
//插入数据
void insertData(String tableName, User user) throws IOException {
TableName atablename = TableName.valueOf(tableName);
Table table = conn.getTable(atablename);
Put put = new Put((user.getCustID()).getBytes());
//参数:1.列族名 2.列名 3.值
put.addColumn("INFO".getBytes(), "CUSTNAME".getBytes(), user.getCustName().getBytes()) ;
put.addColumn("REGION".getBytes(), "REGIONNBR".getBytes(), user.getRegionNBR().getBytes()) ;
put.addColumn("REGION".getBytes(), "REGIONCODE".getBytes(), user.getRegionCode().getBytes()) ;
table.put(put);
}
//根据rowKey前缀进行批量查询
List<User> getDataBatch(String tableName, String filterStr, int limit){
List<User> users= new ArrayList<>();
try {
Table table= conn.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
scan.setRowPrefixFilter(filterStr.getBytes());
scan.setLimit(limit);
ResultScanner resutScanner = table.getScanner(scan);
for(Result result: resutScanner){
User user = new User();
user.setCustID(Bytes.toString(result.getRow()));
for (Cell cell : result.rawCells()){
String colName = Bytes.toString(cell.getQualifierArray(),cell.getQualifierOffset(),cell.getQualifierLength());
String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
switch (colName) {
case "CUSTNAME":
user.setCustName(value);
break;
case "REGIONNBR":
user.setRegionNBR(value);
break;
case "REGIONCODE":
user.setRegionCode(value);
break;
}
}
users.add(user);
}
} catch (IOException e) {
e.printStackTrace();
}
return users;
}
//根据rowKey进行查询
User getDataByRowKey(String tableName, String rowKey) throws IOException {
Table table = conn.getTable(TableName.valueOf(tableName));
Get get = new Get(rowKey.getBytes());
User user = new User();
user.setCustID(rowKey);
//先判断是否有此条数据
if(!get.isCheckExistenceOnly()){
Result result = table.get(get);
for (Cell cell : result.rawCells()){
String colName = Bytes.toString(cell.getQualifierArray(),cell.getQualifierOffset(),cell.getQualifierLength());
String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
switch (colName) {
case "CUSTNAME":
user.setCustName(value);
break;
case "REGIONNBR":
user.setRegionNBR(value);
break;
case "REGIONCODE":
user.setRegionCode(value);
break;
}
}
}
return user;
}
//其它的方法暂时省略
//......
//......
}
最后是主程序:
分别从zg.crm_customer.txt,zk.cm_customer.txt文件中(本来应该从HDFS中读取,无奈测试空间不够),
读取4个字段内容存入HBase的zg_crm表,和zk_cm表中。
以及单个用户和批量的查询两张表,并在一列中比较展示。
package com.ac;
import org.joda.time.DateTime;
import org.joda.time.Period;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HBaseTest2 {
private static HBaseCustAC MyTestH;
private static final int DBImpBatchSize=100000;
public static void main(String[] args){
try {
System.out.println("\n********************* \033[01;36mHBase JX BOSSvsCRM Test 1.0\033[00m *********************");
//MyTestH = new HBaseCustAC("Shionlnx:16000","Shionlnx,Shion1","2181");
if ((args.length >= 4) && (args[3].toUpperCase().equals("IMP"))) {
MyTestH = new HBaseCustAC(args[0],args[1],args[2]);
MyImp();
}
else if ((args.length >= 5) && (args[3].toUpperCase().equals("GET"))) {
MyTestH = new HBaseCustAC(args[0],args[1],args[2]);
MyGet(args[4]);
}
else if ((args.length == 5) && (args[3].toUpperCase().equals("GETS"))) {
MyTestH = new HBaseCustAC(args[0],args[1],args[2]);
MyGets(args[4]);
}
else if ((args.length == 6) && (args[3].toUpperCase().equals("GETS"))) {
MyTestH = new HBaseCustAC(args[0],args[1],args[2]);
MyGets(args[4],Integer.parseInt(args[5]));
}
else{
System.err.println("Usage: HBaseTest2 <HBMaster> <QuoruM> <ZKPort> <Method> [...]");
System.err.println(" HBaseTest2 <HBMaster> <QuoruM> <ZKPort> <imp> : Import ZG_CRM and ZK_CM data to table");
System.err.println(" HBaseTest2 <HBMaster> <QuoruM> <ZKPort> <get> <CustID> : Query one cust");
System.err.println(" HBaseTest2 <HBMaster> <QuoruM> <ZKPort> <gets> <CustIDPrefix> [Limit] : Query custs by prefix and a number limit(Default 100,Max 1000).");
System.exit(1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void MyGets(String CustID) {
MyGets(CustID,100);
}
private static void MyGets(String CustID, int limit) {
if (limit>1000) limit=1000;
DateTime dt1=new DateTime();
System.out.println(/*dt1.toString("[yyyy-MM-dd HH:mm:ss.SSS] ") +*/"批量前缀: "+CustID);
List<User> usersg = MyTestH.getDataBatch("ZG_CRM",CustID,limit);
DateTime dt2=new DateTime();
List<User> usersk = MyTestH.getDataBatch("ZK_CM",CustID,limit);
DateTime dt3=new DateTime();
HashMap<String, User[]> CustMarkList = new HashMap<>();
for (User user:usersg){
CustMarkList.put(user.getCustID(),new User[]{user,null});
}
for (User user:usersk){
if (CustMarkList.containsKey(user.getCustID())) {
User[] entry;
entry=CustMarkList.get(user.getCustID());
entry[1]=user;
CustMarkList.replace(user.getCustID(),entry);
}
else
CustMarkList.put(user.getCustID(),new User[]{null,user});
}
System.out.println("*********************"+"\t"+String.format("%-20s", "***ZG_CRM***")+"\t*\t"+String.format("%-20s", "***ZK_CM***"));
int aSeq=0;
for (Map.Entry<String, User[]> entry : CustMarkList.entrySet()) {
aSeq++;
String key = entry.getKey();
User[] Value = entry.getValue();
String ColorCode;
User user= new User();
if (Value[0]!=null)
user=Value[0];
User user1= new User();
if (Value[1]!=null)
user1=Value[1];
System.out.println("*********************");
System.out.println("CustID: "+"\t"+key+" <结果序号:"+aSeq+">");
if (user.getCustName()==null || !user.getCustName().equals(user1.getCustName()))
ColorCode="\033[01;31m";
else
ColorCode="\033[00m";
System.out.println(ColorCode+"Info:CustName: \033[00m"+"\t"+String.format("%-"+(20-GetChineseCount(user.getCustName()))+"s",user.getCustName())+"\t|\t"+user1.getCustName());
if (user.getRegionNBR()==null || !user.getRegionNBR().equals(user1.getRegionNBR()))
ColorCode="\033[01;31m";
else
ColorCode="\033[00m";
System.out.println(ColorCode+"Region:RegionNBR: \033[00m"+"\t"+String.format("%-"+(20-GetChineseCount(user.getRegionNBR()))+"s",user.getRegionNBR())+"\t|\t"+user1.getRegionNBR());
if (user.getRegionCode()==null || !user.getRegionCode().equals(user1.getRegionCode()))
ColorCode="\033[01;31m";
else
ColorCode="\033[00m";
System.out.println(ColorCode+"Region:RegionCode:\033[00m"+"\t"+String.format("%-"+(20-GetChineseCount(user.getRegionCode()))+"s",user.getRegionCode())+"\t|\t"+user1.getRegionCode());
}
Period period = new Period(dt1, dt2);
Period period2 = new Period(dt2, dt3);
System.out.println("*********************");
System.out.println("耗时: \t"+String.format("%-18s",period.toStandardDuration().getMillis()+"毫秒")+"\t|\t"+String.format("%-18s",period2.toStandardDuration().getMillis()+"毫秒"));
System.out.println();
}
private static void MyGet(String CustID) throws Exception {
DateTime dt1=new DateTime();
System.out.println(/*dt1.toString("[yyyy-MM-dd HH:mm:ss.SSS] ") +*/"查询: "+CustID);
User user = MyTestH.getDataByRowKey("ZG_CRM",CustID);
DateTime dt2=new DateTime();
User user1 = MyTestH.getDataByRowKey("ZK_CM",CustID);
DateTime dt3=new DateTime();
System.out.println("*********************"+"\t"+String.format("%-20s", "***ZG_CRM***")+"\t*\t"+String.format("%-20s", "***ZK_CM***"));
String ColorCode;
if (user.getCustName()==null || !user.getCustName().equals(user1.getCustName()))
ColorCode="\033[01;31m";
else
ColorCode="\033[00m";
System.out.println(ColorCode+"Info:CustName: \033[00m"+"\t"+String.format("%-"+(20-GetChineseCount(user.getCustName()))+"s",user.getCustName())+"\t|\t"+user1.getCustName());
if (user.getRegionNBR()==null || !user.getRegionNBR().equals(user1.getRegionNBR()))
ColorCode="\033[01;31m";
else
ColorCode="\033[00m";
System.out.println(ColorCode+"Region:RegionNBR: \033[00m"+"\t"+String.format("%-"+(20-GetChineseCount(user.getRegionNBR()))+"s",user.getRegionNBR())+"\t|\t"+user1.getRegionNBR());
if (user.getRegionCode()==null || !user.getRegionCode().equals(user1.getRegionCode()))
ColorCode="\033[01;31m";
else
ColorCode="\033[00m";
System.out.println(ColorCode+"Region:RegionCode:\033[00m"+"\t"+String.format("%-"+(20-GetChineseCount(user.getRegionCode()))+"s",user.getRegionCode())+"\t|\t"+user1.getRegionCode());
Period period = new Period(dt1, dt2);
Period period2 = new Period(dt2, dt3);
System.out.println("*********************");
System.out.println("耗时: \t"+String.format("%-18s",period.toStandardDuration().getMillis()+"毫秒")+"\t|\t"+String.format("%-18s",period2.toStandardDuration().getMillis()+"毫秒"));
System.out.println();
}
private static int GetChineseCount(String str){
int count=0;
if (str!=null) {
char[] c = str.toCharArray();
for (char value : c) {
String len = Integer.toBinaryString(value);
if (len.length() > 8)
count++;
}
}
return count;
}
private static void MyImp() throws Exception {
String line;
String[] arrs;
int aCount = 0;
List<User> users = new ArrayList<>();
/*if(MyTestH.isTableExists("ZG_CRM")) {
System.out.println((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] ") + "Deleting ZG_CRM...");
MyTestH.deleteTable("ZG_CRM");
}*/
if (!MyTestH.isTableExists("ZG_CRM")) {
System.out.println((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] ") + "Creating ZG_CRM...");
MyTestH.createTable("ZG_CRM", new String[]{"INFO", "REGION"});
}
System.out.println((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] ") + "importing...");
System.out.print((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] "));
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("/mnt/hgfs/ShareFolder/br1/zg.crm_customer.txt"), Charset.forName("GBK")));
while ((line = br.readLine()) != null) {
arrs = line.split("\\|", -1);
if (arrs.length < 4) continue;
if (arrs[0].trim().isEmpty()) continue;
User user = new User(arrs[0], arrs[1], arrs[2], arrs[3]);
users.add(user);
aCount++;
if ((aCount % DBImpBatchSize) == 0) {
MyTestH.insertData("ZG_CRM", users);
users.clear();
if (aCount % 10000000 == 0) {
System.out.println("用户ZG_CRM:" + arrs[0] + " :" + aCount);
System.out.print((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] "));
}
System.out.print('.');
}
}
if (users.size() > 0) {
MyTestH.insertData("ZG_CRM", users);
users.clear();
}
System.out.println("\nOK.记录数:" + aCount);
br.close();
aCount = 0;
/*if(MyTestH.isTableExists("ZK_CM")) {
System.out.println((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] ") + "Deleting ZK_CM...");
MyTestH.deleteTable("ZK_CM");
}*/
if (!MyTestH.isTableExists("ZK_CM")) {
System.out.println((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] ") + "Creating ZK_CM...");
MyTestH.createTable("ZK_CM", new String[]{"INFO", "REGION"});
}
System.out.println((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] ") + "importing...");
System.out.print((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] "));
br = new BufferedReader(new InputStreamReader(new FileInputStream("/mnt/hgfs/ShareFolder/br1/zk.cm_customer.txt"), Charset.forName("GBK")));
while ((line = br.readLine()) != null) {
arrs = line.split("\\|", -1);
if (arrs.length < 4) continue;
if (arrs[0].trim().isEmpty()) continue;
User user = new User(arrs[0], arrs[1], arrs[2], arrs[3]);
users.add(user);
aCount++;
if ((aCount % DBImpBatchSize) == 0) {
MyTestH.insertData("ZK_CM", users);
users.clear();
if (aCount % 10000000 == 0) {
System.out.println("用户ZK_CM:" + arrs[0] + " :" + aCount);
System.out.print((new DateTime()).toString("[yyyy-MM-dd HH:mm:ss] "));
}
System.out.print('.');
}
}
if (users.size() > 0) {
MyTestH.insertData("ZK_CM", users);
users.clear();
}
System.out.println("\nOK.记录数:" + aCount);
br.close();
}
}
pom.xml
这里有个小插曲,hbase-server 2.1.4依赖的org.glassfish.web/javax.servlet.jsp,是一个非正式的版本没有pom无法自动下载。只好手动排除它,并且写明需要它的一个新版(mvn中央库中查到的)。
后来2.1.6貌似不需要这么做,最后又用了1.4.10版。(代码懒得改了)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ac</groupId>
<artifactId>HBaseTest2</artifactId>
<version>5.01</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.1.4</version>
<exclusions>
<exclusion>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.servlet.jsp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.servlet.jsp</artifactId>
<version>2.3.4</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<archive>
<manifest>
<mainClass>com.ac.HBaseTest2</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<archive>
<manifest>
<mainClass>com.ac.HBaseTest2</mainClass>
</manifest>
<manifestEntries>
<Class-Path>.</Class-Path>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
(四)Spark访问HBase
(4.1)sparkContext.newAPIHadoopRDD
上一篇文章:《从零开始学习大数据平台(Episode 1)》中,
我们用Spark从HDFS中读取文件的代码片段:
JavaRDD<Row> dfboss = spark.read()
.option("sep", "|")
.option("encoding", "GBK")
.csv(aPath + "zk.cm_customer*.txt")
.javaRDD();
JavaPairRDD<String, String[]> rddboss = dfboss
.mapToPair(s ->
new Tuple2<>(s.getString(0), new String[]{s.getString(1), s.getString(2), s.getString(3)})
)
.reduceByKey((i1, i2) -> i1);
JavaRDD<Row> dfhss = spark.read()
.option("sep", "|")
.option("encoding", "GBK")
.csv(aPath + "zg.crm_customer*.txt")
.javaRDD();
JavaPairRDD<String, String[]> rddhss = dfhss
.mapToPair(s ->
new Tuple2<>(s.getString(0), new String[]{s.getString(1), s.getString(2), s.getString(3)})
)
.reduceByKey((i1, i2) -> i1);
上面这段代码稍作修改就可以从HBase中读取表数据了:
不过读取表比读取文件慢了很多,主要是第一步读慢,后面对RDD的操作是完全一样的。
Configuration confboss = HBaseConfiguration.create();
confboss.set("hbase.master", args[0]/*"VM201:16000"*/);
confboss.set("hbase.zookeeper.quorum", args[1]/*"VM201,VM202"*/);
confboss.set("hbase.zookeeper.property.clientPort", args[2]/*"2181"*/);
confboss.set(TableInputFormat.INPUT_TABLE, "ZK_CM");
RDD<Tuple2<ImmutableBytesWritable, Result>> dfboss = spark.sparkContext().newAPIHadoopRDD(confboss, TableInputFormat.class, ImmutableBytesWritable.class, Result.class);
JavaPairRDD<String, String[]> rddboss = dfboss.toJavaRDD()
.mapToPair(s ->
new Tuple2<>(Bytes.toString(s._2.getRow()), new String[]{
Bytes.toString(s._2.getValue(Bytes.toBytes("INFO"),Bytes.toBytes("CUSTNAME"))),
Bytes.toString(s._2.getValue(Bytes.toBytes("REGION"),Bytes.toBytes("REGIONNBR"))),
Bytes.toString(s._2.getValue(Bytes.toBytes("REGION"),Bytes.toBytes("REGIONCODE")))}
)
)
.reduceByKey((i1, i2) -> i1);
Configuration confhss = HBaseConfiguration.create();
confhss.set("hbase.master", args[0]/*"VM201:16000"*/);
confhss.set("hbase.zookeeper.quorum", args[1]/*"VM201,VM202"*/);
confhss.set("hbase.zookeeper.property.clientPort", args[2]/*"2181"*/);
confhss.set(TableInputFormat.INPUT_TABLE, "ZG_CRM");
RDD<Tuple2<ImmutableBytesWritable, Result>> dfhss = spark.sparkContext().newAPIHadoopRDD(confhss, TableInputFormat.class, ImmutableBytesWritable.class, Result.class);
JavaPairRDD<String, String[]> rddhss = dfhss.toJavaRDD()
.mapToPair(s ->
new Tuple2<>(Bytes.toString(s._2.getRow()), new String[]{
Bytes.toString(s._2.getValue(Bytes.toBytes("INFO"),Bytes.toBytes("CUSTNAME"))),
Bytes.toString(s._2.getValue(Bytes.toBytes("REGION"),Bytes.toBytes("REGIONNBR"))),
Bytes.toString(s._2.getValue(Bytes.toBytes("REGION"),Bytes.toBytes("REGIONCODE")))}
)
)
.reduceByKey((i1, i2) -> i1);
(4.2)Jean Grey:Dark Phoenix
呃,我的意思是Apache Phoenix,也可以访问HBase。
Apache Phoenix :为Hadoop提供联机事务处理和操作与分析。
它可以提供标准的JDBC方式访问HBase,听起来非常的诱人。
下载很简单,只需要把包中phoenix-[version]-server.jar拷贝到HBase的lib目录,重启HBase就OK。
所以步骤也略过
最开始感觉这玩意儿有点麻烦……首先HBase的表是区分大小写的,而Phoenix默认全转为大写。所以为了方便只好把表和字段调整为全部大写,否则就需要在表名字段名上加双引号(Shell和程序中都需要)。理论上HBase表不能更名但是可以用快照的方式改。但是字段就没办法了,为了以后不总是打双引号,只好重建了表重新导入数据。
其次到目前为止它的最高版本5.0.0只支持到HBase2.0(而且很久没更新了),目前HBase是2.1.6。然后HBase的hbck工具呢,在2.0+版本上一堆fix命令都不支持。所以我也只好回退到了HBase1.4……不过Phoenix对HBase1.4的支持倒是最近都更新。
那么还是之前Spark从HDFS中读取文件的代码片段,再稍微修改为:
JavaRDD<Row> dfboss = spark.read()
.format("org.apache.phoenix.spark")
.option("zkUrl", args[0]/*"VM201,VM202:2181"*/)
.option("table","ZK_CM")
.load().javaRDD();
JavaPairRDD<String, String[]> rddboss = dfboss
.mapToPair(s ->
new Tuple2<>(s.getString(0), new String[]{s.getString(1), s.getString(3), s.getString(2)})
)
.reduceByKey((i1, i2) -> i1);
JavaRDD<Row> dfhss = spark.read()
.format("org.apache.phoenix.spark")
.option("zkUrl", args[0]/*"VM201,VM202:2181"*/)
.option("table","ZG_CRM")
.load().javaRDD();
JavaPairRDD<String, String[]> rddhss = dfhss
.mapToPair(s ->
new Tuple2<>(s.getString(0), new String[]{s.getString(1), s.getString(3), s.getString(2)})
)
.reduceByKey((i1, i2) -> i1);
我们可以通过phoenix,用SQL的形式在HBASE中创建和使用表。
但是phoenix不能直接读取hbase中已经创建的表,这时候需要创建关联。
第一个办法的建表(为什么大家都可以,到我这里就不行???),
第二个办法建视图(视图就只能查询了)。
1)在phoenix中添加同名表即可映射到hbase的表
--这个办法居然不行,试了几次。只能读到Rowkey,无法读取到表中其它字段的数据。
create table zg_crm(ROW varchar primary key, Info.CustName varchar , Region.RegionCode varchar, Region.RegionNBR varchar);
2)创建一个phoenix的视图
--测试OK
create view zg_crm(id varchar primary key, INFO.CUSTNAME varchar , REGION.REGIONCODE varchar, REGION.REGIONNBR varchar)as select * from zg_crm;
视图创建好后,上面的代码就可以用Phoenix访问我们之前创建好的HBase表ZG_CRM了。
同时由于HBase只能通过Rowkey索引,所以虽然可以用SQL但是非主键字段的条件会很慢。其实40多秒应该不算慢,毕竟记录总数有135,000,000条。而通过Rowkey查询则是30毫秒就搞定。
$Shion@vm201 bin> sqlline.py vm201,vm202:2181
Setting property: [incremental, false]
Setting property: [isolation, TRANSACTION_READ_COMMITTED]
issuing: !connect jdbc:phoenix:vm201,vm202:2181 none none org.apache.phoenix.jdbc.PhoenixDriver
Connecting to jdbc:phoenix:vm201,vm202:2181
Connected to: Phoenix (version 4.14)
Driver: PhoenixEmbeddedDriver (version 4.14)
Autocommit status: true
Transaction isolation: TRANSACTION_READ_COMMITTED
Building list of tables and columns for tab-completion (set fastconnect to true to skip)...
141/141 (100%) Done
Done
sqlline version 1.2.0
#----------------------分割线-----------------------
0: jdbc:phoenix:vm201,vm202:2181> select * from ZG_CRM where id = '400083710838';
+---------------+-----------+-------------+---------------------+
| ID | CUSTNAME | REGIONCODE | REGIONNBR |
+---------------+-----------+-------------+---------------------+
| 400083710838 | 谢X娟 | 792 | 350721198909254923 |
+---------------+-----------+-------------+---------------------+
1 row selected (0.031 seconds)
0: jdbc:phoenix:vm201,vm202:2181> select * from ZG_CRM where regionnbr = '350721198909254923';
+---------------+-----------+-------------+---------------------+
| ID | CUSTNAME | REGIONCODE | REGIONNBR |
+---------------+-----------+-------------+---------------------+
| 400038593889 | 谢X娟 | 792 | 350721198909254923 |
| 400083710838 | 谢X娟 | 792 | 350721198909254923 |
| 400111967614 | XX学院 | 792 | 350721198909254923 |
| 400111967617 | XX学院 | 792 | 350721198909254923 |
+---------------+-----------+-------------+---------------------+
4 rows selected (49.569 seconds)
又试了试count(1),到千万级别的时候,差不多半分钟才返回了。
0: jdbc:phoenix:vm201,vm202:2181> select count(1) from ZG_CRM where id like '400083710%';
+-----------+
| COUNT(1) |
+-----------+
| 973 |
+-----------+
1 row selected (0.086 seconds)
0: jdbc:phoenix:vm201,vm202:2181> select count(1) from ZG_CRM where id like '40008371%';
+-----------+
| COUNT(1) |
+-----------+
| 9805 |
+-----------+
1 row selected (0.077 seconds)
0: jdbc:phoenix:vm201,vm202:2181> select count(1) from ZG_CRM where id like '4000837%';
+-----------+
| COUNT(1) |
+-----------+
| 98218 |
+-----------+
1 row selected (0.209 seconds)
0: jdbc:phoenix:vm201,vm202:2181> select count(1) from ZG_CRM where id like '400083%';
+-----------+
| COUNT(1) |
+-----------+
| 991234 |
+-----------+
1 row selected (1.655 seconds)
0: jdbc:phoenix:vm201,vm202:2181> select count(1) from ZG_CRM where id like '40008%';
+-----------+
| COUNT(1) |
+-----------+
| 9957254 |
+-----------+
1 row selected (27.923 seconds)
(4.3)性能对比
用新的机器和虚拟机集群测试了一下:
双方1.35亿x2数据条数,输出两个存在稽核项,三个差异稽核项。
下面是HBase用GZ方式压缩数据的结果,相对比较满,但是数据表占用空间最小。后来用LZ4压缩就快了很多,再测试Snappy压缩率比LZ4更高一点,速度也差不了太多(没有明显规律,可能是其他因素干扰)。
程序类型 | 处理时间 | 单位 |
---|---|---|
Windows 单机单进程 | 30+ | 分钟 |
Spark + HDFS | 8.2 | 分钟 |
Spark via newAPIHadoopRDD + HBase | 25(GZ),15(LZ4),17(Snappy) | 分钟 |
Spark via Phoenix + HBase | 21(GZ),17(LZ4),16(Snappy) | 分钟 |
从HDFS读文件到DataFrame的最开始阶段非常快,基本一闪而过,而从HBase读就慢了很多。
#sparkContext.newAPIHadoopRDD方式,最开始job的两个stage,划分的tasks很少。2+4=6个RegionServer(应该不是)
$Shion@shionlnx ~> spark-submit --class com.ac.Word2AuditTest --master spark://shionlnx:7077 --deploy-mode client --conf spark.master.rest.enabled=true hdfs://shionlnx:9000/bin/Word2AuditTest-1.3-jar-with-dependencies.jar shionlnx:16000 shionlnx,shion1 2181 hdfs://shionlnx:9000 /output/ 600
*** AuditJava JX BOSSvsCRM HBASE 1.3 ***
[Stage 0:> (0 + 4) / 4][Stage 1:> (0 + 2) / 2]
#通过Phoenix,则最开始job的两个stage,划分的tasks多一些。可以10个worker同时跑。
#但是中途容易随机的出错,失去某个worker的通信。
$Shion@shionlnx ~> spark-submit --class com.ac.Word2AuditTest --master spark://shionlnx:7077 --deploy-mode client --conf spark.master.rest.enabled=true hdfs://shionlnx:9000/bin/Word2AuditTest-1.4-jar-with-dependencies.jar shionlnx,shion1:2181 hdfs://shionlnx:9000 /output/ 600
*** AuditJava JX BOSSvsCRM HBASE 1.4 ***
[Stage 0:> (0 + 10) / 23][Stage 1:> (0 + 0) / 21]
当然,稽核结果也是一致的。
$Shion@vm201 ~> spark-submit --class com.ac.Word2AuditTest --master spark://vm201:7077 --deploy-mode client --conf spark.master.rest.enabled=true hdfs://vm201:9000/bin/Word2AuditTest-1.2.jar hdfs://vm201:9000 /dir1full/ /output/ 600
*** AuditJava JX BOSSvsCRM 1.2 ***
001: 370042
002: 114
存在并不一致: 399004
003: 189745
004: 216903
005: 54
*** Task done ***
$Shion@vm201 ~>
$Shion@vm201 ~> spark-submit --class com.ac.Word2AuditTest --master spark://vm201:7077 --deploy-mode client --conf spark.master.rest.enabled=true hdfs://vm201:9000/bin/Word2AuditTest-1.3-jar-with-dependencies.jar vm201:16000 vm201,vm202 2181 hdfs://vm201:9000 /output/ 600
*** AuditJava JX BOSSvsCRM HBASE 1.3 ***
001: 370042
002: 114
存在并不一致: 399004
003: 189745
004: 216903
005: 54
*** Task done ***
$Shion@vm201 ~>
$Shion@vm201 ~> spark-submit --class com.ac.Word2AuditTest --master spark://vm201:7077 --deploy-mode client --conf spark.master.rest.enabled=true hdfs://vm201:9000/bin/Word2AuditTest-1.4-jar-with-dependencies.jar vm201,vm202:2181 hdfs://vm201:9000 /output/ 600
*** AuditJava JX BOSSvsCRM HBASE 1.4 ***
001: 370042
002: 114
存在并不一致: 399004
003: 189745
004: 216903
005: 54
*** Task done ***
$Shion@vm201 ~>
(五)其它相关项目
(5.1)Trafodion
Apache Trafodion :为Hadoop数据库提供传统SQL操作。
Trafodion本是HP公司资助的一个开源项目,它提供了SQL on HBase解决方案。后来咋就送给了Apache(蛤?)。在网上讨论这个的比较少,感觉更多的人是用Phoenix。但是这个项目并没有停止,至少2019.2提供了2.3.0版本。
(5.2)Thrift
Apache Thrift 跨语言服务开发的软件框架。
HBase Thrift,提供多种语言绑定,主要是方便其它语言比如C++的开发项目访问HBase,但本质上还是Java在访问HBase。
(六)出现问题和解决的记录
(6.1)HBase设置压缩
由于HBase占用的空间比较厉害,所以可以对数据采用压缩。
自己测试不压缩/GZ/snappy看不出什么区别-__-:
$ hbase org.apache.hadoop.hbase.util.LoadTestTool -write 3:15:30 -num_keys 100000 -read 100:30 -num_tables 1 -data_block_encoding NONE -tn TestTable -compression XXX
下面是Google很久以前发布测试结果:
算法 | 压缩比例 | 编码速度 | 解码速度 |
---|---|---|---|
GZIP | 13.4% | 21 MB/s | 118 MB/s |
LZO | 20.5% | 135 MB/s | 410 MB/s |
Zippy/Snappy | 22.2% | 172 MB/s | 409 MB/s |
自己测试用GZ没问题,用Snappy则无法Enable表。RegionServer的日志中会报错。
native snappy library not available: this version of libhadoop was built without snappy support.
…以及…
Compression algorithm ‘snappy’ previously failed test.
网上查了一下,别人是没装snappy,我这里yum install 看了下已经装了。
用官方的方法看看库,也都有。
$Shion@shionlnx ~> hbase --config ~/conf_hbase org.apache.hadoop.util.NativeLibraryChecker
2019-09-19 11:25:23,207 INFO bzip2.Bzip2Factory: Successfully loaded & initialized native-bzip2 library system-native
2019-09-19 11:25:23,210 INFO zlib.ZlibFactory: Successfully loaded & initialized native-zlib library
Native library checking:
hadoop: true /home/Shion/hadoop/lib/native/libhadoop.so.1.0.0
zlib: true /lib64/libz.so.1
snappy: true /lib64/libsnappy.so.1
lz4: true revision:10301
bzip2: true /lib64/libbz2.so.1
openssl: true /lib64/libcrypto.so
再压缩测试一下snappy也是显示成功:
$Shion@shionlnx ~> hbase org.apache.hadoop.hbase.util.CompressionTest hdfs://shionlnx:9000/hbase snappy
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/Shion/hbase/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/Shion/hadoop/share/hadoop/common/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
2019-09-19 12:07:38,227 INFO [main] metrics.MetricRegistries: Loaded MetricRegistries class org.apache.hadoop.hbase.metrics.impl.MetricRegistriesImpl
2019-09-19 12:07:38,234 INFO [main] hfile.CacheConfig: Created cacheConfig: CacheConfig:disabled
2019-09-19 12:07:38,383 INFO [main] compress.CodecPool: Got brand-new compressor [.snappy]
2019-09-19 12:07:38,384 INFO [main] compress.CodecPool: Got brand-new compressor [.snappy]
2019-09-19 12:07:38,631 INFO [main] hfile.CacheConfig: Created cacheConfig: CacheConfig:disabled
2019-09-19 12:07:38,670 INFO [main] compress.CodecPool: Got brand-new decompressor [.snappy]
SUCCESS
最后在官方文档中发现需要设置HBASE_LIBRARY_PATH环境变量,包含libsnappy.so库文件,和libhadoop.so库文件。
当然,最简单的办法是把这俩文件拷贝到{HBASE_HOME}/lib/native/Linux-amd64-64/目录中。然后每个RegionServer都复制一份这个目录结构。
如果不想运行时才报错,那么可以将必须支持的压缩编码写入hbase-site.xml中。
<property>
<name>hbase.regionserver.codecs</name>
<value>lz4,gz,snappy</value>
</property>
这样一来,RegionServer启动时就会检查这些编码方式是否支持。
本文为工作内容记录,会不定期的修改,
不要着急,更多的内容,待继续补充……