久久午夜无码,日日射天天射五月丁香婷婷我来了 ,欧美黑人又长又粗在线视频,午夜天网站

網(wǎng)速4M等于多少KB/S,等于多少kbps

網(wǎng)速4M等于多少KB/S,等于多少kbps

蕭飇 2025-06-08 科技 25 次瀏覽 0個評論

  網(wǎng)易視頻云是網(wǎng)易公司旗下的視頻云服務產(chǎn)品,以Paas服務模式,向開發(fā)者提供音視頻編解碼SDK和開放API,助力APP接入音視頻功能。

  現(xiàn)在,網(wǎng)易視頻云的技術專家給大家分享一篇技術性文章:HBase BlockCache系列 - 性能對比測試報告.

  HBase BlockCache系列文章到了終結篇,幾個主角的是是非非也該有個了斷了,在SlabCache被早早地淘汰之后,站在華山之巔的也就僅剩LRU君(LRUBlockCache)和CBC君(CombinedBlockCache)。誰贏誰輸,我說了不算,你說了也不算,那就來讓數(shù)據(jù)說話。這篇文章主要對比LRU君和CBC君(offheap模式)分別在四種場景下幾種指標(GC、Throughput、Latency、CPU、IO等)的表現(xiàn)情況。四種場景分別是緩存全部命中、少大部分緩存命中、少量緩存命中、緩存基本未命中。

  需要注意的是,本文的所有數(shù)據(jù)都來自社區(qū)文檔,在這里分享也只是給大家一個參考,更加詳細的測試數(shù)據(jù)可以閱讀文章《Comparing BlockCache Deploys》和 HBASE-11323 附件報告。

  說明:本文所有圖都以時間為橫坐標,縱坐標為對應指標。每張圖都會分別顯示LRU君和CBC君的四種場景數(shù)據(jù),總計八種場景,下面數(shù)據(jù)表示LRU君的四種場景分布在時間段21:36:39~22:36:40,CBC君的四種場景分布在時間段23:02:16~00:02:17,看圖的時候需要特別注意。

  LRU君:

  Tue Jul 22 21:36:39 PDT 2014 run size=32, clients=25 ; lrubc time=1200 緩存全部命中

  Tue Jul 22 21:56:39 PDT 2014 run size=72, clients=25 ; lrubc time=1200 大量緩存命中

  Tue Jul 22 22:16:40 PDT 2014 run size=144, clients=25 ; lrubc time=1200 少量緩存命中

  Tue Jul 22 22:36:40 PDT 2014 run size=1000, clients=25 ; lrubc time=1200 緩存基本未命中

  CBC君:

  Tue Jul 22 23:02:16 PDT 2014 run size=32, clients=25 ; bucket time=1200 緩存全部命中

  Tue Jul 22 23:22:16 PDT 2014 run size=72, clients=25 ; bucket time=1200 大量緩存命中

  Tue Jul 22 23:42:17 PDT 2014 run size=144, clients=25 ; bucket time=1200 少量緩存命中

  Wed Jul 23 00:02:17 PDT 2014 run size=1000, clients=25 ; bucket time=1200 緩存基本未命中

  GC

網(wǎng)速4M等于多少KB/S,等于多少kbps

  GC指標是HBase運維最關心的指標,出現(xiàn)一次長時間的GC就會導致這段時間內(nèi)業(yè)務方的所有讀寫請求失敗,如果業(yè)務方?jīng)]有很好的容錯,就會出現(xiàn)丟數(shù)據(jù)的情況出現(xiàn)。根據(jù)下圖可知,只有在‘緩存全部命中’的場景下,LRU君總GC時間25ms比CBC君的75ms短;其他三種場景下,LRU君表現(xiàn)都沒有CBC君好,總GC時間基本均是CBC君的3倍左右。

  

  Thoughput

  吞吐量可能是所有HBase用戶初次使用最關心的問題,這基本反映了HBase的讀寫性能。下圖是隨機讀測試的吞吐量曲線,在‘緩存全部命中’以及‘大量緩存命中’這兩種場景下,LRU君可謂是完勝CBC君,特別是在‘緩存全部命中’的場景下,LRU君的吞吐量甚至是CBC君的兩倍;而在‘少量緩存命中’以及‘緩存基本未命中’這兩種場景下,兩者的表現(xiàn)基本相當;

  

  Latency

  讀寫延遲是另一個用戶很關心的指標,下圖表示在所有四種情況下LRU君和CBC君都在伯仲之間,LRU君略勝一籌。

  

  IO

  接下來兩張圖是資源使用圖,運維同學可能會比較關心。從IO使用情況來看,兩者在四種場景下也基本相同。

  

  CPU

  再來看看CPU使用情況,在‘緩存全部命中’以及‘大量緩存命中’這兩種場景下,LRU君依然完勝CBC君,特別是在‘緩存全部命中’的場景下,CBC君差不多做了兩倍于LRU君的工作;而在‘少量緩存命中’以及‘緩存基本未命中’這兩種場景下,兩者的表現(xiàn)基本相當;

  

  結論

  看完了所有比較重要的指標對比數(shù)據(jù),我們可以得出以下兩點:

  1. 在’緩存全部命中’場景下,LRU君可謂完勝CBC君。因此如果總數(shù)據(jù)量相比JVM內(nèi)存容量很小的時候,選擇LRU君;

  2. 在所有其他存在緩存未命中情況的場景下, LRU君的GC性能幾乎只有CBC君的1/3,而吞吐量、讀寫延遲、IO、CPU等指標兩者基本相當,因此建議選擇CBC。

  至此,HBase BlockCache系列文章就結束了,如果大家還有什么交流討論或者補充的,可以留言或者發(fā)郵件至libisthanks@gmail.

  Categories:HBase, 性能Tags:blockcache

  HBase BlockCache系列 - 探求BlockCache實現(xiàn)機制

  April 26th, 2016范欣欣 閱讀(0)Comments off

  HBase BlockCache系列第一篇文章《走進BlockCache》從全局視角對HBase中緩存、Memstore等作了簡要概述,并重點介紹了幾種BlockCache方案及其演進過程,對此還不了解的可以點這里。本文在上文的基礎上深入BlockCache內(nèi)部,對各種BlockCache方案具體工作原理進行詳細分析。Note:因為SlabCache方案在0.98版本已經(jīng)不被建議使用,因此本文不針對該方案進行講解;至于LRU方案和Bucket方案,因為后者更加復雜,本文也會花更多篇幅詳細介紹該方案的實現(xiàn)細節(jié)。

  LRUBlockCache

  LRUBlockCache是HBase目前默認的BlockCache機制,實現(xiàn)機制比較簡單。它使用一個ConcurrentHashMap管理BlockKey到Block的映射關系,緩存Block只需要將BlockKey和對應的Block放入該HashMap中,查詢緩存就根據(jù)BlockKey從HashMap中獲取即可。同時該方案采用嚴格的LRU淘汰算法,當Block Cache總量達到一定閾值之后就會啟動淘汰機制,最近最少使用的Block會被置換出來。在具體的實現(xiàn)細節(jié)方面,需要關注三點:

  1. 緩存分層策略

  HBase在LRU緩存基礎上,采用了緩存分層設計,將整個BlockCache分為三個部分:single-access、mutil-access和inMemory。需要特別注意的是,HBase系統(tǒng)元數(shù)據(jù)存放在InMemory區(qū),因此設置數(shù)據(jù)屬性InMemory = true需要非常謹慎,確保此列族數(shù)據(jù)量很小且訪問頻繁,否則有可能會將hbase.meta元數(shù)據(jù)擠出內(nèi)存,嚴重影響所有業(yè)務性能。

  2. LRU淘汰算法實現(xiàn)

  系統(tǒng)在每次cache block時將BlockKey和Block放入HashMap后都會檢查BlockCache總量是否達到閾值,如果達到閾值,就會喚醒淘汰線程對Map中的Block進行淘汰。系統(tǒng)設置三個MinMaxPriorityQueue隊列,分別對應上述三個分層,每個隊列中的元素按照最近最少被使用排列,系統(tǒng)會優(yōu)先poll出最近最少使用的元素,將其對應的內(nèi)存釋放??梢?,三個分層中的Block會分別執(zhí)行LRU淘汰算法進行淘汰。

  3. LRU方案優(yōu)缺點

  LRU方案使用JVM提供的HashMap管理緩存,簡單有效。但隨著數(shù)據(jù)從single-access區(qū)晉升到mutil-access區(qū),基本就伴隨著對應的內(nèi)存對象從young區(qū)到old區(qū) ,晉升到old區(qū)的Block被淘汰后會變?yōu)閮?nèi)存垃圾,最終由CMS回收掉(Conccurent Mark Sweep,一種標記清除算法),然而這種算法會帶來大量的內(nèi)存碎片,碎片空間一直累計就會產(chǎn)生臭名昭著的Full GC。尤其在大內(nèi)存條件下,一次Full GC很可能會持續(xù)較長時間,甚至達到分鐘級別。大家知道Full GC是會將整個進程暫停的(稱為stop-the-wold暫停),因此長時間Full GC必然會極大影響業(yè)務的正常讀寫請求。也正因為這樣的弊端,SlabCache方案和BucketCache方案才會橫空出世。

  BucketCache

  相比LRUBlockCache,BucketCache實現(xiàn)相對比較復雜。它沒有使用JVM 內(nèi)存管理算法來管理緩存,而是自己對內(nèi)存進行管理,因此不會因為出現(xiàn)大量碎片導致Full GC的情況發(fā)生。本節(jié)主要介紹BucketCache的具體實現(xiàn)方式(包括BucketCache的內(nèi)存組織形式、緩存寫入讀取流程等)以及如何配置使用BucketCache。

  內(nèi)存組織形式

  下圖是BucketCache的內(nèi)存組織形式圖,其中上面部分是邏輯組織結構,下面部分是對應的物理組織結構。HBase啟動之后會在內(nèi)存中申請大量的bucket,如下圖中黃色矩形所示,每個bucket的大小默認都為2MB。每個bucket會有一個baseoffset變量和一個size標簽,其中baseoffset變量表示這個bucket在實際物理空間中的起始地址,因此block的物理地址就可以通過baseoffset和該block在bucket的偏移量唯一確定;而size標簽表示這個bucket可以存放的block塊的大小,比如圖中左側(cè)bucket的size標簽為65KB,表示可以存放64KB的block,右側(cè)bucket的size標簽為129KB,表示可以存放128KB的block。

  

  HBase中使用BucketAllocator類實現(xiàn)對Bucket的組織管理:

  1. HBase會根據(jù)每個bucket的size標簽對bucket進行分類,相同size標簽的bucket由同一個BucketSizeInfo管理,如上圖,左側(cè)存放64KB block的bucket由65KB BucketSizeInfo管理,右側(cè)存放128KB block的bucket由129KB BucketSizeInfo管理。

  2. HBase在啟動的時候就決定了size標簽的分類,默認標簽有(4+1)K、(8+1)K、(16+1)K … (48+1)K、(56+1)K、(64+1)K、(96+1)K … (512+1)K。而且系統(tǒng)會首先從小到大遍歷一次所有size標簽,為每種size標簽分配一個bucket,最后所有剩余的bucket都分配最大的size標簽,默認分配 (512+1)K,如下圖所示:

  

  3. Bucket的size標簽可以動態(tài)調(diào)整,比如64K的block數(shù)目比較多,65K的bucket被用完了以后,其他size標簽的完全空閑的bucket可以轉(zhuǎn)換成為65K的bucket,但是至少保留一個該size的bucket。

  Block緩存寫入、讀取流程

  下圖是block寫入緩存以及從緩存中讀取block的流程示意圖,圖中主要包括5個模塊,其中RAMCache是一個存儲blockkey和block對應關系的HashMap;WriteThead是整個block寫入的中心樞紐,主要負責異步的寫入block到內(nèi)存空間;BucketAllocator在上一節(jié)詳細介紹過,主要實現(xiàn)對bucket的組織管理,為block分配內(nèi)存空間;IOEngine是具體的內(nèi)存管理模塊,主要實現(xiàn)將block數(shù)據(jù)寫入對應地址的內(nèi)存空間;BackingMap也是一個HashMap,用來存儲blockKey與對應物理內(nèi)存偏移量的映射關系,用來根據(jù)blockkey定位具體的block;其中紫線表示cache block流程,綠線表示get block流程。

  

  Block緩存寫入流程

  1. 將block寫入RAMCache。實際實現(xiàn)中,HBase設置了多個RAMCache,系統(tǒng)首先會根據(jù)blockkey進行hash,根據(jù)hash結果將block分配到對應的RAMCache中;

  2. WriteThead從RAMCache中取出所有的block。和RAMCache相同,HBase會同時啟動多個WriteThead并發(fā)的執(zhí)行異步寫入,每個WriteThead對應一個RAMCache;

  3. 每個WriteThead會將遍歷RAMCache中所有block數(shù)據(jù),分別調(diào)用bucketAllocator為這些block分配內(nèi)存空間;

  4. BucketAllocator會選擇與block大小對應的bucket進行存放(具體細節(jié)可以參考上節(jié)‘內(nèi)存組織形式’所述),并且返回對應的物理地址偏移量offset;

  5. WriteThead將block以及分配好的物理地址偏移量傳給IOEngine模塊,執(zhí)行具體的內(nèi)存寫入操作;

  6. 寫入成功后,將類似<blockkey,offset>這樣的映射關系寫入BackingMap中,方便后續(xù)查找時根據(jù)blockkey可以直接定位;

  Block緩存讀取流程

  1. 首先從RAMCache中查找。對于還沒有來得及寫入到bucket的緩存block,一定存儲在RAMCache中;

  2. 如果在RAMCache中沒有找到,再在BackingMap中根據(jù)blockKey找到對應物理偏移地址offset;

  3. 根據(jù)物理偏移地址offset可以直接從內(nèi)存中查找對應的block數(shù)據(jù);

  BucketCache工作模式

  BucketCache默認有三種工作模式:heap、offheap和file;這三種工作模式在內(nèi)存邏輯組織形式以及緩存流程上都是相同的,參見上節(jié)講解。不同的是三者對應的最終存儲介質(zhì)有所不同,即上述所講的IOEngine有所不同。

  其中heap模式和offheap模式都使用內(nèi)存作為最終存儲介質(zhì),內(nèi)存分配查詢也都使用Java NIO ByteBuffer技術,不同的是,heap模式分配內(nèi)存會調(diào)用byteBuffer.allocate方法,從JVM提供的heap區(qū)分配,而后者會調(diào)用byteBuffer.allocateDirect方法,直接從操作系統(tǒng)分配。這兩種內(nèi)存分配模式會對HBase實際工作性能產(chǎn)生一定的影響。影響最大的無疑是GC ,相比heap模式,offheap模式因為內(nèi)存屬于操作系統(tǒng),所以基本不會產(chǎn)生CMS GC,也就在任何情況下都不會因為內(nèi)存碎片導致觸發(fā)Full GC。除此之外,在內(nèi)存分配以及讀取方面,兩者性能也有不同,比如,內(nèi)存分配時heap模式需要首先從操作系統(tǒng)分配內(nèi)存再拷貝到JVM heap,相比offheap直接從操作系統(tǒng)分配內(nèi)存更耗時;但是反過來,讀取緩存時heap模式可以從JVM heap中直接讀取,而offheap模式則需要首先從操作系統(tǒng)拷貝到JVM heap再讀取,顯得后者更費時。

  file模式和前面兩者不同,它使用Fussion-IO或者SSD等作為存儲介質(zhì),相比昂貴的內(nèi)存,這樣可以提供更大的存儲容量,因此可以極大地提升緩存命中率。

  BucketCache配置使用

  BucketCache方案的配置說明一直被HBaser所詬病,官方一直沒有相關文檔對此進行介紹。本人也是一直被其所困,后來通過查看源碼才基本了解清楚,在此分享出來,以便大家學習。需要注意的是,BucketCache三種工作模式的配置會有所不同,下面也是分開介紹,并且沒有列出很多不重要的參數(shù):

  heap模式

  <hbase.bucketcache.ioengine>heap</hbase.bucketcache.ioengine>

  //bucketcache占用整個jvm內(nèi)存大小的比例

  <hbase.bucketcache.size>0.4</hbase.bucketcache.size>

  //bucketcache在combinedcache中的占比

  <hbase.bucketcache.combinedcache.percentage>0.9</hbase.bucketcache.combinedcache.percentage>

  offheap模式

  <hbase.bucketcache.ioengine>offheap</hbase.bucketcache.ioengine>

  <hbase.bucketcache.size>0.4</hbase.bucketcache.size>

  <hbase.bucketcache.combinedcache.percentage>0.9</hbase.bucketcache.combinedcache.percentage>

  file模式

  <hbase.bucketcache.ioengine>file:/cache_path</hbase.bucketcache.ioengine>

  //bucketcache緩存空間大小,單位為MB

  <hbase.bucketcache.size>10 * 1024</hbase.bucketcache.size>

  //高速緩存路徑

  <hbase.bucketcache.persistent.path>file:/cache_path</hbase.bucketcache.persistent.path>

  總結

  HBase中緩存的設置對隨機讀寫性能至關重要,本文通過對LRUBlockCache和BucketCache兩種方案的實現(xiàn)進行介紹,希望能夠讓各位看官更加深入地了解BlockCache的工作原理。BlockCache系列經(jīng)過兩篇內(nèi)容的介紹,基本已經(jīng)解析完畢,那在實際線上應用中到底應該選擇哪種方案呢?下一篇文章讓我們一起看看HBase社區(qū)發(fā)布的BlockCache報告!

  Categories:BucketCache, HBase, LRUBlockCacheTags:blockcache

  HBase – RegionServer宕機案件偵查

  April 15th, 2016范欣欣 閱讀(0)Comments off

  本來靜謐的晚上,吃著葡萄干看著球賽,何等愜意??善粭l報警短信如閃電一般打破了夜晚的寧靜,線上集群一臺RS宕了!于是倏地從床上坐起來,看了看監(jiān)控,瞬間驚呆了:單臺機器的讀寫吞吐量竟然達到了5w ops/sec!RS宕機是因為這么大的寫入量造成的?如果真是這樣,它是怎么造成的?如果不是這樣,那又是什么原因?各種疑問瞬間從腦子里一一閃過,甭管那么多,先把日志備份一份,再把RS拉起來。接下來還是Bug排查老套路:日志、監(jiān)控和源碼三管齊下,來看看到底發(fā)生了什么!

  案件現(xiàn)場篇

  下圖是使用監(jiān)控工具Ganglia對事發(fā)RegionServer當時讀寫吞吐量的監(jiān)控曲線,從圖中可以看出,大約在19點~21點半的時間段內(nèi),這臺RS的吞吐量都維持了3w ops/sec左右,峰值更是達到了6w ops/sec。之前我們就線上單臺RS能夠承受的最大讀寫吞吐量進行過測定,基本也就維持在2w左右,主要是因為網(wǎng)絡帶寬瓶頸。而在宕機前這臺RS的讀寫吞吐量超出這么多,直覺告訴我RS宕機原因就是它!

  

  接著就趕緊把日志拉出來看,滿屏的responseTooSlow,如下圖所示:

  

  很顯然,這種異常最大可能原因就是Full GC,果然,經(jīng)過耐心地排查,可以看到很多如下所示的Full GC日志片段:

  2016-04-14 21:27:13,174 WARN [JvmPauseMonitor] util.JvmPauseMonitor: Detected pause in JVM or host machine (eg GC): pause of approximately 20542ms

  GC pool 'ParNew' had collection(s): count=1 time=0ms

  GC pool 'ConcurrentMarkSweep' had collection(s): count=2 time=20898ms

  2016-04-14 21:27:13,174 WARN [regionserver60020.periodicFlusher] util.Sleeper: We slept 20936ms instead of 100ms, this is likely due to a long garbage collecting pause and it's usually bad, see https://hbase.apache.org/book.html#trouble.rs.runtime.zkexpired

  可以看出,HBase執(zhí)行了一次CMS GC,導致整個進程所有線程被掛起了20s。通過對MemStore的監(jiān)控也可以看出這段時間GC力度之大,如下圖所示:

  

  GC時間長最明顯的危害是會造成上層業(yè)務的阻塞,通過日志也可以看出些許端倪:

  java.io.IOException: Connection reset by peer

  at sun.nio.ch.FileDispatcherImpl.read0(Native Method)

  at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)

  at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)

  at sun.nio.ch.IOUtil.read(IOUtil.java:197)

  at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:384)

  at org.apache.hadoop.hbase.ipc.RpcServer.channelRead(RpcServer.java:2246)

  at org.apache.hadoop.hbase.ipc.RpcServer$Connection.readAndProcess(RpcServer.java:1496)

  ....

  2016-04-14 21:32:40,173 WARN [B.DefaultRpcServer.handler=125,queue=5,port=60020] ipc.RpcServer: RpcServer.respondercallId: 7540 service: ClientService methodName: Multi size: 100.2 K connection: 10.160.247.139:56031: output error

  2016-04-14 21:32:40,173 WARN [B.DefaultRpcServer.handler=125,queue=5,port=60020] ipc.RpcServer: B.DefaultRpcServer.handler=125,queue=5,port=60020: caught a ClosedChannelException, this means that the server was processing a request but the client went away. The error message was: null

  上述日志表示HBase服務端因為Full GC導致一直無法響應用戶請求,用戶客戶端程序在一定時間過后就會SocketTimeout并斷掉此Connection。連接斷掉之后,服務器端就會打印如上日志。然而,這些和我們的終極目標好像并沒有太大關系,別忘了我們的目標是找到RS宕機的原因哦!

  破案鋪墊篇

  經(jīng)過對案件現(xiàn)場的排查,唯一有用的線索就是HBase在宕機前經(jīng)歷了很嚴重、很頻繁的Full GC,從下面日志可以進一步看出,這些Full GC都是在 concurrent mode failure模式下發(fā)生的,也就是虛擬機還未執(zhí)行完本次GC的情況下又來了大量數(shù)據(jù)導致JVM內(nèi)存不夠,此時虛擬機會將所有用戶線程掛起,執(zhí)行長時間的Full GC!

  (concurrent mode failure): 45876255K->21800674K(46137344K), 10.0625300 secs] 48792749K->21800674K(49283072K), [CMS Perm : 43274K->43274K(262144K)], 10.2083040 secs] [Times: user=12.02 sys=0.00, real=10.20 secs]

  2016-04-14 21:22:43,990 WARN [JvmPauseMonitor] util.JvmPauseMonitor: Detected pause in JVM or host machine (eg GC): pause of approximately 10055ms

  GC pool 'ParNew' had collection(s): count=2 time=244ms

  GC pool 'ConcurrentMarkSweep' had collection(s): count=1 time=10062ms

  上文提到Full GC會對上層業(yè)務產(chǎn)生很嚴重的影響,那有沒有可能會對下層依賴方也產(chǎn)生很大的影響呢?事實是Yes!而且,RS宕機的大部分原因也要歸咎于此!

  進一步查看日志,發(fā)現(xiàn)HBase日志中出現(xiàn)下述異常:

  2016-04-14 21:22:44,006 WARN [ResponseProcessor for block BP-632656502-10.160.173.93-1448595094942:blk_1073941840_201226] hdfs.DFSClient: DFSOutputStream ResponseProcessor exception for block BP-632656502-10.160.173.93-1448595094942:blk_1073941840_201226java.io.IOException: Bad response ERROR for block BP-632656502-10.160.173.93-1448595094942:blk_1073941840_201226 from datanode 10.160.173.93:50010

  at org.apache.hadoop.hdfs.DFSOutputStream$DataStreamer$ResponseProcessor.run(DFSOutputStream.java:732)

  從日志內(nèi)容來看應該是hbase調(diào)用DFSClient從datanode加載block數(shù)據(jù)”BP-632656502-10.160.173.93-1448595094942:blk_1073941840_201226″,但是datanode返回失敗。具體失敗原因需要查看datanode節(jié)點日志,如下所示:

  2016-04-14 21:22:43,789 INFO org.apache.hadoop.hdfs.server.datanode.DataNode: opWriteBlock BP-632656502-10.160.173.93-1448595094942:blk_1073941840_201226 received exception java.net.SocketTimeoutException: 10000 millis timeout while waiting for channel to be ready for read. ch : java.nio.channels.SocketChannel[connected local=/10.160.173.94:50010 remote=/10.160.173.94:30110]

  2016-04-14 21:22:43,779 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: hz-hbase4.photo.163.org:50010:DataXceiver error processing WRITE_BLOCK operation src: /10.160.173.94:30123 dest: /10.160.173.94:50010

  java.net.SocketTimeoutException: 10000 millis timeout while waiting for channel to be ready for read. ch : java.nio.channels.SocketChannel[connected local=/10.160.173.94:50010 remote=/10.160.173.94:30123]

  很顯然,從日志可以看出,datanode一直在等待來自客戶端的read請求,但是直至SocketTimeout,請求都沒有過來,此時datanode會將該連接斷開,導致客戶端收到上述”Bad response ERROR ***”的異常。

  那這和Full GC有什么關系呢?很簡單,就是因為Full GC導致HBase所有內(nèi)部線程掛起,因此發(fā)往datanode的read請求也被掛起了,datanode就等啊等,左等右等都等不到,萬不得已才將連接斷掉。

  查看Hadoop客戶端源碼可知,如果DFSClient發(fā)生上述異常,DFSClient會將一個全局標志errorIndex設為一個非零值。具體可參見DFSOutputStream類中如下代碼片段:

  破案結局篇

  上述鋪墊篇最后的結果就是Hadoop客戶端會將一個全局標志errorIndex設為一個非零值,那這到底和最終RS宕掉有什么關系呢?來繼續(xù)往下看。下圖HBase日志相關片段截圖,記錄了比較詳細的RS宕機異常信息,我們就以這些異常信息作為切入點進行分析,可以看出至少三條有用的線索,如下圖所示:

  

  線索一:RS宕機最直接的原因是因為系統(tǒng)在關閉LogWriter(之后會重新開啟一個新的HLog)的時候失敗

  線索二:執(zhí)行LogWriter關閉失敗的原因是”writing trailer”時發(fā)生IOException異常

  線索三:而發(fā)生IOException異常的原因是”All datanodes *** are bad”

  到這里為止,我們能夠獲得的最靠譜的情報就是RS宕機本質(zhì)是因為”All datanodes *** are bad”造成的,看字面意思就是這臺datanode因為某種原因壞掉了,那我們趕緊去看看datanode的日志,看看那個時間段有沒有相關的異?;蛘咤e誤日志。

  然而很遺憾,datanode日志在那個時間點沒有打印任何異?;蛘咤e誤日志,而且顯示所有服務都正常,信息如下所示:

  2016-04-14 21:32:38,893 INFO org.apache.hadoop.hdfs.server.datanode.DataNode.clienttrace: src: 127.0.0.1, dest: 127.0.0.1, op: REQUEST_SHORT_CIRCUIT_FDS, blockid: 1073941669, srvID: DS-22834907-10.160.173.94-50010-1448595406972, success: true

  2016-04-14 21:32:38,894 INFO org.apache.hadoop.hdfs.server.datanode.DataNode.clienttrace: src: 127.0.0.1, dest: 127.0.0.1, op: REQUEST_SHORT_CIRCUIT_FDS, blockid: 1073941669, srvID: DS-22834907-10.160.173.94-50010-1448595406972, success: true

  ...

  看到這里,是不是有點蒙圈:HBase日志里面明明打印說這臺datanode壞掉了,但是實際datanode日志顯示服務一切正常。這個時候就得翻翻源碼了,看看HBase在哪里打印的”All datanodes *** are bad“,通過查看源碼,可以看出最終元兇就是上文提到的errorIndex,如下圖所示:

  

  終于撥開天日了,再不完結就要暈了!上文鋪墊篇鋪墊到最后就得出來因為Full GC最終導致DFSClient將一個全局標志errorIndex設為一個非零值,在這里終于碰頭了,簡直淚流滿面!

  案件梳理篇

  整個流程走下來本人都有點暈暈的,涉及的方方面面太多,因此有必要把整個流程完整的梳理一遍,下面簡單畫了一個示意圖:

  

  經(jīng)過對整個案件的整理分析,一方面再次鍛煉了如何通過監(jiān)控、日志以及源碼定位排查問題的功底,另一方面在HBase運維過程中也需要特別關注如下幾點:

  1. Full GC不僅會嚴重影響上層業(yè)務,造成業(yè)務讀寫請求的卡頓。另外還有可能造成與HDFS之間數(shù)據(jù)請求的各種異常,這種異常嚴重的時候甚至會導致RegionServer宕機。

  2. 上文中提到Full GC基本是由于Concurrent Mode Failure造成,這種Full GC場景比較少見,通??梢酝ㄟ^減小 JVM 參數(shù)XX:CMSInitiatingOccupancyFraction來避免,這個參數(shù)用來設置CMS垃圾回收時機,假如此時設置為60,表示JVM內(nèi)已使用內(nèi)存占到總內(nèi)存的60%的時候就會進行垃圾回收,減少該值可以使得垃圾回收更早進行。

  3. 一定要嚴格限制業(yè)務層面的流量。一方面需要和業(yè)務方交流,由業(yè)務方進行限制,另一方面可以探索HBase業(yè)務資源隔離的新方案;

  Categories:HBase, RegionServer, 宕機Tags:

  HBase BlockCache系列 – 走進BlockCache

  April 14th, 2016fan xinxin 閱讀(191)No comments

  和其他數(shù)據(jù)庫一樣,優(yōu)化IO也是HBase提升性能的不二法寶,而提供緩存更是優(yōu)化的重中之重。最理想的情況是,所有數(shù)據(jù)都能夠緩存到內(nèi)存,這樣就不會有任何文件IO請求,讀寫性能必然會提升到極致。然而現(xiàn)實是殘酷的,隨著請求數(shù)據(jù)的不斷增多,將數(shù)據(jù)全部緩存到內(nèi)存顯得不合實際。幸運的是,我們并不需要將所有數(shù)據(jù)都緩存起來,根據(jù)二八法則,80%的業(yè)務請求都集中在20%的熱點數(shù)據(jù)上,因此將這部分數(shù)據(jù)緩存起就可以極大地提升系統(tǒng)性能。

  HBase在實現(xiàn)中提供了兩種緩存結構:MemStore和BlockCache。其中MemStore稱為寫緩存,HBase執(zhí)行寫操作首先會將數(shù)據(jù)寫入MemStore,并順序?qū)懭際Log,等滿足一定條件后統(tǒng)一將MemStore中數(shù)據(jù)刷新到磁盤,這種設計可以極大地提升HBase的寫性能。不僅如此,MemStore對于讀性能也至關重要,假如沒有MemStore,讀取剛寫入的數(shù)據(jù)就需要從文件中通過IO查找,這種代價顯然是昂貴的!BlockCache稱為讀緩存,HBase會將一次文件查找的Block塊緩存到Cache中,以便后續(xù)同一請求或者鄰近數(shù)據(jù)查找請求,可以直接從內(nèi)存中獲取,避免昂貴的IO操作。MemStore相關知識可以戳這里,本文將重點分析BlockCache。

  在介紹BlockCache之前,簡單地回顧一下HBase中Block的概念,詳細介紹戳這里。 Block是HBase中最小的數(shù)據(jù)存儲單元,默認為64K,在建表語句中可以通過參數(shù)BlockSize指定。HBase中Block分為四種類型:Data Block,Index Block,Bloom Block和Meta Block。其中Data Block用于存儲實際數(shù)據(jù),通常情況下每個Data Block可以存放多條KeyValue數(shù)據(jù)對;Index Block和Bloom Block都用于優(yōu)化隨機讀的查找路徑,其中Index Block通過存儲索引數(shù)據(jù)加快數(shù)據(jù)查找,而Bloom Block通過一定算法可以過濾掉部分一定不存在待查KeyValue的數(shù)據(jù)文件,減少不必要的IO操作;Meta Block主要存儲整個HFile的元數(shù)據(jù)。

  BlockCache是Region Server級別的,一個Region Server只有一個Block Cache,在Region Server啟動的時候完成Block Cache的初始化工作。到目前為止,HBase先后實現(xiàn)了3種Block Cache方案,LRUBlockCache是最初的實現(xiàn)方案,也是默認的實現(xiàn)方案;HBase 0.92版本實現(xiàn)了第二種方案SlabCache,見HBASE-4027;HBase 0.96之后官方提供了另一種可選方案BucketCache,見HBASE-7404。

  這三種方案的不同之處在于對內(nèi)存的管理模式,其中LRUBlockCache是將所有數(shù)據(jù)都放入JVM Heap中,交給JVM進行管理。而后兩者采用了不同機制將部分數(shù)據(jù)存儲在堆外,交給HBase自己管理。這種演變過程是因為LRUBlockCache方案中JVM垃圾回收機制經(jīng)常會導致程序長時間暫停,而采用堆外內(nèi)存對數(shù)據(jù)進行管理可以有效避免這種情況發(fā)生。

  LRUBlockCache

  HBase默認的BlockCache實現(xiàn)方案。Block數(shù)據(jù)塊都存儲在 JVM heap內(nèi),由JVM進行垃圾回收管理。它將內(nèi)存從邏輯上分為了三塊:single-access區(qū)、mutil-access區(qū)、in-memory區(qū),分別占到整個BlockCache大小的25%、50%、25%。一次隨機讀中,一個Block塊從HDFS中加載出來之后首先放入signle區(qū),后續(xù)如果有多次請求訪問到這塊數(shù)據(jù)的話,就會將這塊數(shù)據(jù)移到mutil-access區(qū)。而in-memory區(qū)表示數(shù)據(jù)可以常駐內(nèi)存,一般用來存放訪問頻繁、數(shù)據(jù)量小的數(shù)據(jù),比如元數(shù)據(jù),用戶也可以在建表的時候通過設置列族屬性IN-MEMORY= true將此列族放入in-memory區(qū)。很顯然,這種設計策略類似于JVM中young區(qū)、old區(qū)以及perm區(qū)。無論哪個區(qū),系統(tǒng)都會采用嚴格的Least-Recently-Used算法,當BlockCache總量達到一定閾值之后就會啟動淘汰機制,最少使用的Block會被置換出來,為新加載的Block預留空間。

  SlabCache

  為了解決LRUBlockCache方案中因為JVM垃圾回收導致的服務中斷,SlabCache方案使用Java NIO DirectByteBuffer技術實現(xiàn)了堆外內(nèi)存存儲,不再由JVM管理數(shù)據(jù)內(nèi)存。默認情況下,系統(tǒng)在初始化的時候會分配兩個緩存區(qū),分別占整個BlockCache大小的80%和20%,每個緩存區(qū)分別存儲固定大小的Block塊,其中前者主要存儲小于等于64K大小的Block,后者存儲小于等于128K Block,如果一個Block太大就會導致兩個區(qū)都無法緩存。和LRUBlockCache相同,SlabCache也使用Least-Recently-Used算法對過期Block進行淘汰。和LRUBlockCache不同的是,SlabCache淘汰Block的時候只需要將對應的bufferbyte標記為空閑,后續(xù)cache對其上的內(nèi)存直接進行覆蓋即可。

  線上集群環(huán)境中,不同表不同列族設置的BlockSize都可能不同,很顯然,默認只能存儲兩種固定大小Block的SlabCache方案不能滿足部分用戶場景,比如用戶設置BlockSize = 256K,簡單使用SlabCache方案就不能達到這部分Block緩存的目的。因此HBase實際實現(xiàn)中將SlabCache和LRUBlockCache搭配使用,稱為DoubleBlockCache。一次隨機讀中,一個Block塊從HDFS中加載出來之后會在兩個Cache中分別存儲一份;緩存讀時首先在LRUBlockCache中查找,如果Cache Miss再在SlabCache中查找,此時如果命中再將該Block放入LRUBlockCache中。

  經(jīng)過實際測試,DoubleBlockCache方案有很多弊端。比如SlabCache設計中固定大小內(nèi)存設置會導致實際內(nèi)存使用率比較低,而且使用LRUBlockCache緩存Block依然會因為JVM GC產(chǎn)生大量內(nèi)存碎片。因此在HBase 0.98版本之后,該方案已經(jīng)被不建議使用。

  BucketCache

  SlabCache方案在實際應用中并沒有很大程度改善原有LRUBlockCache方案的GC弊端,還額外引入了諸如堆外內(nèi)存使用率低的缺陷。然而它的設計并不是一無是處,至少在使用堆外內(nèi)存這個方面給予了阿里大牛們很多啟發(fā)。站在SlabCache的肩膀上,他們開發(fā)了BucketCache緩存方案并貢獻給了社區(qū)。

  BucketCache通過配置可以工作在三種模式下:heap,offheap和file。無論工作在那種模式下,BucketCache都會申請許多帶有固定大小標簽的Bucket,和SlabCache一樣,一種Bucket存儲一種指定BlockSize的數(shù)據(jù)塊,但和SlabCache不同的是,BucketCache會在初始化的時候申請14個不同大小的Bucket,而且即使在某一種Bucket空間不足的情況下,系統(tǒng)也會從其他Bucket空間借用內(nèi)存使用,不會出現(xiàn)內(nèi)存使用率低的情況。接下來再來看看不同工作模式,heap模式表示這些Bucket是從JVM Heap中申請,offheap模式使用DirectByteBuffer技術實現(xiàn)堆外內(nèi)存存儲管理,而file模式使用類似SSD的高速緩存文件存儲數(shù)據(jù)塊。

  實際實現(xiàn)中,HBase將BucketCache和LRUBlockCache搭配使用,稱為CombinedBlockCache。和DoubleBlockCache不同,系統(tǒng)在LRUBlockCache中主要存儲Index Block和Bloom Block,而將Data Block存儲在BucketCache中。因此一次隨機讀需要首先在LRUBlockCache中查到對應的Index Block,然后再到BucketCache查找對應數(shù)據(jù)塊。BucketCache通過更加合理的設計修正了SlabCache的弊端,極大降低了JVM GC對業(yè)務請求的實際影響,但也存在一些問題,比如使用堆外內(nèi)存會存在拷貝內(nèi)存的問題,一定程度上會影響讀寫性能。當然,在后來的版本中這個問題也得到了解決,見HBASE-11425。

  本文是HBase BlockCache系列文章的第一篇,主要概述了HBase中MemStore和BlockCache,再分別對三種BlockCache方案進行了基本介紹。接下來第二篇文章會主要對LRUBlockCache和BucketCache兩種方案進行詳細的介紹,敬請期待!

  Categories:HBaseTags:blockcache, hbase

  MySQL X Plugin來也

  April 12th, 2016AskInside 閱讀(0)Comments off

  MySQL 5.7.12版本發(fā)布,雖然之前5.7已經(jīng)GA,但這個版本依然承上啟下,舉足輕重,因為MySQL X Plugin來了。

  X Plugin extends MySQL Server to be able to function as a d[......]

  閱讀全文

  Categories:MySQLTags:

  HBase BlockCache系列 – 走進BlockCache

  April 8th, 2016范欣欣 閱讀(0)Comments off

  和其他數(shù)據(jù)庫一樣,優(yōu)化IO也是HBase提升性能的不二法寶,而提供緩存更是優(yōu)化的重中之重。最理想的情況是,所有數(shù)據(jù)都能夠緩存到內(nèi)存,這樣就不會有任何文件IO請求,讀寫性能必然會提升到極致。然而現(xiàn)實是殘酷的,隨著請求數(shù)據(jù)的不斷增多,將數(shù)據(jù)全部緩存到內(nèi)存顯得不合實際。幸運的是,我們并不需要將所有數(shù)據(jù)都緩存起來,根據(jù)二八法則,80%的業(yè)務請求都集中在20%的熱點數(shù)據(jù)上,因此將這部分數(shù)據(jù)緩存起就可以極大地提升系統(tǒng)性能。

  HBase在實現(xiàn)中提供了兩種緩存結構:MemStore和BlockCache。其中MemStore稱為寫緩存,HBase執(zhí)行寫操作首先會將數(shù)據(jù)寫入MemStore,并順序?qū)懭際Log,等滿足一定條件后統(tǒng)一將MemStore中數(shù)據(jù)刷新到磁盤,這種設計可以極大地提升HBase的寫性能。不僅如此,MemStore對于讀性能也至關重要,假如沒有MemStore,讀取剛寫入的數(shù)據(jù)就需要從文件中通過IO查找,這種代價顯然是昂貴的!BlockCache稱為讀緩存,HBase會將一次文件查找的Block塊緩存到Cache中,以便后續(xù)同一請求或者鄰近數(shù)據(jù)查找請求,可以直接從內(nèi)存中獲取,避免昂貴的IO操作。MemStore相關知識可以戳這里,本文將重點分析BlockCache。

  在介紹BlockCache之前,簡單地回顧一下HBase中Block的概念,詳細介紹戳這里。 Block是HBase中最小的數(shù)據(jù)存儲單元,默認為64K,在建表語句中可以通過參數(shù)BlockSize指定。HBase中Block分為四種類型:Data Block,Index Block,Bloom Block和Meta Block。其中Data Block用于存儲實際數(shù)據(jù),通常情況下每個Data Block可以存放多條KeyValue數(shù)據(jù)對;Index Block和Bloom Block都用于優(yōu)化隨機讀的查找路徑,其中Index Block通過存儲索引數(shù)據(jù)加快數(shù)據(jù)查找,而Bloom Block通過一定算法可以過濾掉部分一定不存在待查KeyValue的數(shù)據(jù)文件,減少不必要的IO操作;Meta Block主要存儲整個HFile的元數(shù)據(jù)。

  BlockCache是Region Server級別的,一個Region Server只有一個Block Cache,在Region Server啟動的時候完成Block Cache的初始化工作。到目前為止,HBase先后實現(xiàn)了3種Block Cache方案,LRUBlockCache是最初的實現(xiàn)方案,也是默認的實現(xiàn)方案;HBase 0.92版本實現(xiàn)了第二種方案SlabCache,見HBASE-4027;HBase 0.96之后官方提供了另一種可選方案BucketCache,見HBASE-7404。

  這三種方案的不同之處在于對內(nèi)存的管理模式,其中LRUBlockCache是將所有數(shù)據(jù)都放入JVM Heap中,交給JVM進行管理。而后兩者采用了不同機制將部分數(shù)據(jù)存儲在堆外,交給HBase自己管理。這種演變過程是因為LRUBlockCache方案中JVM垃圾回收機制經(jīng)常會導致程序長時間暫停,而采用堆外內(nèi)存對數(shù)據(jù)進行管理可以有效避免這種情況發(fā)生。

  LRUBlockCache

  HBase默認的BlockCache實現(xiàn)方案。Block數(shù)據(jù)塊都存儲在 JVM heap內(nèi),由JVM進行垃圾回收管理。它將內(nèi)存從邏輯上分為了三塊:single-access區(qū)、mutil-access區(qū)、in-memory區(qū),分別占到整個BlockCache大小的25%、50%、25%。一次隨機讀中,一個Block塊從HDFS中加載出來之后首先放入signle區(qū),后續(xù)如果有多次請求訪問到這塊數(shù)據(jù)的話,就會將這塊數(shù)據(jù)移到mutil-access區(qū)。而in-memory區(qū)表示數(shù)據(jù)可以常駐內(nèi)存,一般用來存放訪問頻繁、數(shù)據(jù)量小的數(shù)據(jù),比如元數(shù)據(jù),用戶也可以在建表的時候通過設置列族屬性IN-MEMORY= true將此列族放入in-memory區(qū)。很顯然,這種設計策略類似于JVM中young區(qū)、old區(qū)以及perm區(qū)。無論哪個區(qū),系統(tǒng)都會采用嚴格的Least-Recently-Used算法,當BlockCache總量達到一定閾值之后就會啟動淘汰機制,最少使用的Block會被置換出來,為新加載的Block預留空間。

  SlabCache

  為了解決LRUBlockCache方案中因為JVM垃圾回收導致的服務中斷,SlabCache方案使用Java NIO DirectByteBuffer技術實現(xiàn)了堆外內(nèi)存存儲,不再由JVM管理數(shù)據(jù)內(nèi)存。默認情況下,系統(tǒng)在初始化的時候會分配兩個緩存區(qū),分別占整個BlockCache大小的80%和20%,每個緩存區(qū)分別存儲固定大小的Block塊,其中前者主要存儲小于等于64K大小的Block,后者存儲小于等于128K Block,如果一個Block太大就會導致兩個區(qū)都無法緩存。和LRUBlockCache相同,SlabCache也使用Least-Recently-Used算法對過期Block進行淘汰。和LRUBlockCache不同的是,SlabCache淘汰Block的時候只需要將對應的bufferbyte標記為空閑,后續(xù)cache對其上的內(nèi)存直接進行覆蓋即可。

  線上集群環(huán)境中,不同表不同列族設置的BlockSize都可能不同,很顯然,默認只能存儲兩種固定大小Block的SlabCache方案不能滿足部分用戶場景,比如用戶設置BlockSize = 256K,簡單使用SlabCache方案就不能達到這部分Block緩存的目的。因此HBase實際實現(xiàn)中將SlabCache和LRUBlockCache搭配使用,稱為DoubleBlockCache。一次隨機讀中,一個Block塊從HDFS中加載出來之后會在兩個Cache中分別存儲一份;緩存讀時首先在LRUBlockCache中查找,如果Cache Miss再在SlabCache中查找,此時如果命中再將該Block放入LRUBlockCache中。

  經(jīng)過實際測試,DoubleBlockCache方案有很多弊端。比如SlabCache設計中固定大小內(nèi)存設置會導致實際內(nèi)存使用率比較低,而且使用LRUBlockCache緩存Block依然會因為JVM GC產(chǎn)生大量內(nèi)存碎片。因此在HBase 0.98版本之后,該方案已經(jīng)被不建議使用。

  BucketCache

  SlabCache方案在實際應用中并沒有很大程度改善原有LRUBlockCache方案的GC弊端,還額外引入了諸如堆外內(nèi)存使用率低的缺陷。然而它的設計并不是一無是處,至少在使用堆外內(nèi)存這個方面給予了阿里大牛們很多啟發(fā)。站在SlabCache的肩膀上,他們開發(fā)了BucketCache緩存方案并貢獻給了社區(qū)。

  BucketCache通過配置可以工作在三種模式下:heap,offheap和file。無論工作在那種模式下,BucketCache都會申請許多帶有固定大小標簽的Bucket,和SlabCache一樣,一種Bucket存儲一種指定BlockSize的數(shù)據(jù)塊,但和SlabCache不同的是,BucketCache會在初始化的時候申請14個不同大小的Bucket,而且即使在某一種Bucket空間不足的情況下,系統(tǒng)也會從其他Bucket空間借用內(nèi)存使用,不會出現(xiàn)內(nèi)存使用率低的情況。接下來再來看看不同工作模式,heap模式表示這些Bucket是從JVM Heap中申請,offheap模式使用DirectByteBuffer技術實現(xiàn)堆外內(nèi)存存儲管理,而file模式使用類似SSD的高速緩存文件存儲數(shù)據(jù)塊。

  實際實現(xiàn)中,HBase將BucketCache和LRUBlockCache搭配使用,稱為CombinedBlockCache。和DoubleBlockCache不同,系統(tǒng)在LRUBlockCache中主要存儲Index Block和Bloom Block,而將Data Block存儲在BucketCache中。因此一次隨機讀需要首先在LRUBlockCache中查到對應的Index Block,然后再到BucketCache查找對應數(shù)據(jù)塊。BucketCache通過更加合理的設計修正了SlabCache的弊端,極大降低了JVM GC對業(yè)務請求的實際影響,但也存在一些問題,比如使用堆外內(nèi)存會存在拷貝內(nèi)存的問題,一定程度上會影響讀寫性能。當然,在后來的版本中這個問題也得到了解決,見HBASE-11425。

  本文是HBase BlockCache系列文章的第一篇,主要概述了HBase中MemStore和BlockCache,再分別對三種BlockCache方案進行了基本介紹。接下來第二篇文章會主要對LRUBlockCache和BucketCache兩種方案進行詳細的介紹,敬請期待!。

  Categories:BucketCache, HBase, LRUBlockCache, SlabCacheTags:blockcache

  開源MySQL多線程邏輯導入工具myloader原理與改進

  April 5th, 2016wzpywzh 閱讀(0)Comments off

  在上一篇中,介紹了多線程備份工具mydumper的實現(xiàn)及網(wǎng)易對其所做的優(yōu)化,本篇聊聊與mydumper配合使用的myloader工具。

  myloader是MySQL領域少有的多線程的恢復工具,為了能夠更好的理解其如何進行工作,有必要對mydumper[……]

  閱讀全文

  Categories:MySQLTags:

  支持redis節(jié)點高可用的twemproxy

  April 5th, 2016何李夫 閱讀(419)No comments

  原生twemporxy

  twemproxy支持一個proxy實例同時代理多個分布式集群(server pools),每個集群使用不同的網(wǎng)絡端口實現(xiàn)數(shù)據(jù)流的隔離,下圖中port1應用于cluster1代理,port2應用于cluster2代理:

  

  今天要介紹的是twemproxy對redis節(jié)點高可用的支持,拿上圖的其中一個分布式集群進行示例,邏輯結構如下:

  

  客戶端client流入的請求,在proxy上進行路由分片,然后轉(zhuǎn)發(fā)到后端的redis節(jié)點上存儲或者讀取。事實上,大家已經(jīng)注意到后端的redis節(jié)點只有一個點,在出現(xiàn)異常情況下,是很容易掉線的。按twemproxy的設計,它可以自動識別失效節(jié)點并將其剔除,同時落在原來節(jié)點上的請求會分攤到其余的節(jié)點上。這是分布式緩存系統(tǒng)的一種通用做法,但需要忍受這個失效節(jié)點上的數(shù)據(jù)丟失,這種情況是否可以接受?

  在業(yè)內(nèi),redis雖然被定位為緩存系統(tǒng),但事實上,無論哪種業(yè)務場景(我們接觸過的)都不愿意接受節(jié)點掉線帶來的數(shù)據(jù)丟失,因為那樣對他們系統(tǒng)的影響實在太大了,更有甚者在壓力大的時候引起后端數(shù)據(jù)庫被擊穿的風險。所以,我們打算改造twemproxy,前后總共有幾個版本,下面分享給各位的是我們目前線上在跑的版本。

  定制化改造

  在上圖的基礎上,我們增加了與manager交互的模塊、增加了與sentinel(redis-sentinel)交互的模塊,修改了redis連接管理模塊,圖中三個紅色虛線框所示:

  

  manager交互模塊

  增加連接manager的客戶端交互模塊,用于發(fā)送心跳消息,從心跳應答包里獲取group名稱列表和sentinel列表(IP/PORT信息),即整個分布式集群的配置信息,其中心跳消息帶有版本信息,發(fā)送間隔可配置。

  sentinel交互模塊

  增加與sentinel客戶端交互模塊(IP/PORT信息來自于manager),發(fā)送group名稱給sentinel獲取redis主節(jié)點的IP/PORT信息,一個group對應一個主節(jié)點。取到所有主節(jié)點后,訂閱主從切換頻道,獲取切換消息用于觸發(fā)proxy和主節(jié)點間的連接切換。這里需要解析sentinel的響應消息,會比較繁瑣一些。當proxy開始與sentinel節(jié)點的交互過程,需要啟動定時器,用以控制交互結果,當定時器超時交互未結束(或者proxy未正常工作),proxy將主動切換到下一個sentinel節(jié)點,并啟動新的交互過程??紤]到proxy與sentinel之間網(wǎng)絡連接的重要性(連接假死,proxy收不到主從切換消息,不能正常切換),增加了定時心跳機制,確保這條TCP鏈路的可用性。

  redis連接管理模塊

  原先redis節(jié)點的IP/PORT信息來自于靜態(tài)配置文件,是固定的,而改造以后這些信息是從sentinel節(jié)點獲取。為了確保獲取到的IP/PORT信息的準確性,需要向IP/PORT對應的節(jié)點驗證是否是主節(jié)點的邏輯,只有返回確認是主節(jié)點,才認為是合法的。整個過程,按官方指導實現(xiàn),不存在漏洞。

  詳細消息流

  為了清晰的描述proxy的內(nèi)部處理邏輯,制作了如下消息流圖:

  

  綠色為業(yè)務通道,用于透傳業(yè)務層數(shù)據(jù);

  紫色為命令通道(紅線的細化),用于初始化和節(jié)點主從切換:

箭頭1:manager heartbeat req;

箭頭2:manager heartbeat rsp;

箭頭3:sentinel get-master-addr-by-name req;

箭頭4:sentinel get-master-addr-by-name rsp;

箭頭5:redis auth & role req;

箭頭6:redis auth & role rsp;

箭頭7:sentinel psubscribe +switch-master req;

箭頭8:sentinel psubscribe +switch-master rsp;

箭頭9:sentinel pmessage;

命令通道命令順序按數(shù)字1-8進行,7/8是proxy與sentinel的心跳消息,9是主從切換消息;

  高可用影響面分析

在sentinel節(jié)點切換的過程中,存在proxy正在對外提供業(yè)務服務的狀態(tài),這時候正在處理的數(shù)據(jù)將繼續(xù)處理,不會受到影響,而新接入的客戶端連接將會被拒絕,已有的客戶端連接上的新的業(yè)務請求數(shù)據(jù)也會被拒絕。sentinel節(jié)點切換,對系統(tǒng)的影響是毫秒級別,前面的設計對業(yè)務系統(tǒng)來講會顯得比較友好、不那么粗魯;

而redis節(jié)點的主從切換對系統(tǒng)的影響,主要集中在proxy發(fā)現(xiàn)主節(jié)點異常到sentinel集群做出主從切換這個過程,這段時間內(nèi)落在該節(jié)點上的業(yè)務都將失敗,而該時間段的長度主要依賴在sentinel節(jié)點上的down-after-milliseconds配置字段;

  經(jīng)驗總結

作為代理中間件,支持pipeline的能力有限,容易產(chǎn)生消息積壓,導致客戶端大量超時,所以慎用pipeline功能;

高負荷下容易吃內(nèi)存,struct msg和struct mbuf對象會被大量緩存在進程內(nèi)(內(nèi)存池化);

zero copy,對于多個連續(xù)請求(TCP粘包)進行拆分,拷貝是無法避免的,但是有優(yōu)化空間;

  問卷調(diào)查

  Read more…

  Categories:redisTags:

  HBase – 探索HFile索引機制

  April 3rd, 2016范欣欣 閱讀(0)Comments off

  HFile索引結構解析

  HFile中索引結構根據(jù)索引層級的不同分為兩種:single-level和mutil-level,前者表示單層索引,后者表示多級索引,一般為兩級或三級。HFile V1版本中只有single-level一種索引結構,V2版本中引入多級索引。之所以引入多級索引,是因為隨著HFile文件越來越大,Data Block越來越多,索引數(shù)據(jù)也越來越大,已經(jīng)無法全部加載到內(nèi)存中(V1版本中一個Region Server的索引數(shù)據(jù)加載到內(nèi)存會占用幾乎6G空間),多級索引可以只加載部分索引,降低內(nèi)存使用空間。上一篇文章 《HBase-存儲文件HFile結構解析》,我們提到Bloom Filter內(nèi)存使用問題是促使V1版本升級到V2版本的一個原因,再加上這個原因,這兩個原因就是V1版本升級到V2版本最重要的兩個因素。

  V2版本Index Block有兩類:Root Index Block和NonRoot Index Block,其中NonRoot Index Block又分為Intermediate Index Block和Leaf Index Block兩種。HFile中索引結構類似于一棵樹,Root Index Block表示索引數(shù)根節(jié)點,Intermediate Index Block表示中間節(jié)點,Leaf Index block表示葉子節(jié)點,葉子節(jié)點直接指向?qū)嶋H數(shù)據(jù)塊。

  HFile中除了Data Block需要索引之外,上一篇文章提到過Bloom Block也需要索引,索引結構實際上就是采用了single-level結構,文中Bloom Index Block就是一種Root Index Block。

  對于Data Block,由于HFile剛開始數(shù)據(jù)量較小,索引采用single-level結構,只有Root Index一層索引,直接指向數(shù)據(jù)塊。當數(shù)據(jù)量慢慢變大,Root Index Block滿了之后,索引就會變?yōu)閙util-level結構,由一層索引變?yōu)閮蓪?,根?jié)點指向葉子節(jié)點,葉子節(jié)點指向?qū)嶋H數(shù)據(jù)塊。如果數(shù)據(jù)量再變大,索引層級就會變?yōu)槿龑印?/p>

  下面就針對Root index Block和NonRoot index Block兩種結構進行解析,因為Root Index Block已經(jīng)在上面一篇文章中分析過,此處簡單帶過,重點介紹NonRoot Index Block結構(InterMediate Index Block和Ieaf Index Block在內(nèi)存和磁盤中存儲格式相同,都為NonRoot Index Block格式)。

  Root Index Block

  Root Index Block表示索引樹根節(jié)點索引塊,可以作為bloom的直接索引,也可以作為data索引的根索引。而且對于single-level和mutil-level兩種索引結構對應的Root Index Block略有不同,本文以mutil-level索引結構為例進行分析(single-level索引結構是mutual-level的一種簡化場景),在內(nèi)存和磁盤中的格式如下圖所示:

  

  其中Index Entry表示具體的索引對象,每個索引對象由3個字段組成,Block Offset表示索引指向數(shù)據(jù)塊的偏移量,BlockDataSize表示索引指向數(shù)據(jù)塊在磁盤上的大小,BlockKey表示索引指向數(shù)據(jù)塊中的第一個key。除此之外,還有另外3個字段用來記錄MidKey的相關信息,MidKey表示HFile所有Data Block中中間的一個Data Block,用于在對HFile進行split操作時,快速定位HFile的中間位置。需要注意的是single-level索引結構和mutil-level結構相比,就只缺少MidKey這三個字段。

  Root Index Block會在HFile解析的時候直接加載到內(nèi)存中,此處需要注意在Trailer Block中有一個字段為dataIndexCount,就表示此處Index Entry的個數(shù)。因為Index Entry并不定長,只有知道Entry的個數(shù)才能正確的將所有Index Entry加載到內(nèi)存。

  NonRoot Index Block

  當HFile中Data Block越來越多,single-level結構的索引已經(jīng)不足以支撐所有數(shù)據(jù)都加載到內(nèi)存,需要分化為mutil-level結構。mutil-level結構中NonRoot Index Block作為中間層節(jié)點或者葉子節(jié)點存在,無論是中間節(jié)點還是葉子節(jié)點,其都擁有相同的結構,如下圖所示:

  

  和Root Index Block相同,NonRoot Index Block中最核心的字段也是Index Entry,用于指向葉子節(jié)點塊或者數(shù)據(jù)塊。不同的是,NonRoot Index Block結構中增加了block塊的內(nèi)部索引entry Offset字段,entry Offset表示index Entry在該block中的相對偏移量(相對于第一個index Entry),用于實現(xiàn)block內(nèi)的二分查找。所有非根節(jié)點索引塊,包括Intermediate index block和leaf index block,在其內(nèi)部定位一個key的具體索引并不是通過遍歷實現(xiàn),而是使用二分查找算法,這樣可以更加高效快速地定位到待查找key。

  HFile數(shù)據(jù)完整索引流程

  了解了HFile中數(shù)據(jù)索引塊的兩種結構之后,就來看看如何使用這些索引數(shù)據(jù)塊進行數(shù)據(jù)的高效檢索。整個索引體系類似于MySQL的B+樹結構,但是又有所不同,比B+樹簡單,并沒有復雜的分裂操作。具體見下圖所示:

  

  圖中上面三層為索引層,在數(shù)據(jù)量不大的時候只有最上面一層,數(shù)據(jù)量大了之后開始分裂為多層,最多三層,如圖所示。最下面一層為數(shù)據(jù)層,存儲用戶的實際keyvalue數(shù)據(jù)。這個索引樹結構類似于InnoSQL的聚集索引,只是HBase并沒有輔助索引的概念。

  圖中紅線表示一次查詢的索引過程(HBase中相關類為HFileBlockIndex和HFileReaderV2),基本流程可以表示為:

  1. 用戶輸入rowkey為fb,在root index block中通過二分查找定位到fb在’a’和’m’之間,因此需要訪問索引’a’指向的中間節(jié)點。因為root index block常駐內(nèi)存,所以這個過程很快。

  2. 將索引’a’指向的中間節(jié)點索引塊加載到內(nèi)存,然后通過二分查找定位到fb在index ‘d’和’h’之間,接下來訪問索引’d’指向的葉子節(jié)點。

  3. 同理,將索引’d’指向的中間節(jié)點索引塊加載到內(nèi)存,一樣通過二分查找定位找到fb在index ‘f’和’g’之間,最后需要訪問索引’f’指向的數(shù)據(jù)塊節(jié)點。

  4. 將索引’f’指向的數(shù)據(jù)塊加載到內(nèi)存,通過遍歷的方式找到對應的keyvalue。

  上述流程中因為中間節(jié)點、葉子節(jié)點和數(shù)據(jù)塊都需要加載到內(nèi)存,所以io次數(shù)正常為3次。但是實際上HBase為block提供了緩存機制,可以將頻繁使用的block緩存在內(nèi)存中,可以進一步加快實際讀取過程。所以,在HBase中,通常一次隨機讀請求最多會產(chǎn)生3次io,如果數(shù)據(jù)量?。ㄖ挥幸粚铀饕?,數(shù)據(jù)已經(jīng)緩存到了內(nèi)存,就不會產(chǎn)生io。

  索引塊分裂

  上文中已經(jīng)提到,當數(shù)據(jù)量少、文件小的時候,只需要一個root index block就可以完成索引,即索引樹只有一層。當數(shù)據(jù)不斷寫入,文件變大之后,索引數(shù)據(jù)也會相應變大,索引結構就會由single-level變?yōu)閙ulit-level,期間涉及到索引塊的寫入和分裂,本節(jié)來關注一下數(shù)據(jù)寫入是如何引起索引塊分裂的。

  如果大家之前看過HBase系列另一篇博文《HBase數(shù)據(jù)寫入之Memstore Flush》,可以知道m(xù)emstore flush主要分為3個階段,第一個階段會講memstore中的keyvalue數(shù)據(jù)snapshot,第二階段再將這部分數(shù)據(jù)flush的HFile,并生成在臨時目錄,第三階段將臨時文件移動到指定的ColumnFamily目錄下。很顯然,第二階段將keyvalue數(shù)據(jù)flush到HFile將會是關注的重點(flush相關代碼在DefaultStoreFlusher類中)。整個flush階段又可以分為兩階段:

  1. append階段:memstore中keyvalue首先會寫入到HFile中數(shù)據(jù)塊

  2. finalize階段:修改HFlie中meta元數(shù)據(jù)塊,索引數(shù)據(jù)塊以及Trailer數(shù)據(jù)塊等

  append流程

  具體keyvalue數(shù)據(jù)的append以及finalize過程在HFileWriterV2文件中,其中append流程可以大體表征為:

  

  a. 預檢查:檢查key的大小是否大于前一個key,如果大于則不符合HBase順序排列的原理,拋出異常;檢查value是否是null,如果為null也拋出異常

  b. block是否寫滿:檢查當前Data Block是否已經(jīng)寫滿,如果沒有寫滿就直接寫入keyvalue;否則就需要執(zhí)行數(shù)據(jù)塊落盤以及索引塊修改操作;

  c. 數(shù)據(jù)落盤并修改索引:如果DataBlock寫滿,首先將block塊寫入流;再生成一個leaf index entry,寫入leaf Index block;再檢查該leaf index block是否已經(jīng)寫滿需要落盤,如果已經(jīng)寫滿,就將該leaf index block寫入到輸出流,并且為索引樹根節(jié)點root index block新增一個索引,指向葉子節(jié)點(second-level index)

  d. 生成一個新的block:重新reset輸出流,初始化startOffset為-1

  e. 寫入keyvalue:將keyvalue以流的方式寫入輸出流,同時需要寫入memstoreTS;除此之外,如果該key是當前block的第一個key,需要賦值給變量firstKeyInBlock

  finalize階段

  memstore中所有keyvalue都經(jīng)過append階段輸出到HFile后,會執(zhí)行一次finalize過程,主要更新HFile中meta元數(shù)據(jù)塊、索引數(shù)據(jù)塊以及Trailer數(shù)據(jù)塊,其中對索引數(shù)據(jù)塊的更新是我們關心的重點,此處詳細解析,上述append流程中c步驟’數(shù)據(jù)落盤并修改索引’會使得root index block不斷增多,當增大到一定程度之后就需要分裂,分裂示意圖如下圖所示:

  

  上圖所示,分裂前索引結構為second-level結構,圖中沒有畫出Data Blocks,根節(jié)點索引指向葉子節(jié)點索引塊。finalize階段系統(tǒng)會對Root Index Block進行大小檢查,如果大小大于規(guī)定的大小就需要進行分裂,圖中分裂過程實際上就是將原來的Root Index Block塊分割成4塊,每塊獨立形成中間節(jié)點InterMediate Index Block,系統(tǒng)再重新生成一個Root Index Block(圖中紅色部分),分別指向分割形成的4個interMediate Index Block。此時索引結構就變成了third-level結構。

  總結

  這篇文章是HFile結構解析的第二篇文章,主要集中介紹HFile中的數(shù)據(jù)索引塊。首先分Root Index Block和NonRoot Index Block兩部分對HFile中索引塊進行了解析,緊接著基于此介紹了HBase如何使用索引對數(shù)據(jù)進行檢索,最后結合Memstore Flush的相關知識分析了keyvalue數(shù)據(jù)寫入的過程中索引塊的分裂過程。希望通過這兩篇文章的介紹,能夠?qū)Base中數(shù)據(jù)存儲文件HFile有一個更加全面深入的認識。

  Categories:HBaseTags:HFile, 索引

  新一代列式存儲格式Parquet

  March 28th, 2016feng yu 閱讀(387)No comments

  Apache Parquet是Hadoop生態(tài)圈中一種新型列式存儲格式,它可以兼容Hadoop生態(tài)圈中大多數(shù)計算框架(Hadoop、Spark等),被多種查詢引擎支持(Hive、Impala、Drill等),并且它是語言和平臺無關的。Parquet最初是由Twitter和Cloudera(由于Impala的緣故)合作開發(fā)完成并開源,2015年5月從Apache的孵化器里畢業(yè)成為Apache頂級項目,最新的版本是1.8.1。

  Parquet是什么

  Parquet的靈感來自于2010年Google發(fā)表的Dremel論文,文中介紹了一種支持嵌套結構的存儲格式,并且使用了列式存儲的方式提升查詢性能,在Dremel論文中還介紹了Google如何使用這種存儲格式實現(xiàn)并行查詢的,如果對此感興趣可以參考論文和開源實現(xiàn)Apache Drill。

  嵌套數(shù)據(jù)模型

  在接觸大數(shù)據(jù)之前,我們簡單的將數(shù)據(jù)劃分為結構化數(shù)據(jù)和非結構化數(shù)據(jù),通常我們使用關系數(shù)據(jù)庫存儲結構化數(shù)據(jù),而關系數(shù)據(jù)庫中使用數(shù)據(jù)模型都是扁平式的,遇到諸如List、Map和自定義Struct的時候就需要用戶在應用層解析。但是在大數(shù)據(jù)環(huán)境下,通常數(shù)據(jù)的來源是服務端的埋點數(shù)據(jù),很可能需要把程序中的某些對象內(nèi)容作為輸出的一部分,而每一個對象都可能是嵌套的,所以如果能夠原生的支持這種數(shù)據(jù),這樣在查詢的時候就不需要額外的解析便能獲得想要的結果。例如在Twitter,在他們的生產(chǎn)環(huán)境中一個典型的日志對象(一條記錄)有87個字段,其中嵌套了7層,如下圖:

  

  另外,隨著嵌套格式的數(shù)據(jù)的需求日益增加,目前Hadoop生態(tài)圈中主流的查詢引擎都支持更豐富的數(shù)據(jù)類型,例如Hive、SparkSQL、Impala等都原生的支持諸如struct、map、array這樣的復雜數(shù)據(jù)類型,這樣也就使得諸如Parquet這種原生支持嵌套數(shù)據(jù)類型的存儲格式也變得至關重要,性能也會更好。

  列式存儲

  列式存儲,顧名思義就是按照列進行存儲數(shù)據(jù),把某一列的數(shù)據(jù)連續(xù)的存儲,每一行中的不同列的值離散分布。列式存儲技術并不新鮮,在關系數(shù)據(jù)庫中都已經(jīng)在使用,尤其是在針對OLAP場景下的數(shù)據(jù)存儲,由于OLAP場景下的數(shù)據(jù)大部分情況下都是批量導入,基本上不需要支持單條記錄的增刪改操作,而查詢的時候大多數(shù)都是只使用部分列進行過濾、聚合,對少數(shù)列進行計算(基本不需要select * from xx之類的查詢)。列式存儲可以大大提升這類查詢的性能,較之于行是存儲,列式存儲能夠帶來這些優(yōu)化:

  1、由于每一列中的數(shù)據(jù)類型相同,所以可以針對不同類型的列使用不同的編碼和壓縮方式,這樣可以大大降低數(shù)據(jù)存儲空間。

  2、讀取數(shù)據(jù)的時候可以把映射(Project)下推,只需要讀取需要的列,這樣可以大大減少每次查詢的I/O數(shù)據(jù)量,更甚至可以支持謂詞下推,跳過不滿足條件的列。

  3、由于每一列的數(shù)據(jù)類型相同,可以使用更加適合CPU pipeline的編碼方式,減小CPU的緩存失效。

  Parquet的組成

  Parquet僅僅是一種存儲格式,它是語言、平臺無關的,并且不需要和任何一種數(shù)據(jù)處理框架綁定,目前能夠和Parquet適配的組件包括下面這些,可以看出基本上通常使用的查詢引擎和計算框架都已適配,并且可以很方便的將其它序列化工具生成的數(shù)據(jù)轉(zhuǎn)換成Parquet格式。

  查詢引擎: Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL

  計算框架: MapReduce, Spark, Cascading, Crunch, Scalding, Kite

  數(shù)據(jù)模型: Avro, Thrift, Protocol Buffers, POJOs

  項目組成

  Parquet項目由以下幾個子項目組成:

  parquet-format項目由java實現(xiàn),它定義了所有Parquet元數(shù)據(jù)對象,Parquet的元數(shù)據(jù)是使用Apache Thrift進行序列化并存儲在Parquet文件的尾部。

  parquet-mr項目由java實現(xiàn),它包括多個模塊,包括實現(xiàn)了讀寫Parquet文件的功能,并且提供一些和其它組件適配的工具,例如Hadoop Input/Output Formats、Hive Serde(目前Hive已經(jīng)自帶Parquet了)、Pig loaders等。

  parquet-compatibility項目,包含不同編程語言之間(JAVA和C/C++)讀寫文件的測試代碼。

  parquet-cpp項目,它是用于用于讀寫Parquet文件的C++庫。

  下圖展示了Parquet各個組件的層次以及從上到下交互的方式。

  

  數(shù)據(jù)存儲層定義了Parquet的文件格式,其中元數(shù)據(jù)在parquet-format中定義,包括Parquet原始類型定義、Page類型、編碼類型、壓縮類型等等。

  對象轉(zhuǎn)換層完成其他對象模型與Parquet內(nèi)部數(shù)據(jù)模型的映射和轉(zhuǎn)換,Parquet的編碼方式使用的是striping and assembly算法。

  對象模型層定義了如何讀取Parquet文件的內(nèi)容,這一層轉(zhuǎn)換包括Avro、Thrift、PB等序列化格式、Hive serde等的適配。并且為了幫助大家理解和使用,Parquet提供了org.apache.parquet.example包實現(xiàn)了java對象和Parquet文件的轉(zhuǎn)換。

  數(shù)據(jù)模型

  Parquet支持嵌套的數(shù)據(jù)模型,類似于Protocol Buffers,每一個數(shù)據(jù)模型的schema包含多個字段,每一個字段又可以包含多個字段,每一個字段有三個屬性:重復數(shù)、數(shù)據(jù)類型和字段名,重復數(shù)可以是以下三種:required(出現(xiàn)1次),repeated(出現(xiàn)0次或多次),optional(出現(xiàn)0次或1次)。每一個字段的數(shù)據(jù)類型可以分成兩種:group(復雜類型)和primitive(基本類型)。例如Dremel中提供的Document的schema示例,它的定義如下:

  message Document {

  required int64 DocId;

  optional group Links {

  repeated int64 Backward;

  repeated int64 Forward;

  }

  repeated group Name {

  repeated group Language {

  required string Code;

  optional string Country;

  }

  optional string Url;

  }

  }

  可以把這個Schema轉(zhuǎn)換成樹狀結構,根節(jié)點可以理解為repeated類型,如下圖:

  

  可以看出在Schema中所有的基本類型字段都是葉子節(jié)點,在這個Schema中一共存在6個葉子節(jié)點,如果把這樣的Schema轉(zhuǎn)換成扁平式的關系模型,就可以理解為該表包含六個列。Parquet中沒有Map、Array這樣的復雜數(shù)據(jù)結構,但是可以通過repeated和group組合來實現(xiàn)這樣的需求。在這個包含6個字段的表中有以下幾個字段和每一條記錄中它們可能出現(xiàn)的次數(shù):

  DocId int64 只能出現(xiàn)一次

  Links.Backward int64 可能出現(xiàn)任意多次,但是如果出現(xiàn)0次則需要使用NULL標識

  Links.Forward int64 同上

  Name.Language.Code string 同上

  Name.Language.Country string 同上

  Name.Url string 同上

  由于在一個表中可能存在出現(xiàn)任意多次的列,對于這些列需要標示出現(xiàn)多次或者等于NULL的情況,它是由Striping/Assembly算法實現(xiàn)的。

  Striping/Assembly算法

  上文介紹了Parquet的數(shù)據(jù)模型,在Document中存在多個非required列,由于Parquet一條記錄的數(shù)據(jù)分散的存儲在不同的列中,如何組合不同的列值組成一條記錄是由Striping/Assembly算法決定的,在該算法中列的每一個值都包含三部分:value、repetition level和definition level。

  Repetition Levels

  為了支持repeated類型的節(jié)點,在寫入的時候該值等于它和前面的值在哪一層節(jié)點是不共享的。在讀取的時候根據(jù)該值可以推導出哪一層上需要創(chuàng)建一個新的節(jié)點,例如對于這樣的一個schema和兩條記錄。

  message nested {

  repeated group leve1 {

  repeated string leve2;

  }

  }

  r1:[[a,b,c,] , [d,e,f,g]]

  r2:[[h] , [i,j]]

  計算repetition level值的過程如下:

  value=a是一條記錄的開始,和前面的值(已經(jīng)沒有值了)在根節(jié)點(第0層)上是不共享的,所以repeated level=0.

  value=b它和前面的值共享了level1這個節(jié)點,但是level2這個節(jié)點上是不共享的,所以repeated level=2.

  同理value=c, repeated level=2.

  value=d和前面的值共享了根節(jié)點(屬于相同記錄),但是在level1這個節(jié)點上是不共享的,所以repeated level=1.

網(wǎng)速4M等于多少KB/S,等于多少kbps

  value=h和前面的值不屬于同一條記錄,也就是不共享任何節(jié)點,所以repeated level=0.

  根據(jù)以上的分析每一個value需要記錄的repeated level值如下:

  

  在讀取的時候,順序的讀取每一個值,然后根據(jù)它的repeated level創(chuàng)建對象,當讀取value=a時repeated level=0,表示需要創(chuàng)建一個新的根節(jié)點(新記錄),value=b時repeated level=2,表示需要創(chuàng)建一個新的level2節(jié)點,value=d時repeated level=1,表示需要創(chuàng)建一個新的level1節(jié)點,當所有列讀取完成之后可以創(chuàng)建一條新的記錄。本例中當讀取文件構建每條記錄的結果如下:

  

  可以看出repeated level=0表示一條記錄的開始,并且repeated level的值只是針對路徑上的repeated類型的節(jié)點,因此在計算該值的時候可以忽略非repeated類型的節(jié)點,在寫入的時候?qū)⑵淅斫鉃樵摴?jié)點和路徑上的哪一個repeated節(jié)點是不共享的,讀取的時候?qū)⑵淅斫鉃樾枰谀囊粚觿?chuàng)建一個新的repeated節(jié)點,這樣的話每一列最大的repeated level值就等于路徑上的repeated節(jié)點的個數(shù)(不包括根節(jié)點)。減小repeated level的好處能夠使得在存儲使用更加緊湊的編碼方式,節(jié)省存儲空間。

  Definition Levels

  有了repeated level我們就可以構造出一個記錄了,為什么還需要definition levels呢?由于repeated和optional類型的存在,可能一條記錄中某一列是沒有值的,假設我們不記錄這樣的值就會導致本該屬于下一條記錄的值被當做當前記錄的一部分,從而造成數(shù)據(jù)的錯誤,因此對于這種情況需要一個占位符標示這種情況。

  definition level的值僅僅對于空值是有效的,表示在該值的路徑上第幾層開始是未定義的,對于非空的值它是沒有意義的,因為非空值在葉子節(jié)點是定義的,所有的父節(jié)點也肯定是定義的,因此它總是等于該列最大的definition levels。例如下面的schema。

  message ExampleDefinitionLevel {

  optional group a {

  optional group b {

  optional string c;

  }

  }

  }

  它包含一個列a.b.c,這個列的的每一個節(jié)點都是optional類型的,當c被定義時a和b肯定都是已定義的,當c未定義時我們就需要標示出在從哪一層開始時未定義的,如下面的值:

  

  由于definition level只需要考慮未定義的值,而對于repeated類型的節(jié)點,只要父節(jié)點是已定義的,該節(jié)點就必須定義(例如Document中的DocId,每一條記錄都該列都必須有值,同樣對于Language節(jié)點,只要它定義了Code必須有值),所以計算definition level的值時可以忽略路徑上的required節(jié)點,這樣可以減小definition level的最大值,優(yōu)化存儲。

  一個完整的例子

  本節(jié)我們使用Dremel論文中給的Document示例和給定的兩個值r1和r2展示計算repeated level和definition level的過程,這里把未定義的值記錄為NULL,使用R表示repeated level,D表示definition level。

  

  首先看DocuId這一列,對于r1,DocId=10,由于它是記錄的開始并且是已定義的,所以R=0,D=0,同樣r2中的DocId=20,R=0,D=0。

  對于Links.Forward這一列,在r1中,它是未定義的但是Links是已定義的,并且是該記錄中的第一個值,所以R=0,D=1,在r1中該列有兩個值,value1=10,R=0(記錄中該列的第一個值),D=2(該列的最大definition level)。

  對于Name.Url這一列,r1中它有三個值,分別為url1=’https://A’,它是r1中該列的第一個值并且是定義的,所以R=0,D=2;value2=’https://B’,和上一個值value1在Name這一層是不相同的,所以R=1,D=2;value3=NULL,和上一個值value2在Name這一層是不相同的,所以R=1,但它是未定義的,而Name這一層是定義的,所以D=1。r2中該列只有一個值value3=’https://C’,R=0,D=2.

  最后看一下Name.Language.Code這一列,r1中有4個值,value1=’en-us’,它是r1中的第一個值并且是已定義的,所以R=0,D=2(由于Code是required類型,這一列repeated level的最大值等于2);value2=’en’,它和value1在Language這個節(jié)點是不共享的,所以R=2,D=2;value3=NULL,它是未定義的,但是它和前一個值在Name這個節(jié)點是不共享的,在Name這個節(jié)點是已定義的,所以R=1,D=1;value4=’en-gb’,它和前一個值在Name這一層不共享,所以R=1,D=2。在r2中該列有一個值,它是未定義的,但是Name這一層是已定義的,所以R=0,D=1.

  Parquet文件格式

  Parquet文件是以二進制方式存儲的,所以是不可以直接讀取的,文件中包括該文件的數(shù)據(jù)和元數(shù)據(jù),因此Parquet格式文件是自解析的。在HDFS文件系統(tǒng)和Parquet文件中存在如下幾個概念。

  HDFS塊(Block):它是HDFS上的最小的副本單位,HDFS會把一個Block存儲在本地的一個文件并且維護分散在不同的機器上的多個副本,通常情況下一個Block的大小為256M、512M等。

  HDFS文件(File):一個HDFS的文件,包括數(shù)據(jù)和元數(shù)據(jù),數(shù)據(jù)分散存儲在多個Block中。

  行組(Row Group):按照行將數(shù)據(jù)物理上劃分為多個單元,每一個行組包含一定的行數(shù),在一個HDFS文件中至少存儲一個行組,Parquet讀寫的時候會將整個行組緩存在內(nèi)存中,所以如果每一個行組的大小是由內(nèi)存大的小決定的,例如記錄占用空間比較小的Schema可以在每一個行組中存儲更多的行。

  列塊(Column Chunk):在一個行組中每一列保存在一個列塊中,行組中的所有列連續(xù)的存儲在這個行組文件中。一個列塊中的值都是相同類型的,不同的列塊可能使用不同的算法進行壓縮。

  頁(Page):每一個列塊劃分為多個頁,一個頁是最小的編碼的單位,在同一個列塊的不同頁可能使用不同的編碼方式。

  文件格式

  通常情況下,在存儲Parquet數(shù)據(jù)的時候會按照Block大小設置行組的大小,由于一般情況下每一個Mapper任務處理數(shù)據(jù)的最小單位是一個Block,這樣可以把每一個行組由一個Mapper任務處理,增大任務執(zhí)行并行度。Parquet文件的格式如下圖所示。

  

  上圖展示了一個Parquet文件的內(nèi)容,一個文件中可以存儲多個行組,文件的首位都是該文件的Magic Code,用于校驗它是否是一個Parquet文件,F(xiàn)ooter length了文件元數(shù)據(jù)的大小,通過該值和文件長度可以計算出元數(shù)據(jù)的偏移量,文件的元數(shù)據(jù)中包括每一個行組的元數(shù)據(jù)信息和該文件存儲數(shù)據(jù)的Schema信息。除了文件中每一個行組的元數(shù)據(jù),每一頁的開始都會存儲該頁的元數(shù)據(jù),在Parquet中,有三種類型的頁:數(shù)據(jù)頁、字典頁和索引頁。數(shù)據(jù)頁用于存儲當前行組中該列的值,字典頁存儲該列值的編碼字典,每一個列塊中最多包含一個字典頁,索引頁用來存儲當前行組下該列的索引,目前Parquet中還不支持索引頁,但是在后面的版本中增加。

  在執(zhí)行MR任務的時候可能存在多個Mapper任務的輸入是同一個Parquet文件的情況,每一個Mapper通過InputSplit標示處理的文件范圍,如果多個InputSplit跨越了一個Row Group,Parquet能夠保證一個Row Group只會被一個Mapper任務處理。

  映射下推(Project PushDown)

  說到列式存儲的優(yōu)勢,映射下推是最突出的,它意味著在獲取表中原始數(shù)據(jù)時只需要掃描查詢中需要的列,由于每一列的所有值都是連續(xù)存儲的,所以分區(qū)取出每一列的所有值就可以實現(xiàn)TableScan算子,而避免掃描整個表文件內(nèi)容。

  在Parquet中原生就支持映射下推,執(zhí)行查詢的時候可以通過Configuration傳遞需要讀取的列的信息,這些列必須是Schema的子集,映射每次會掃描一個Row Group的數(shù)據(jù),然后一次性得將該Row Group里所有需要的列的Cloumn Chunk都讀取到內(nèi)存中,每次讀取一個Row Group的數(shù)據(jù)能夠大大降低隨機讀的次數(shù),除此之外,Parquet在讀取的時候會考慮列是否連續(xù),如果某些需要的列是存儲位置是連續(xù)的,那么一次讀操作就可以把多個列的數(shù)據(jù)讀取到內(nèi)存。

  謂詞下推(Predicate PushDown)

  在數(shù)據(jù)庫之類的查詢系統(tǒng)中最常用的優(yōu)化手段就是謂詞下推了,通過將一些過濾條件盡可能的在最底層執(zhí)行可以減少每一層交互的數(shù)據(jù)量,從而提升性能,例如”select count(1) from A Join B on A.id = B.id where A.a > 10 and B.b < 100″SQL查詢中,在處理Join操作之前需要首先對A和B執(zhí)行TableScan操作,然后再進行Join,再執(zhí)行過濾,最后計算聚合函數(shù)返回,但是如果把過濾條件A.a > 10和B.b < 100分別移到A表的TableScan和B表的TableScan的時候執(zhí)行,可以大大降低Join操作的輸入數(shù)據(jù)。

  無論是行式存儲還是列式存儲,都可以在將過濾條件在讀取一條記錄之后執(zhí)行以判斷該記錄是否需要返回給調(diào)用者,在Parquet做了更進一步的優(yōu)化,優(yōu)化的方法時對每一個Row Group的每一個Column Chunk在存儲的時候都計算對應的統(tǒng)計信息,包括該Column Chunk的最大值、最小值和空值個數(shù)。通過這些統(tǒng)計值和該列的過濾條件可以判斷該Row Group是否需要掃描。另外Parquet未來還會增加諸如Bloom Filter和Index等優(yōu)化數(shù)據(jù),更加有效的完成謂詞下推。

  在使用Parquet的時候可以通過如下兩種策略提升查詢性能:1、類似于關系數(shù)據(jù)庫的主鍵,對需要頻繁過濾的列設置為有序的,這樣在導入數(shù)據(jù)的時候會根據(jù)該列的順序存儲數(shù)據(jù),這樣可以最大化的利用最大值、最小值實現(xiàn)謂詞下推。2、減小行組大小和頁大小,這樣增加跳過整個行組的可能性,但是此時需要權衡由于壓縮和編碼效率下降帶來的I/O負載。

  性能

  相比傳統(tǒng)的行式存儲,Hadoop生態(tài)圈近年來也涌現(xiàn)出諸如RC、ORC、Parquet的列式存儲格式,它們的性能優(yōu)勢主要體現(xiàn)在兩個方面:1、更高的壓縮比,由于相同類型的數(shù)據(jù)更容易針對不同類型的列使用高效的編碼和壓縮方式。2、更小的I/O操作,由于映射下推和謂詞下推的使用,可以減少一大部分不必要的數(shù)據(jù)掃描,尤其是表結構比較龐大的時候更加明顯,由此也能夠帶來更好的查詢性能。

  

  上圖是展示了使用不同格式存儲TPC-H和TPC-DS數(shù)據(jù)集中兩個表數(shù)據(jù)的文件大小對比,可以看出Parquet較之于其他的二進制文件存儲格式能夠更有效的利用存儲空間,而新版本的Parquet(2.0版本)使用了更加高效的頁存儲方式,進一步的提升存儲空間。

  

  上圖展示了Twitter在Impala中使用不同格式文件執(zhí)行TPC-DS基準測試的結果,測試結果可以看出Parquet較之于其他的行式存儲格式有較明顯的性能提升。

  

  上圖展示了criteo公司在Hive中使用ORC和Parquet兩種列式存儲格式執(zhí)行TPC-DS基準測試的結果,測試結果可以看出在數(shù)據(jù)存儲方面,兩種存儲格式在都是用snappy壓縮的情況下量中存儲格式占用的空間相差并不大,查詢的結果顯示Parquet格式稍好于ORC格式,兩者在功能上也都有優(yōu)缺點,Parquet原生支持嵌套式數(shù)據(jù)結構,而ORC對此支持的較差,這種復雜的Schema查詢也相對較差;而Parquet不支持數(shù)據(jù)的修改和ACID,但是ORC對此提供支持,但是在OLAP環(huán)境下很少會對單條數(shù)據(jù)修改,更多的則是批量導入。

  項目發(fā)展

  自從2012年由Twitter和Cloudera共同研發(fā)Parquet開始,該項目一直處于高速發(fā)展之中,并且在項目之初就將其貢獻給開源社區(qū),2013年,Criteo公司加入開發(fā)并且向Hive社區(qū)提交了向hive集成Parquet的patch(HIVE-5783),在Hive 0.13版本之后正式加入了Parquet的支持;之后越來越多的查詢引擎對此進行支持,也進一步帶動了Parquet的發(fā)展。

  目前Parquet正處于向2.0版本邁進的階段,在新的版本中實現(xiàn)了新的Page存儲格式,針對不同的類型優(yōu)化編碼算法,另外豐富了支持的原始類型,增加了Decimal、Timestamp等類型的支持,增加更加豐富的統(tǒng)計信息,例如Bloon Filter,能夠盡可能得將謂詞下推在元數(shù)據(jù)層完成。

  總結

  本文介紹了一種支持嵌套數(shù)據(jù)模型對的列式存儲系統(tǒng)Parquet,作為大數(shù)據(jù)系統(tǒng)中OLAP查詢的優(yōu)化方案,它已經(jīng)被多種查詢引擎原生支持,并且部分高性能引擎將其作為默認的文件存儲格式。通過數(shù)據(jù)編碼和壓縮,以及映射下推和謂詞下推功能,Parquet的性能也較之其它文件格式有所提升,可以預見,隨著數(shù)據(jù)模型的豐富和Ad hoc查詢的需求,Parquet將會被更廣泛的使用。

  更多技術分享,請關注網(wǎng)易視頻云官方網(wǎng)站(https://vcloud.163.com/)或者網(wǎng)易視頻云官方微信(vcloud163)。

轉(zhuǎn)載請注明來自夕逆IT,本文標題:《網(wǎng)速4M等于多少KB/S,等于多少kbps》

每一天,每一秒,你所做的決定都會改變你的人生!

發(fā)表評論

快捷回復:

評論列表 (暫無評論,25人圍觀)參與討論

還沒有評論,來說兩句吧...

青青草原97在线| 中文字幕精品久久一区二区三区| 中文字幕人妻丝袜一区三| 激情另类图区| 久久精品首页| 国产巨大乳一区二区| 好爽毛片一区二区三区四无码视色 | 国产三级黄色在线网址| 麻豆av一区二区三区久久为用户带来| 国产慕精品无码| 久久精品国产一级| 久草婷婷| 日韩无马一区二区三区| 亚洲国产毛片一区二区三区| 一本大道中文日本香蕉| 在线中文字幕亚洲第一| 亚洲国产天堂一区二区三区| 国产亚洲精品无码成人| 久久精品日韩亚洲欧洲免费无码| 极品亚洲久久| 久久久精品94久久精品| 欧美偷拍中文字幕| 自拍偷拍九九| 52精品免费视频国产专区| AA成人精品视频| 亚洲中文字幕久久精品无码一区| 牲欲强老熟女乱| 不卡中文字幕一二三区| 国产欧阳娜娜换脸av| 人成精品视频三区二区一区| 日产久久精品| 伊人 色 婷婷| 免费av导航在线| 日韩高清无码一级特黄免费网站| 国产欧美日韩图片一区二区| 都市亚洲图片小说专区校园| 色日韩一区| 操操操色呦呦| 久久久久人妻精品二区| 亚洲第一精品在线观看AV| 2020天堂在线亚洲精品专区|