HBase實戰 | HBase在人工智慧場景的使用
- 2019 年 10 月 6 日
- 筆記
近幾年來,人工智慧逐漸火熱起來,特別是和大數據一起結合使用。人工智慧的主要場景又包括影像能力、語音能力、自然語言處理能力和用戶畫像能力等等。這些場景我們都需要處理海量的數據,處理完的數據一般都需要存儲起來,這些數據的特點主要有如下幾點:
- 大:數據量越大,對我們後面建模越會有好處;
- 稀疏:每行數據可能擁有不同的屬性,比如用戶畫像數據,每個人擁有屬性相差很大,可能用戶A擁有這個屬性,但是用戶B沒有這個屬性;那麼我們希望存儲的系統能夠處理這種情況,沒有的屬性在底層不佔用空間,這樣可以節約大量的空間使用;
- 列動態變化:每行數據擁有的列數是不一樣的。
為了更好的介紹 HBase 在人工智慧場景下的使用,下面以某人工智慧行業的客戶案例進行分析如何利用 HBase 設計出一個快速查找人臉特徵的系統。
目前該公司的業務場景裡面有很多人臉相關的特徵數據,總共3400多萬張,每張人臉數據大概 3.2k。這些人臉數據又被分成很多組,每個人臉特徵屬於某個組。目前總共有近62W個人臉組,每個組的人臉張數範圍為 1 ~ 1W不等,每個組裡面會包含同一個人不同形式的人臉數據。組和人臉的分布如下:
- 43%左右的組含有1張人臉數據;
- 47%左右的組含有 2 ~ 9張人臉數據;
- 其餘的組人臉數範圍為 10 ~ 10000。
現在的業務需求主要有以下兩類:
- 根據人臉組 id 查找該組下面的所有人臉;
- 根據人臉組 id +人臉 id 查找某個人臉的具體數據。
MySQL + OSS 方案
之前業務數據量比較小的情況使用的存儲主要為 MySQL 以及 OSS(對象存儲)。相關表主要有人臉組表group和人臉表face。表的格式如下:
group表:
group_id |
size |
---|---|
1 |
2 |
face表:
face_id |
group_id |
feature |
---|---|---|
"c5085f1ef4b3496d8b4da050cab0efd2" |
1 |
"cwI4S/HO/nm6H……" |
其中 feature 大小為3.2k,是二進位數據 base64 後存入的,這個就是真實的人臉特徵數據。
現在人臉組 id 和人臉 id 對應關係存儲在 MySQL 中,對應上面的 group 表;人臉 id 和人臉相關的特徵數據存儲在 OSS 裡面,對應上面的 face 表。
因為每個人臉組包含的人類特徵數相差很大(1 ~ 1W),所以基於上面的表設計,我們需要將人臉組以及每張人臉特徵id存儲在每一行,那麼屬於同一個人臉組的數據在MySQL 裡面上實際上存儲了很多行。比如某個人臉組id對應的人臉特徵數為1W,那麼需要在 MySQL 裡面存儲 1W 行。
我們如果需要根據人臉組 id 查找該組下面的所有人臉,那麼需要從 MySQL 中讀取很多行的數據,從中獲取到人臉組和人臉對應的關係,然後到 OSS 裡面根據人臉id獲取所有人臉相關的特徵數據,如下圖的左部分所示。

我們從上圖的查詢路徑可以看出,這樣的查詢導致鏈路非常長。從上面的設計可看出,如果查詢的組包含的人臉張數比較多的情況下,那麼我們需要從 MySQL 裡面掃描很多行,然後再從 OSS 裡面拿到這些人臉的特徵數據,整個查詢時間在10s左右,遠遠不能滿足現有業務快速發展的需求。
HBase 方案
上面的設計方案有兩個問題:
- 原本屬於同一條數據的內容由於數據本身大小的原因無法存儲到一行裡面,導致後續查下需要訪問兩個存儲系統;
- 由於MySQL不支援動態列的特性,所以屬於同一個人臉組的數據被拆成多行存儲。
針對上面兩個問題,我們進行了分析,得出這個是 HBase 的典型場景,原因如下:
- HBase 擁有動態列的特性,支援萬億行,百萬列;
- HBase 支援多版本,所有的修改都會記錄在 HBase 中;
- HBase 2.0 引入了 MOB(Medium-Sized Object)特性,支援小文件存儲。HBase 的 MOB 特性針對文件大小在 1k~10MB 範圍的,比如圖片,短影片,文檔等,具有低延遲,讀寫強一致,檢索能力強,水平易擴展等關鍵能力。
我們可以使用這三個功能重新設計上面 MySQL + OSS 方案。結合上面應用場景的兩大查詢需求,我們可以將人臉組 id 作為 HBase 的 Rowkey,系統的設計如上圖的右部分顯示,在創建表的時候打開 MOB 功能,如下:
create 'face', {NAME => 'c', IS_MOB => true, MOB_THRESHOLD => 2048}
上面我們創建了名為 face 的表,IS_MOB 屬性說明列簇 c 將啟用 MOB 特性,MOB_THRESHOLD 是 MOB 文件大小的閾值,單位是位元組,這裡的設置說明文件大於 2k 的列都當做小文件存儲。大家可能注意到上面原始方案中採用了 OSS 對象存儲,那我們為什麼不直接使用 OSS 存儲人臉特徵數據呢,如果有這個疑問,可以看看下面表的性能測試:
對比屬性 |
對象存儲 |
雲 HBase |
---|---|---|
建模能力 |
KV |
KV、表格、稀疏表、SQL、全文索引、時空、時序、圖查詢 |
查詢能力 |
前綴查找 |
前綴查找、過濾器、索引 |
性能 |
優 |
優,特別對小對象有更低的延遲;在複雜查詢場景下,比對象存儲有10倍以上的性能提升 |
成本 |
按流量,請求次數計費,適合訪問頻率低的場景 |
託管式,在高並發,高吞吐場景有更低的成本 |
擴展性 |
優 |
優 |
適用對象範圍 |
通用 |
<10MB |
根據上面的對比,使用 HBase MOB特性來存儲小於10MB的對象相比直接使用對象存儲有一些優勢。 我們現在來看看具體的表設計,如下圖:

上面 HBase 表的列簇名為c,我們使用人臉id作為列名。我們只使用了 HBase 的一張表就替換了之前方面的三張表!雖然我們啟用了 MOB,但是具體插入的方法和正常使用一樣,程式碼片段如下:
String CF_DEFAULT = "c"; Put put = new Put(groupId.getBytes()); put.addColumn(CF_DEFAULT.getBytes(),faceId1.getBytes(), feature1.getBytes()); put.addColumn(CF_DEFAULT.getBytes(),faceId2.getBytes(), feature2.getBytes()); …… put.addColumn(CF_DEFAULT.getBytes(),faceIdn.getBytes(), featuren.getBytes()); table.put(put);
用戶如果需要根據人臉組id獲取所有人臉的數據,可以使用下面方法:
Get get = new Get(groupId.getBytes()); Result re=table.get(get);
這樣我們可以拿到某個人臉組id對應的所有人臉數據。如果需要根據人臉組id+人臉id查找某個人臉的具體數據,看可以使用下面方法:
Get get = new Get(groupId.getBytes()); get.addColumn(CF_DEFAULT.getBytes(), faceId1.getBytes()) Result re=table.get(get);
經過上面的改造,在2台 HBase Worker 節點記憶體為32GB,核數為8,每個節點掛載四塊大小為 250GB 的 SSD 磁碟,並寫入 100W 行,每行有1W列,讀取一行的時間在100ms-500ms左右。在每行有1000個face的情況下,讀取一行的時間基本在20-50ms左右,相比之前的10s提升200~500倍。
下面是各個方案的對比性能對比情況。
對比屬性 |
對象存儲 |
MySQL+對象存儲 |
HBase MOB |
---|---|---|---|
讀寫強一致 |
Y |
N |
Y |
查詢能力 |
弱 |
強 |
強 |
查詢響應時間 |
高 |
高 |
低 |
運維成本 |
低 |
高 |
低 |
水平擴展 |
Y |
Y |
Y |
使用 Spark 加速數據分析
我們已經將人臉特徵數據存儲在阿里雲 HBase 之中,這個只是數據應用的第一步,如何將隱藏在這些數據背後的價值發揮出來?這就得藉助於數據分析,在這個場景就需要採用機器學習的方法進行聚類之類的操作。我們可以藉助 Spark 對存儲於 HBase 之中的數據進行分析,而且 Spark 本身支援機器學習的。但是如果直接採用開源的 Spark 讀取 HBase 中的數據,會對 HBase 本身的讀寫有影響的。
針對這些問題,阿里雲 HBase 團隊對 Spark 進行了相關優化,比如直接讀取 HFile、運算元下沉等;並且提供全託管的 Spark 產品,通過SQL服務ThriftServer、作業服務LivyServer簡化Spark的使用等。目前這套 Spark 的技術棧如下圖所示。

通過 Spark 服務,我們可以和 HBase 進行很好的整合,將實時流和人臉特徵挖掘整合起來,整個架構圖如下:

我們可以收集各種人臉數據源的實時數據,經過 Spark Streaming 進行簡單的 ETL 操作;其次,我們通過 Spark MLib 類庫對剛剛試試收集到的數據進行人臉特徵挖掘,最後挖掘出來的結果存儲到 HBase 之中。最後,用戶可以通過訪問 HBase 裡面已經挖掘好的人臉特徵數據進行其他的應用。