Java HBase 多線程執行個體程式詳解

來源:互聯網
上載者:User

經過多次修改運行非常穩定,已經用於生產環境。內部線程通訊使用wait()/notify()機制,效率很高。本文只在Java層面討論HBase的多線程,HBase API內部多線程機制不在本文討論範圍之內。HBase用戶端需要的jar包自行下載,這裡就不提供了,本類源碼結尾處可以下載。下面通過demo示範一下用法,結尾處有代碼打包下載。

特點:

■從Java層面用多線程最大化了讀寫效能。
■大量減少了應用程式的代碼量,現在可以集中精力到資料分析上了。
■效能平均下來至少比單線程高出幾個數量級。
HScan

HScan有5個public方法:

1.public HScan(String table, String[] columns, int threadNum),
table指定表名,columns指定掃描的列,如果設為null代表掃描所有的列,如果設為空白數組代表只掃描rowkey,threadNum設定掃描器的並發數。

2.public void addTask(String start, String end, int num),前兩個參數是起止rowkey,第三個參數用於控制rowkey平分成num段,這個參數主用用於可整型計算的時間戳記開頭的rowkey,如果非整型範圍內數字這個參數設為1即可。這個方法可以調用多次,每次添加num個任務到任務池。
3.public void fuck(),繼承來的方法,開始進行掃描。
4.public HashMap<String, String> fetch(),從緩衝區裡取一個結果出來,結果中的鍵由列名和”rowKey”組成,如果返回null表示所有行都已經讀完了。
5.public void status(),一個非同步方法,用於近似顯示線程的運行狀態,總是在當前行顯示,內容由4部分組成,第一列顯示已經返回的結果數,第二列顯示緩衝區中的行數,第三列分別表示 當前並發線程數/Thread.State.WAITING數/Thread.State.BLOCKED數,第四列顯示任務池中的任務數。
Demo,掃描gm_player_detail表2013年1月1日到10日的所有資料

 代碼如下 複製代碼


package joyport.hbase.gm;

import java.util.HashMap;

public class Test {
 private static int threadNum = 30;
 private static int taskNum = 1000;
 private static String htable = "gm_player_detail";

 public static void main(String[] args) throws Exception {
  int[] time = new int[2];
  if (args.length == 2) {
   time = Util.getTimeScale(args[0], args[1]);
  } else if (args.length == 1) {
   time = Util.getTimeScale(-1);
   time[0] = Util.getTimeScale(args[0], args[0])[0];
  } else {
   time = Util.getTimeScale(-1);
  }
  HScan hScan = new HScan(htable, null, threadNum);
  hScan.addTask(String.valueOf(time[0]), String.valueOf(time[1]), taskNum);
  hScan.fuck();
  Test test = new Test();
  test.analyse(hScan);
 }

 public void analyse(HScan hScan) throws InterruptedException {
  HashMap<String, String> row = null;
  for (row = hScan.fetch(); row != null; row = hScan.fetch()) {
   hScan.status();
   //System.out.println(row);
  }
 }
}


Util.getTimeScale(-1)擷取昨天的起止時間戳記
Util.getTimeScale(args[0], args[1])根據yyyy:mm:dd格式的日期擷取起止時間戳記,java用的是微秒計算的時候可能跨天,如果總是用這個函數就不會出現時間斷裂或重複。
編譯然後運行
java joyport.hbase/gm/Test 2013-01-01 2013-01-10

HGet

get操作還是很有用途的,比如有些需求需要這樣來做,統計每一天的前n條記錄,但是一條記錄欄位特別多,可以先只Scan必要欄位計算出前n,然後根據rowkey再回去get詳細資料,比直接在詳細資料上計算效能高多了。因為需要get的rowkey都是放在記憶體的,所以如果需要大量get需要自己控制記憶體使用量率。public 方法和HScan類似,自己看代碼就明白。

Demo,只掃描rowkey然後根據rowkey用get擷取所有列。

 代碼如下 複製代碼


package joyport.hbase.gm;

import java.util.HashMap;

public class Test {
 private static int threadNum = 30;
 private static int taskNum = 1000;
 private static String htable = "gm_player_detail";

 public static void main(String[] args) throws Exception {
  int[] time = new int[2];
  if (args.length == 2) {
   time = Util.getTimeScale(args[0], args[1]);
  } else if (args.length == 1) {
   time = Util.getTimeScale(-1);
   time[0] = Util.getTimeScale(args[0], args[0])[0];
  } else {
   time = Util.getTimeScale(-1);
  }
  String[] cols = {};
  HScan hScan = new HScan(htable, cols, threadNum);
  hScan.addTask(String.valueOf(time[0]), String.valueOf(time[1]), taskNum);
  hScan.fuck();
  Test test = new Test();
  test.analyse(hScan);
 }

 public void analyse(HScan hScan) throws Exception {
  HashMap<String, String> row = null;
  HGet hGet = new HGet(htable, null, 30);
  for (row = hScan.fetch(); row != null; row = hScan.fetch()) {
   hScan.status();
   //System.out.println(row);
   hGet.addTask(row.get("rowKey"));
  }
  hGet.fuck();
  for (row = hGet.fetch(); row != null; row = hGet.fetch()) {
   hScan.status();
   //System.out.println(row);
  }
 }
}


 

HPut

HPut和HScan,HGet不太一樣,因為put操作的資料來源可以有多個,而且類型可以任意(從文本,資料庫,HBase表,流),所以對資料來源線程也進行了封裝,資料來源線程數由使用者決定。

1.public HPut(String table, int threadNum) threadNum是寫入線程的數目。
2.public void addTask(final Callable<HashMap<String, String>> task) 返回一行記錄的介面,每調用一次產生一個新的資料來源線程,當返回null的時候資料來源線程結束。
3.public void enableStatus(boolean enable),即使是一個近似的狀態也需要在合適的地方顯示,使用者程式中顯示基本上誤差非常大,所以放到put操作的時候顯示,這個函數用來控制是否顯示put狀態,預設顯示。

Demo,只展示一個資料來源的情況,多資料來源沒有測試(理論上應該沒問題)。從gm_player_detail讀取一天的資料插入到hbase_test表。

 代碼如下 複製代碼

package joyport.hbase.gm;

import java.util.HashMap;
import java.util.concurrent.Callable;

public class Test extends Thread implements Callable<HashMap<String, String>> {
 private int threadNum = 30;
 private int taskNum = 1000;
 private String htable = "gm_player_detail";
 private HScan hScan;

 public static void main(String[] args) throws Exception {
  int[] time = new int[2];
  if (args.length == 2) {
   time = Util.getTimeScale(args[0], args[1]);
  } else if (args.length == 1) {
   time = Util.getTimeScale(-1);
   time[0] = Util.getTimeScale(args[0], args[0])[0];
  } else {
   time = Util.getTimeScale(-1);
  }
  Test t1 = new Test(time[0], time[1]);
  // 往hbase_test寫資料
  HPut hPut = new HPut("hbase_test", 10);
  hPut.addTask(t1);
  hPut.fuck();
 }

 public Test(int startkey, int endkey) throws Exception {
  String[] cols = null;
  hScan = new HScan(htable, cols, threadNum);
  hScan.addTask(String.valueOf(startkey), String.valueOf(endkey), taskNum);
  hScan.fuck();
 }

 public HashMap<String, String> call() throws InterruptedException {
  return hScan.fetch();
 }
}


HDelete

delete操作只需要rowkey就可以,但是緩衝區資料結構是死的,所以也使用HashMap<String,String>格式,以rowKey為鍵。如果row.isEmpty()為真則這條資料跳過。

Demo,從hbase_test讀取所有行,並刪除所有行。

 代碼如下 複製代碼


package joyport.hbase.gm;

import java.util.HashMap;
import java.util.concurrent.Callable;

public class Test implements Callable<HashMap<String, String>> {
 private static int threadNum = 30;
 private static String htable = "hbase_test";
 private static HScan hScan;

 public static void main(String[] args) throws Exception {
  String[] cols = {};
  hScan = new HScan(htable, cols, threadNum);
  hScan.addTask("0", "2", 1);
  hScan.fuck();

  HDelete h = new HDelete(htable, 10);
  h.addTask(new Test());
  h.fuck();
 }

 public HashMap<String, String> call() throws Exception {
  return hScan.fetch();
 }
}


需要注意的問題:

■預設緩衝區大小是10000,外部程式線程數(不是構造方法中定義的那個並發)不能超過這個值,否則可能導致無限wait()。
■目前HScan的任務池沒有進行排重處理,也就是兩個任務的起止rowkey交叉和相同任務重複沒有進行內部處理,為避免重複掃描需要外部保證。HGet任務排重也需要外部保證。
■HScan和HGet任務必須一次性添加完畢,因為threadStart()一旦判斷完成將關閉緩衝區而造成任務丟失。
■本類只在Java層面進行多線程操作!HBaseClient內部同步機制不在本類管轄範圍之內!
■Config.java用到了同目錄下的config.conf檔案,配置zookeepers和port應該就能用了。


可能的改進:

■HBuffer的基本類型是HashMap<String,String>,分別是列名和值。如果把HashMap<String,String>改成數組只儲存值,建立一個HRow替換HashMap<String,String>作為一行資料,列名作為HRow的靜態屬性只儲存一份,HRow維護列名和值的對應關係,這樣會省下大量列名重複佔據的記憶體並降低GC的負擔,在大資料量記憶體運算的情況下能有效提高記憶體使用量率。HRow維護對應關係佔用的CPU完全可以忽略。
■HBase讀線程和程式讀線程公用一個緩衝區,導致生產和消費使用同一個對象監視器,這樣的話notify()效率不是最優,在速度很不穩定的情況下效率可能會有小幅度降低,目前沒有解決辦法。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.