HBase phoenix二级索引
1. 為什么需要用二級索引?
對于HBase而言,如果想精確地定位到某行記錄,唯一的辦法是通過rowkey來查詢。如果不通過rowkey來查找數(shù)據(jù),就必須逐行地比較每一列的值,即全表掃瞄。對于較大的表,全表掃描的代價(jià)是不可接受的。但是,很多情況下,需要從多個(gè)角度查詢數(shù)據(jù)。例如,在定位某個(gè)人的時(shí)候,可以通過姓名、身份證號、學(xué)籍號等不同的角度來查詢,要想把這么多角度的數(shù)據(jù)都放到rowkey中幾乎不可能(業(yè)務(wù)的靈活性不允許,對rowkey長度的要求也不允許)。所以,需要secondary index(二級索引)來完成這件事。secondary index的原理很簡單,但是如果自己維護(hù)的話則會麻煩一些。現(xiàn)在,Phoenix已經(jīng)提供了對HBase secondary index的支持。
2. Phoenix Global Indexing And Local Indexing
2.1 Global Indexing
Global indexing,全局索引,適用于讀多寫少的業(yè)務(wù)場景。使用Global indexing在寫數(shù)據(jù)的時(shí)候開銷很大,因?yàn)樗袑?shù)據(jù)表的更新操作(DELETE, UPSERT VALUES and UPSERT SELECT),都會引起索引表的更新,而索引表是分布在不同的數(shù)據(jù)節(jié)點(diǎn)上的,跨節(jié)點(diǎn)的數(shù)據(jù)傳輸帶來了較大的性能消耗。在讀數(shù)據(jù)的時(shí)候Phoenix會選擇索引表來降低查詢消耗的時(shí)間。在默認(rèn)情況下如果想查詢的字段不是索引字段的話索引表不會被使用,也就是說不會帶來查詢速度的提升。
2.2 Local Indexing
Local indexing,本地索引,適用于寫操作頻繁以及空間受限制的場景。與Global indexing一樣,Phoenix會自動(dòng)判定在進(jìn)行查詢的時(shí)候是否使用索引。使用Local indexing時(shí),索引數(shù)據(jù)和數(shù)據(jù)表的數(shù)據(jù)存放在相同的服務(wù)器中,這樣避免了在寫操作的時(shí)候往不同服務(wù)器的索引表中寫索引帶來的額外開銷。使用Local indexing的時(shí)候即使查詢的字段不是索引字段索引表也會被使用,這會帶來查詢速度的提升,這點(diǎn)跟Global indexing不同。對于Local Indexing,一個(gè)數(shù)據(jù)表的所有索引數(shù)據(jù)都存儲在一個(gè)單一的獨(dú)立的可共享的表中。
3. Immutable index And Mutable index
3.1 immutable index
immutable index,不可變索引,適用于數(shù)據(jù)只增加不更新并且按照時(shí)間先后順序存儲(time-series data)的場景,如保存日志數(shù)據(jù)或者事件數(shù)據(jù)等。不可變索引的存儲方式是write one,append only。當(dāng)在Phoenix使用create table語句時(shí)指定IMMUTABLE_ROWS = true表示該表上創(chuàng)建的索引將被設(shè)置為不可變索引。Phoenix默認(rèn)情況下如果在create table時(shí)不指定IMMUTABLE_ROW = true時(shí),表示該表為mutable。不可變索引分為Global immutable index和Local immutable index兩種。
3.2 mutable index
mutable index,可變索引,適用于數(shù)據(jù)有增刪改的場景。Phoenix默認(rèn)情況創(chuàng)建的索引都是可變索引,除非在create table的時(shí)候顯式地指定IMMUTABLE_ROWS = true??勺兯饕瑯臃譃镚lobal immutable index和Local immutable index兩種。
4.配置HBase支持Phoenix二級索引
如果要啟用phoenix的二級索引功能,需要對HMaster以及每一個(gè)RegionServer上的hbase-site.xml進(jìn)行額外的配置。首先,在每一個(gè)RegionServer的hbase-site.xml里加入如下屬性:
<property> <name>hbase.regionserver.wal.codec</name> <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value> </property><property> <name>hbase.region.server.rpc.scheduler.factory.class</name><value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value> <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description> </property><property><name>hbase.rpc.controllerfactory.class</name><value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value><description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description> </property><property><name>hbase.coprocessor.regionserver.classes</name><value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value> </property>- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
如果沒有在每個(gè)regionserver上的hbase-site.xml里面配置如上屬性,那么使用create index語句創(chuàng)建二級索引將會拋出如下異常:
Error: ERROR 1029 (42Y88): Mutable secondary indexes must have the hbase.regionserver.wal.codec property set to org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec in the hbase-sites.xml of every region server tableName=TEST_INDEXES (state=42Y88,code=1029)- 1
- 1
然后在每一個(gè)master的hbase-site.xml里加入如下屬性:
<property><name>hbase.master.loadbalancer.class</name> <value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value> </property><property><name>hbase.coprocessor.master.classes</name><value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value> </property>- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
完成上述修改后重啟hbase集群使配置生效。
【特別注意】?
如果使用的是CDH部署的HBase,需要在Cloudera Manager管理頁面里面的HBase“配置”頁面里的hbase-site.xml項(xiàng)增加上述配置,并在管理頁面里面重啟HBase才能使得配置生效。
5. 測試案例
5.1 測試案例1
HBase 1000w數(shù)據(jù)?
users_test表
【Global Indexing】
在沒有創(chuàng)建二級索引之前查詢特定USER_NAME的用戶信息耗時(shí)大約16s左右。
在USER_NAME列上面創(chuàng)建二級索引:
create index USERS_TEST_IDX0 on "users_test ("info".USER_NAME)- 1
- 1
創(chuàng)建二級索引后查詢特定USER_NAME的用戶名稱耗時(shí)為ms級別
【說明】?可以通過explain命令來查看查詢是否用到二級索引
【注意】?如果在select條件里面選擇了其他的列,如USER_NO,因?yàn)樵摿袥]有存在于索引表,因此查詢不會走索引表。
如果想在select USER_NAME,USER_NO查詢?nèi)匀蛔咚饕?#xff0c;必須創(chuàng)建如下索引:
- 方式一,采取INCLUDE(index cover,即索引覆蓋)的方式:
- 1
- 1
索引覆蓋其實(shí)就是將INCLUDE里面包含的列都存儲到索引表里面,當(dāng)檢索的時(shí)候就可以從索引表里直接帶回這些列值。要特別注意索引列和索引覆蓋列的區(qū)別,索引列在索引表里面是以rowkey的形式存在,多個(gè)索引列以某個(gè)約定的字節(jié)分割然后一起存儲在rowkey里面,也就是說當(dāng)索引列有很多個(gè)的時(shí)候,rowkey的長度也相應(yīng)會變長,大小取決于索引列值的大小。而索引覆蓋列,是存儲在索引表的列族中。
- 方式二,采取多列索引:
- 1
- 1
【說明】
多列索引在滿足前綴式的情況才會用到,如創(chuàng)建了A,B,C順序的多列索引,當(dāng)在where條件指定A條件、A B條件或者A B C條件均會走索引,但是 B C條件則無法走索引。
【Local Indexing】
在users_test表創(chuàng)建local index類型的二級索引:
create local index USERS_TEST_LOCAL_IDX ON "users_test"("info".USER_NAME)- 1
- 1
與Global Indexing不同的是,如果select子句里面帶有除了索引列(USER_NAME)以外的列,仍然可以走索引表。
【說明】
創(chuàng)建Local Indexing時(shí)候指定的索引名稱會與實(shí)際創(chuàng)建在Hbase里面的表名稱不一致,這應(yīng)該是Phoenix做了映射的關(guān)系,而且對于同一個(gè)Hbase里面的table創(chuàng)建多個(gè)Local Indexing,索引表在Hbse list命令查詢的時(shí)候也只有一個(gè)。
5.2 測試案例2
HBase 1e數(shù)據(jù)?
ammeter_test表
【Global Indexing】
create index AMMETER_TEST_IDXon AMMETER_TEST ("info"."ammeter_no1", "info"."ammeter_no2") include("info"."ammeter_price");- 1
- 2
- 1
- 2
(1) 條件查詢包含rowkey
> explain select * from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' AND "ammeter_no2" = '11000000001004' and ROW = '11000002310462' > select * from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' AND "ammeter_no2" = '11000000001004' and ROW = '11000002310462'- 1
- 2
- 1
- 2
(2) 條件查詢不包含rowkey但滿足二級索引查找條件
> explain select ROW,"ammeter_price" from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' and "ammeter_no2" = '11000000001004' > select ROW,"ammeter_price" from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' and "ammeter_no2" = '11000000001004' LIMIT 5- 1
- 2
- 1
- 2
【分析】
- 對于包含rowkey的條件查詢,Phoenix會啟用服務(wù)器端過濾器快速篩選匹配的行并返回,億級數(shù)據(jù)也能達(dá)到毫秒級別響應(yīng)。
- 對于沒有包含rowkey的條件查詢,如果條件滿足Phoenix二級索引查找,Phoenix會查二級索引表并快速返回記錄。
6. 同步創(chuàng)建索引與異步創(chuàng)建索引
前面所講的創(chuàng)建索引為同步創(chuàng)建索引,當(dāng)執(zhí)行create index的時(shí)候,索引表會直接與源數(shù)據(jù)表進(jìn)行同步。但是,有時(shí)候我們的源表數(shù)據(jù)量很大,同步創(chuàng)建索引會拋出異常。異常信息大致如下所示:
15/12/11 14:20:08 WARN client.ScannerCallable: Ignore, probably already closed org.apache.hadoop.hbase.UnknownScannerException: org.apache.hadoop.hbase.UnknownScannerException: Name: 37, already closed? at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2092) at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:31443) at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2035) at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:107) at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130) at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107) at java.lang.Thread.run(Thread.java:745) at sun.reflect.GeneratedConstructorAccessor13.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at org.apache.hadoop.ipc.RemoteException.instantiateException(RemoteException.java:106) at org.apache.hadoop.ipc.RemoteException.unwrapRemoteException(RemoteException.java:95) at org.apache.hadoop.hbase.protobuf.ProtobufUtil.getRemoteException(ProtobufUtil.java:313) at org.apache.hadoop.hbase.client.ScannerCallable.close(ScannerCallable.java:329) at org.apache.hadoop.hbase.client.ScannerCallable.call(ScannerCallable.java:184) at org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:136) at org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:56) at org.apache.hadoop.hbase.client.RpcRetryingCaller.callWithoutRetries(RpcRetryingCaller.java:200) at org.apache.hadoop.hbase.client.ClientScanner.call(ClientScanner.java:288) at org.apache.hadoop.hbase.client.ClientScanner.close(ClientScanner.java:507) at org.apache.phoenix.iterate.ScanningResultIterator.close(ScanningResultIterator.java:49) at org.apache.phoenix.iterate.TableResultIterator.close(TableResultIterator.java:95) at org.apache.phoenix.jdbc.PhoenixResultSet.close(PhoenixResultSet.java:162) at org.apache.phoenix.compile.UpsertCompiler.upsertSelect(UpsertCompiler.java:199) at org.apache.phoenix.compile.UpsertCompiler.access$000(UpsertCompiler.java:114) at org.apache.phoenix.compile.UpsertCompiler$UpsertingParallelIteratorFactory.mutate(UpsertCompiler.java:229) at org.apache.phoenix.compile.MutatingParallelIteratorFactory.newIterator(MutatingParallelIteratorFactory.java:62) at org.apache.phoenix.iterate.ParallelIterators$1.call(ParallelIterators.java:109) at org.apache.phoenix.iterate.ParallelIterators$1.call(ParallelIterators.java:100) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at org.apache.phoenix.job.JobManager$InstrumentedJobFutureTask.run(JobManager.java:183) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745) Caused by: org.apache.hadoop.hbase.ipc.RemoteWithExtrasException(org.apache.hadoop.hbase.UnknownScannerException): org.apache.hadoop.hbase.UnknownScannerException: Name: 37, already closed? at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2092) at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:31443) at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2035) at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:107) at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130) at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107) at java.lang.Thread.run(Thread.java:745) at org.apache.hadoop.hbase.ipc.RpcClientImpl.call(RpcClientImpl.java:1199) at org.apache.hadoop.hbase.ipc.AbstractRpcClient.callBlockingMethod(AbstractRpcClient.java:216) at org.apache.hadoop.hbase.ipc.AbstractRpcClient$BlockingRpcChannelImplementation.callBlockingMethod(AbstractRpcClient.java:300) at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$BlockingStub.scan(ClientProtos.java:31889) at org.apache.hadoop.hbase.client.ScannerCallable.close(ScannerCallable.java:327) ... 20 more- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
這個(gè)時(shí)候,我們可以采用異步創(chuàng)建索引,方式如下:
CREATE INDEX async_index ON my_schema.my_table (v) ASYNC- 1
- 1
通過create index的時(shí)候指定?ASYNC?關(guān)鍵字來指定異步創(chuàng)建索引。執(zhí)行這個(gè)命令之后并不會引起索引表與源表的直接同步。這個(gè)時(shí)候查詢并不會使用這個(gè)索引表。那么索引數(shù)據(jù)的導(dǎo)入還需要采用phoenix提供的索引同步工具類?IndexTool?, 這是一個(gè)mapreduce工具類,使用方式如下:
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool--schema MY_SCHEMA --data-table MY_TABLE --index-table ASYNC_IDX--output-path ASYNC_IDX_HFILES- 1
- 2
- 3
- 1
- 2
- 3
當(dāng)mapreduce任務(wù)執(zhí)行結(jié)束,這個(gè)索引表才會變成可用。
7. 參考
(1)?可變索引與不可變索引
(2)?Globaling Indexing
(3)?Local Indexing
(4)?配置二級索引及測試
(5)?Phoenix官網(wǎng)文檔
總結(jié)
以上是生活随笔為你收集整理的HBase phoenix二级索引的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hbase总结(八)Hbase中的Cop
- 下一篇: HBase总结(九)Bloom Filt