Hadoop之Hbase從入門到精通_第1頁
Hadoop之Hbase從入門到精通_第2頁
Hadoop之Hbase從入門到精通_第3頁
Hadoop之Hbase從入門到精通_第4頁
Hadoop之Hbase從入門到精通_第5頁
已閱讀5頁,還剩64頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

./HBase性能調優(yōu)我們經常看到一些文章吹噓某產品如何如何快,如何如何強,而自己測試時卻不如描述的一些數(shù)據(jù)。其實原因可能在于你還不是真正理解其內部結構,對于其性能調優(yōu)方法不夠了解。本文轉自TaoBao的KenWu同學的博客,是目前看到比較完整的HBase調優(yōu)文章。原文鏈接:HBase性能調優(yōu)因官方BookPerformanceTuning部分章節(jié)沒有按配置項進行索引,不能達到快速查閱的效果。所以我以配置項驅動,重新整理了原文,并補充一些自己的理解,如有錯誤,歡迎指正。配置優(yōu)化默認值:3分鐘〔180000ms說明:RegionServer與Zookeeper間的連接超時時間。當超時時間到后,ReigonServer會被Zookeeper從RS集群清單中移除,HMaster收到移除通知后,會對這臺server負責的regions重新balance,讓其他存活的RegionServer接管.調優(yōu):這個timeout決定了RegionServer是否能夠及時的failover。設置成1分鐘或更低,可以減少因等待超時而被延長的failover時間。不過需要注意的是,對于一些Online應用,RegionServer從宕機到恢復時間本身就很短的〔網絡閃斷,crash等故障,運維可快速介入,如果調低timeout時間,反而會得不償失。因為當ReigonServer被正式從RS集群中移除時,HMaster就開始做balance了〔讓其他RS根據(jù)故障機器記錄的WAL日志進行恢復。當故障的RS在人工介入恢復后,這個balance動作是毫無意義的,反而會使負載不均勻,給RS帶來更多負擔。特別是那些固定分配regions的場景。hbase默認值:10說明:RegionServer的請求處理IO線程數(shù)。調優(yōu):這個參數(shù)的調優(yōu)與內存息息相關。較少的IO線程,適用于處理單次請求內存消耗較高的BigPUT場景〔大容量單次PUT或設置了較大cache的scan,均屬于BigPUT或ReigonServer的內存比較緊張的場景。

較多的IO線程,適用于單次請求內存消耗低,TPS要求非常高的場景。設置該值的時候,以監(jiān)控內存為主要參考。這里需要注意的是如果server的region數(shù)量很少,大量的請求都落在一個region上,因快速充滿memstore觸發(fā)flush導致的讀寫鎖會影響全局TPS,不是IO線程數(shù)越高越好。壓測時,開啟\o"EnablingRPC-levellogging"EnablingRPC-levellogging,可以同時監(jiān)控每次請求的內存消耗和GC的狀況,最后通過多次壓測結果來合理調節(jié)IO線程數(shù)。這里是一個案例HadoopandHBaseOptimizationforReadIntensiveSearchApplications,作者在SSD的機器上設置IO線程數(shù)為100,僅供參考。默認值:256M說明:在當前ReigonServer上單個Reigon的最大存儲空間,單個Region超過該值時,這個Region會被自動split成更小的region。調優(yōu):小region對split和compaction友好,因為拆分region或compact小region里的storefile速度很快,內存占用低。缺點是split和compaction會很頻繁。特別是數(shù)量較多的小region不停地split,compaction,會導致集群響應時間波動很大,region數(shù)量太多不僅給管理上帶來麻煩,甚至會引發(fā)一些Hbase的bug。

一般512以下的都算小region。大region,則不太適合經常split和compaction,因為做一次compact和split會產生較長時間的停頓,對應用的讀寫性能沖擊非常大。此外,大region意味著較大的storefile,compaction時對內存也是一個挑戰(zhàn)。

當然,大region也有其用武之地。如果你的應用場景中,某個時間點的訪問量較低,那么在此時做compact和split,既能順利完成split和compaction,又能保證絕大多數(shù)時間平穩(wěn)的讀寫性能。既然split和compaction如此影響性能,有沒有辦法去掉?

compaction是無法避免的,split倒是可以從自動調整為手動。

只要通過將這個參數(shù)值調大到某個很難達到的值,比如100G,就可以間接禁用自動split〔RegionServer不會對未到達100G的region做split。

再配合RegionSplitter這個工具,在需要split時,手動split。

手動split在靈活性和穩(wěn)定性上比起自動split要高很多,相反,管理成本增加不多,比較推薦online實時系統(tǒng)使用。內存方面,小region在設置memstore的大小值上比較靈活,大region則過大過小都不行,過大會導致flush時app的IOwait增高,過小則因storefile過多影響讀性能。默認值:0.4/0.35upperlimit說明:hbase.hregion.memstore.flush.size這個參數(shù)的作用是當單個memstore達到指定值時,flush該memstore。但是,一臺ReigonServer可能有成百上千個memstore,每個memstore也許未達到flush.size,jvm的heap就不夠用了。該參數(shù)就是為了限制memstores占用的總內存。

當ReigonServer內所有的memstore所占用的內存總和達到heap的40%時,HBase會強制block所有的更新并flush這些memstore以釋放所有memstore占用的內存。lowerLimit說明:同upperLimit,只不過當全局memstore的內存達到35%時,它不會flush所有的memstore,它會找一些內存占用較大的memstore,做個別flush,當然更新還是會被block。lowerLimit算是一個在全局flush導致性能暴跌前的補救措施。為什么說是性能暴跌?可以想象一下,如果memstore需要在一段較長的時間內做全量flush,且這段時間內無法接受任何讀寫請求,對HBase集群的性能影響是很大的。調優(yōu):這是一個Heap內存保護參數(shù),默認值已經能適用大多數(shù)場景。它的調整一般是為了配合某些專屬優(yōu)化,比如讀密集型應用,將讀緩存開大,降低該值,騰出更多內存給其他模塊使用。

這個參數(shù)會給使用者帶來什么影響?比如,10G內存,100個region,每個memstore64M,假設每個region只有一個memstore,那么當100個memstore平均占用到50%左右時,就會達到lowerLimit的限制。假設此時,其他memstore同樣有很多的寫請求進來。在那些大的region未flush完,就可能又超過了upperlimit,則所有region都會被block,開始觸發(fā)全局flush。不過,除了你的內存非常小或你的應用場景里大多數(shù)都是讀,我覺得不需要去調這個參數(shù)。默認值:0.2說明:storefile的讀緩存占用Heap的大小百分比,0.2表示20%。該值直接影響數(shù)據(jù)讀的性能。調優(yōu):當然是越大越好,如果讀比寫少,開到0.4-0.5也沒問題。如果讀寫較均衡,0.3左右。如果寫比讀多,果斷默認吧。設置這個值的時候,你同時要參考hbase.regionserver.global.memstore.upperLimit,該值是memstore占heap的最大百分比,兩個參數(shù)一個影響讀,一個影響寫。如果兩值加起來超過80-90%,會有OOM的風險,謹慎設置。默認值:7說明:在compaction時,如果一個Store〔CoulmnFamily內有超過7個storefile需要合并,則block所有的寫請求,進行flush,限制storefile數(shù)量增長過快。調優(yōu):block寫請求會影響當前region的性能,將值設為單個region可以支撐的最大storefile數(shù)量會是個不錯的選擇,即允許comapction時,memstore繼續(xù)生成storefile。最大storefile數(shù)量可通過regionsize/memstoresize來計算。如果你將regionsize設為無限大,那么你需要預估一個region可能產生的最大storefile數(shù)。默認值:2說明:當一個region里的memstore超過單個memstore.size兩倍的大小時,block該region的所有請求,進行flush,釋放內存。雖然我們設置了memstore的總大小,比如64M,但想象一下,在最后63.9M的時候,我Put了一個100M的數(shù)據(jù),此時memstore的大小會瞬間暴漲到超過預期的memstore.size。這個參數(shù)的作用是當memstore的大小增至超過memstore.size時,block所有請求,遏制風險進一步擴大。調優(yōu):這個參數(shù)的默認值還是比較靠譜的。如果你預估你的正常應用場景〔不包括異常不會出現(xiàn)突發(fā)寫或寫的量可控,那么保持默認值即可。如果正常情況下,你的寫請求量就會經常暴長到正常的幾倍,那么你應該調大這個倍數(shù)并調整其他參數(shù)值,比如hfile.block.cache.size和hbase.regionserver.global.memstore.upperLimit/lowerLimit,以預留更多內存,防止HBaseserverOOM。其他啟用LZO壓縮LZO對比Hbase默認的GZip,前者性能較高,后者壓縮比較高,具體參見UsingLZOCompression。對于想提高HBase讀寫性能的開發(fā)者,采用LZO是比較好的選擇。對于非常在乎存儲空間的開發(fā)者,則建議保持默認。不要在一張表里定義太多的ColumnFamilyHbase目前不能良好的處理超過包含2-3個CF的表。因為某個CF在flush發(fā)生時,它鄰近的CF也會因關聯(lián)效應被觸發(fā)flush,最終導致系統(tǒng)產生更多IO。批量導入在批量導入數(shù)據(jù)到Hbase前,你可以通過預先創(chuàng)建regions,來平衡數(shù)據(jù)的負載。詳見TableCreation:Pre-CreatingRegions避免CMSconcurrentmodefailureHBase使用CMSGC。默認觸發(fā)GC的時機是當年老代內存達到90%的時候,這個百分比由-XX:CMSInitiatingOccupancyFraction=N這個參數(shù)來設置。concurrentmodefailed發(fā)生在這樣一個場景:當年老代內存達到90%的時候,CMS開始進行并發(fā)垃圾收集,于此同時,新生代還在迅速不斷地晉升對象到年老代。當年老代CMS還未完成并發(fā)標記時,年老代滿了,悲劇就發(fā)生了。CMS因為沒內存可用不得不暫停mark,并觸發(fā)一次全jvm的stoptheworld〔掛起所有線程,然后采用單線程拷貝方式清理所有垃圾對象。這個過程會非常漫長。為了避免出現(xiàn)concurrentmodefailed,我們應該讓GC在未到90%時,就觸發(fā)。通過設置-XX:CMSInitiatingOccupancyFraction=N這個百分比,可以簡單的這么計算。如果你的hfile.block.cache.size和hbase.regionserver.global.memstore.upperLimit加起來有60%〔默認,那么你可以設置70-80,一般高10%左右差不多。Hbase客戶端優(yōu)化AutoFlush將HTable的setAutoFlush設為false,可以支持客戶端批量更新。即當Put填滿客戶端flush緩存時,才發(fā)送到服務端。默認是true。ScanCachingscanner一次緩存多少數(shù)據(jù)來scan〔從服務端一次抓多少數(shù)據(jù)回來scan。

默認值是1,一次只取一條。ScanAttributeSelectionscan時建議指定需要的ColumnFamily,減少通信量,否則scan操作默認會返回整個row的所有數(shù)據(jù)〔所有CoulmnFamily。CloseResultScanners通過scan取完數(shù)據(jù)后,記得要關閉ResultScanner,否則RegionServer可能會出現(xiàn)問題〔對應的Server資源無法釋放。OptimalLoadingofRowKeys當你scan一張表的時候,返回結果只需要rowkey〔不需要CF,qualifier,values,timestaps時,你可以在scan實例中添加一個filterList,并設置MUST_PASS_ALL操作,filterList中addFirstKeyOnlyFilter或KeyOnlyFilter。這樣可以減少網絡通信量。TurnoffWALonPuts當Put某些非重要數(shù)據(jù)時,你可以設置writeToWAL<false>,來進一步提高寫性能。writeToWAL<false>會在Put時放棄寫WALlog。風險是,當RegionServer宕機時,可能你剛才Put的那些數(shù)據(jù)會丟失,且無法恢復。啟用BloomFilterBloomFilter通過空間換時間,提高讀操作性能關于HFile的思考本文是一篇轉載文章,原文作者郭鵬〔@逖靖寒,國內Cassandra領域的先驅者和實踐者。資深軟件開發(fā)工程師,擅長分布式應用程序的開發(fā)和使用,實踐經驗極其豐富。在本文中,作者推薦了HFile文件格式的經典論文,并對HFile的blocksize的應用進行了實例探討。0.90.x版本的HBase中的文件是存儲在HFile中的。關于HFile文件的詳細介紹,可以查看這篇文章:hfile.pdf這篇文章中介紹了以下五點內容:HFile的作用。HFile的格式。HFile的性能。HFile的使用注意事項。HFile的編程接口。HFile中有一個很重要的參數(shù),那就是blocksize。如果我們寫入hfile中的某一個value的值大于blocksize會怎么樣?于是有如下的測試代碼://createlocalfilesystemFileSystemfs=newRawLocalFileSystem<>;fs.setConf<newConfiguration<>>;//blocksize=1kbHFile.Writerhwriter=newHFile.Writer<fs,newPath<"hfile">,1,<Compression.Algorithm>null,null>;//createkey&value,thevalueis8kb,largerthan1kbbyte[]key="".getBytes<>;byte[]value=newbyte[8*1024];for<inti=0;i<8*1024;i++>{value[i]='0';}//addvaluestohfilefor<inti=0;i<10;i++>{hwriter.append<key,value>;}//closehfilehwriter.close<>;上面的代碼可以看出來,每一個value的值都是8kb,已經超過了hfile預設的1kb的blocksize。實際的寫入情況是如果value大于blocksize,那么就按照實際的情況來寫。上面的測試用例執(zhí)行完畢以后,整個hile文件只有1個datablock。這個hfile的讀取代碼如下://createlocalfilesystemFileSystemfs=newRawLocalFileSystem<>;fs.initialize<URI.create<"file:///">,newConfiguration<>>;fs.setConf<newConfiguration<>>;HFile.Readerhreader=newHFile.Reader<fs,newPath<"hfile">,null,false>;//loadFileInfohreader.loadFileInfo<>;HFileScannerhscanner=hreader.getScanner<false,false>;//seektothestartpositionofthehfile.hscanner.seekTo<>;//printindex=1;while<hscanner.next<>>{System.out.println<"index:"+index++>;System.out.println<"key:"+hscanner.getKeyString<>>;System.out.println<"value:"+hscanner.getValueString<>>;}//closehfile.hreader.close<>;上面的代碼將讀取hfile,并將這個文件中的所有kv打印出來。通過上面的測試可以看出:如果某一個key有非常非常多的value,那么查找這些value就無法通過索引去快速查找,而是需要通過遍歷進行。另外,JIRA上面的HBASE-3857也提出了一種新的HFile格式,HFilev2他主要是針對現(xiàn)有HFile的兩個主要缺陷提出來的:暫用過多內存啟動加載時間緩慢有興趣的朋友可以詳細了解一下。HBase性能優(yōu)化方法總結1.表的設計1.1Pre-CreatingRegions默認情況下,在創(chuàng)建HBase表的時候會自動創(chuàng)建一個region分區(qū),當導入數(shù)據(jù)的時候,所有的HBase客戶端都向這一個region寫數(shù)據(jù),直到這個region足夠大了才進行切分。一種可以加快批量寫入速度的方法是通過預先創(chuàng)建一些空的regions,這樣當數(shù)據(jù)寫入HBase時,會按照region分區(qū)情況,在集群內做數(shù)據(jù)的負載均衡。有關預分區(qū),詳情參見:TableCreation:Pre-CreatingRegions,下面是一個例子:1234567891011121314151617181920212223242526publicstaticbooleancreateTable<HBaseAdminadmin,HTableDescriptortable,byte[][]splits>throwsIOException{

try{

admin.createTable<table,splits>;

returntrue;

}catch<TableExistsExceptione>{

<"table"+table.getNameAsString<>+"alreadyexists">;

//thetablealreadyexists...

returnfalse;

}}

publicstaticbyte[][]getHexSplits<StringstartKey,StringendKey,intnumRegions>{

byte[][]splits=newbyte[numRegions-1][];

BigIntegerlowestKey=newBigInteger<startKey,16>;

BigIntegerhighestKey=newBigInteger<endKey,16>;

BigIntegerrange=highestKey.subtract<lowestKey>;

BigIntegerregionIncrement=range.divide<BigInteger.valueOf<numRegions>>;

lowestKey=lowestKey.add<regionIncrement>;

for<inti=0;i<numRegions-1;i++>{

BigIntegerkey=lowestKey.add<regionIncrement.multiply<BigInteger.valueOf<i>>>;

byte[]b=String.format<"%016x",key>.getBytes<>;

splits[i]=b;

}

returnsplits;}1.2RowKeyHBase中rowkey用來檢索表中的記錄,支持以下三種方式:通過單個rowkey訪問:即按照某個rowkey鍵值進行get操作;通過rowkey的range進行scan:即通過設置startRowKey和endRowKey,在這個范圍內進行掃描;全表掃描:即直接掃描整張表中所有行記錄。在HBase中,rowkey可以是任意字符串,最大長度64KB,實際應用中一般為10~100bytes,存為byte[]字節(jié)數(shù)組,一般設計成定長的。rowkey是按照字典序存儲,因此,設計rowkey時,要充分利用這個排序特點,將經常一起讀取的數(shù)據(jù)存儲到一塊,將最近可能會被訪問的數(shù)據(jù)放在一塊。舉個例子:如果最近寫入HBase表中的數(shù)據(jù)是最可能被訪問的,可以考慮將時間戳作為rowkey的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE–timestamp作為rowkey,這樣能保證新寫入的數(shù)據(jù)在讀取時可以被快速命中。1.3ColumnFamily不要在一張表里定義太多的columnfamily。目前Hbase并不能很好的處理超過2~3個columnfamily的表。因為某個columnfamily在flush的時候,它鄰近的columnfamily也會因關聯(lián)效應被觸發(fā)flush,最終導致系統(tǒng)產生更多的I/O。感興趣的同學可以對自己的HBase集群進行實際測試,從得到的測試結果數(shù)據(jù)驗證一下。1.4InMemory創(chuàng)建表的時候,可以通過HColumnDescriptor.setInMemory<true>將表放到RegionServer的緩存中,保證在讀取的時候被cache命中。1.5MaxVersion創(chuàng)建表的時候,可以通過HColumnDescriptor.setMaxVersions<intmaxVersions>設置表中數(shù)據(jù)的最大版本,如果只需要保存最新版本的數(shù)據(jù),那么可以設置setMaxVersions<1>。1.6TimeToLive創(chuàng)建表的時候,可以通過HColumnDescriptor.setTimeToLive<inttimeToLive>設置表中數(shù)據(jù)的存儲生命期,過期數(shù)據(jù)將自動被刪除,例如如果只需要存儲最近兩天的數(shù)據(jù),那么可以設置setTimeToLive<2*24*60*60>。1.7Compact&Split在HBase中,數(shù)據(jù)在更新時首先寫入WAL日志<HLog>和內存<MemStore>中,MemStore中的數(shù)據(jù)是排序的,當MemStore累計到一定閾值時,就會創(chuàng)建一個新的MemStore,并且將老的MemStore添加到flush隊列,由單獨的線程flush到磁盤上,成為一個StoreFile。于此同時,系統(tǒng)會在zookeeper中記錄一個redopoint,表示這個時刻之前的變更已經持久化了<minorcompact>。StoreFile是只讀的,一旦創(chuàng)建后就不可以再修改。因此Hbase的更新其實是不斷追加的操作。當一個Store中的StoreFile達到一定的閾值后,就會進行一次合并<majorcompact>,將對同一個key的修改合并到一起,形成一個大的StoreFile,當StoreFile的大小達到一定閾值后,又會對StoreFile進行分割<split>,等分為兩個StoreFile。由于對表的更新是不斷追加的,處理讀請求時,需要訪問Store中全部的StoreFile和MemStore,將它們按照rowkey進行合并,由于StoreFile和MemStore都是經過排序的,并且StoreFile帶有內存中索引,通常合并過程還是比較快的。實際應用中,可以考慮必要時手動進行majorcompact,將同一個rowkey的修改進行合并形成一個大的StoreFile。同時,可以將StoreFile設置大些,減少split的發(fā)生。2.寫表操作2.1多HTable并發(fā)寫創(chuàng)建多個HTable客戶端用于寫操作,提高寫數(shù)據(jù)的吞吐量,一個例子:12345678staticfinalConfigurationconf=HBaseConfiguration.create<>;staticfinalStringtable_log_name="user_log";wTableLog=newHTable[tableN];for<inti=0;i<tableN;i++>{

wTableLog[i]=newHTable<conf,table_log_name>;

wTableLog[i].setWriteBufferSize<5*1024*1024>;//5MB

wTableLog[i].setAutoFlush<false>;}2.2HTable參數(shù)設置2.2.1AutoFlush通過調用HTable.setAutoFlush<false>方法可以將HTable寫客戶端的自動flush關閉,這樣可以批量寫入數(shù)據(jù)到HBase,而不是有一條put就執(zhí)行一次更新,只有當put填滿客戶端寫緩存時,才實際向HBase服務端發(fā)起寫請求。默認情況下autoflush是開啟的。WriteBuffer通過調用HTable.setWriteBufferSize<writeBufferSize>方法可以設置HTable客戶端的寫buffer大小,如果新設置的buffer小于當前寫buffer中的數(shù)據(jù)時,buffer將會被flush到服務端。其中,writeBufferSize的單位是byte字節(jié)數(shù),可以根據(jù)實際寫入數(shù)據(jù)量的多少來設置該值。WALFlag在HBae中,客戶端向集群中的RegionServer提交數(shù)據(jù)時〔Put/Delete操作,首先會先寫WAL〔WriteAheadLog日志〔即HLog,一個RegionServer上的所有Region共享一個HLog,只有當WAL日志寫成功后,再接著寫MemStore,然后客戶端被通知提交數(shù)據(jù)成功;如果寫WAL日志失敗,客戶端則被通知提交失敗。這樣做的好處是可以做到RegionServer宕機后的數(shù)據(jù)恢復。因此,對于相對不太重要的數(shù)據(jù),可以在Put/Delete操作時,通過調用Put.setWriteToWAL<false>或Delete.setWriteToWAL<false>函數(shù),放棄寫WAL日志,從而提高數(shù)據(jù)寫入的性能。值得注意的是:謹慎選擇關閉WAL日志,因為這樣的話,一旦RegionServer宕機,Put/Delete的數(shù)據(jù)將會無法根據(jù)WAL日志進行恢復。2.3批量寫通過調用HTable.put<Put>方法可以將一個指定的rowkey記錄寫入HBase,同樣HBase提供了另一個方法:通過調用HTable.put<List<Put>>方法可以將指定的rowkey列表,批量寫入多行記錄,這樣做的好處是批量執(zhí)行,只需要一次網絡I/O開銷,這對于對數(shù)據(jù)實時性要求高,網絡傳輸RTT高的情景下可能帶來明顯的性能提升。2.4多線程并發(fā)寫在客戶端開啟多個HTable寫線程,每個寫線程負責一個HTable對象的flush操作,這樣結合定時flush和寫buffer〔writeBufferSize,可以既保證在數(shù)據(jù)量小的時候,數(shù)據(jù)可以在較短時間內被flush〔如1秒內,同時又保證在數(shù)據(jù)量大的時候,寫buffer一滿就及時進行flush。下面給個具體的例子:12345678910111213141516171819202122for<inti=0;i<threadN;i++>{

Threadth=newThread<>{

publicvoidrun<>{

while<true>{

try{

sleep<1000>;//1second

}catch<InterruptedExceptione>{

e.printStackTrace<>;

}

synchronized<wTableLog[i]>{

try{

wTableLog[i].flushCommits<>;

}catch<IOExceptione>{

e.printStackTrace<>;

}

}

}

}

};

th.setDaemon<true>;

th.start<>;}3.讀表操作3.1多HTable并發(fā)讀創(chuàng)建多個HTable客戶端用于讀操作,提高讀數(shù)據(jù)的吞吐量,一個例子:1234567staticfinalConfigurationconf=HBaseConfiguration.create<>;staticfinalStringtable_log_name="user_log";rTableLog=newHTable[tableN];for<inti=0;i<tableN;i++>{

rTableLog[i]=newHTable<conf,table_log_name>;

rTableLog[i].setScannerCaching<50>;}3.2HTable參數(shù)設置ScannerCaching通過調用HTable.setScannerCaching<intscannerCaching>可以設置HBasescanner一次從服務端抓取的數(shù)據(jù)條數(shù),默認情況下一次一條。通過將此值設置成一個合理的值,可以減少scan過程中next<>的時間開銷,代價是scanner需要通過客戶端的內存來維持這些被cache的行記錄。ScanAttributeSelectionscan時指定需要的ColumnFamily,可以減少網絡傳輸數(shù)據(jù)量,否則默認scan操作會返回整行所有ColumnFamily的數(shù)據(jù)。CloseResultScanner通過scan取完數(shù)據(jù)后,記得要關閉ResultScanner,否則RegionServer可能會出現(xiàn)問題〔對應的Server資源無法釋放。3.3批量讀通過調用HTable.get<Get>方法可以根據(jù)一個指定的rowkey獲取一行記錄,同樣HBase提供了另一個方法:通過調用HTable.get<List>方法可以根據(jù)一個指定的rowkey列表,批量獲取多行記錄,這樣做的好處是批量執(zhí)行,只需要一次網絡I/O開銷,這對于對數(shù)據(jù)實時性要求高而且網絡傳輸RTT高的情景下可能帶來明顯的性能提升。3.4多線程并發(fā)讀在客戶端開啟多個HTable讀線程,每個讀線程負責通過HTable對象進行get操作。下面是一個多線程并發(fā)讀取HBase,獲取店鋪一天內各分鐘PV值的例子:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146publicclassDataReaderServer{

//獲取店鋪一天內各分鐘PV值的入口函數(shù)

publicstaticConcurrentHashMapgetUnitMinutePV<longuid,longstartStamp,longendStamp>{

longmin=startStamp;

intcount=<int><<endStamp-startStamp>/<60*1000>>;

Listlst=newArrayList<>;

for<inti=0;i<=count;i++>{

min=startStamp+i*60*1000;

lst.add<uid+"_"+min>;

}

returnparallelBatchMinutePV<lst>;

}

//多線程并發(fā)查詢,獲取分鐘PV值privatestaticConcurrentHashMapparallelBatchMinutePV<ListlstKeys>{

ConcurrentHashMaphashRet=newConcurrentHashMap<>;

intparallel=3;

List<List<String>>lstBatchKeys

=null;

if<lstKeys.size<><parallel>{

lstBatchKeys

=newArrayList<List<String>><1>;

lstBatchKeys.add<lstKeys>;

}

else{

lstBatchKeys

=newArrayList<List<String>><parallel>;

for<inti=0;i<parallel;i++

>{

Listlst=newArrayList<>;

lstBatchKeys.add<lst>;

}

for<inti=0;i<lstKeys.size<>;i++>{

lstBatchKeys.get<i%parallel>.add<lstKeys.get<i>>;

}

}

List>>futures=newArrayList>><5>;

ThreadFactoryBuilderbuilder=newThreadFactoryBuilder<>;

builder.setNameFormat<"ParallelBatchQuery">;

ThreadFactoryfactory=builder.build<>;

ThreadPoolExecutorexecutor=<ThreadPoolExecutor>Executors.newFixedThreadPool<lstBatchKeys.size<>,factory>;

for<Listkeys:lstBatchKeys>{

Callable<ConcurrentHashMap>callable=newBatchMinutePVCallable<keys>;

FutureTask<ConcurrentHashMap>future=<FutureTask<ConcurrentHashMap>>executor.submit<callable>;

futures.add<future>;

}

executor.shutdown<>;

//Waitforallthetaskstofinish

try{

booleanstillRunning=!executor.awaitTermination<

5000000,TimeUnit.MILLISECONDS>;

if<stillRunning>{

try{

executor.shutdownNow<>;

}catch<Exceptione>{

//TODOAuto-generatedcatchblock

e.printStackTrace<>;

}

}

}catch<InterruptedExceptione>{

try{

Thread.currentThread<>.interrupt<>;

}catch<Exceptione1>{

//TODOAuto-generatedcatchblock

e1.printStackTrace<>;

}

}

//Lookforanyexception

for<Futuref:futures>{

try{

if<f.get<>!=null>

{

hashRet.putAll<<ConcurrentHashMap>f.get<>>;

}

}catch<InterruptedExceptione>{

try{

Thread.currentThread<>.interrupt<>;

}catch<Exceptione1>{

//TODOAuto-generatedcatchblock

e1.printStackTrace<>;

}

}catch<ExecutionExceptione>{

e.printStackTrace<>;

}

}

returnhashRet;

}

//一個線程批量查詢,獲取分鐘PV值

protectedstaticConcurrentHashMapgetBatchMinutePV<ListlstKeys>{

ConcurrentHashMaphashRet=null;

ListlstGet=newArrayList<>;

String[]splitValue=null;

for<Strings:lstKeys>{

splitValue=s.split<"_">;

longuid=Long.parseLong<splitValue[0]>;

longmin=Long.parseLong<splitValue[1]>;

byte[]key=newbyte[16];

Bytes.putLong<key,0,uid>;

Bytes.putLong<key,8,min>;

Getg=newGet<key>;

g.addFamily<fp>;

lstGet.add<g>;

}

Result[]res=null;

try{

res=tableMinutePV[rand.nextInt<tableN>].get<lstGet>;

}catch<IOExceptione1>{

logger.error<"tableMinutePVexception,e="+e1.getStackTrace<>>;

}

if<res!=null&&res.length>0>{

hashRet=newConcurrentHashMap<res.length>;

for<Resultre:res>{

if<re!=null&&!re.isEmpty<>>{

try{

byte[]key=re.getRow<>;

byte[]value=re.getValue<fp,cp>;

if<key!=null&&value!=null>{

hashRet.put<String.valueOf<Bytes.toLong<key,

Bytes.SIZEOF_LONG>>,String.valueOf<Bytes

.toLong<value>>>;

}

}catch<Exceptione2>{

logger.error<e2.getStackTrace<>>;

}

}

}

}

returnhashRet;

}}//調用接口類,實現(xiàn)Callable接口classBatchMinutePVCallableimplementsCallable>{

privateListkeys;

publicBatchMinutePVCallable<ListlstKeys>{

this.keys=lstKeys;

}

publicConcurrentHashMapcall<>throwsException{

returnDataReadServer.getBatchMinutePV<keys>;

}}3.5緩存查詢結果對于頻繁查詢HBase的應用場景,可以考慮在應用程序中做緩存,當有新的查詢請求時,首先在緩存中查找,如果存在則直接返回,不再查詢HBase;否則對HBase發(fā)起讀請求查詢,然后在應用程序中將查詢結果緩存起來。至于緩存的替換策略,可以考慮LRU等常用的策略。3.6BlockcacheHBase上Regionserver的內存分為兩個部分,一部分作為Memstore,主要用來寫;另外一部分作為BlockCache,主要用于讀。寫請求會先寫入Memstore,Regionserver會給每個region提供一個Memstore,當Memstore滿64MB以后,會啟動flush刷新到磁盤。當Memstore的總大小超過限制時〔heapsize*hbase.regionserver.global.memstore.upperLimit*0.9,會強行啟動flush進程,從最大的Memstore開始flush直到低于限制。讀請求先到Memstore中查數(shù)據(jù),查不到就到BlockCache中查,再查不到就會到磁盤上讀,并把讀的結果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache達到上限<heapsize*hfile.block.cache.size*0.85>后,會啟動淘汰機制,淘汰掉最老的一批數(shù)據(jù)。一個Regionserver上有一個BlockCache和N個Memstore,它們的大小之和不能大于等于heapsize*0.8,否則HBase不能啟動。默認BlockCache為0.2,而Memstore為0.4。對于注重讀響應時間的系統(tǒng),可以將BlockCache設大些,比如設置BlockCache=0.4,Memstore=0.39,以加大緩存的命中率。有關BlockCache機制,請參考這里:HBase的Blockcache,HBase的blockcache機制,hbase中的緩存的計算與使用。4.數(shù)據(jù)計算4.1服務端計算Coprocessor運行于HBaseRegionServer服務端,各個Regions保持對與其相關的coprocessor實現(xiàn)類的引用,coprocessor類可以通過RegionServer上classpath中的本地jar或HDFS的classloader進行加載。目前,已提供有幾種coprocessor:Coprocessor:提供對于region管理的鉤子,例如region的open/close/split/flush/compact等;

RegionObserver:提供用于從客戶端監(jiān)控表相關操作的鉤子,例如表的get/put/scan/delete等;

Endpoint:提供可以在region上執(zhí)行任意函數(shù)的命令觸發(fā)器。一個使用例子是RegionServer端的列聚合,這里有代碼示例。

以上只是有關coprocessor的一些基本介紹,本人沒有對其實際使用的經驗,對它的可用性和性能數(shù)據(jù)不得而知。感興趣的同學可以嘗試一下,歡迎討論。4.2寫端計算計數(shù)HBase本身可以看作是一個可以水平擴展的Key-Value存儲系統(tǒng),但是其本身的計算能力有限〔Coprocessor可以提供一定的服務端計算,因此,使用HBase時,往往需要從寫端或者讀端進行計算,然后將最終的計算結果返回給調用者。舉兩個簡單的例子:PV計算:通過在HBase寫端內存中,累加計數(shù),維護PV值的更新,同時為了做到持久化,定期〔如1秒將PV計算結果同步到HBase中,這樣查詢端最多會有1秒鐘的延遲,能看到秒級延遲的PV結果。

分鐘PV計算:與上面提到的PV計算方法相結合,每分鐘將當前的累計PV值,按照rowkey+minute作為新的rowkey寫入HBase中,然后在查詢端通過scan得到當天各個分鐘以前的累計PV值,然后順次將前后兩分鐘的累計PV值相減,就得到了當前一分鐘內的PV值,從而最終也就得到當天各個分鐘內的PV值。去重對于UV的計算,就是個去重計算的例子。分兩種情況:如果內存可以容納,那么可以在Hash表中維護所有已經存在的UV標識,每當新來一個標識時,通過快速查找Hash確定是否是一個新的UV,若是則UV值加1,否則UV值不變。另外,為了做到持久化或提供給查詢接口使用,可以定期〔如1秒將UV計算結果同步到HBase中。

如果內存不能容納,可以考慮采用BloomFilter來實現(xiàn),從而盡可能的減少內存的占用情況。除了UV的計算外,判斷URL是否存在也是個典型的應用場景。4.3讀端計算如果對于響應時間要求比較苛刻的情況〔如單次http請求要在毫秒級時間內返回,個人覺得讀端不宜做過多復雜的計算邏輯,盡量做到讀端功能單一化:即從HBaseRegionServer讀到數(shù)據(jù)〔scan或get方式后,按照數(shù)據(jù)格式進行簡單的拼接,直接返回給前端使用。當然,如果對于響應時間要求一般,或者業(yè)務特點需要,也可以在讀端進行一些計算邏輯。5.總結作為一個Key-Value存儲系統(tǒng),HBase并不是萬能的,它有自己獨特的地方。因此,基于它來做應用時,我們往往需要從多方面進行優(yōu)化改進〔表設計、讀表操作、寫表操作、數(shù)據(jù)計算等,有時甚至還需要從系統(tǒng)級對HBase進行配置調優(yōu),更甚至可以對HBase本身進行優(yōu)化。這屬于不同的層次范疇??傊?概括來講,對系統(tǒng)進行優(yōu)化時,首先定位到影響你的程序運行性能的瓶頸之處,然后有的放矢進行針對行的優(yōu)化。如果優(yōu)化后滿足你的期望,那么就可以停止優(yōu)化;否則繼續(xù)尋找新的瓶頸之處,開始新的優(yōu)化,直到滿足性能要求。以上就是從項目開發(fā)中總結的一點經驗,如有不對之處,歡迎大家不吝賜教。HBase系統(tǒng)架構HBase是ApacheHadoop的數(shù)據(jù)庫,能夠對大型數(shù)據(jù)提供隨機、實時的讀寫訪問。HBase的目標是存儲并處理大型的數(shù)據(jù)。HBase是一個開源的,分布式的,多版本的,面向列的存儲模型。它存儲的是松散型數(shù)據(jù)。HBase特性:1高可靠性2高效性3面向列4可伸縮5可在廉價PCServer搭建大規(guī)模結構化存儲集群HBase是GoogleBigTable的開源實現(xiàn),其相互對應如下:GoogleHBase

文件存儲系統(tǒng)

GFS

HDFS

海量數(shù)據(jù)處理

MapReduceHadoopMapReduce

協(xié)同服務管理ChubbyZookeeperHBase關系圖:HBase位于結構化存儲層,圍繞HBase,各部件對HBase的支持情況:

Hadoop部件作用

HDFS高可靠的底層存儲支持

MapReduce

高性能的計算能力

Zookeeper穩(wěn)定服務和failover機制

Pig&Hive

高層語言支持,便于數(shù)據(jù)統(tǒng)計

Sqoop

提供RDBMS數(shù)據(jù)導入,便于傳統(tǒng)數(shù)據(jù)庫向HBase遷移訪問HBase的接口方式特點場合

NativeJavaAPI最常規(guī)和高效HadoopMapReduceJob并行處理HBase表數(shù)據(jù)

HBaseShell

最簡單接口

HBase管理使用

ThriftGateway利用Thrift序列化支持多種語言

異構系統(tǒng)在線訪問HBase表數(shù)據(jù)

RestGateway

解除語言限制Rest風格HttpAPI訪問

PigPigLatin六十編程語言處理數(shù)據(jù)數(shù)據(jù)統(tǒng)計

Hive

簡單,SqlLikeHBase數(shù)據(jù)模型組成部件說明:RowKey:Table主鍵行鍵Table中記錄按照RowKey排序

Timestamp:

每次對數(shù)據(jù)操作對應的時間戳,也即數(shù)據(jù)的versionnumber

ColumnFamily:列簇,一個table在水平方向有一個或者多個列簇,列簇可由任意多個Column組成,列簇支持動態(tài)擴展,無須預定義數(shù)量及類型,二進制存儲,用戶需自行進行類型轉換Table&Region1.Table隨著記錄增多不斷變大,會自動分裂成多份Splits,成為Regions

2.一個region由[startkey,endkey>表示

3.不同region會被Master分配給相應的RegionServer進行管理兩張?zhí)厥獗恚?ROOT-&.META..META.記錄用戶表的Region信息,同時,.META.也可以有多個region

-ROOT-

記錄.META.表的Region信息,但是,-ROOT-只有一個region

Zookeeper中記錄了-ROOT-表的location

客戶端訪問數(shù)據(jù)的流程:

Client->Zookeeper->-ROOT-->.META.->用戶數(shù)據(jù)表

多次網絡操作,不過client端有cache緩存HBase系統(tǒng)架構圖組成部件說明

Client:

使用HBaseRPC機制與HMaster和HRegionServer進行通信

Client與HMaster進行通信進行管理類操作

Client與HRegionServer進行數(shù)據(jù)讀寫類操作

Zookeeper:

ZookeeperQuorum存儲-ROOT-表地址、HMaster地址

HRegionServer把自己以Ephedral方式注冊到Zookeeper中,HMaster隨時感知各個HRegionServer的健康狀況

Zookeeper避免HMaster單點問題

HMaster:

HMaster沒有單點問題,HBase中可以啟動多個HMaster,通過Zookeeper的MasterElection機制保證總有一個Master在運行

主要負責Table和Region的管理工作:

1管理用戶對表的增刪改查操作

2管理HRegionServer的負載均衡,調整Region分布

3RegionSplit后,負責新Region的分布

4在HRegionServer停機后,負責失效HRegionServer上Region遷移

HRegionServer:

HBase中最核心的模塊,主要負責響應用戶I/O請求,向HDFS文件系統(tǒng)中讀寫數(shù)據(jù)

HRegionServer管理一些列HRegion對象;

每個HRegion對應Table中一個Region,HRegion由多個HStore組成;

每個HStore對應Table中一個ColumnFamily的存儲;

ColumnFamily就是一個集中的存儲單元,故將具有相同IO特性的Column放在一個ColumnFamily會更高效HStore:

HBase存儲的核心。由MemStore和StoreFile組成。

MemStore是SortedMemoryBuffer。用戶寫入數(shù)據(jù)的流程:

Client寫入->存入MemStore,一直到MemStore滿->Flush成一個StoreFile,直至增長到一定閾值->出發(fā)Compact合并操作->多個StoreFile合并成一個StoreFile,同時進行版本合并和數(shù)據(jù)刪除->當StoreFilesCompact后,逐步形成越來越大的StoreFile->單個StoreFile大小超過一定閾值后,觸發(fā)Split操作,把當前RegionSplit成2個Region,Region會下線,新Split出的2個孩子Region會被HMaster分配到相應的HRegionServer上,使得原先1個Region的壓力得以分流到2個Region上

由此過程可知,HBase只是增加數(shù)據(jù),有所得更新和刪除操作,都是在Compact階段做的,所以,用戶寫操作只需要進入到內存即可立即返回,從而保證I/O高性能。HLog

引入HLog原因:

在分布式系統(tǒng)環(huán)境中,無法避免系統(tǒng)出錯或者宕機,一旦HRegionServer以外退出,MemStore中的內存數(shù)據(jù)就會丟失,引入HLog就是防止這種情況

工作機制:

每個HRegionServer中都會有一個HLog對象,HLog是一個實現(xiàn)WriteAheadLog的類,每次用戶操作寫入Memstore的同時,也會寫一份數(shù)據(jù)到HLog文件,HLog文件定期會滾動出新,并刪除舊的文件<已持久化到StoreFile中的數(shù)據(jù)>。當HRegionServer意外終止后,HMaster會通過Zookeeper感知,HMaster首先處理遺留的HLog文件,將不同region的log數(shù)據(jù)拆分,分別放到相應region目錄下,然后再將失效的region重新分配,領取到這些region的HRegionServer在LoadRegion的過程中,會發(fā)現(xiàn)有歷史HLog需要處理,因此會ReplayHLog中的數(shù)據(jù)到MemStore中,然后flush到StoreFiles,完成數(shù)據(jù)恢復。HBase存儲格式

HBase中的所有數(shù)據(jù)文件都存儲在HadoopHDFS文件系統(tǒng)上,格式主要有兩種:

1HFileHBase中KeyValue數(shù)據(jù)的存儲格式,HFile是Hadoop的二進制格式文件,實際上StoreFile就是對HFile做了輕量級包裝,即StoreFile底層就是HFile

2HLogFile,HBase中WAL〔WriteAheadLog的存儲格式,物理上是Hadoop的SequenceFileHFile

圖片解釋:

HFile文件不定長,長度固定的塊只有兩個:Trailer和FileInfo

Trailer中指針指向其他數(shù)據(jù)塊的起始點

FileInfo中記錄了文件的一些Meta信息,例如:AVG_KEY_LEN,AVG_VALUE_LEN,LAST_KEY,COMPARATOR,MAX_SEQ_ID_KEY等

DataIndex和MetaIndex塊記錄了每個Data塊和Meta塊的起始點

DataBlock是HBaseI/O的基本單元,為了提高效率,HRegionServer中有基于LRU的BlockCache機制

每個Data塊的大小可以在創(chuàng)建一個Table的時候通過參數(shù)指定,大號的Block有利于順序Scan,小號Block利于隨機查詢

每個Data塊除了開頭的Magic以外就是一個個KeyValue對拼接而成,Magic內容就是一些隨機數(shù)字,目的是防止數(shù)據(jù)損壞HFile里面的每個KeyValue對就是一個簡單的byte數(shù)組。這個byte數(shù)組里面包含了很多項,并且有固定的結構。

KeyLength和ValueLength:兩個固定的長度,分別代表Key和Value的長度

Key部分:RowLength是固定長度的數(shù)值,表示RowKey的長度,Row就是RowKey

ColumnFamilyLength是固定長度的數(shù)值,表示Family的長度

接著就是ColumnFamily,再接著是Qualifier,然后是兩個固定長度的數(shù)值,表示TimeStamp和KeyType〔Put/Delete

Value部分沒有這么復雜的結構,就是純粹的二進制數(shù)據(jù)HLogFile

HLog文件就是一個普通的HadoopSequenceFile,SequenceFile的Key是HLogKey對象,HLogKey中記錄了寫入數(shù)據(jù)的歸屬信息,除了table和region名字外,同時還包括sequencenumber和timestamp,timestamp是"寫入時間",sequencenumber的起始值為0,或者是最近一次存入文件系統(tǒng)中sequencenumber。

HLogSequeceFile的Value是HBase的KeyValue對象,即對應HFile中的KeyValueHBaseJava客戶端編程本文以HBase0.90.2為例,介紹如何在Windows系統(tǒng),EclipseIDE集成環(huán)境下,使用Java語言,進行HBase客戶端編程,包含建立表、刪除表、插入記錄、刪除記錄、各種方式下的查詢操作等。1.準備工作1、下載后安裝jdk包〔這里使用的是jdk-6u10-rc2-bin-b32-windows-i586-p-12_sep_2008;2、下載eclipse,解壓到本地〔這里使用的是eclipse-java-helios-SR2-win32;3、下載HBase包,解壓安裝包到本地〔這里使用的是hbase-0.90.2。2.搭建開發(fā)環(huán)境1、運行Eclipse,創(chuàng)建一個新的Java工程"HBaseClient",右鍵項目根目錄,選擇"Properties"->"JavaBuildPath"->"Library"->"AddExternalJARs",將HBase解壓后根目錄下的hbase-0.90.2.jar、hbase-0.90.2-tests.jar和lib子目錄下所有jar包添加到本工程的Classpath下,如圖1所示。圖1EclilseIDE下添加HBase的Jar包2、按照步驟1中的操作,將自己所連接的HBase的配置文件hbase-site.xml添加到本工程的Classpath中,如下所示為配置文件的一個示例。123456789101112131415161718<configuration><property><name>hbase.rootdir</name><value>hdfs://hostname:9000/hbase</value></property><property><name>hbase.cluster.distributed</name><value>true</value></property><property><name>hbase.zookeeper.quorum</name><value>*.*.*.*,*.*.*.*,*.*.*.*</value></property><propertyskipInDoc="true"><name>hbase.defaults.for.version</name><value>0.90.2</value></property></configuration>3、下面可以在Eclipse環(huán)境下進行HBase編程了。3.HBase基本操作代碼示例3.1初始化配置1234567privatestaticConfigurationconf=null;/**

*初始化配置

*/static{

conf=HBaseConfiguration.create<>;}3.2創(chuàng)建表123456789101112131415161718/**

*創(chuàng)建表操作

*@throwsIOException

*/publicvoidcreateTable<Stringta

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論