Hbase

HBase存儲(chǔ)架構(gòu)圖

Hbase Overview.png

HBase Master

  • 為Region server分配region
  • 負(fù)責(zé)Region server的負(fù)載均衡
  • 發(fā)現(xiàn)失效的Region server并重新分配其上的region
  • HDFS上的垃圾文件回收(刪除表后的遺留文件)
  • 處理schema更新請(qǐng)求(對(duì)表的增刪改查)

HBase RegionServer

  • 維護(hù)master分配給他的region竞漾,處理對(duì)這些region的io請(qǐng)求
  • 負(fù)責(zé)切分正在運(yùn)行過程中變的過大的region

HBase 數(shù)據(jù)讀取過程

Hbase里有一張?zhí)厥獾脑獢?shù)據(jù)表"hbase:meta",它保存著hbase集群中所有region所處的主機(jī)位置信息足画。而zookeeper中保存這這張表所在的主機(jī)位置信息蟀淮。

Hbase Read.png

region定位過程

  1. client首先從zookeeper獲取meta表所在的region server
  2. client查詢出meta表中包含所需查詢key范圍的region所在region server跑揉。

同時(shí)client會(huì)緩存下region與region server的映射信息,避免重復(fù)查詢冕象。如果region由于split或者balancing等原因改變了對(duì)應(yīng)的region server 丁鹉,則client會(huì)重新從zk中查詢一遍胆敞,并再次緩存。

  1. 連接此region server携悯,查詢祭芦。

client與region server查詢交互

  1. 查詢r(jià)egion server的讀緩存BlockCache 是否存在rowkey對(duì)應(yīng)數(shù)據(jù),如果有就返回憔鬼,沒有的話就行進(jìn)行第二步查詢龟劲。
  2. 查詢memstore(一個(gè)按key排序的樹形結(jié)構(gòu)的緩沖區(qū)),即寫內(nèi)存是否存儲(chǔ)有待查rowkey數(shù)據(jù)逊彭,如果有就返回咸灿,沒有進(jìn)行第三步查詢;
  3. 用Block Cache indexes和bloom filters加載對(duì)應(yīng)的HFile侮叮,根據(jù)rowkey遍歷查詢數(shù)據(jù)避矢,不管有沒有都返回到client。

HBase 數(shù)據(jù)寫入過程

write_path.png

HBase WAL(write ahead log)

WAL.png

一個(gè)regionserver上所有的region共享一個(gè)HLog囊榜,一次數(shù)據(jù)的提交是先寫WAL审胸,再寫memstore
HLog類
實(shí)現(xiàn)了WAL的類叫做HLog,當(dāng)hregion被實(shí)例化時(shí),HLog實(shí)例會(huì)被當(dāng)做一個(gè)參數(shù)傳到HRegion的構(gòu)造器中卸勺,當(dāng)一個(gè)Region接收到一個(gè)更新操作時(shí)砂沛,它可以直接把數(shù)據(jù)保存到一個(gè)共享的WAL實(shí)例中去.
HLog.png

HLogKey類
1、當(dāng)前的WAL使用的是hadoop的sequencefile格式曙求,其key是HLogKey實(shí)例碍庵。HLogKey中記錄了寫入數(shù)據(jù)的歸屬信息映企,除了table和region名字外,同時(shí)還包括sequence number和timestamp静浴,timestamp是“寫入時(shí)間“堰氓,sequence number的起始值為0,或者是最近一次存入文件系統(tǒng)中sequence number苹享。Region打開存儲(chǔ)文件双絮,讀取每個(gè)HFile中的最大的sequence number,如果該值大于HLog 的sequence number, 就將它作為HLog 的sequence number的值得问。最后當(dāng)讀取了所有hfile的sequence number囤攀,hlog也就獲得了最近一次數(shù)據(jù)持久化的位置。
2宫纬、HLog sequence File的value是HBase的KeyValue對(duì)象焚挠,即對(duì)應(yīng)HFile中的KeyValue
WALEdit類
1、客戶端發(fā)送的每個(gè)修改都會(huì)封裝成WALEdit類哪怔,一個(gè)WALEdit類包含了多個(gè)更新操作宣蔚,可以說一個(gè)WALEdit就是一個(gè)原子操作,包含若干個(gè)操作的集合

LogSyncer類
1认境、Table在創(chuàng)建的時(shí)候胚委,有一個(gè)參數(shù)可以設(shè)置毫别,是否每次寫Log日志都需要往集群的其他機(jī)器同步一次欲账,默認(rèn)是每次都同步本谜,同步的開銷是比較大的炸裆,但不及時(shí)同步又可能因?yàn)闄C(jī)器宕而丟日志。同步的操作現(xiàn)在是通過pipeline的方式來實(shí)現(xiàn)的,pipeline是指datanode接收數(shù)據(jù)后笆凌,再傳給另外一臺(tái)datanode赊窥,是一種串行的方式靶橱,n-Way writes是指多datanode同時(shí)接收數(shù)據(jù)佳遂,最慢的一臺(tái)結(jié)束就是整個(gè)結(jié)束营袜,差別在于一個(gè)延遲大,一個(gè)并發(fā)高丑罪,hdfs現(xiàn)在正在開發(fā)中荚板,以便可以選擇是按pipeline還是n-way writes來實(shí)現(xiàn)寫操作
2、Table如果設(shè)置每次不同步吩屹,則寫操作會(huì)被RegionServer緩存跪另,并啟動(dòng)一個(gè)LogSyncer線程來定時(shí)同步日志。

hbase.regionserver.optionallogflushinterval:將Hlog同步到HDFS的間隔煤搜。如果Hlog沒有積累到一定的數(shù)量免绿,到了時(shí)間,也會(huì)觸發(fā)同步擦盾。默認(rèn)是1秒嘲驾,單位毫秒淌哟。

LogRoller類

  1. 日志寫入的大小是有限制的,LogRoller類會(huì)作為一個(gè)后臺(tái)線程運(yùn)行距淫,在特定的時(shí)間間隔內(nèi)滾動(dòng)日志绞绒,通過hbase.regionserver.logroll.period屬性控制婶希,默認(rèn)1小時(shí)榕暇。所以每60分鐘,會(huì)打開一個(gè)新的log文件喻杈。久而久之彤枢,會(huì)有一大堆的文件需要維護(hù)。首先筒饰,LogRoller調(diào)用HLog.rollWriter()缴啡,定時(shí)滾動(dòng)日志,之后瓷们,利用HLog.cleanOldLogs()可以清除舊的日志业栅。它首先取得所有存儲(chǔ)文件HFile中的最大的sequence number,之后檢查是否存在一個(gè)log所有的條目的“sequence number”均低于這個(gè)值谬晕,如果存在碘裕,將刪除這個(gè)log。
  2. log file的數(shù)目超過了log files的最大值攒钳。這時(shí)帮孔,會(huì)強(qiáng)制調(diào)用flush out 以減少log的數(shù)目。

“hbase.regionserver.hlog.blocksize”和“hbase.regionserver.logroll.multiplier”兩個(gè)參數(shù)默認(rèn)將在log大小為SequenceFile(默認(rèn)為64MB)的95%時(shí)回滾不撑。所以文兢,log的大小和log使用的時(shí)間都會(huì)導(dǎo)致回滾,以先到達(dá)哪個(gè)限定為準(zhǔn)焕檬。

HBase Meta Table

HBase Meta Table.png

hbase meta 表保存所有hbase集群中所有的region信息

META Table.png
Meta Table Structure.png
  • regionId = 創(chuàng)建region時(shí)的timestamp+"."+encode值(舊版hbase的regionId只有時(shí)間戳)+"."
  • name = tablename+","+startKey+","+regionId(等同于rowkey)
  • startKey姆坚,region的開始key,第一個(gè)region的startKey是空字符串
  • endKey实愚,region的結(jié)束key兼呵,最后一個(gè)region的endKey是空字符串。當(dāng)startKey,endKey都為空則表示只有一個(gè)region
  • encoded(Hash值)爆侣,該值會(huì)作為hdfs文件系統(tǒng)中對(duì)應(yīng)region的目錄名
  • serverstartcode 是服務(wù)開始的時(shí)候的timestamp
  • server 指服務(wù)器的地址和端口
  • seqnumDuringOpen:?
hbase sample data.png

hbase原先的設(shè)計(jì)還含有一張-ROOT-表萍程,用來保存meta表的region信息。HBase 0.96 版本移除了這個(gè)特性(HBASE-3171)兔仰,并且設(shè)置meta表再大的數(shù)據(jù)量也不會(huì)像普通表一樣進(jìn)行split茫负,因此meta表總是只有一個(gè)region(HBASE-2415)。

HBase的后臺(tái)合并

小合并:把小HFile合并成一個(gè)大HFile乎赴,這樣可以避免在讀一行的時(shí)候引用過多文件忍法,提升讀性能潮尝。在執(zhí)行合并的時(shí)候,HBase讀出已有的多個(gè)HFile內(nèi)容饿序,并把記錄寫入一個(gè)新文件勉失。然后把新文件設(shè)置為激活狀態(tài),刪除所有老文件原探,它會(huì)占用大量的磁盤和網(wǎng)絡(luò)IO乱凿,但相比大合并還是輕量級(jí)的,可以頻繁發(fā)生咽弦。

HBase Minor Compaction.png

大合并:處理給定region的一個(gè)列族的所有HFile徒蟆,將這個(gè)列族所有的HFile合并成一個(gè)文件。這個(gè)動(dòng)作相當(dāng)耗費(fèi)資源型型,可以從Shell中手工觸發(fā)大合并段审,這也是清理被刪除記錄的唯一機(jī)會(huì)。
HBase Major Compaction.png

從合并的操作可以看出闹蒜,HBase其實(shí)不適合存儲(chǔ)經(jīng)常刪改的數(shù)據(jù)寺枉,因?yàn)閯h除的記錄在大合并前依舊占用空間,而大合并又十分耗費(fèi)資源绷落。

HBase的Delete命令并不立即刪除內(nèi)容姥闪,而是針對(duì)那個(gè)內(nèi)容寫入一條新的刪除標(biāo)記,這個(gè)刪除標(biāo)記叫做“墓碑”(tombstone)嘱函。被標(biāo)記的內(nèi)容不能在Get和Scan操作中返回結(jié)果甘畅。作為磁盤文件,HFile在非合并的時(shí)候是不能被改變的往弓。且因?yàn)椤澳贡庇涗洸⒉灰欢ê捅粍h除的記錄在同一個(gè)HFile里面疏唾,所以HFile只有在執(zhí)行一次大合并的時(shí)候才會(huì)處理墓碑記錄,被刪除記錄占用的空間才會(huì)被釋放函似。

Hbase Memstore&Flush

Memstore Usage in HBase ReadWrite Paths.png

用到Memstore最主要的原因是:存儲(chǔ)在HDFS上的數(shù)據(jù)需要按照row key 排序槐脏。而HDFS本身被設(shè)計(jì)為順序讀寫(sequential reads/writes),不允許修改撇寞。這樣的話顿天,HBase就不能夠高效的寫數(shù)據(jù),因?yàn)橐獙懭氲紿Base的數(shù)據(jù)不會(huì)被排序蔑担,這也就意味著沒有為將來的檢索優(yōu)化牌废。為了解決這個(gè)問題,HBase將最近接收到的數(shù)據(jù)緩存在內(nèi)存中(in Memstore)啤握,在持久化到HDFS之前完成排序鸟缕,然后再快速的順序?qū)懭際DFS。
除了解決“無序”問題外,Memstore還有一些其他的好處:

  • 作為一個(gè)內(nèi)存級(jí)緩存懂从,緩存最近增加數(shù)據(jù)授段。一種顯而易見的場(chǎng)合是,新插入數(shù)據(jù)總是比老數(shù)據(jù)頻繁使用番甩。
  • 在持久化寫入之前侵贵,在內(nèi)存中對(duì)Rows/Cells可以做某些優(yōu)化。比如缘薛,當(dāng)數(shù)據(jù)的version被設(shè)為1的時(shí)候窍育,對(duì)于某些CF的一些數(shù)據(jù),Memstore緩存了數(shù)個(gè)對(duì)該Cell的更新掩宜,在寫入HFile的時(shí)候蔫骂,僅需要保存一個(gè)最新的版本就好了,其他的都可以直接拋棄牺汤。

每一次Memstore的flush,會(huì)為每一個(gè)ColumnFamily創(chuàng)建一個(gè)新的HFile浩嫌。

MemStore的最小flush單元是Region而不是單個(gè)MemStore檐迟。可想而知码耐,如果一個(gè)Region中Memstore過多追迟,當(dāng)其中某一個(gè)Memstore滿足flush條件,則該region對(duì)應(yīng)的所有Memstore都會(huì)被flush骚腥,這樣每次flush的開銷必然會(huì)很大敦间,因此我們也建議在進(jìn)行表設(shè)計(jì)的時(shí)候盡量減少ColumnFamily的個(gè)數(shù)。

Flush操作如果只選擇某個(gè)Region的Store內(nèi)的MemStore寫入磁盤束铭,而不是統(tǒng)一寫入磁盤廓块,那么HLog上key的一致性在Region中各個(gè)Store(ColumnFamily)下的MemStore內(nèi)就會(huì)有不一致的key區(qū)間。
如下圖所示契沫,我們假定該RegionServer上僅有一個(gè)Region带猴,由于不同的Row在列簇上有所區(qū)別,就會(huì)出現(xiàn)有些不同Store內(nèi)占用的內(nèi)存不一致的情況懈万,這里會(huì)根據(jù)整體內(nèi)存使用的情況拴清,或者RS使用內(nèi)存的情況來決定是否執(zhí)行Flush操作。如果僅僅flush使用內(nèi)存較大的memstore会通,那么在使用的過程中口予,一是Scan操作在執(zhí)行時(shí)就不夠統(tǒng)一(同一個(gè)rowkey的cf有些flush有些還沒flush),二是在HLog Replayer還原Region內(nèi)Memstore故障前的狀態(tài)涕侈,不能簡(jiǎn)單的根據(jù)Hlog的Flush_marker的標(biāo)記位來執(zhí)行Replay沪停。

Hbase flush.png

Memstore Flush觸發(fā)條件

1. Memstore級(jí)別限制:當(dāng)Region中任意一個(gè)MemStore的大小達(dá)到了上限(hbase.hregion.memstore.flush.size,默認(rèn)128MB)驾凶,會(huì)觸發(fā)Memstore刷新牙甫。
2. Region級(jí)別限制:當(dāng)Region中所有Memstore的大小總和達(dá)到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size掷酗,默認(rèn) 2 x 128M = 256M),會(huì)觸發(fā)memstore刷新窟哺。
3. Region Server級(jí)別限制:當(dāng)一個(gè)Region Server中所有Memstore的大小總和達(dá)到了上限(hbase.regionserver.global.memstore.upperLimit * hbase_heapsize泻轰,默認(rèn) 40%的JVM內(nèi)存使用量),會(huì)觸發(fā)部分Memstore刷新且轨。Flush順序是按照Memstore由大到小執(zhí)行浮声,先Flush Memstore最大的Region,再執(zhí)行次大的旋奢,直至總體Memstore內(nèi)存使用量低于閾值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize泳挥,默認(rèn)38%的JVM內(nèi)存使用量)。
4. 當(dāng)一個(gè)Region Server中HLog數(shù)量達(dá)到上限(可通過參數(shù)hbase.regionserver.max.logs配置)時(shí)至朗,系統(tǒng)會(huì)選取最早的一個(gè) HLog對(duì)應(yīng)的一個(gè)或多個(gè)Region進(jìn)行flush
5. HBase定期刷新Memstore:默認(rèn)周期為1小時(shí)屉符,確保Memstore不會(huì)長(zhǎng)時(shí)間沒有持久化。為避免所有的MemStore在同一時(shí)間都進(jìn)行flush導(dǎo)致的問題锹引,定期的flush操作有20000左右的隨機(jī)延時(shí)矗钟。
6. 手動(dòng)執(zhí)行flush:用戶可以通過shell命令 flush ‘tablename’或者flush ‘region name’分別對(duì)一個(gè)表或者一個(gè)Region進(jìn)行flush。

Memstore Flush流程
為了減少flush過程對(duì)讀寫的影響嫌变,HBase采用了類似于兩階段提交的方式吨艇,將整個(gè)flush過程分為三個(gè)階段:

  1. prepare階段:遍歷當(dāng)前Region中的所有Memstore,將Memstore中當(dāng)前數(shù)據(jù)集kvset做一個(gè)快照snapshot腾啥,然后再新建一個(gè)新的kvset东涡。后期的所有寫入操作都會(huì)寫入新的kvset中,而整個(gè)flush階段讀操作會(huì)首先分別遍歷kvset和snapshot倘待,如果查找不到再會(huì)到HFile中查找疮跑。prepare階段需要加一把updateLock對(duì)寫請(qǐng)求阻塞,結(jié)束之后會(huì)釋放該鎖延柠。因?yàn)榇穗A段沒有任何費(fèi)時(shí)操作祸挪,因此持鎖時(shí)間很短。
  2. flush階段:遍歷所有Memstore贞间,將prepare階段生成的snapshot持久化為臨時(shí)文件贿条,臨時(shí)文件會(huì)統(tǒng)一放到目錄.tmp下。這個(gè)過程因?yàn)樯婕暗酱疟PIO操作增热,因此相對(duì)比較耗時(shí)整以。
  3. commit階段:遍歷所有的Memstore,將flush階段生成的臨時(shí)文件移到指定的ColumnFamily目錄下峻仇,針對(duì)HFile生成對(duì)應(yīng)的storefile和Reader公黑,把storefile添加到HStore的storefiles列表中,最后再清空prepare階段生成的snapshot。

上述flush流程可以通過日志信息查看:

/******* prepare階段 ********/
2016-02-04 03:32:41,516 INFO  [MemStoreFlusher.1] regionserver.HRegion: Started memstore flush for sentry_sgroup1_data,{\xD4\x00\x00\x01|\x00\x00\x03\x82\x00\x00\x00?\x06\xDA`\x13\xCAE\xD3C\xA3:_1\xD6\x99:\x88\x7F\xAA_\xD6[L\xF0\x92\xA6\xFB^\xC7\xA4\xC7\xD7\x8Fv\xCAT\xD2\xAF,1452217805884.572ddf0e8cf0b11aee2273a95bd07879., current region memstore size 128.9 M

/******* flush階段 ********/
2016-02-04 03:32:42,423 INFO  [MemStoreFlusher.1] regionserver.DefaultStoreFlusher: Flushed, sequenceid=1726212642, memsize=128.9 M, hasBloomFilter=true, into tmp file hdfs://hbase1/hbase/data/default/sentry_sgroup1_data/572ddf0e8cf0b11aee2273a95bd07879/.tmp/021a430940244993a9450dccdfdcb91d

/******* commit階段 ********/
2016-02-04 03:32:42,464 INFO  [MemStoreFlusher.1] regionserver.HStore: Added hdfs://hbase1/hbase/data/default/sentry_sgroup1_data/572ddf0e8cf0b11aee2273a95bd07879/d/021a430940244993a9450dccdfdcb91d, entries=643656, sequenceid=1726212642, filesize=7.1 M

其中第二階段flush又細(xì)分為兩個(gè)階段

  1. append階段:memstore中keyvalue首先會(huì)寫入到HFile中數(shù)據(jù)塊
  2. finalize階段:修改HFlie中meta元數(shù)據(jù)塊凡蚜,索引數(shù)據(jù)塊以及Trailer數(shù)據(jù)塊等
    append流程
    具體keyvalue數(shù)據(jù)的append以及finalize過程在HFileWriterV2文件中人断,其中append流程可以大體表征為:
    Block Write.png

    a. 預(yù)檢查:檢查key的大小是否大于前一個(gè)key,如果大于則不符合HBase順序排列的原理朝蜘,拋出異常恶迈;檢查value是否是null,如果為null也拋出異常
    b. block是否寫滿:檢查當(dāng)前Data Block是否已經(jīng)寫滿谱醇,如果沒有寫滿就直接寫入keyvalue暇仲;否則就需要執(zhí)行數(shù)據(jù)塊落盤以及索引塊修改操作;
    c. 數(shù)據(jù)落盤并修改索引:如果DataBlock寫滿副渴,首先將block塊寫入流奈附;再生成一個(gè)leaf index entry,寫入leaf Index block煮剧;再檢查該leaf index block是否已經(jīng)寫滿需要落盤斥滤,如果已經(jīng)寫滿,就將該leaf index block寫入到輸出流轿秧,并且為索引樹根節(jié)點(diǎn)root index block新增一個(gè)索引中跌,指向葉子節(jié)點(diǎn)(second-level index)
    d. 生成一個(gè)新的block:重新reset輸出流,初始化startOffset為-1
    e. 寫入keyvalue:將keyvalue以流的方式寫入輸出流菇篡,同時(shí)需要寫入memstore;除此之外一喘,如果該key是當(dāng)前block的第一個(gè)key驱还,需要賦值給變量firstKeyInBlock
    finalize階段
    memstore中所有keyvalue都經(jīng)過append階段輸出到HFile后,會(huì)執(zhí)行一次finalize過程凸克,主要更新HFile中meta元數(shù)據(jù)塊议蟆、索引數(shù)據(jù)塊以及Trailer數(shù)據(jù)塊,其中對(duì)索引數(shù)據(jù)塊的更新是我們關(guān)心的重點(diǎn)萎战,此處詳細(xì)解析咐容,上述append流程中c步驟’數(shù)據(jù)落盤并修改索引’會(huì)使得root index block不斷增多,當(dāng)增大到一定程度之后就需要分裂蚂维,分裂示意圖如下圖所示:
    Index Split.png

    上圖所示戳粒,分裂前索引結(jié)構(gòu)為second-level結(jié)構(gòu),圖中沒有畫出Data Blocks虫啥,根節(jié)點(diǎn)索引指向葉子節(jié)點(diǎn)索引塊蔚约。finalize階段系統(tǒng)會(huì)對(duì)Root Index Block進(jìn)行大小檢查,如果大小大于規(guī)定的大小就需要進(jìn)行分裂涂籽,圖中分裂過程實(shí)際上就是將原來的Root Index Block塊分割成4塊苹祟,每塊獨(dú)立形成中間節(jié)點(diǎn)InterMediate Index Block,系統(tǒng)再重新生成一個(gè)Root Index Block(圖中紅色部分),分別指向分割形成的4個(gè)interMediate Index Block树枫。此時(shí)索引結(jié)構(gòu)就變成了third-level結(jié)構(gòu)直焙。

Hfile文件格式

HFile的核心設(shè)計(jì)思想是(數(shù)據(jù))分塊和(索引)分級(jí)

HFile Structure.jpg

如上圖所示, HFile會(huì)被切分為多個(gè)大小相等的block塊砂轻,每個(gè)block的大小可以在創(chuàng)建表列簇的時(shí)候通過參數(shù)blocksize => ‘65535’進(jìn)行指定奔誓,默認(rèn)為64k,大號(hào)的Block有利于順序Scan舔清,小號(hào)Block利于隨機(jī)查詢丝里,因而需要權(quán)衡。而且所有block塊都擁有相同的數(shù)據(jù)結(jié)構(gòu)体谒,如圖左側(cè)所示杯聚,HBase將block塊抽象為一個(gè)統(tǒng)一的HFileBlock。HFileBlock支持兩種類型抒痒,一種類型不支持checksum幌绍,一種不支持。

HFile V2.png

HFile V2中故响,主要包括四個(gè)部分:

  • Scanned Block(數(shù)據(jù)block傀广,表示順序掃描HFile時(shí)所有的數(shù)據(jù)塊將會(huì)被讀取,包括Leaf Index Block和Bloom Block)
  • Non-Scanned block(元數(shù)據(jù)block彩届,表示在HFile順序掃描的時(shí)候數(shù)據(jù)不會(huì)被讀取伪冰,主要包括Meta Block和Intermediate Level Data Index Blocks兩部分)
  • Load-on-open(這部分?jǐn)?shù)據(jù)在HBase的region server啟動(dòng)時(shí)需要加載到內(nèi)存中,包括FileInfo樟蠕、Bloom filter block贮聂、data block index和meta block index)
  • trailer(文件尾,主要記錄了HFile的基本信息寨辩、各個(gè)部分的偏移值和尋址信息吓懈。)。

一個(gè)HFile文件包含了多種類型的HFileBlock塊靡狞,每種類型的HFileBlock主要包括兩部分:BlockHeader和BlockData耻警。其中Header主要存儲(chǔ)block元數(shù)據(jù),Data用來存儲(chǔ)具體數(shù)據(jù)甸怕。block元數(shù)據(jù)中最核心的字段是BlockType字段甘穿,用來標(biāo)示該block塊的類型,HBase中定義了8種BlockType蕾各,每種BlockType對(duì)應(yīng)的block都存儲(chǔ)不同的數(shù)據(jù)內(nèi)容扒磁,有的存儲(chǔ)用戶數(shù)據(jù),有的存儲(chǔ)索引數(shù)據(jù)式曲,有的存儲(chǔ)meta元數(shù)據(jù)妨托。對(duì)于任意一種類型的HFileBlock缸榛,都擁有相同結(jié)構(gòu)的BlockHeader,但是BlockData結(jié)構(gòu)卻不相同兰伤。下面通過一張表簡(jiǎn)單羅列最核心的幾種BlockType:

HFile BlockType.png

  • Trailer Block
    主要記錄了HFile的基本信息内颗、各個(gè)部分的偏移值和尋址信息,下圖為Trailer內(nèi)存和磁盤中的數(shù)據(jù)結(jié)構(gòu)敦腔,其中只顯示了部分核心字段:
    Trailer Block.png

HFile在讀取的時(shí)候首先會(huì)解析Trailer Block并加載到內(nèi)存均澳,然后再進(jìn)一步加載LoadOnOpen區(qū)的數(shù)據(jù),具體步驟如下:

  1. 首先加載version版本信息符衔,HBase中version包含majorVersion和minorVersion兩部分找前,前者決定了HFile的主版本: V1、V2 還是V3判族;后者在主版本確定的基礎(chǔ)上決定是否支持一些微小修正躺盛,比如是否支持checksum等。不同的版本決定了使用不同的Reader對(duì)象對(duì)HFile進(jìn)行讀取解析
  2. 根據(jù)Version信息獲取trailer的長(zhǎng)度(不同version的trailer長(zhǎng)度不同)形帮,再根據(jù)trailer長(zhǎng)度加載整個(gè)HFileTrailer Block
  3. 最后加載load-on-open部分到內(nèi)存中槽惫,起始偏移地址是trailer中的LoadOnOpenDataOffset字段,load-on-open部分的結(jié)束偏移量為HFile長(zhǎng)度減去Trailer長(zhǎng)度辩撑,load-on-open部分主要包括索引樹的根節(jié)點(diǎn)以及FileInfo兩個(gè)重要模塊界斜,F(xiàn)ileInfo是固定長(zhǎng)度的塊,它紀(jì)錄了文件的一些Meta信息合冀,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等各薇;
  • Data Block
    DataBlock是HBase中用戶數(shù)據(jù)存儲(chǔ)的最小單元。DataBlock中主要存儲(chǔ)用戶的KeyValue數(shù)據(jù)(KeyValue后面一般會(huì)跟一個(gè)timestamp君躺,圖中未標(biāo)出)得糜,而KeyValue結(jié)構(gòu)是HBase存儲(chǔ)的核心,每個(gè)數(shù)據(jù)都是以KeyValue結(jié)構(gòu)在HBase中進(jìn)行存儲(chǔ)晰洒。KeyValue結(jié)構(gòu)在內(nèi)存和磁盤中可以表示為:
    Data Block.png

每個(gè)KeyValue都由4個(gè)部分構(gòu)成,分別為key length啥箭,value length谍珊,key和value。其中key value和value length是兩個(gè)固定長(zhǎng)度的數(shù)值急侥,而key是一個(gè)復(fù)雜的結(jié)構(gòu)砌滞,首先是rowkey的長(zhǎng)度,接著是rowkey坏怪,然后是ColumnFamily的長(zhǎng)度贝润,再是ColumnFamily,最后是時(shí)間戳和KeyType(keytype有四種類型铝宵,分別是Put打掘、Delete华畏、 DeleteColumn和DeleteFamily),value就沒有那么復(fù)雜尊蚁,就是一串純粹的二進(jìn)制數(shù)據(jù)亡笑。

  • BloomFilter Metadata Block & Bloom Block
    BloomFilter對(duì)于HBase的隨機(jī)讀性能至關(guān)重要,對(duì)于get操作以及部分scan操作可以剔除掉不會(huì)用到的HFile文件横朋,減少實(shí)際IO次數(shù)仑乌,提高隨機(jī)讀性能。在此簡(jiǎn)單地介紹一下Bloom Filter的工作原理琴锭,Bloom Filter使用位數(shù)組來實(shí)現(xiàn)過濾晰甚,初始狀態(tài)下位數(shù)組每一位都為0,如下圖所示:

    BloomFilter-1.png

    假如此時(shí)有一個(gè)集合S = {x1, x2, … xn}决帖,Bloom Filter使用k個(gè)獨(dú)立的hash函數(shù)厕九,分別將集合中的每一個(gè)元素映射到{1,…,m}的范圍。對(duì)于任何一個(gè)元素古瓤,被映射到的數(shù)字作為對(duì)應(yīng)的位數(shù)組的索引止剖,該位會(huì)被置為1。比如元素x1被hash函數(shù)映射到數(shù)字8落君,那么位數(shù)組的第8位就會(huì)被置為1穿香。下圖中集合S只有兩個(gè)元素x和y,分別被3個(gè)hash函數(shù)進(jìn)行映射绎速,映射到的位置分別為(0皮获,2,6)和(4纹冤,7洒宝,10),對(duì)應(yīng)的位會(huì)被置為1:
    BloomFilter-2.png

    現(xiàn)在假如要判斷另一個(gè)元素是否是在此集合中萌京,只需要被這3個(gè)hash函數(shù)進(jìn)行映射雁歌,查看對(duì)應(yīng)的位置是否有0存在,如果有的話知残,表示此元素肯定不存在于這個(gè)集合靠瞎,否則有可能存在。下圖所示就表示z肯定不在集合{x求妹,y}中:
    BloomFilter-3.png

    HBase中每個(gè)HFile都有對(duì)應(yīng)的位數(shù)組乏盐,KeyValue在寫入HFile時(shí)會(huì)先經(jīng)過幾個(gè)hash函數(shù)的映射,映射后將對(duì)應(yīng)的數(shù)組位改為1制恍,get請(qǐng)求進(jìn)來之后再進(jìn)行hash映射父能,如果在對(duì)應(yīng)數(shù)組位上存在0,說明該get請(qǐng)求查詢的數(shù)據(jù)肯定不在該HFile中净神。
    HFile中的位數(shù)組就是上述Bloom Block中存儲(chǔ)的值何吝,可以想象溉委,一個(gè)HFile文件越大,里面存儲(chǔ)的KeyValue值越多岔霸,位數(shù)組就會(huì)相應(yīng)越大薛躬。一旦太大就不適合直接加載到內(nèi)存了,因此HFile V2在設(shè)計(jì)上將位數(shù)組進(jìn)行了拆分呆细,拆成了多個(gè)獨(dú)立的位數(shù)組(根據(jù)Key進(jìn)行拆分型宝,一部分連續(xù)的Key使用一個(gè)位數(shù)組)。這樣一個(gè)HFile中就會(huì)包含多個(gè)位數(shù)組絮爷,根據(jù)Key進(jìn)行查詢趴酣,首先會(huì)定位到具體的某個(gè)位數(shù)組,只需要加載此位數(shù)組到內(nèi)存進(jìn)行過濾即可坑夯,減少了內(nèi)存開支岖寞。
    在結(jié)構(gòu)上每個(gè)位數(shù)組對(duì)應(yīng)HFile中一個(gè)Bloom Block,為了方便根據(jù)Key定位具體需要加載哪個(gè)位數(shù)組柜蜈,HFile V2又設(shè)計(jì)了對(duì)應(yīng)的索引Bloom Index Block仗谆,對(duì)應(yīng)的內(nèi)存和邏輯結(jié)構(gòu)圖如下:
    Bloom Index Block.png

    Bloom Index Block結(jié)構(gòu)中totalByteSize表示位數(shù)組的bit數(shù),numChunks表示Bloom Block的個(gè)數(shù)淑履,hashCount表示hash函數(shù)的個(gè)數(shù)隶垮,hashType表示hash函數(shù)的類型,totalKeyCount表示bloom filter當(dāng)前已經(jīng)包含的key的數(shù)目秘噪,totalMaxKeys表示bloom filter當(dāng)前最多包含的key的數(shù)目, Bloom Index Entry對(duì)應(yīng)每一個(gè)bloom filter block的索引條目狸吞,作為索引分別指向"scanned block section" 部分的Bloom Block,Bloom Block中就存儲(chǔ)了對(duì)應(yīng)的位數(shù)組指煎。
    Bloom Index Entry的結(jié)構(gòu)見上圖左邊所示蹋偏,BlockOffset表示對(duì)應(yīng)Bloom Block在HFile中的偏移量,F(xiàn)irstKey表示對(duì)應(yīng)BloomBlock的第一個(gè)Key至壤。根據(jù)上文所說威始,一次get請(qǐng)求進(jìn)來,首先會(huì)根據(jù)key在所有的索引條目中進(jìn)行二分查找像街,查找到對(duì)應(yīng)的Bloom Index Entry字逗,就可以定位到該key對(duì)應(yīng)的位數(shù)組,加載到內(nèi)存進(jìn)行過濾判斷宅广。

  • Index Block

HFile V1的時(shí)候,在數(shù)據(jù)塊索引很大時(shí)些举,很難全部load到內(nèi)存跟狱。假設(shè)每個(gè)數(shù)據(jù)塊使用默認(rèn)大小64KB,每個(gè)索引項(xiàng)64Byte户魏,這樣如果每臺(tái)及其上存放了60TB的數(shù)據(jù)驶臊,那索引數(shù)據(jù)就得有60G挪挤,所以內(nèi)存的占用還是很高的。此外关翎,由于直到加載完所有塊索引數(shù)據(jù)之后扛门,才能認(rèn)為region啟動(dòng)完成,因此這樣的塊索引大小會(huì)明顯地拖慢region的啟動(dòng)速度纵寝。所以论寨,將這些索引以樹狀結(jié)構(gòu)進(jìn)行組織,只讓頂層索引常駐內(nèi)存爽茴,其他索引按需讀取并通過LRU cache進(jìn)行緩存葬凳,這樣就不用全部加載到內(nèi)存了。

HFile中索引結(jié)構(gòu)根據(jù)索引層級(jí)的不同分為兩種:single-level和mutil-level室奏,前者表示單層索引火焰,后者表示多級(jí)索引,一般為兩級(jí)或三級(jí)胧沫。HFile V1版本中只有single-level一種索引結(jié)構(gòu)昌简,V2版本中引入多級(jí)索引。
HFile V2版本Index Block則分為兩類:Root Index Block和NonRoot Index Block绒怨,其中NonRoot Index Block又分為Intermediate Index Block和Leaf Index Block兩種纯赎。HFile中索引結(jié)構(gòu)類似于一棵樹,Root Index Block表示索引數(shù)根節(jié)點(diǎn)窖逗,記錄每個(gè)塊首個(gè)key及其索引址否,Intermediate Index Block表示中間節(jié)點(diǎn),記錄每個(gè)塊最后的key及其索引碎紊,Leaf Index block表示葉子節(jié)點(diǎn)佑附,葉子節(jié)點(diǎn)直接指向?qū)嶋H數(shù)據(jù)塊。隨著dateblock數(shù)量的不斷增多仗考,(root_index-->intermediate_index-->leaf_index-->data_block), 索引的層級(jí)會(huì)逐漸增多音同。

HFile index.png

HFile中除了Data Block需要索引之外,上面提到的Bloom Block也需要索引秃嗜,Bloom Block的索引結(jié)構(gòu)實(shí)際上就是采用了single-level結(jié)構(gòu)权均,是一種Root Index Block。


Root Index Block
Root Index Block表示索引樹根節(jié)點(diǎn)索引塊锅锨,可以作為bloom的直接索引叽赊,也可以作為data索引的根索引。而且對(duì)于single-level和mutil-level兩種索引結(jié)構(gòu)對(duì)應(yīng)的Root Index Block略有不同必搞,這里以mutil-level的Root Index Block索引結(jié)構(gòu)為例進(jìn)行分析必指,在內(nèi)存和磁盤中的格式如下圖所示:

Root Index Block.png

其中Index Entry表示具體的索引對(duì)象,每個(gè)索引對(duì)象由3個(gè)字段組成恕洲,Block Offset表示索引指向數(shù)據(jù)塊的偏移量塔橡,BlockDataSize表示索引指向數(shù)據(jù)塊在磁盤上的大小梅割,BlockKey表示索引指向數(shù)據(jù)塊中的第一個(gè)key。除此之外葛家,還有另外3個(gè)字段用來記錄MidKey的相關(guān)信息户辞,MidKey表示HFile所有Data Block中中間的一個(gè)Data Block,用于在對(duì)HFile進(jìn)行split操作時(shí)癞谒,快速定位HFile的中間位置底燎。需要注意的是single-level索引結(jié)構(gòu)和mutil-level結(jié)構(gòu)相比,就只缺少M(fèi)idKey這三個(gè)字段扯俱。
Root Index Block會(huì)在HFile解析的時(shí)候直接加載到內(nèi)存中书蚪,此處需要注意在Trailer Block中有一個(gè)字段為dataIndexCount,就表示此處Index Entry的個(gè)數(shù)迅栅。因?yàn)镮ndex Entry并不定長(zhǎng)殊校,只有知道Entry的個(gè)數(shù)才能正確的將所有Index Entry加載到內(nèi)存。


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

NonRoot Index Block.png

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


在HFile V2中數(shù)據(jù)完整索引流程:

  1. 先在內(nèi)存中對(duì)HFile的root索引進(jìn)行二分查找痹升,如果支持多級(jí)索引建炫,則定位到leaf index/intermediate index,如果是單級(jí)索引疼蛾,則定位到數(shù)據(jù)塊data block踱卵;
  2. 如果支持多級(jí)索引,則會(huì)從cache/hdfs中讀取leaf/intermediate index chunk,在leaf/intermediate chunk根據(jù)key值進(jìn)行二分查找(leaf/intermediate index chunk支持二分查找)惋砂,找到對(duì)應(yīng)的data block。
  3. 從cache/hdfs中讀取數(shù)據(jù)塊绳锅;
  4. 在數(shù)據(jù)塊中遍歷查找對(duì)應(yīng)的數(shù)據(jù)西饵。
data index.png

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

  1. 用戶輸入rowkey為fb鳞芙,在root index block中通過二分查找定位到fb在’a’和’m’之間眷柔,因此需要訪問索引’a’指向的中間節(jié)點(diǎn)。因?yàn)閞oot index block常駐內(nèi)存筐眷,所以這個(gè)過程很快弓叛。
  2. 將索引’a’指向的中間節(jié)點(diǎn)索引塊加載到內(nèi)存义郑,然后通過二分查找定位到fb在index ‘d’和’h’之間,接下來訪問索引’d’指向的葉子節(jié)點(diǎn)鞠评。
  3. 同理,將索引’d’指向的中間節(jié)點(diǎn)索引塊加載到內(nèi)存壕鹉,一樣通過二分查找定位找到fb在index ‘f’和’g’之間剃幌,最后需要訪問索引’f’指向的數(shù)據(jù)塊節(jié)點(diǎn)。
  4. 將索引’f’指向的數(shù)據(jù)塊加載到內(nèi)存晾浴,通過遍歷的方式找到對(duì)應(yīng)的keyvalue负乡。

上述流程中因?yàn)橹虚g節(jié)點(diǎn)、葉子節(jié)點(diǎn)和數(shù)據(jù)塊都需要加載到內(nèi)存脊凰,所以io次數(shù)正常為3次抖棘。但是實(shí)際上HBase為block提供了緩存機(jī)制,可以將頻繁使用的block緩存在內(nèi)存中狸涌,可以進(jìn)一步加快實(shí)際讀取過程切省。所以,在HBase中杈抢,通常一次隨機(jī)讀請(qǐng)求最多會(huì)產(chǎn)生3次io数尿,如果數(shù)據(jù)量小(只有一層索引)惶楼,數(shù)據(jù)已經(jīng)緩存到了內(nèi)存右蹦,就不會(huì)產(chǎn)生io。


HBase表誤刪恢復(fù)

hdfs的回收站機(jī)制
在hdfs上有一個(gè)回收站的設(shè)置歼捐,可以將刪除的數(shù)據(jù)移動(dòng)到回收站目錄/user/$<username>/.Trash/中何陆,設(shè)置回收站的相關(guān)參數(shù)如下:

  • fs.trash.interval=360
    以分鐘為單位的垃圾回收時(shí)間,垃圾站中數(shù)據(jù)超過此時(shí)間豹储,會(huì)被刪除贷盲。如果是0,垃圾回收機(jī)制關(guān)閉」剩可以配置在服務(wù)器端和客戶端铝穷。如果在服務(wù)器端配置trash無效,會(huì)檢查客戶端配置佳魔。如果服務(wù)器端配置有效曙聂,客戶端配置會(huì)忽略。也就是說鞠鲜,Server端的值優(yōu)先于Client宁脊。如有同名文件被刪除,會(huì)給文件順序編號(hào)贤姆,例如:a.txt,a.txt(1)
  • fs.trash.checkpoint.interval=0
    以分鐘為單位的垃圾回收檢查間隔榆苞。應(yīng)該小于或等于fs.trash.interval,如果是0霞捡,值等同于fs.trash.interval坐漏。該值只在服務(wù)器端設(shè)置。

如果disable+drop誤刪了hbase表數(shù)據(jù)弄砍,數(shù)據(jù)不會(huì)放到回收站中仙畦,hbase有自己的一套刪除策略。
HBase的數(shù)據(jù)主要存儲(chǔ)在分布式文件系統(tǒng)HFile和HLog兩類文件中音婶。Compaction操作會(huì)將合并完的不用的小Hfile移動(dòng)到<.archive>文件夾慨畸,并設(shè)置ttl過期時(shí)間。HLog文件在數(shù)據(jù)完全flush到hfile中時(shí)便會(huì)過期衣式,被移動(dòng)到.oldlog(oldWALs)文件夾中寸士。

HMaster上的定時(shí)線程HFileCleaner/LogCleaner周期性掃描.archive目錄和.oldlog目錄, 判斷目錄下的HFile或者HLog是否可以被刪除,如果可以,就直接刪除文件碴卧。

關(guān)于hfile文件和hlog文件的過期時(shí)間弱卡,其中涉及到兩個(gè)參數(shù),如下:

(1)hbase.master.logcleaner.ttl
HLog在.oldlogdir目錄中生存的最長(zhǎng)時(shí)間住册,過期則被Master的線程清理婶博,默認(rèn)是600000(ms);
(2)hbase.master.hfilecleaner.plugins
HFile的清理插件列表荧飞,逗號(hào)分隔凡人,被HFileService調(diào)用,可以自定義叹阔,默認(rèn)org.apache.hadoop.hbase.master.cleaner.TimeToLiveHFileCleaner挠轴。

默認(rèn)hfile的失效時(shí)間是5分鐘(300000ms)。由于一般的hadoop平臺(tái)默認(rèn)都沒有對(duì)該參數(shù)的設(shè)置耳幢,可以在配置選項(xiàng)中添加對(duì)hbase.master.hfilecleaner.ttl的設(shè)置岸晦。

實(shí)際在測(cè)試的過程中,刪除一個(gè)hbase表,在hbase的hdfs目錄下的archive文件夾中启上,會(huì)立即發(fā)現(xiàn)刪除表的所有region數(shù)據(jù)(不包含regioninfo邢隧、tabledesc等元數(shù)據(jù)文件),超時(shí)5分鐘所有region(hfile)數(shù)據(jù)被刪除冈在。
恢復(fù)步奏:

  1. 搶救數(shù)據(jù)
    保證在刪除表之后的5分鐘之內(nèi)將hdfs目錄/apps/hbase/data/archive/文件夾下的相關(guān)數(shù)據(jù)拷貝一份到另外一個(gè)安全目錄下府框。
  2. 新建與刪除表同名和同列族的表
  3. 將搶救下來的region數(shù)據(jù)拷貝到hbase表對(duì)應(yīng)的目錄下
hadoop fs -cp /apps/hbase/data/archive/data/default/member/0705a8ce0ead4618839b4c9cf9977fa5 /apps/hbase/data/data/default/member/
  1. hbase元數(shù)據(jù)修復(fù)
    因?yàn)楸粍h除的數(shù)據(jù)文件夾中并沒有包含.regioninfo文件,需要進(jìn)行元數(shù)據(jù)修復(fù)
hbase hbck -repair #一次可能不成功讥邻,多試幾次。

hbase元數(shù)據(jù)損壞
Hbase的一些啟動(dòng)必要的文件放置在hdfs上院峡,由于人為刪除了hdfs的數(shù)據(jù)塊文件兴使,這些文件塊恰好包含了hbase的文件數(shù)據(jù),所以導(dǎo)致hbase啟動(dòng)失敗照激。

  1. 停止hbase服務(wù)
  2. 查看hdfs的文件健康狀態(tài): hdfs fsck / | egrep -v '^.+$' | grep -v replica | grep -v Replica
  3. 根據(jù)輸出列出的所有損壞的文件塊发魄,由于已被刪除無法恢復(fù),所以清理hdfs中損壞或缺失的數(shù)據(jù)塊俩垃。hdfs fsck -delete 或者 hdfs fs -rm /test/xxx.txt ...
  4. 再次復(fù)查hdfs的文件健康狀態(tài)励幼, 結(jié)果顯示HEALTHY。
  5. 備份hbase口柳,hadoop fs -mv /apps/hbase /apps/hbasebak
  6. 登錄 zookeeper 苹粟,/usr/lib/zookeeper/zkCli.sh 刪除其中的hbase目錄 rmr /hbase
  7. 啟動(dòng)hbase服務(wù),確認(rèn)所有相關(guān)服務(wù)都正常

hbase的行鎖與多版本并發(fā)控制(MVCC)
http://my.oschina.net/u/189445/blog/597226

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跃闹,一起剝皮案震驚了整個(gè)濱河市嵌削,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌望艺,老刑警劉巖苛秕,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異找默,居然都是意外死亡艇劫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門惩激,熙熙樓的掌柜王于貴愁眉苦臉地迎上來店煞,“玉大人,你說我怎么就攤上這事咧欣∏掣祝” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵魄咕,是天一觀的道長(zhǎng)衩椒。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么毛萌? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任苟弛,我火速辦了婚禮,結(jié)果婚禮上阁将,老公的妹妹穿的比我還像新娘膏秫。我一直安慰自己,他們只是感情好做盅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布缤削。 她就那樣靜靜地躺著,像睡著了一般吹榴。 火紅的嫁衣襯著肌膚如雪亭敢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天图筹,我揣著相機(jī)與錄音帅刀,去河邊找鬼。 笑死远剩,一個(gè)胖子當(dāng)著我的面吹牛扣溺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瓜晤,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锥余,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了活鹰?” 一聲冷哼從身側(cè)響起哈恰,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎志群,沒想到半個(gè)月后着绷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锌云,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年荠医,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桑涎。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡彬向,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出攻冷,到底是詐尸還是另有隱情娃胆,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布等曼,位于F島的核電站里烦,受9級(jí)特大地震影響凿蒜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胁黑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一废封、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丧蘸,春花似錦漂洋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弟孟,卻和暖如春爽冕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背披蕉。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乌奇,地道東北人没讲。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像礁苗,于是被迫代替她去往敵國(guó)和親爬凑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 初次接觸HBase的讀者试伙,建議先閱讀淺析HBase:為高效的可擴(kuò)展大規(guī)模分布式系統(tǒng)而生 HBase的構(gòu)成 物理上來...
    耀凱考前突擊大師閱讀 5,250評(píng)論 0 12
  • 出軌這種事疏叨,在如今看來已經(jīng)不是什么新鮮事了潘靖。我們身邊的朋友,多多少少都會(huì)有幾個(gè)出軌的蚤蔓,有男人也有女人卦溢,即使身體沒有...
    以沫同學(xué)閱讀 2,131評(píng)論 0 0
  • 一個(gè)人,怕的不應(yīng)該是辛苦秀又,而是自己一直沒有成長(zhǎng)的價(jià)值单寂。 人的一生,都在不斷成長(zhǎng)吐辙。我們應(yīng)該知道宣决,一個(gè)人,最怕的是一直...
    天使de翅膀閱讀 956評(píng)論 6 43
  • 一個(gè)月就這么過去了昏苏,想要開發(fā)一個(gè)原生的應(yīng)用實(shí)在不是那么容易尊沸,還好公司需求最近有變化威沫,最后應(yīng)用是以WkWebView...
    14cat閱讀 495評(píng)論 2 0
  • 在那么一瞬間,你鼓起勇氣椒丧,面對(duì)一塌糊涂的現(xiàn)實(shí)壹甥,選擇想要擁有的未來。去拼壶熏、去闖句柠、去探索、去追求……前行路上棒假,奔跑的你...
    東山星阿閱讀 73評(píng)論 0 1