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;