(零)前言
這篇接着上一篇:《從零開始學習大數據平臺(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啓動時就會檢查這些編碼方式是否支持。
本文爲工作內容記錄,會不定期的修改,
不要着急,更多的內容,待繼續補充……