大數據查詢——HBase讀寫設計與實踐

2021-02-21 AI前線
AI 前線導語:本文介紹的項目主要解決 check 和 opinion2 張歷史數據表(歷史數據是指當業務發生過程中的完整中間流程和結果數據)的在線查詢。原實現基於 Oracle 提供存儲查詢服務,隨著數據量的不斷增加,在寫入和讀取過程中面臨性能問題,且歷史數據僅供業務查詢參考,並不影響實際流程,從系統結構上來說,放在業務鏈條上遊比較重。該項目將其置於下遊數據處理 Hadoop 分布式平臺來實現此需求。


更多乾貨內容請關注微信公眾號「AI 前線」(ID:ai-front)

本項目主要解決 check 和 opinion2 張歷史數據表(歷史數據是指當業務發生過程中的完整中間流程和結果數據)的在線查詢。原實現基於 Oracle 提供存儲查詢服務,隨著數據量的不斷增加,在寫入和讀取過程中面臨性能問題,且歷史數據僅供業務查詢參考,並不影響實際流程,從系統結構上來說,放在業務鏈條上遊比較重。本項目將其置於下遊數據處理 Hadoop 分布式平臺來實現此需求。下面列一些具體的需求指標:

數據量:目前 check 表的累計數據量為 5000w+ 行,11GB;opinion 表的累計數據量為 3 億 +,約 100GB。每日增量約為每張表 50 萬 + 行,只做 insert,不做 update。

查詢要求:check 表的主鍵為 id(Oracle 全局 id),查詢鍵為 check_id,一個 check_id 對應多條記錄,所以需返回對應記錄的 list; opinion 表的主鍵也是 id,查詢鍵是 bussiness_no 和 buss_type,同理返回 list。單筆查詢返回 List 大小約 50 條以下,查詢頻率為 100 筆 / 天左右,查詢響應時間 2s。

從數據量及查詢要求來看,分布式平臺上具備大數據量存儲,且提供實時查詢能力的組件首選 HBase。根據需求做了初步的調研和評估後,大致確定 HBase 作為主要存儲組件。將需求拆解為寫入和讀取 HBase 兩部分。

讀取 HBase 相對來說方案比較確定,基本根據需求設計 RowKey,然後根據 HBase 提供的豐富 API(get,scan 等)來讀取數據,滿足性能要求即可。

寫入 HBase 的方法大致有以下幾種:

1、 Java 調用 HBase 原生 API,HTable.add(List(Put))。

2、 MapReduce 作業,使用 TableOutputFormat 作為輸出。

3、 Bulk Load,先將數據按照 HBase 的內部數據格式生成持久化的 HFile 文件,然後複製到合適的位置並通知 RegionServer ,即完成海量數據的入庫。其中生成 Hfile 這一步可以選擇 MapReduce 或 Spark。

本文採用第 3 種方式,Spark + Bulk Load 寫入 HBase。該方法相對其他 2 種方式有以下優勢:

① BulkLoad 不會寫 WAL,也不會產生 flush 以及 split。

②如果我們大量調用 PUT 接口插入數據,可能會導致大量的 GC 操作。除了影響性能之外,嚴重時甚至可能會對 HBase 節點的穩定性造成影響,採用 BulkLoad 無此顧慮。

③過程中沒有大量的接口調用消耗性能。

④可以利用 Spark 強大的計算能力。

圖示如下:

Hadoop 2.5-2.7HBase 0.98.6Spark 2.0.0-2.1.1Sqoop 1.4.6

本段的重點在於討論 HBase 表的設計,其中 RowKey 是最重要的部分。為了方便說明問題,我們先來看看數據格式。以下以 check 舉例,opinion 同理。

check 表(原表欄位有 18 個,為方便描述,本文截選 5 個欄位示意)

如上圖所示,主鍵為 id,32 位字母和數字隨機組成,業務查詢欄位 check_id 為不定長欄位(不超過 32 位),字母和數字組成,同一 check_id 可能對應多條記錄,其他為相關業務欄位。眾所周知,HBase 是基於 RowKey 提供查詢,且要求 RowKey 是唯一的。RowKey 的設計主要考慮的是數據將怎樣被訪問。初步來看,我們有 2 種設計方法。

① 拆成 2 張表,一張表 id 作為 RowKey,列為 check 表對應的各列;另一張表為索引表,RowKey 為 check_id,每一列對應一個 id。查詢時,先找到 check_id 對應的 id list,然後根據 id 找到對應的記錄。均為 HBase 的 get 操作。

②將本需求可看成是一個範圍查詢,而不是單條查詢。將 check_id 作為 RowKey 的前綴,後面跟 id。查詢時設置 Scan 的 startRow 和 stopRow,找到對應的記錄 list。

第一種方法優點是表結構簡單,RowKey 容易設計,缺點為 1)數據寫入時,一行原始數據需要寫入到 2 張表,且索引表寫入前需要先掃描該 RowKey 是否存在,如果存在,則加入一列,否則新建一行,2)讀取的時候,即便是採用 List, 也至少需要讀取 2 次表。第二種設計方法,RowKey 設計較為複雜,但是寫入和讀取都是一次性的。綜合考慮,我們採用第二種設計方法。

熱點問題

HBase 中的行是以 RowKey 的字典序排序的,其熱點問題通常發生在大量的客戶端直接訪問集群的一個或極少數節點。默認情況下,在開始建表時,表只會有一個 region,並隨著 region 增大而拆分成更多的 region,這些 region 才能分布在多個 regionserver 上從而使負載均分。對於我們的業務需求,存量數據已經較大,因此有必要在一開始就將 HBase 的負載均攤到每個 regionserver,即做 pre-split。常見的防治熱點的方法為加鹽,hash 散列,自增部分(如時間戳)翻轉等。

RowKey 設計

Step1:確定預分區數目,創建 HBase Table

不同的業務場景及數據特點確定數目的方式不一樣,我個人認為應該綜合考慮數據量大小和集群大小等因素。比如 check 表大小約為 11G,測試集群大小為 10 臺機器,hbase.hregion.max.filesize=3G(當 region 的大小超過這個數時,將拆分為 2 個),所以初始化時儘量使得一個 region 的大小為 1~2G(不會一上來就 split),region 數據分到 11G/2G=6 個,但為了充分利用集群資源,本文中 check 表劃分為 10 個分區。如果數據量為 100G,且不斷增長,集群情況不變,則 region 數目增大到 100G/2G=50 個左右較合適。Hbase check 表建表語句如下:

其中,Column Family =『f』,越短越好。

COMPRESSION => 'SNAPPY',HBase 支持 3 種壓縮 LZO, GZIP and Snappy。GZIP 壓縮率高,但是耗 CPU。後兩者差不多,Snappy 稍微勝出一點,cpu 消耗的比 GZIP 少。一般在 IO 和 CPU 均衡下,選擇 Snappy。

DATA_BLOCK_ENCODING => 'FAST_DIFF',本案例中 RowKey 較為接近,通過以下命令查看 key 長度相對 value 較長。

Step2:RowKey 組成

Salt

讓數據均衡的分布到各個 Region 上,結合 pre-split,我們對查詢鍵即 check 表的 check_id 求 hashcode 值,然後 modulus(numRegions) 作為前綴,注意補齊數據。

說明:如果數據量達上百 G 以上,則 numRegions 自然到 2 位數,則 salt 也為 2 位。

Hash 散列

因為 check_id 本身是不定長的字符數字串,為使數據散列化,方便 RowKey 查詢和比較,我們對 check_id 採用 SHA1 散列化,並使之 32 位定長化。

唯一性

以上 salt+hash 作為 RowKey 前綴,加上 check 表的主鍵 id 來保障 RowKey 唯一性。綜上,check 表的 RowKey 設計如下:(check_id=A208849559)

為增強可讀性,中間還可以加上自定義的分割符,如』+』,』|』等。

以上設計能保證對每次查詢而言,其 salt+hash 前綴值是確定的,並且落在同一個 region 中。需要說明的是 HBase 中 check 表的各列同數據源 Oracle 中 check 表的各列存儲。

RowKey 設計與查詢息息相關,查詢方式決定 RowKey 設計,反之基於以上 RowKey 設計,查詢時通過設置 Scan 的 [startRow,stopRow], 即可完成掃描。以查詢 check_id=A208849559 為例,根據 RowKey 的設計原則,對其進行 salt+hash 計算,得前綴。

Step0: prepare work

因為是從上遊系統承接的業務數據,存量數據採用 sqoop 抽到 hdfs;增量數據每日以文件的形式從 ftp 站點獲取。因為業務數據欄位中包含一些換行符,且 sqoop1.4.6 目前只支持單字節,所以本文選擇』0x01』作為列分隔符,』0x10』作為行分隔符。

Step1: Spark read hdfs text file

SparkContext.textfile() 默認行分隔符為」\n」,此處我們用「0x10」,需要在 Configuration 中配置。應用配置,我們調用 newAPIHadoopFile 方法來讀取 hdfs 文件,返回 JavaPairRDD,其中 LongWritable 和 Text 分別為 Hadoop 中的 Long 類型和 String 類型(所有 Hadoop 數據類型和 java 的數據類型都很相像,除了它們是針對網絡序列化而做的特殊優化)。我們需要的數據文件放在 pairRDD 的 value 中,即 Text 指代。為後續處理方便,可將 JavaPairRDD轉換為 JavaRDD< String >。

Step2: Transfer and sort RDD

① 將 avaRDD< String>轉換成 JavaPairRDD<tuple2,String>,其中參數依次表示為,RowKey,col,value。做這樣轉換是因為 HBase 的基本原理是基於 RowKey 排序的,並且當採用 bulk load 方式將數據寫入多個預分區(region)時,要求 Spark 各 partition 的數據是有序的,RowKey,column family(cf),col name 均需要有序。在本案例中因為只有一個列簇,所以將 RowKey 和 col name 組織出來為 Tuple2格式的 key。請注意原本資料庫中的一行記錄(n 個欄位),此時會被拆成 n 行。</tuple2

② 基於 JavaPairRDD<tuple2,String>進行 RowKey,col 的二次排序。如果不做排序,會報以下異常:</tuple2

③ 將數據組織成 HFile 要求的 JavaPairRDDhfileRDD。

Step3:create hfile and bulk load to HBase

①主要調用 saveAsNewAPIHadoopFile 方法:

② hfilebulk load to HBase

註:如果集群開啟了 kerberos,step4 需要放置在 ugi.doAs()方法中,在進行如下驗證後實現

訪問 HBase 集群的 60010 埠 web,可以看到 region 分布情況。

本文基於 spring boot 框架來開發 web 端訪問 HBase 內數據。

use connection pool(使用連接池)

創建連接是一個比較重的操作,在實際 HBase 工程中,我們引入連接池來共享 zk 連接,meta 信息緩存,region server 和 master 的連接。

也可以通過以下方法,覆蓋默認線程池。

process query

Step1: 根據查詢條件,確定 RowKey 前綴

根據 3.3 RowKey 設計介紹,HBase 的寫和讀都遵循該設計規則。此處我們採用相同的方法,將 web 調用方傳入的查詢條件,轉化成對應的 RowKey 前綴。例如,查詢 check 表傳遞過來的 check_id=A208849559,生成前綴 7+7c9498b4a83974da56b252122b9752bf。

Step2:確定 scan 範圍

A208849559 對應的查詢結果數據即在 RowKey 前綴為 7+7c9498b4a83974da56b252122b9752bf 對應的 RowKey 及 value 中。

Step3:查詢結果組成返回對象

遍歷 ResultScanner 對象,將每一行對應的數據封裝成 table entity,組成 list 返回。

從原始數據中隨機抓取 1000 個 check_id,用於模擬測試,連續發起 3 次請求數為 2000(200 個線程並發,循環 10 次),平均響應時間為 51ms,錯誤率為 0。

如上圖,經歷 N 次累計測試後,各個 region 上的 Requests 數較為接近,符合負載均衡設計之初。

如果集群開啟了安全認證,那麼在進行 Spark 提交作業以及訪問 HBase 時,均需要進行 kerberos 認證。

本文採用 yarn cluster 模式,像提交普通作業一樣,可能會報以下錯誤。

定位到 HbaseKerberos.java:18,代碼如下:

這是因為 executor 在進行 HBase 連接時,需要重新認證,通過 --keytab 上傳的 tina.keytab 並未被 HBase 認證程序塊獲取到,所以認證的 keytab 文件需要另外通過 --files 上傳。示意如下

其中 tina.keytab.hbase 是將 tina.keytab 複製並重命名而得。因為 Spark 不允許同一個文件重複上傳。

解決方法一

如果 sc 作為類的成員變量,在方法中被引用,則加 transient 關鍵字,使其不被序列化。

解決方法二

將 sc 作為方法參數傳遞,同時使涉及 RDD 操作的類 implements Serializable。 代碼中採用第二種方法。詳見代碼。

或者

查看下面 issue 以及一次排查問題的過程,可能是 open file 超過限制。

https://github.com/spring-projects/spring-boot/issues/1106

http://mp.weixin.qq.com/s/34GVlaYDOdY1OQ9eZs-iXg

使用 ulimit-a 查看每個用戶默認打開的文件數為 1024。

在系統文件 /etc/security/limits.conf 中修改這個數量限制,在文件中加入以下內容, 即可解決問題。

soft nofile 65536

hard nofile 65536

汪婷,中國民生銀行大數據開發工程師,專注於 Spark 大規模數據處理和 Hbase 系統設計。

參考文獻

http://hbase.apache.org/book.html#perf.writing

http://www.opencore.com/blog/2016/10/efficient-bulk-load-of-hbase-using-spark/

http://hbasefly.com/2016/03/23/hbase_writer/

https://github.com/spring-projects/spring-boot/issues/1106

http://mp.weixin.qq.com/s/34GVlaYDOdY1OQ9eZs-iXg

今日薦文

點擊下方圖片即可閱讀

圍繞人工智慧的落地經驗,大體有三個來源:

純學術界的前沿研究,如清華、復旦等高等學府;

純工業界的實踐探索,如:阿里、百度、騰訊等網際網路巨頭;

橫跨學術與工業界的橋梁:如 Microsoft、Snap、京東、微博等企業研究院或實驗室。

在接下來 12 月 8-11 日北京舉行的 ArchSummit 全球架構師峰會上,我們有幸邀請到來自上述學府與企業的資深教授和技術人,從算法到實踐,從機器學習到人工智慧,與你探討分別來自這三種來源上的 AI 感悟。

大會 9 折報名最後一周,大會完整演講目錄可識別下方二維碼或點擊 閱讀原文 了解。

相關焦點

  • HBase的讀寫和javaAPI的使用
    一、hbase系統管理表hbase:namespace,記錄了hbase中所有namespace的信息 ,當前系統下有哪些namespace信息scan 'hbase:namespace'hbase:meta,記錄了region信息scan 'hbase:meta'二、讀寫思想client(get、scan)rowkey條件(1)由於rowkey是存儲在region
  • HBase調優 | HBase Compaction參數調優
    影響查詢效率進階: 這個值的設置還和業務數據的特點有關係,比如類似詳單雲系統,業務邏輯是 按月建表,每個月建一個表,rowkey是reverse(手機號碼)+時間戳 數據每3-5分鐘導入一次。查詢邏輯是根據手機號碼+時間段查詢 通常手機流量使用情況會某個號碼會不斷的產生,所以一個手機號碼產生的數據基本上會按照分布在很多HFile中。 如果hbase.hstore.compaction.min 設置過大的話,則一個查詢時候會訪問較多的 HFile 影響查詢效率。 這種業務就不適合設置的特別大。
  • HBase實踐篇 | 為HBase的Thrift 客戶端API設計連接池
    我們線上大部分的業務是由 happybase 封裝的 Python API 來提供數據的讀寫服務,小部分業務則是用 Java 語言,例如:Flink 實時作業中的 HBaseSink。實時程序讀寫 HBase 最開始使用的是hbase-client提供的 API,隨著越來越多實時業務的上線,被佔用的 zookeeper 的連接資源也隨之增加,甚至在有些極端場景下(也有可能是程序 BUG),ZK 的連接會被迅速消耗,導致 HBase 服務拒絕連接,流作業無法正常拉起。
  • 不拘一格降HBase,數據存儲的利器,學完摸清Google存儲套路——大數據課程更新09.01
    第七階段:分布式資料庫 HBase章節1:hbase第1章1:hbase介紹2:數據結構和存儲結構3:hbase架構014:hbase架構025:hbase高可用完全分布式搭建6:hbase客戶端常用操作7:hbase數據模型進階8:LSMTree9:大合併(major)和小合併(minor)10:hbase讀寫流程 章節2:hbase第2章11:創建表phone12:添加數據和根據
  • HBase二級索引方案
    欄位進行數據檢索和查詢, 往往要通過MapReduce/Spark等分布式計算框架進行,硬體資源消耗和時間延遲都會比較高。為了HBase的數據查詢更高效、適應更多的場景, 諸如使用非rowkey欄位檢索也能做到秒級響應,或者支持各個欄位進行模糊查詢和多欄位組合查詢等, 因此需要在HBase上面構建二級索引, 以滿足現實中更複雜多樣的業務需求。
  • 深入淺出HBase keyValue動態讀寫流程
    帶著這個問題,我們來一起學習下客戶端是如何讀寫HBase數據的,進而幫助大家理解HBase讀寫相關原理。Region是HBase架構的核心,HBase大部分工作基本都是圍繞Region來展開的,現在我們來詳細總結一下Client在讀寫的時候是如何定位到RegionServer進而讀取數據的。關於Region的查找,早期的設計(0.96.0之前)是被稱為三層查詢架構,如下圖:
  • 通過Java API 像 MySQL一樣查詢HBASE
    隨著大數據的應用普及,HBASE作為一種非常適應海量數據存儲和查詢的資料庫也逐步流行起來。
  • 深入 HBase 架構解析
    管理namespace和table的元數據(實際存儲在HDFS上)。權限控制(ACL)。HRegionServer節點用於:存放和管理本地HRegion。讀寫HDFS,管理Table中的數據。Client直接通過HRegionServer讀寫數據(從HMaster中獲取元數據,找到RowKey所在的HRegion/HRegionServer後)。ZooKeeper集群是協調系統,用於:存放整個 HBase集群的元數據以及集群的狀態信息。實現HMaster主從節點的failover。
  • 「HBase筆記」從洗襪子到hbase(hbase是什麼鬼東西)
    基於 hdfs的資料庫系統,提供高可靠性,高性能,列存儲,可擴展性,實時讀寫 nosql。2、介於 nosql和 RDBMS之間,數據只能通過主鍵(row key)和主鍵的range進行檢索。3、只支持單行事務(可以通過hive支持實現複雜的操作,比如多表join)。4、通常用於存儲結構化和半結構化鬆散數據。
  • 10大HBase常見運維工具整理
    HBase組件介紹HBase作為當前比較熱門和廣泛使用的NoSQL資料庫,由於本身設計架構和流程上比較複雜,對大數據經驗較少的運維人員門檻較高,本文對當前HBase上已有的工具做一些介紹以及總結。Canary工具HBase Canary是檢測HBase集群當前狀態的工具,用簡單的查詢來檢查HBASE上的region是否可用(可讀)。它主要分為兩種模式1) region模式(默認),對每個region下每個CF隨機查詢一條數據,列印是否成功以及查詢時延。
  • HBase原理 | HBase Compaction介紹與參數調優
    會越來越多,將會影響HBase查詢性能,同時會對HDFS造成一定影響。Minor Compaction稱為小合併,主要是選取一些小的、相鄰的HFile將他們合併成較大的HFile,並刪除HFile中的過期數據。Major Compaction稱為大合併,會將一個列族下的所有HFile合併成一個大的HFile,同時刪除過期數據、已刪除數據(打了Delete標記的)、版本過大的數據等三類無效數據。
  • Flink SQL 實戰:HBase 的結合應用
    而當下 Flink SQL 的火熱程度不用多說,Flink SQL 也為 HBase 提供了 connector,因此 HBase 與 Flink SQL 的結合非常有必要實踐實踐。當然,本文假設用戶有一定的 HBase 知識基礎,不會詳細去介紹 HBase 的架構和原理,本文著重介紹 HBase 和 Flink 在實際場景中的結合使用。
  • SHC:使用 Spark SQL 高效地讀寫 HBase
    Apache Spark 和 Apache HBase 是兩個使用比較廣泛的大數據組件。很多場景需要使用 Spark 分析/查詢 HBase 中的數據,而目前 Spark 內置是支持很多數據源的,其中就包括了 HBase,但是內置的讀取數據源還是使用了 TableInputFormat 來讀取 HBase 中的數據。
  • Hbase命令大全
    行的一次讀寫是原子操作 (不論一次讀寫多少列)區域Region    Table在行的方向上分割為多個Region。    Region是按大小分割的,每個表開始只有一個region,隨著數據的增多,region不斷增大,當增大到一個閥值的時候,region就會等分為兩個新的region,之後會有越來越多的region。
  • HBase與mapreduce集成
    在公司中,大多數都是hbase和mapreduce共同進行業務操作,hbase多數都是讀寫操作,hbase和hadoop在內部已經封裝好了,我們要做的就是調用。常見的模式1、從hbase讀取數據將hbase的數據作為map的輸入2、將數據寫入hbase將hbase作為reduce輸出3、從hbase讀,再寫入hbase
  • 後端接口訪問數據查詢如何提高性能?從MySQL、ES、HBASE等技術出發...
    1.6 大表場景在未二次開發的 MYSQL 中,上億的表肯定算大表,這種情況即使在索引、查詢層面做到了較好實現,面對頻繁聚合操作也可能會出現 IO 或 CPU 瓶頸,即使是單純查詢,效率也會下降。我們知道,innodb_buffer_pool 用來緩存表及索引,如果索引數據較大,緩存命中率就堪憂,同時 innodb_buffer_pool 採用 LRU 算法進行頁面淘汰,如果數據量過大,對老或非熱點數據的查詢可能就會把熱點數據給擠出去。所以對於大表常見優化即是分庫分表和讀寫分離了。
  • 搞懂Hadoop、MapReduce、Hive、HBase、YARN及Spark的區別與聯繫
    Hadoop是一個開源框架,允許使用簡單的編程模型,在跨計算機集群的分布式環境中,存儲和處理大數據。它的設計是從單個伺服器擴展到數千個機器,每個都提供本地計算和存儲。hbase的刪除操作hbase批量命令bulkload為hbase的批量插入命令,應用於大數據量的插入,沒有性能問題。
  • Hive整合HBase完整筆記(親測)
    (1)測試數據文件創建測試文件course.csv第一列為課程名稱,第二列為課程分數(2)數據加載到HDFS在hdfs文件系統中創建hive-hbase目錄,並加載測試數據。查看hdfs文件目錄從查詢語句的執行結果可以看出,Hive表中的數據和hdfs上的數據一致。
  • 大數據入門:Hbase Rowkey設計
    Hbase的原型來自Google的BigTable,各方面性能優異,這其實得益於Hbase的內部設計。今天的大數據入門分享,我們就來具體講講,Hbase Rowkey設計。Hbase與一般傳統分布式關係型資料庫相比,明顯不同的是,它是基於列模式存儲,同時是非常適合非結構化數據存儲的。
  • 大數據基礎知識:Hadoop分布式系統介紹
    隨著智能化、萬物互聯時代的快速發展,數據量開始暴增,一方面我們需要開始思考如何高效可靠地存儲海量的數據,另一方面我們還需要對這些數據進行分析處理,以獲得更多有價值的信息。這時期我們就需要用到Hadoop了。