背景
最近有個需求是寫一個資料查詢接口,資料存放在CDH搭建的Hadoop叢集HBase中。一直以來是個堅定的Pythoner(其實是懶),不過今年以來逐漸接觸和嘗試Go之後覺得很對胃口,再加上公司令人作嘔的運維管控機制,使用Go的項目靜态編譯為單個檔案,可以将運維依賴降到最低,是以對Go越發心儀。
用net/http配合gorilla/mux包輕車熟路的實作了一套簡單的查詢接口之後,本以為輕松完成HBase的DAL就可以打包測試了,但是要是coding都這麼一帆風順那就顯示不出什麼技(bi)術(ge)了。是以意料之外又情理之中的,用Go擷取HBase中并沒有那麼簡單。
本以為HBase這麼成熟的資料庫,Go會有很友善實用的官方或第三方庫友善通路,但在搜尋一番之後,發現隻有兩個選擇:HBase提供的Thrift,以及這個 仍被開發者标記為Beta版本的第三方庫GoHbase。在起初調試Thrift始終無果的情況下,筆者嘗試了GoHbase,使用簡單,可以成功擷取HBase資料。考慮到這是線上的項目,本着認(zhe)真(teng)負(dao)責(di)的态度,經過了又一番調試,總算用Thrift達成了目的,下面流水賬記錄一下具體過程。
使用的軟體環境:
- go version go1.7.4 linux/amd64 & windows/amd64*
- Thrift version 0.10.0*
- HBase 1.2.0-cdh5.7.2
步驟
- 确定HBase安裝目錄,啟動指令
- 用Thrift生成HBase SDK
- 實作用戶端代碼
具體過程
查詢HBase目錄及運作指令
HBase提供了兩套thrift接口,首先要确定啟動的hbase thrift server使用了哪套接口, 比如我的:

是第一套接口,如果參數是thrift2,就是第二套接口。
進入HBase的目錄,找到Thrift檔案:
[[email protected] thrift]$ ls -l /opt/cloudera/parcels/CDH/lib/hbase/include/thrift
total 44
-rw-r–r– 1 root root 24870 Jul 23 2016 hbase1.thrift
-rw-r–r– 1 root root 15126 Jul 23 2016 hbase2.thrift
用Thrift生成代碼
在上一步中找到對應的thrift檔案,将檔案拷貝到個人目錄下,運作:
thrift -out . -r hbase–gen go ${THRIFT}
生成的代碼目錄如下:
其中hbase-remote目錄為生成的用戶端測試代碼,但如果直接運作,會得到一堆報錯:
..\hbase1.go:1662: cannot use temp (type Text) as type string in assignment
..\hbase1.go:11229: cannot use temp (type Text) as type string in assignment
..\hbase1.go:12252: cannot use temp (type Text) as type string in assignment
..\hbase1.go:12669: cannot use temp (type Text) as type string in assignment
..\hbase1.go:13121: cannot use temp (type Text) as type string in assignment
..\hbase1.go:13531: cannot use temp (type Text) as type string in assignment
..\hbase1.go:13925: cannot use temp (type Text) as type string in assignment
..\hbase1.go:14330: cannot use temp (type Text) as type string in assignment
..\hbase1.go:14759: cannot use temp (type Text) as type string in assignment
..\hbase1.go:15173: cannot use temp (type Text) as type string in assignment
..\hbase1.go:15173: too many errors
錯誤: 程序退出代碼 2.
可能是thrift版本不相容造成的,在代碼中發現如下定義:
type Text []byte
定位到報錯的位置:
var _key1 string
if v, err := iprot.ReadString(); err != nil {
return thrift.PrependError(“error reading field 0: “, err)
} else {
temp := Text(v)
_key1 = temp
}
發現temp被指派給string類型的_key1沒有做類型轉換,手動把所有報錯位置都修改如下:
temp := Text(v)
_key1 = string(temp)
修改代碼中host和port為實際位址,再次運作:
[[email protected] hbase-remote]$ go run hbase-remote.go
Usage of /tmp/go-build890271332/command-line-arguments/_obj/exe/hbase-remote
[-h host:port] [-u url] [-f[ramed]] function [arg1 [arg2…]]:
-P string Specify the protocol (binary, compact, simplejson, json) (default “binary”)
-framed Use framed transport
-h string Specify host and port (default “10.59.74.135”)
-http Use http
-p int Specify port (default 9090)
-u string Specify the url
…….
錯誤解決。
現在可以将生成的hbase目錄拷貝到$GOPATH/src中。
實作用戶端
簡單的示例代碼如下:
package main
import (
"fmt"
"net"
"os"
"hbase1"
"github.com/apache/thrift/lib/go/thrift"
)
func main() {
host := "10.59.74.135"
port := "9090"
trans, err := thrift.NewTSocket(net.JoinHostPort(host, port))
if err != nil {
fmt.Println("Build socked failed: ", err)
os.Exit)
}
defer trans.Close()
var protocolFactory thrift.TProtocolFactory
//protocolFactory = thrift.NewTSimpleJSONProtocolFactory()
protocolFactory = thrift.NewTBinaryProtocolFactoryDefault()
client := hbase1.NewHbaseClientFactory(trans, protocolFactory)
if err := trans.Open(); err != nil {
fmt.Println("Opening socket failed: ", err)
os.Exit)
}
tableName := "agentBasicInfo" // tablename
rowKey := "1970010121012971" // rowkey
family := "basicinfo:entry_date" // column
tables, err := client.GetTableNames()
if err != nil {
fmt.Println("Get tables failed: ", err)
os.Exit)
}
for _, table := range tables {
fmt.Println("table: ", string(table))
}
fmt.Println("-------------------")
fmt.Printf("trying to get table: {%s}, rowkey: {%s}\n", tableName, rowKey)
//attr := map[string]hbase1.Text {"basicinfo":[]byte("entry_date")}
data, err := client.Get([]byte(tableName), []byte(rowKey), []byte(family), nil)
if err != nil {
fmt.Println("Get data failed: ", err)
}
for _, ele := range data {
fmt.Println("value: ", ele.Timestamp, " ", string(ele.Value))
}
}
執行結果如下:
[[email protected] test_hbase]$ go run test_thrift.go
table: KYLIN_010EV7WZQ6
table: KYLIN_228LAP2P5A
table: KYLIN_3AYUR4WPJW
table: KYLIN_4DX8LTMC7A
table: KYLIN_4XR1LT20V4
table: KYLIN_959ZEKZBEM
table: KYLIN_9OHU8KSWI3
table: KYLIN_A6DW68YNOX
table: KYLIN_A6JKAAU8KS
table: KYLIN_BB5KKOWPCN
table: KYLIN_BUNDHMMD78
table: KYLIN_BZTUAMVLK6
table: KYLIN_CMQF0PAX8T
table: KYLIN_DK8AAXFNR7
table: KYLIN_DPFEWKDP5N
……
Python用戶端
其實Hbase源碼包中已經有很多語言用戶端的示例代碼
[[email protected] repos]$ ls hbase-1.2.0-cdh5.7.2/hbase-examples/src/main
cpp java perl php protobuf python ruby sh
python用戶端示例檔案:
[email protected] python]$ tree .
├── thrift1
│ ├── DemoClient.py
│ └── gen-py
│ └── hbase
│ ├── constants.py
│ ├── Hbase.py
│ ├── Hbase.pyc
│ ├── Hbase-remote
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── ttypes.py
│ └── ttypes.pyc
└── thrift2
├── DemoClient.py
└── gen-py
└── hbase
├── constants.py
├── __init__.py
├── __init__.pyc
├── THBaseService.py
├── THBaseService.pyc
├── THBaseService-remote
├── ttypes.py
└── ttypes.pyc
分别對應兩個版本的Thrift接口,參考其中DemoClient.py,即可實作自己的HBase用戶端。
目錄
-
- 背景
- 步驟
- 具體過程
- 查詢HBase目錄及運作指令
- 用Thrift生成代碼
- 實作用戶端
- Python用戶端
- 目錄
- 背景