從零開始學習大數據平臺(Episode 2)

(零)前言

這篇接着上一篇:《從零開始學習大數據平臺(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啓動時就會檢查這些編碼方式是否支持。


在這裏插入圖片描述
本文爲工作內容記錄,會不定期的修改,
不要着急,更多的內容,待繼續補充……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章