2018年11月29日 星期四

Quick-Start Apache HBase at Local Site

注意事項:
    1. 安裝前請先自行安裝 Java 1.7 JDK 及設定 JAVA_HOME
    2. 此方式啟動時會抓取本機的 ip 變動時會無法連線
       可砍掉 /tmp/zookeeper 後重新啟動
    3. 文章以 1.2.X 版本為範例
  • Download Apache HBase hbase-1.2.9-bin.tar.gz
  • Unzip hbase-1.2.9-bin.tar.gz
    $ mkdir ~/opt && tar -zxvf hbase-1.2.9-bin.tar.gz -C $_
    $ ln -s ~/opt/hbase-1.2.9 ~/opt/hbase
  • Edit ~/.bash_profile add HBASE_HOME and PATH
    HBASE_HOME=~/opt/hbase
    export HBASE_HOME
    
    export PATH=$HBASE_HOME/bin:$PATH
    • After editing using command source ~/.bash_profile
  • Edit ~/opt/hbase/conf/hbase-site.xml Update Content
    <?xml version="1.0"?>
    <?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
    <configuration>
        <property>
            <name>hbase.rootdir</name>
            <value>file:///tmp/hbase</value>
        </property>
        <property>
            <name>hbase.zookeeper.property.dataDir</name>
            <value>/tmp/zookeeper</value>
        </property>
        <property>
            <name>hbase.zookeeper.quorum</name>
            <value>localhost</value>
        </property>
    </configuration>
  • Start The HBase
    $ start-hbase.sh

2018年10月9日 星期二

HBase Split Table And Base64 Pre-Split Shell

Split Table 的三種方法

第一種:Pre-Splitting

當 Table 被建立時,預設會配置一個 Region 給 Table,這時所有讀寫請求都會訪問到同一個 RegionServer 的同一個 Region 中,此時是沒有負載平衡的效果的,集群中可能存在空閒的 RegionServer。為了解決這個問題可以利用 Pre-Splitting,在建立 Table 的時候配置多個 Region 到同一個 Table 以期許達到分散的效果。
在 Table 初始化時,HBase 是不知道如何去 Split Region 的,因為 HBase 不知道RowKey 會以怎樣的方式被配置及產生。HBase 自帶了數種 Pre-Split 的算法,如 HexStringSplit 和 UniformSplit,我們可以根據 RowKey 的前綴來決定使用哪種 Split 方法。
範例
Bash
$ hbase org.apache.hadoop.hbase.util.RegionSplitter pre_split_table HexStringSplit -c 10 -f f1
P.S. -c 10 為 Region 數目;-f f1 為 Table's Column Family.

Base64 Pre-Split Table Shell

由於小編用的是 Base64 字元當前綴,所以另外寫了一個小工具可以幫助切割 Table,產出的結果可在 HBase Shell 中執行。
preSplit.sh
Bash
#!/bin/bash

CHARS="1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM+/"
TABLE=$1
PARTS=$2

CHARS_LEN=$(echo $CHARS | perl -pe 's/(\S)/\1\n/g' | xargs -I {} | wc -l)
RANGE=$(($CHARS_LEN/($PARTS)))
SPLIT=$(($PARTS-1))

function split_table() {
    echo $CHARS | perl -pe 's/(\S)/\1\n/g' | LC_ALL=C sort | xargs -n $2 \
    | cut -s -d ' ' -f $2 | head -n $SPLIT | xargs | sed "s/ /','/g" \
    | sed "s/^/create '$1', 'cf', SPLITS => ['/g" | sed "s/$/']/g"
}

split_table $TABLE $RANGE
Demo
Bash
# 執行方式
$ ./preSplit.sh {Table Name} {Region Count}

# Sample 1
$ ./preSplit.sh UserTable 3
create 'UserTable', 'cf', SPLITS => ['I','d']

# Sample 2
$ seq 2 10 | xargs -I {} ./preSplit.sh UserTable {}
create 'UserTable', 'cf', SPLITS => ['T']
create 'UserTable', 'cf', SPLITS => ['I','d']
create 'UserTable', 'cf', SPLITS => ['D','T','j']
create 'UserTable', 'cf', SPLITS => ['9','L','X','j']
create 'UserTable', 'cf', SPLITS => ['7','H','R','b','l']
create 'UserTable', 'cf', SPLITS => ['6','F','O','X','g','p']
create 'UserTable', 'cf', SPLITS => ['5','D','L','T','b','j','r']
create 'UserTable', 'cf', SPLITS => ['4','B','I','P','W','d','k','r']
create 'UserTable', 'cf', SPLITS => ['3','9','F','L','R','X','d','j','p']

第二種:Auto Splitting

觸發時機

  1. After Memstore Flush
  2. After Compact

Split 策略

  • ConstantSizeRegionSplitPolicy:0.94 版本前默認的切割策略。
程式註解
vim
A RegionSplitPolicy implementation which splits a region as soon as any of its store 
files exceeds a maximum configurable size. 

This is the default split policy. From 0.94.0 on the default split policy has changed 
to IncreasingToUpperBoundRegionSplitPolicy
關鍵程式碼
Java
@Override
protected boolean shouldSplit() {
  boolean force = region.shouldForceSplit();
  boolean foundABigStore = false;

  for (HStore store : region.getStores()) {
    if ((!store.canSplit())) {
       return false;
    }
    if (store.getSize() > desiredMaxFileSize) {
      foundABigStore = true;
    }
  }
  return foundABigStore || force;
}
切分規則
使用 ConstantSizeRegionSplitPolicy 策略,即 storefile 固定大小策略,當任一個 Storefile 的大小大於 hbase.hregion.max.filesize(Default: 10G) 的時候 Region 就會自動切割。

  • IncreasingToUpperBoundRegionSplitPolicy:0.94 ~ 2.0版本間默認的切割策略。
程式註解
vim
Split size is the number of regions that are on this server that all are of the same table, 
cubed, times 2x the region flush size OR the maximum region split size, whichever is smaller.

For example, if the flush size is 128MB, then after two flushes (256MB) we will split which 
will make two regions that will split when their size is 2^3 * 128MB*2 = 2048MB.

If one of these regions splits, then there are three regions and now the split size is 
3^3 * 128MB*2 = 6912MB, and so on until we reach the configured maximum file size and then 
from there on out, we'll use that.
關鍵程式碼
Java
protected long initialSize;

@Override
protected void configureForRegion(HRegion region) {
  super.configureForRegion(region);
  Configuration conf = getConf();
  initialSize = conf.getLong("hbase.increasing.policy.initial.size", -1);
  if (initialSize > 0) {
    return;
  }
  TableDescriptor desc = region.getTableDescriptor();
  if (desc != null) {
    initialSize = 2 * desc.getMemStoreFlushSize();
  }
  if (initialSize <= 0) {
    initialSize = 2 * conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE,
                                   TableDescriptorBuilder.DEFAULT_MEMSTORE_FLUSH_SIZE);
  }
}

@Override
protected boolean shouldSplit() {
  boolean force = region.shouldForceSplit();
  boolean foundABigStore = false;
  int tableRegionsCount = getCountOfCommonTableRegions();
  long sizeToCheck = getSizeToCheck(tableRegionsCount);
  
  for (HStore store : region.getStores()) {
    if (!store.canSplit()) {
      return false;
    }
    long size = store.getSize();
    if (size > sizeToCheck) {
      foundABigStore = true;
    }
  }
  return foundABigStore || force;
}

protected long getSizeToCheck(final int tableRegionsCount) {
  // safety check for 100 to avoid numerical overflow in extreme cases
  return tableRegionsCount == 0 || tableRegionsCount > 100
             ? getDesiredMaxFileSize()
             : Math.min(getDesiredMaxFileSize(),
                        initialSize * tableRegionsCount * tableRegionsCount * tableRegionsCount);
}
切分規則
使用 IncreasingToUpperBoundRegionSplitPolicy 策略,0.94 (包含)之後默認使用此策略,當 Storefile 大於 getSizeToCheck() 所回傳的值時會進行切割。此策略與 RegionServer 上的 Region 個數有關(假定其個數為 RS)
getSizeToCheck() 的規則如下:
  1. 當 RS 等於 0 或大於 100 時,與 ConstantSizeRegionSplitPolicy 策略相同
  2. 當 RS 介於 0 ~ 100 之間時,公式如下:
    • Min(hbase.hregion.max.filesize, hbase.increasing.policy.initial.size * RS ^ 3)
註解
  1. hbase.increasing.policy.initial.size 在未設定的情況下為 hbase.hregion.memstore.flush.size * 2
  2. hbase.hregion.memstore.flush.size 預設值為 128M

  • SteppingSplitPolicy:從 2.0版本開始默認的切割策略。
關鍵程式碼
Java
public class SteppingSplitPolicy extends IncreasingToUpperBoundRegionSplitPolicy {
  @Override
  protected long getSizeToCheck(final int tableRegionsCount) {
    return tableRegionsCount == 1 ? this.initialSize : getDesiredMaxFileSize();
  }
}
切分規則
繼承 IncreasingToUpperBoundRegionSplitPolicy 策略,當 Storefile 大於 getSizeToCheck() 所回傳的值時會進行切割。
getSizeToCheck() 的規則如下:
  1. 當 Regions 數量為 1 時,回傳 hbase.increasing.policy.initial.size
  2. 當 Regions 數量不為 1 時,與 ConstantSizeRegionSplitPolicy 策略相同

第三種:forced splits

使用 HBase Shell 可以強制切分 Table,方法如下:
HBase Shell
Bash
hbase(main):001:0> split

ERROR: wrong number of arguments (0 for 1)

Here is some help for this command:
Split entire table or pass a region to split individual region.  With the
second parameter, you can specify an explicit split key for the region.
Examples:
    split 'tableName'
    split 'namespace:tableName'
    split 'regionName' # format: 'tableName,startKey,id'
    split 'tableName', 'splitKey'
    split 'regionName', 'splitKey'

相關的配置參數

  1. hbase.regionserver.regionSplitLimit 預設值為 Integer.MAX(Cloudera)
控制 RegionServer 的最大 Region 數量,超過則不會進行 Auto Splitting。如果不禁止 Auto Splitting 在每次 flush 或著 compact 之後,RegionServer 都會檢查是否需要 Split,Split 過程老 Region 會先下線在上線 Split 後的 Region,在此期間 Client 訪問會失敗,Retry 過程中會成功但是如果是提供實時服務的系統則想印時長會增加,同時 Split 後的 Compact 是一個比較消耗資源的行為。
  1. hbase.hregion.max.filesize 預設值為 10G
設定 Region's Store Size 的 Max 值,此參數是主要觸發 Split 的條件之一,建議勿將此值設定過低,否則可能會有過多的零碎的 Region,一般的建議是 5G ~ 10G。

2018年6月7日 星期四

從 Log 來進行伺服器的優化之 3 秒處理 2 億資料

注意事項:
    1. 本文僅供參考
    2. 內容經過實際驗證及使用
    3. 電腦規格, 4 Num of CPUs, 8G Memory, 50G Storage 僅一台

目錄

前言

自上一篇文章從 Log 來進行伺服器的優化後,系統順利運行了一陣子,接著就遇到了一個巨大的困擾。當大量的 Log 放入到 Impala 後,任意使用一個 SQL 指令會需要運行約 5 ~ 6 分鐘甚至 10 分鐘以上,此時的資料大約為 6 千萬筆資料。當期望透過這些資料產出一些報表時,通常會需要反覆的查詢資料,而單一次的執行時間就如此長,使得整個系統變得不堪使用。
為此,小編開始進行一連串的優化,在使用 Gzip, Partition, Parquet  Compute Stats 相關的技術後,系統成功達到比原先小量資料更快的效能,而且更加節省硬碟的空間。在本文中的成果看到,目前伺服器存在 2 億筆資料,而一個簡單的計數查詢可以在 3 秒左右完成,可以說在優化後有著天翻地覆的變化,文章中小編將分享這幾項技術,以及使用的差異與重點。

方法

Gzip + Partition + Parquet + Compute Stats 

Information
  • Gzip
    這是一種壓縮格式,Hive 跟 Impala 都有支援,可以新增使用 TXT 格式的 Table,載入 gzip 過的 TXT 文件,運作是正常的,但是由於 gzip 在 Hive 和 Impala 中不可切割,需注意單一檔案的資料大小。
    P.S. 小編使用此壓縮格式的原因是 Log 太大,而伺服器只有空間不足,如果空間充裕可以不使用。
  • Partition
    一般來說如果未作優化的 Hive 會將資料完全存放於同一個資料夾中,當今天要找部分資料時,Hive 或 Impala 會將所有資料掃過所有資料,將有符合條件的資料取出。而使用了 Partition 的效果等於我們事先將資料分類好,我們可以使用 WHERE 的條件,只找特定資料夾所對應的內容。因此,在資料量極大時這樣只取需要的資料處理,就變得非常的重要。
  • Parquet
    Apache Parquet is a columnar storage format available to any project in the Hadoop ecosystem, regardless of the choice of data processing framework, data model or programming language. Source From:Apache Parquet官網
    Apache Parquet 是一種使用 Column-oriented 方式存儲的資料格式,這一類型的資料格式非常適合用於查詢與統計運算。其最主要快速的原因為同一類型的資料會被存放在同一行裡面,在取得資料時不會去取得多餘的資料,以及這一類型的資料通常在存放時,會額外先算出每一行的基本資訊,如:筆數。因此,在實際運算時甚至不必去取出資料就可以算出筆數。
  • Compute Stats
    這是一個 Impala 官方提供可以提升效率的一個指令,使用指令後 Impala 會去收集資料表中的一些統計數據數據, 資料分佈位置及各區塊的資料量... 等,幫助 Impala 在執行時可以有效的去運行整個流程,減少不必要的運算。
Step Flow
  1. 建立一個 Table 使用 TXT 格式(In Hive)
    CREATE EXTERNAL TABLE TxtLog (
      t TIMESTAMP,
      ui STRING,
      li STRING,
      api STRING,
      ua STRING,
      rt INT,
      st STRING
    )
    ROW FORMAT DELIMITED
    FIELDS TERMINATED BY ','
    STORED AS TEXTFILE;
  2. 將 Gzip 資料匯入第一步所產生的 TXT Table(In Hive)
    LOAD DATA LOCAL INPATH '/home/cloudera/log.csv.gz' INTO TABLE TxtLog;
    註解:此指令中的路徑並非 HDFS 的路徑,而是當前伺服器的檔案系統。
  3. 建立一個 Table 使用 Parquet 格式並且設定 Partition(In Hive)
    CREATE EXTERNAL TABLE PaPaLog (
      t TIMESTAMP,
      ui STRING,
      li STRING,
      api STRING,
      ua STRING,
      rt INT,
      st STRING
    )
    PARTITIONED BY (m INT, d INT)
    STORED AS PARQUET;
    註解:這裡的 Partition 是使用 Month 與 Day 做分類,可依需求自行調整
  4. Impala 刷新同步 Hive's Metadata(In Impala)
    INVALIDATE METADATA;
    註解:Impala 可以通過 REFRESH 和 INVALIDATE METADATA 進行同步,可依需求使用。
  5. 從 TXT Table 寫入 Parquet Table(In Impala)
    • 全部匯入
      INSERT OVERWRITE TABLE PaPaLog PARTITION(m, d)
      SELECT *, month(t) as m, day(t) AS d FROM TxtLog;
    • 部分匯入
      INSERT OVERWRITE TABLE PaPaLog PARTITION(m=3, d)
      SELECT *, day(t) AS d FROM TxtLog WHERE month(t)=3;
    註解 1:此步驟要注意如果 TxtLog 資料過大有可能發生 OOM,請依需求調整成部分匯入 
    註解 2:在這一步匯入時,小編會將 TxtLog 僅保留一個月的資料,這樣可以在匯入時會加快速度
  6. 執行 Compute Stats(In Impala)
    COMPUTE STATS PaPaLog;
    註解:推薦執行,但在此案例中COMPUTE STATS做之後的速度差異性很小

成果

Starting Impala Shell without Kerberos authentication
Connected to quickstart.cloudera:21000
Server version: impalad version 2.9.0-cdh5.12.0 RELEASE (build 03c6ddbdcec39238be4f5b14a300d5c4f576097e)
***********************************************************************************
Welcome to the Impala shell.
(Impala Shell v2.9.0-cdh5.12.0 (03c6ddb) built on Thu Jun 29 04:17:31 PDT 2017)

You can change the Impala daemon that you're connected to by using the CONNECT
command.To see how Impala will plan to run your query without actually executing
it, use the EXPLAIN command. You can change the level of detail in the EXPLAIN
output by setting the EXPLAIN_LEVEL query option.
***********************************************************************************
[quickstart.cloudera:21000] > SELECT COUNT(*) FROM PaPaLog;
Query: select COUNT(*) FROM PaPaLog
Query submitted at: 2018-06-07 10:17:17 (Coordinator: http://quickstart.cloudera:25000)
Query progress can be monitored at: 
http://quickstart.cloudera:25000/query_plan?query_id=bb44c4b554854e7d:766d66a000000000
+-----------+
| count(*)  |
+-----------+
| 212103093 |
+-----------+
Fetched 1 row(s) in 3.03s

結語

除了以上的方法,Hive 及 Impala 還有其他方式可以提升效能,小編只使用了一部分方法,也是一些比較主流的方法。能夠真正有效加快效能的永遠是適當的格式,以及只取必要的資料,還有適當的演算方式。因此,如果文章無法滿足需求,小編建議可以優先往如何只取必要性資料當方向。