天天看點

hbase shell實作原理簡析

hbase的互動式指令行是通過jruby實作的,當我們輸入hbase shell時,實際上最終執行的是org.jruby.Main,并以bin/hirb.rb作為參數,注意是根目錄下bin目錄中的hirb.rb,而不是hbase-shell中的irb/hirb.rb;

這個類來自jruby的包,作用是把ruby編寫的代碼轉換成java位元組碼,進而能夠運作在JVM中;

實作邏輯大體可分為2個階段:初始化階段和指令執行階段,前者是啟動shell時的執行邏輯,後者是輸入指令後的執行邏輯,以下分别簡述其流程;

初始化階段

1、建立HBaseConfiguration執行個體,并将啟動時帶的鍵值對參數設定進去;

2、建立Hbase執行個體,初始化connection,代碼在hbase.rb中;

3、建立Shell執行個體,此時會執行一些load_command_group方法,這些方法實際上是初始化了commands和command_groups這2個map變量,commands中存放了各個指令的name與class的映射關系,代碼在shell.rb中;

4、接下來執行Shell執行個體的export_commands方法,通過instance_eval為commands中的所有指令動态添加一個方法到Shell執行個體中;

指令執行階段(以list指令為例)

1、執行前述動态生成的list方法;

2、執行Shell執行個體的command方法,參數為list;

3、執行internal_command,該方法内部先調用command_instance按一定規則建立該指令對應class的執行個體:List,所有指令的class都會繼承Command類;

4、執行List的command_safe方法,這個方法在Command類中,該方法内部通過調用send(cmd, *args)來執行List的command方法,List類定義在list.rb中,Command類定義在commands.rb中;

5、List的command方法先後調用了Command、Shell、Hbase等類中的admin方法,最後得到一個Admin執行個體,該類定義在admin.rb中;

6、執行Admin執行個體的list方法,該方法内部實際上執行了HBaseAdmin的listTableNames來得到結果;

如何調試

如果希望在本地環境啟動hbase shell,可參考如下配置;

//Main class    
org.jruby.Main
//VM Options    
-Dhbase.ruby.sources=E:\github\hbase\hbase-shell\src\main\ruby
//Program argument    
E:\github\hbase\bin\hirb.rb
//Use classpath of module    
hbase-shell           

預設情況下連的是localhost的hbase,如果希望連遠端叢集,可以修改hbase-shell子產品中hbase.rb的configuration,指定hbase.zookeeper.quorum參數即可;

修改示例

以deleteall指令為例,先檢視下它的幫助, 執行help 'deleteall',會列印如下說明資訊:

Delete all cells in a given row; pass a table name, row, and optionally
a column and timestamp. Deleteall also support deleting a row range using a
row key prefix. Examples:

  hbase> deleteall 'ns1:t1', 'r1'
  hbase> deleteall 't1', 'r1'
  hbase> deleteall 't1', 'r1', 'c1'
  hbase> deleteall 't1', 'r1', 'c1', ts1
  hbase> deleteall 't1', 'r1', 'c1', ts1, {VISIBILITY=>'PRIVATE|SECRET'}           

可以看到,如果想删除某一行中所有小于指定時間戳的資料,是不支援的,這是因為參數是按照位置讀取的,如果把時間戳放到行鍵後面,會被當做列資訊進而報錯,但通過api是可以的,這裡我們通過用空字元串占位的方式去解決,即用如下指令:

deleteall 't1', 'r1', '', ts1           

要支援這個指令,隻需要在使用列資訊的地方加上空字元串判斷就行了,根據前述指令執行流程的說明,可以知道deleteall指令的代碼是在deleteall.rb中,并且最終是在table.rb的_createdelete_internal方法中使用到列資訊進行Delete對象的建立,代碼如下:

if column && all_version
  family, qualifier = parse_column_name(column)
  d.addColumns(family, qualifier, timestamp)
elsif column && !all_version
  family, qualifier = parse_column_name(column)
  d.addColumn(family, qualifier, timestamp)
end           

是以隻需要給這部分外層加上判斷即可,修改如下:

if column != ""
  if column && all_version
    family, qualifier = parse_column_name(column)
    d.addColumns(family, qualifier, timestamp)
  elsif column && !all_version
    family, qualifier = parse_column_name(column)
    d.addColumn(family, qualifier, timestamp)
  end
end           

有興趣的話可以本地修改下試試效果,加深對hbase指令行實作原理的了解,另外這個小優化也已經送出給社群:

HBASE-24335