hbase/thrift/go連接失敗

問題

在通過Go連接hbase的過程中, 發現 get操作可以查到數據, 但是scanner命令訪問數據失敗, 也沒有報錯, 就是單純的查不到數據. 而且Python PHP都一切正常.

這裏簡單複述一下我出現問題的情況, 安裝過程和網上大部分內容一致, 這裏簡單列一下, 只是爲了查詢問題時參考安裝過程的差異:

# 安裝hbase
wget -O /opt/hbase.tar.gz https://dlcdn.apache.org/hbase/2.4.11/hbase-2.4.11-bin.tar.gz
tar -xzf /opt/hbase.tar.gz -C /opt/
rm /opt/hbase.tar.gz
# 安裝thrift
apt install -y libboost-dev  \
    libboost-test-dev  \
    libboost-program-options-dev  \
    libevent-dev  \
    automake  \
    libtool  \
    flex  \
    bison  \
    pkg-config  \
    g++  \
    libssl-dev \
    gcc  \
    autoconf \
    make \
    && wget -O /opt/thrift.tar.gz https://dlcdn.apache.org/thrift/0.16.0/thrift-0.16.0.tar.gz \
    && tar -xzf /opt/thrift.tar.gz -C /opt \
    && rm /opt/thrift.tar.gz \
    && cd /opt/thrift-0.16.0 \
    && ./configure \
    && make \
    && make install
# 測試使用, 將hbase服務改爲單機模式
cat > /opt/hbase-2.4.11/conf/hbase-site.xml <<- EOF
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <!-- hbase存放數據目錄 -->
    <property>
        <name>hbase.rootdir</name>
        <value>file:///data/hbase</value>
    </property>
    <!-- ZooKeeper數據文件路徑 -->
    <property>
        <name>hbase.zookeeper.property.dataDir</name>
        <value>/data/zookeeper</value>
    </property>
    <property>
        <name>hbase.unsafe.stream.capability.enforce</name>
        <value>false</value>
    </property>
</configuration>
EOF
# 啓動hbase服務
/opt/hbase-2.4.11/bin/start-hbase.sh
# 啓動htrift服務
/opt/hbase-2.4.11/bin/hbase-daemon.sh start thrift2 -p 9091

# 生成go和python的thrift工具文件
wget -O ~/hbase_src.tar.gz https://dlcdn.apache.org/hbase/2.4.11/hbase-2.4.11-src.tar.gz
tar -xzf ~/hbase_src.tar.gz -C ~
cd ~/hbase-2.4.11/hbase-thrift/src/main/resources/org/apache/hadoop/hbase/thrift2
thrift --gen go hbase.thrift
thrift --gen py hbase.thrift

服務啓動後, 使用GO進行查詢(其中test表已經提前放入數據):

transport, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "9091"))
if err != nil {
  panic(err)
}
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
client := hbase.NewTHBaseServiceClientFactory(transport, protocolFactory)
transport.Open()
scan := &hbase.TScan{}
ret, err := client.GetScannerResults(context.Background(), []byte("test"), scan, 100)
if err != nil {
  panic(err)
}
fmt.Println("scanner result")
fmt.Println(ret)

發現使用go查詢不到數據???這個就奇怪了, 因此開始進行檢查(簡單復現一下問題查詢步驟).

原因追溯

首先, 在使用GO查詢之前已經使用hbase shell連接過了, 因此可以確定不是hbase啓動失敗的問題. 因此思考各種問題逐一排查.

端口未開啓

使用命令telnet 127.0.0.1 9091進行驗證, 端口沒有問題, 可以排除.

是否僅Go有問題

使用python php進行連接並訪問相同方法, 發現是有返回數據的, Python代碼:

from thrift.transport import TSocket
from thrift.protocol import TBinaryProtocol
from thrift.transport import TTransport
from hbase import THBaseService
from hbase.ttypes import TScan

if __name__ == '__main__':
    transport = TTransport.TBufferedTransport(TSocket.TSocket('127.0.0.1', 9091))
    protocol = TBinaryProtocol.TBinaryProtocolAccelerated(transport)
    client = THBaseService.Client(protocol)
    transport.open()
    # 使用 client 實例進行操作
    ret = client.getScannerResults(
        table="test",
        tscan=TScan(),
        numRows=100
    )
    print(ret)
    transport.close()

由此可以證明, thrift也沒有問題, 一定是Go在連接的時候出了問題.

是否Go所有方法都有問題

嘗試調用thrift其他方法, 判斷是僅scanner方法有問題, 還是所有方法都有問題:

tGet := hbase.TGet{
  Row: []byte("row1"), // row1 數據是存在的
}
getRet, err := client.Get(context.Background(), []byte("test"), &tGet)
if err != nil {
  panic(err)
}
fmt.Println("get result")
fmt.Println(getRet)

查詢後發現, Get方法是可以獲取到數據的, 那麼, 就是說僅GoGetScannerResults方法是有問題的???

當問題追溯到這裏的時候, 我已經有些懵了, 嘗試這谷歌查找原因, 各種關鍵詞都試過了, 沒有找到想要的答案(這也是爲什麼我要把這個錯記下來, 因爲沒查到啊)

抓包比較差異

找了半天沒有找到問題, 既然Python可以查到數據 而Go查不到, 那麼, 就可以嘗試抓包比較兩者差異了嘛.

依然使用上面的GetScannerResults, 分別對pythongo進行抓包分析, 通過命令tmpdump port 9091 -w thrift.cap:

image-20220417134443514

image-20220417134458800

可以看到, 其中的thrift請求也能夠被wireshark識別了, 那就太好了, 不用比較二進制了, 來看一下兩個請求的區別:

Python請求如下:

image-20220417134751836

Go請求如下:

image-20220417134903197

其中, 有一個字段兩個是明顯不一樣的, 我很貼心的用紅框圈出來了, 那麼, 問題來了, 這個字段是什麼呢? 我把上面的hbase.thrift文件中的一部分拿出來, 就很明顯了:

/**
 * Any timestamps in the columns are ignored but the colFamTimeRangeMap included, use timeRange to select by timestamp.
 * Max versions defaults to 1.
 */
struct TScan {
  1: optional binary startRow,
  2: optional binary stopRow,
  3: optional list<TColumn> columns
  4: optional i32 caching,
  5: optional i32 maxVersions=1,
  6: optional TTimeRange timeRange,
  7: optional binary filterString,
  8: optional i32 batchSize,
  9: optional map<binary, binary> attributes
  10: optional TAuthorization authorizations
  11: optional bool reversed
  12: optional bool cacheBlocks
  13: optional map<binary,TTimeRange> colFamTimeRangeMap
  14: optional TReadType readType
  15: optional i32 limit
  16: optional TConsistency consistency
  17: optional i32 targetReplicaId
  18: optional binary filterBytes

}

service THBaseService {
  /**
   * Get results for the provided TScan object.
   * This helper function opens a scanner, get the results and close the scanner.
   *
   * @return between zero and numRows TResults
   */
  list<TResult> getScannerResults(
    /** the table to get the Scanner for */
    1: required binary table,

    /** the scan object to get a Scanner for */
    2: required TScan tscan,

    /** number of rows to return */
    3: i32 numRows = 1
  ) throws (
    1: TIOError io
  )
}

雖然我沒用過thrift, 但是看着這個數據包, 在看看這個數據結構的定義, 很明顯Field Id就是定義文件中前面的數字啊.

很好, 這就可以定位到不用的字段爲: maxVersion, 而這個字段指定了返回的版本數量, 0個版本自然就沒有數據咯.

解決

既然查詢到時因爲maxVersion字段不同而導致的問題, 那麼在查詢的時候手動指定即可. 修改代碼後重試:

scan := &hbase.TScan{
  maxVersion: 1
}
ret, err := client.GetScannerResults(context.Background(), []byte("test"), scan, 100)
if err != nil {
  panic(err)
}
fmt.Println("scanner result")
fmt.Println(ret)

OK, 現在有數據了, 查半天沒想到是這個問題. 至此, 問題解決...

我是不懂爲什麼不同語言訪問還有這種差異? 是因爲字段的默認值不同? 搞不懂.

再順便提一句, 難道大家都沒有碰到這個問題麼? 我擱網上查了半天, 愣是沒找到一個沾邊的...若你也碰到了這個問題, 希望能夠幫到你

原文地址: https://hujingnb.com/archives/767

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章