一、背景
訂單本地化系統目前一個月的訂單的讀寫已經切至jimkv存儲,對應的HBase集群已下線。但存儲全量數據的HBase集群仍在使用,計劃將這個HBase集群中的數據全部遷到jimkv,徹底下線這個HBase集群。由于這個集群目前仍在線上讀寫,本文從原理和實踐的角度探索對HBase集群數據的在線遷移的方案,歡迎大家補充。
二、基礎理論梳理
HBase整體架構
重溫一下各個模塊的職責
HBase客戶端
HBase客戶端(Client)提供了Shell命令行接口、原生Java API編程接口、Thrift/REST API編程接口以及MapReduce編程接口。HBase客戶端支持所有常見的DML操作以及DDL操作,即數據的增刪改查和表的日常維護等。其中Thrift/REST API主要用于支持非Java的上層業務需求,MapReduce接口主要用于批量數據導入以及批量數據讀取。HBase客戶端訪問數據行之前,首先需要通過元數據表定位目標數據所在RegionServer,之后才會發送請求到該RegionServer。同時這些元數據會被緩存在客戶端本地,以方便之后的請求訪問。如果集群RegionServer發生宕機或者執行了負載均衡等,從而導致數據分片發生遷移,客戶端需要重新請求最新的元數據并緩存在本地。
Master
Master主要負責HBase系統的各種管理工作:
?處理用戶的各種管理請求,包括建表、修改表、權限操作、切分表、合并數據分片以及Compaction等。
?管理集群中所有RegionServer,包括RegionServer中Region的負載均衡、RegionServer的宕機恢復以及Region的遷移等。
?清理過期日志以及文件,Master會每隔一段時間檢查HDFS中HLog是否過期、HFile是否已經被刪除,并在過期之后將其刪除。
RegionServer
RegionServer主要用來響應用戶的IO請求,是HBase中最核心的模塊,由WAL(HLog)、BlockCache以及多個Region構成。
?WAL(HLog):HLog在HBase中有兩個核心作用——其一,用于實現數據的高可靠性,HBase數據隨機寫入時,并非直接寫入HFile數據文件,而是先寫入緩存,再異步刷新落盤。為了防止緩存數據丟失,數據寫入緩存之前需要首先順序寫入HLog,這樣,即使緩存數據丟失,仍然可以通過HLog日志恢復;其二,用于實現HBase集群間主從復制,通過回放主集群推送過來的HLog日志實現主從復制。
?BlockCache:HBase系統中的讀緩存。客戶端從磁盤讀取數據之后通常會將數據緩存到系統內存中,后續訪問同一行數據可以直接從內存中獲取而不需要訪問磁盤。對于帶有大量熱點讀的業務請求來說,緩存機制會帶來極大的性能提升。
對于帶有大量熱點讀的業務請求來說,緩存機制會帶來極大的性能提升。BlockCache緩存對象是一系列Block塊,一個Block默認為64K,由物理上相鄰的多個KV數據組成。BlockCache同時利用了空間局部性和時間局部性原理,前者表示最近將讀取的KV數據很可能與當前讀取到的KV數據在地址上是鄰近的,緩存單位是Block(塊)而不是單個KV就可以實現空間局部性;后者表示一個KV數據正在被訪問,那么近期它還可能再次被訪問。當前BlockCache主要有兩種實現——LRUBlockCache和BucketCache,前者實現相對簡單,而后者在GC優化方面有明顯的提升。
?Region:數據表的一個分片,當數據表大小超過一定閾值就會“水平切分”,分裂為兩個Region。Region是集群負載均衡的基本單位。通常一張表的Region會分布在整個集群的多臺RegionServer上,一個RegionServer上會管理多個Region,當然,這些Region一般來自不同的數據表。
一個Region由一個或者多個Store構成,Store的個數取決于表中列簇(column family)的個數,多少個列簇就有多少個Store。HBase中,每個列簇的數據都集中存放在一起形成一個存儲單元Store,因此建議將具有相同IO特性的數據設置在同一個列簇中。每個Store由一個MemStore和一個或多個HFile組成。MemStore稱為寫緩存,用戶寫入數據時首先會寫到MemStore,當MemStore寫滿之后(緩存數據超過閾值,默認128M)系統會異步地將數據f lush成一個HFile文件。顯然,隨著數據不斷寫入,HFile文件會越來越多,當HFile文件數超過一定閾值之后系統將會執行Compact操作,將這些小文件通過一定策略合并成一個或多個大文件。
HDFS
HBase底層依賴HDFS組件存儲實際數據,包括用戶數據文件、HLog日志文件等最終都會寫入HDFS落盤。HDFS是Hadoop生態圈內最成熟的組件之一,數據默認三副本存儲策略可以有效保證數據的高可靠性。HBase內部封裝了一個名為DFSClient的HDFS客戶端組件,負責對HDFS的實際數據進行讀寫訪問。
三、數據遷移方案調研
1、如何定位一條數據在哪個region
HBase一張表的數據是由多個Region構成,而這些Region是分布在整個集群的RegionServer上的。那么客戶端在做任何數據操作時,都要先確定數據在哪個Region上,然后再根據Region的RegionServer信息,去對應的RegionServer上讀取數據。因此,HBase系統內部設計了一張特殊的表——hbase:meta表,專門用來存放整個集群所有的Region信息。
hbase:meta表的結構如下,整個表只有一個名為info的列族。而且HBase保證hbase:meta表始終只有一個Region,這是為了確保meta表多次操作的原子性。
hbase:meta的一個rowkey就對應一個Region,rowkey主要由TableName(業務表名)、StartRow(業務表Region區間的起始rowkey)、Timestamp(Region創建的時間戳)、EncodedName(上面3個字段的MD5 Hex值)4個字段拼接而成。每一行數據又分為4列,分別是info:regioninfo、info:seqnumDuringOpen、info:server、info:serverstartcode。
?info:regioninfo:該列對應的Value主要存儲4個信息,即EncodedName、RegionName、Region的StartRow、Region的StopRow。
?info:seqnumDuringOpen:該列對應的Value主要存儲Region打開時的sequenceId。
?info:server:該列對應的Value主要存儲Region落在哪個RegionServer上。
?info:serverstartcode:該列對應的Value主要存儲所在RegionServer的啟動Timestamp。
為了解決如果所有的流量都先請求hbase:meta表找到Region,再請求Region所在的RegionServer,那么hbase:meta表的將承載巨大的壓力,這個Region將馬上成為熱點Region這個問題,HBase會把hbase:meta表的Region信息緩存在HBase客戶端。在HBase客戶端有一個叫做MetaCache的緩存,在調用HBase API時,客戶端會先去MetaCache中找到業務rowkey所在的Region。
2、HBase client Scan用法
HBase客戶端提供了一系列可以批量掃描數據的api,主要有ScanAPI、TableScanMR、SnapshotScanM
HBase client scan demo
public class TestDemo {
private static final HBaseTestingUtility TEST_UTIL=new HBaseTestingUtility();
public static final TableName tableName=TableName.valueOf("testTable");
public static final byte[] ROW_KEY0=Bytes.toBytes("rowkey0");
public static final byte[] ROW_KEY1=Bytes.toBytes("rowkey1");
public static final byte[] FAMILY=Bytes.toBytes("family");
public static final byte[] QUALIFIER=Bytes.toBytes("qualifier");
public static final byte[] VALUE=Bytes.toBytes("value");
@BeforeClass
public static void setUpBeforeClass() throws Exception {
TEST_UTIL.startMiniCluster();
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
@Test
public void test() throws IOException {
Configuration conf=TEST_UTIL.getConfiguration();
try (Connection conn=ConnectionFactory.createConnection(conf)) {
try (Table table=conn.getTable(tableName)) {
for (byte[] rowkey : new byte[][] { ROW_KEY0, ROW_KEY1 }) {
Put put=new Put(rowkey).addColumn(FAMILY, QUALIFIER, VALUE);
table.put(put);
}
Scan scan=new Scan().withStartRow(ROW_KEY1).setLimit(1);
try (ResultScanner scanner=table.getScanner(scan)) {
List< Cell > cells=new ArrayList< >();
for (Result result : scanner) {
cells.addAll(result.listCells());
}
Assert.assertEquals(cells.size(), 1);
Cell firstCell=cells.get(0);
Assert.assertArrayEquals(CellUtil.cloneRow(firstCell), ROW_KEY1);
Assert.assertArrayEquals(CellUtil.cloneFamily(firstCell), FAMILY);
Assert.assertArrayEquals(CellUtil.cloneQualifier(firstCell), QUALIFIER);
Assert.assertArrayEquals(CellUtil.cloneValue(firstCell), VALUE);
}
}
}
}
}
Scan API
使用ScanAPI獲取Result的流程如下,在上面的demo代碼中,table.getScanner(scan)可以拿到一個scanner,然后只要不斷地執行scanner.next()就能拿到一個Result,客戶端Scan的核心流程如下
用戶每次執行scanner.next(),都會嘗試去名為cache的隊列中拿result(步驟4)。如果cache隊列已經為空,則會發起一次RPC向服務端請求當前scanner的后續result數據(步驟1)??蛻舳耸盏絩esult列表之后(步驟2),通過scanResultCache把這些results內的多個cell進行重組,最終組成用戶需要的result放入到Cache中(步驟3)。其中,步驟1+步驟2+步驟3統稱為loadCache操作。為什么需要在步驟3對RPC response中的result進行重組呢?這是因為RegionServer為了避免被當前RPC請求耗盡資源,實現了多個維度的資源限制(例如timeout、單次RPC響應最大字節數等),一旦某個維度資源達到閾值,就馬上把當前拿到的cell返回給客戶端。這樣客戶端拿到的result可能就不是一行完整的數據,因此在步驟3需要對result進行重組。
梳理完scanner的執行流程之后,再看下Scan的幾個重要的概念。
?caching:每次loadCache操作最多放caching個result到cache隊列中。控制caching,也就能控制每次loadCache向服務端請求的數據量,避免出現某一次scanner.next()操作耗時極長的情況。
?batch:用戶拿到的result中最多含有一行數據中的batch個cell。如果某一行有5個cell,Scan設的batch為2,那么用戶會拿到3個result,每個result中cell個數依次為2,2,1。
?allowPartial:用戶能容忍拿到一行部分cell的result。設置了這個屬性,將跳過圖4-3中的第三步重組流程,直接把服務端收到的result返回給用戶。
?maxResultSize:loadCache時單次RPC操作最多拿到maxResultSize字節的結果集。
缺點:不能并發執行,如果掃描的數據分布在不同的region上面,scan不會并發執行,而是一行一行的去掃,且在步驟1和步驟2期間,client端一致在阻塞等待,所以從客戶端視角來看整個掃描時間=客戶端處理數據時間+服務器端掃描數據時間。
應用場景:根據上面的分析,scan API的效率很大程度上取決于掃描的數據量。通常建議OLTP業務中少量數據量掃描的scan可以使用scan API,大量數據的掃描使用scan API,掃描性能有時候并不能夠得到有效保證。
最佳實踐:
1.批量OLAP掃描業務建議不要使用ScanAPI,ScanAPI適用于少量數據掃描場景(OLTP場景)
2.建議所有scan盡可能都設置startkey以及stopkey減少掃描范圍
3.建議所有僅需要掃描部分列的scan盡可能通過接口setFamilyMap設置列族以及列
TableScanMR
對于掃描大量數據的這類業務,HBase目前提供了兩種基于MR掃描的用法,分別為TableScanMR以及SnapshotScanMR。首先來介紹TableScanMR,具體用法參考官方文檔https://hbase.apache.org/book.html#mapreduce.example.read,TableScanMR的工作原理說白了就是ScanAPI的并行化。如下圖所示:
TableScanMR會將scan請求根據目標region的分界進行分解,分解成多個sub-scan,每個sub-scan本質上就是一個ScanAPI。假如scan是全表掃描,那這張表有多少region,就會將這個scan分解成多個sub-scan,每個sub-scan的startkey和stopkey就是region的startkey和stopkey。
最佳實踐:
1.TableScanMR設計為OLAP場景使用,因此在離線掃描時盡可能使用該種方式
2.TableScanMR原理上主要實現了ScanAPI的并行化,將scan按照region邊界進行切分。這種場景下整個scan的時間基本等于最大region掃描的時間。在某些有數據傾斜的場景下可能出現某一個region上有大量待掃描數據,而其他大量region上都僅有很少的待掃描數據。這樣并行化效果并不好。針對這種數據傾斜的場景TableScanMR做了平衡處理,它會將大region上的scan切分成多個小的scan使得所有分解后的scan掃描的數據量基本相當。這個優化默認是關閉的,需要設置參數”hbase.mapreduce.input.autobalance”為true。因此建議大家使用TableScanMR時將該參數設置為true。
3.盡量將掃描表中相鄰的小region合并成大region,而將大region切分成稍微小點的region
SnapshotScanMR
SnapshotScanMR與TableScanMR相同都是使用MR并行化對數據進行掃描,兩者用法也基本相同,直接使用TableScanMR的用法,在此基礎上做部分修改即可,它們最大的區別主要有兩點:
1.SnapshotScanMR掃描于原始表對應的snapshot之上(更準確來說根據snapshot restore出來的hfile),而TableScanMR掃描于原始表。
2.SnapshotScanMR直接會在客戶端打開region掃描HDFS上的文件,不需要發送Scan請求給RegionServer,再有RegionServer掃描HDFS上的文件。在客戶端直接掃描HDFS上的文件,這類scanner稱之為ClientSideRegionScanner。
總體來看和TableScanMR工作流程基本一致,最大的不同來自region掃描HDFS這個模塊,TableScanMR中這個模塊來自于regionserver,而SnapshotScanMR中客戶端直接繞過regionserver在客戶端借用region中的掃描機制直接掃描hdfs中數據。這樣做的優點如下:
1.減小對RegionServer的影響。很顯然,SnapshotScanMR這種繞過RegionServer的實現方式最大限度的減小了對集群中其他業務的影響。
2.極大的提升了掃描效率。SnapshotScanMR相比TableScanMR在掃描效率上會有2倍~N倍的性能提升(下一小節對各種掃描用法性能做個對比評估)。主要原因如下:掃描的過程少了一次網絡傳輸,對于大數據量的掃描,網絡傳輸花費的時間是非常龐大的,這主要可能牽扯到數據的序列化以及反序列化開銷。TableScanMR掃描中RegionServer很可能會成為瓶頸,而SnapshotScanMR掃描并沒有這個瓶頸點。
性能對比(來自網圖)
3、業界常用的數據遷移方案
Hadoop層數據遷移
Hadoop層的數據遷移主要用到DistCp(Distributed Copy), HBase的所有文件都存儲在HDFS上,因此只要使用Hadoop提供的文件復制工具distcp將HBASE目錄復制到同一HDFS或者其他HDFS的另一個目錄中,就可以完成對源HBase集群的備份工作。
缺點:源集群需要停寫,不可行。
HBase層數據遷移
- copyTable
copyTable也是屬于HBase層的數據遷移工具之一,從0.94版本開始支持,以表級別進行數據遷移。通過MapReduce程序全表掃描待備份表數據并寫入另一個集群,與DistCp不同的時,它是利用MR去scan 原表的數據,然后把scan出來的數據寫入到目標集群的表。
缺點:全表臊面+數據深拷貝,對源表的讀寫性能有很大影響,效率低。需要在使用過程中限定掃描原表的速度和傳輸的帶寬,且copyTable不保證數據一致性,在大型集群遷移上這個工具使用比較少,很難控制。
- Export/Import
此方式與CopyTable類似,主要是將HBase表數據轉換成Sequence File并dump到HDFS,也涉及Scan表數據,與CopyTable相比,還多支持不同版本數據的拷貝,同時它拷貝時不是將HBase數據直接Put到目標集群表,而是先轉換成文件,把文件同步到目標集群后再通過Import到線上表。
- Snapshot
Snapshot備份以快照技術為基礎原理,備份過程不需要拷貝任何數據,因此對當前集群幾乎沒有任何影響,備份速度非常快而且可以保證數據一致性。筆者推薦在0.98之后的版本都使用Snapshot工具來完成在線備份任務。 參考: https://hbase.apache.org/book.html#ops.backup
4、Snapshot功能
常用場景
HBase在0.98版本推出在線Snapshot備份功能,使用在線snapshot備份功能常用于如下兩種情況:
?全量/增量備份。
○使用場景一:通常情況下,對于重要的業務數據,建議每天執行一次Snapshot來保存數據的快照記錄,并且定期清理過期快照,這樣如果業務發生嚴重錯誤,可以回滾到之前的一個快照點。
○使用場景二:如果要對集群做重大升級,建議升級前對重要的表執行一次Snapshot,一旦升級有任何異常可以快速回滾到升級前。
?數據遷移
可以使用ExportSnapshot功能將快照導出到另一個集群,實現數據的遷移。
○使用場景一:機房在線遷移。比如業務集群在A機房,因為A機房機位不夠或者機架不夠需要將整個集群遷移到另一個容量更大的B集群,而且在遷移過程中不能停服。基本遷移思路是,先使用Snapshot在B集群恢復出一個全量數據,再使用replication技術增量復制A集群的更新數據,等待兩個集群數據一致之后將客戶端請求重定向到B機房。
○使用場景二:利用Snapshot將表數據導出到HDFS,再使用HiveSpark等進行離線OLAP分析,比如審計報表、月度報表等。
基本原理
Snapshot機制并不會拷貝數據,可以理解為它是原數據的一份指針。在HBase的LSM樹類型系統結構下是比較容易理解的,我們知道HBase數據文件一旦落到磁盤就不再允許更新刪除等原地修改操作,如果想更新刪除只能追加寫入新文件。這種機制下實現某個表的Snapshot,只需為當前表的所有文件分別新建一個引用(指針)。對于其他新寫入的數據,重新創建一個新文件寫入即可。如下圖所示
實踐過程
我在阿里云上搭建了一個輕量級的haddop和HBase集群,使用Snapshot功能步驟如下:
- 在目標集群上建立表結構一樣的表。
$ hbase shell
hbase > create 'myTable', 'cf1', 'cf2'
- 在原集群上對表初始化數據。
$ hbase shell
hbase > put 'myTable', 'row1', 'cf1:a', 'value1'
hbase > put 'myTable', 'row2', 'cf2:b', 'value2'
hbase > scan 'myTable'
ROW COLUMN+CELL row1 column=cf1:a, timestamp=2023-08-09T16:43:10.024, value=value1 row2 column=cf2:b, timestamp=2023-08-09T16:43:20.036, value=value2
- 使用 hbase shell 在原始集群中創建一個快照。
$ hbase shell
hbase >snapshot 'myTable', 'myTableSnapshot'
這里 'myTable' 是 hbase 的表名,'myTableSnapshot' 是快照的名稱。創建完成后可使用 list_snapshots 確認是否成功,或使用 delete_snapshot 刪除快照。
hbase > delete_snapshot 'myTableSnapshot'
- 在源集群中導出快照到目標集群。
hbase org.apache.hadoop.hbase.snapshot.ExportSnapshot -snapshot myTableSnapshot -copy-to hdfs://10.0.0.38:4007/hbase/snapshot/myTableSnapshot
這里 10.0.0.38:4007 是目標集群的 **activeip:**rpcport,導出快照時系統級別會啟動一個 mapreduce 的任務,可以在后面增加 -mappers 16 -bandwidth 200 來指定 mapper 和帶寬。其中200指的是200MB/sec。
- 快照還原到目標集群的目標 HDFS,在目標集群中執行如下命令。
hbase org.apache.hadoop.hbase.snapshot.ExportSnapshot -snapshot myTableSnapshot -copy-from /hbase/snapshot/myTableSnapshot -copy-to /hbase/
- 在目標集群從 hdfs 恢復相應的 hbase 表及數據。
hbase > disable "myTable"
hbase > restore_snapshot 'myTableSnapshot'
hbase > enable 'myTable'
- 對于新表進行測試
注意事項
訂單本地化HDD目前order_info_2023這張表分布在1500多個不同的region上,按照各種參考資料的理論來說,使用snapshot易超時,該如何解決這個問題?有沒有什么方法能使做Snapshot更高效?網上關于這部分的資料太少,目前找不到太深入全面的解析。還有測試環境如何走這個流程?
使用snapshot注意需要申請zk、hbase-master節點的shell的管理員權限。
5、讀數據時的注意事項
關于scan時緩存的設置
原理:HBase業務通常一次scan就會返回大量數據,因此客戶端發起一次scan請求,實際并不會一次就將所有數據加載到本地,而是分成多次RPC請求進行加載,這樣設計一方面因為大量數據請求可能會導致網絡帶寬嚴重消耗進而影響其他業務,另一方面因為數據量太大可能導致本地客戶端發生OOM。在這樣的設計體系下,用戶會首先加載一部分數據到本地,然后遍歷處理,再加載下一部分數據到本地處理,如此往復,直至所有數據都加載完成。數據加載到本地就存放在scan緩存中,默認為100條數據。通常情況下,默認的scan緩存設置是可以正常工作的。但是對于一些大scan(一次scan可能需要查詢幾萬甚至幾十萬行數據),每次請求100條數據意味著一次scan需要幾百甚至幾千次RPC請求,這種交互的代價是很大的。因此可以考慮將scan緩存設置增大,比如設為500或者1000條可能更加合適?!禜Base原理與實踐》作者提到,之前做過一次試驗,在一次scan 10w+條數據量的條件下,將scan緩存從100增加到1000條,可以有效降低scan請求的總體延遲,延遲降低了25%左右。
建議:大scan場景下將scan緩存從100增大到500或者1000,用以減少RPC次數。
關于離線批量讀時緩存的設置
原理:通常在離線批量讀取數據時會進行一次性全表掃描,一方面數據量很大,另一方面請求只會執行一次。這種場景下如果使用scan默認設置,就會將數據從HDFS加載出來放到緩存??上攵罅繑祿M入緩存必將其他實時業務熱點數據擠出,其他業務不得不從HDFS加載,進而造成明顯的讀延遲毛刺。
建議:離線批量讀取請求設置禁用緩存,scan.setCacheBlocks (false)。
四、方案設計
五、代碼實現
對于HBase各種常用的DDL、DML操作的api匯總到這里
http://xingyun.jd.com/codingRoot/yanghelin3/hbase-api/
使用mapreduce方式遷移實現匯總到這里
http://xingyun.jd.com/codingRoot/yanghelin3/hbase-mapreduce/
REFERENCE
參考資料
HBase官方文檔:[http://hbase.apache.org/book.html#arch.overview]
HBase官方博客:[https://blogs.apache.org/hbase]
cloudera官方HBase博客:[https://blog.cloudera.com/blog/category/hbase]
HBasecon官網:[http://hbase.apache.org/www.hbasecon.com]
HBase開發社區:[https://issues.apache.org/jira/pr
HBase中文社區:[http://hbase.group]
《Hbase原理與實踐 》
snapshot功能介紹
[https://blog.cloudera.com/introduction-to-apache-hbase-snapshots/]
[https://blog.cloudera.com/introduction-to-apache-hbase-snapshots-part-2-deeper-dive/]
[https://hbase.apache.org/book.html#ops.backup]
如何使用snapshot進行復制
[https://docs.cloudera.com/documentation/enterprise/5-5-x/topics/cdh_bdr_hbase_replication.html#topic_20_11_7]
審核編輯 黃宇
-
接口
+關注
關注
33文章
8650瀏覽量
151409 -
編程
+關注
關注
88文章
3628瀏覽量
93816 -
數據遷移
+關注
關注
0文章
72瀏覽量
6959 -
HDFS
+關注
關注
1文章
30瀏覽量
9616 -
Hbase
+關注
關注
0文章
27瀏覽量
11192
發布評論請先 登錄
相關推薦
評論