大資料基礎學習-8.Hbase-1.2.0

閒人勿-發表於2018-04-29

一、HBase基礎

參考:https://blog.csdn.net/jack__cj/article/details/52634518

1.HBASE概述

HBase – Hadoop Database,是一個高可靠性、高效能、面向列、可伸縮的分散式儲存系統,利用HBase技術可在廉價PC Server上搭建起大規模結構化儲存叢集。HBase是一個開源的非關係型分散式資料庫(NoSQL),它參考了谷歌的BigTable建模,實現的程式語言為Java,是Apache軟體基金會Hadoop專案的一部分,執行於HDFS檔案系統之上,因此可以容錯地儲存海量稀疏的資料。


上圖描述了Hadoop EcoSystem中的各層系統,其中HBase位於結構化儲存層,Hadoop HDFS為HBase提供了高可靠性的底層儲存支援,Hadoop MapReduce為HBase提供了高效能的計算能力,Zookeeper為HBase提供了穩定服務和failover機制。此外,Pig和Hive還為HBase提供了高層語言支援,使得在HBase上進行資料統計處理變的非常簡單。 Sqoop則為HBase提供了方便的RDBMS資料匯入功能,使得傳統資料庫資料向HBase中遷移變的非常方便。

2.行儲存vs列儲存

• 行儲存:優點是寫入一次性完成,保持資料完整性;缺點是資料讀取過程中產生冗餘資料,若只有少量資料可以忽略。

• 列儲存:優點是讀取過程,不會產生冗餘資料,適合對資料完整性要求不高的大資料領域;缺點是寫入效率差,保證資料完整性方面差。

3.HBase應用場景

物件儲存:我們知道不少的頭條類、新聞類的的新聞、網頁、圖片儲存在HBase之中,一些病毒公司的病毒庫也是儲存在HBase之中。

時序資料:HBase之上有OpenTSDB模組,可以滿足時序類場景的需求。

推薦畫像:特別是使用者的畫像,是一個比較大的稀疏矩陣,螞蟻的風控就是構建在HBase之上。

時空資料:主要是軌跡、氣象網格之類,滴滴叫車的軌跡資料主要存在HBase之中,另外在技術所有大一點的資料量的車聯網企業,資料都是存在HBase之中。

CubeDB OLAP:Kylin一個cube分析工具,底層的資料就是儲存在HBase之中,不少客戶自己基於離線計算構建cube儲存在hbase之中,滿足線上報表查詢的需求。

訊息/訂單:在電信領域、銀行領域,不少的訂單查詢底層的儲存,另外不少通訊、訊息同步的應用構建在HBase之上。

Feeds流:典型的應用就是xx朋友圈類似的應用。

NewSQL:之上有Phoenix的外掛,可以滿足二級索引、SQL的需求,對接傳統資料需要SQL非事務的需求。

4.HBase和Hive

Hive適合用對一段時間內的資料進行分析查詢,例如,用來計算趨勢或者網站的日誌。Hive不應該用來進行實時的查詢,因為它需要很長時間才可以返回結果;HBase則非常適合用來進行大資料的實時查詢,例如Facebook用HBase進行訊息和實時的分析。簡單來說HBASE對應hdfs,而hive對應的是MapReduce,二者有著本質的區別。

對於Hive和HBase的部署來說,也有一些區別,Hive一般只要有Hadoop便可以工作。而HBase則還需要Zookeeper的幫助。再而,HBase本身只提供了Java的API介面,並不直接支援SQL的語句查詢,而Hive則可以直接使用HQL。如果想要在HBase上使用SQL,則需要聯合使用Apache Phonenix,或者聯合使用Hive和HBase。但如果整合使用Hive查詢HBase的資料,則無法繞過MapReduce,那麼實時性還是有一定的損失。PhoenixHBase的組合則不經過MapReduce的框架,因此當使用Phoneix加HBase的組成,實時性上會優於Hive加HBase的組合。預設情況下Hive和HBase的儲存層都是HDFS。但是HBase在一些特殊的情況下也可以直接使用本機的檔案系統。例如Ambari中的AMS服務直接在本地檔案系統上執行HBase。

5.RDBMS和HBase

RDBMS使用sql語句,HBase使用API;RDBMS基於行儲存,HBase基於列儲存且支援更好的壓縮;RDBMS適用於儲存結構化資料,HBase適用於儲存結構化和非結構化資料;RDBMS支援事務處理,HBase不支援事務處理;RDBMS支援多表Join,HBase不支援多表Join;RDMBS更新表資料會自動更新索引檔案,HBase需要手動建立索引,手動更新;RDMBS適用於業務邏輯複雜的儲存環境,HBase不適合;RDMBS不適合儲存超大資料量的單表,HBase適合。

二、HBase核心

1.Hbase系統架構

從HBase的架構圖上可以看出,HBase中的儲存包括HMaster、HRegionServer、HRegion、Store、MemStore、StoreFile、HFile、HLog等,HBase中的每張表都通過行鍵(rowkey按照一定的範圍被分割成多個子表(HRegion),預設一個HRegion超過256M就要被分割成兩個,這個過程由HRegionServer管理,而HRegion的分配由HMaster管理。

2.Client

– 訪問Hbase的介面,並維護Cache加速Region Server的訪問。HBase Client使用HBase的RPC機制與HMaster和HRegionServer進行通訊,對於管理類操作,Client與HMaster進行RPC;對於資料讀寫類操作,Client與HRegionServer進行RPC。

3.HMaster

HMaster沒有單點問題,HBase中可以啟動多個HMaster,通過Zookeeper的Master Election機制保證總有一個Master執行,HMaster在功能上主要負責Table和Region的管理工作:

    – 為Region server分配region

    – 負責Region server的負載均衡

    – 發現失效的Region server並重新分配其上的region

    – HDFS上的垃圾檔案回收

    – 處理schema更新請求

4.HRegion Server

處理使用者I/O請求,向HDFS檔案系統中讀寫資料,是HBase中最核心的模組。


HRegionServer內部管理了一系列HRegion物件,每個HRegion對應了Table中的一個Region,HRegion中由多個HStore組成。每個HStore對應了Table中的一個Column Family的儲存,可以看出每個Column Family其實就是一個集中的儲存單元,因此最好將具備共同IO特性的column放在一個Column Family中,這樣最高效。

HStore儲存是HBase儲存的核心了,其中由兩部分組成,一部分是MemStore,一部分是StoreFiles。MemStore是Sorted Memory Buffer,使用者寫入的資料首先會放入MemStore,當MemStore滿了以後會Flush成一個StoreFile(底層實現是HFile),當StoreFile檔案數量增長到一定閾值,會觸發Compact合併操作,將多個StoreFiles合併成一個StoreFile,合併過程中會進行版本合併和資料刪除,因此可以看出HBase其實只有增加資料,所有的更新和刪除操作都是在後續的compact過程中進行的,這使得使用者的寫操作只要進入記憶體中就可以立即返回,保證了HBase I/O的高效能。當StoreFiles Compact後,會逐步形成越來越大的StoreFile,當單個StoreFile大小超過一定閾值後,會觸發Split操作,同時把當前Region Split成2個Region,父Region會下線,新Split出的2個孩子Region會被HMaster分配到相應的HRegionServer上,使得原先1個Region的壓力得以分流到2個Region上。下圖描述了Compaction和Split的過程:

在理解了上述HStore的基本原理後,還必須瞭解一下HLog的功能,因為上述的HStore在系統正常工作的前提下是沒有問題的,但是在分散式系統環境中,無法避免系統出錯或者當機,因此一旦HRegionServer意外退出,MemStore中的記憶體資料將會丟失,這就需要引入HLog了。每個HRegionServer中都有一個HLog物件,HLog是一個實現Write Ahead Log的類,在每次使用者操作寫入MemStore的同時,也會寫一份資料到HLog檔案中(HLog檔案格式見後續),HLog檔案定期會滾動出新的,並刪除舊的檔案(已持久化到StoreFile中的資料)。當HRegionServer意外終止後,HMaster會通過Zookeeper感知到,HMaster首先會處理遺留的 HLog檔案,區分不同Region對Log資料進行拆分,分別放到相應region的目錄下,然後再將失效的region重新分配,領取到這些region的HRegionServer在Load Region的過程中,會發現有歷史HLog需要處理,因此會載入HLog中的資料到MemStore中,然後flush到StoreFiles,完成資料恢復。

5.Zookeeper

Zookeeper Quorum中除了儲存了-ROOT-表的地址和HMaster的地址,HRegionServer也會把自己以Ephemeral方式註冊到Zookeeper中,使得HMaster可以隨時感知到各個HRegionServer的健康狀態。此外,Zookeeper也避免了HMaster的單點問題。

    – 保證叢集中只有一個Master。

    – 儲存所有Region的入口(ROOT)地址,其中的znode資訊:

        –/hbase/root-region-server ,Root region的位置

        –/hbase/table/-ROOT-,根後設資料資訊

        –/hbase/table/.META.,後設資料資訊

        –/hbase/master,當選的Mater

        – /hbase/backup-masters,備選的Master

        – /hbase/rs,RegionServer的資訊

        –/hbase/unassigned,未分配的Region

    – 實時監控Region Server的上下線資訊,並通知Master。

ZooKeeper協調叢集所有節點的共享資訊,在HMaster和HRegionServer連線到ZooKeeper後建立Ephemeral節點,並使用Heartbeat機制維持這個節點的存活狀態,如果某個Ephemeral節點實效,則HMaster會收到通知,並做相應的處理。

可以看到,client訪問hbase上的資料並不需要master參與(定址訪問zookeeper和region server,資料讀寫訪問region server),master僅僅維護table和region的後設資料資訊(table的後設資料資訊儲存在zookeeper上),負載很低。

HBase內建有zookeeper,但一般我們會有其他的Zookeeper叢集來監管master和regionserver,Zookeeper通過選舉,保證任何時候,叢集中只有一個活躍的HMaster,HMaster與HRegionServer 啟動時會向ZooKeeper註冊,儲存所有HRegion的定址入口,實時監控HRegionserver的上線和下線資訊。並實時通知給HMaster,儲存HBaseschematable後設資料,預設情況下,HBase管理ZooKeeper 例項,Zookeeper的引入使得HMaster不再是單點故障。一般情況下會啟動兩個HMaster,非Active的HMaster會定期的和Active HMaster通訊以獲取其最新狀態,從而保證它是實時更新的,因而如果啟動了多個HMaster反而增加了Active HMaster的負擔。如圖:

6.HBase儲存格式

HBase中的所有資料檔案都儲存在Hadoop HDFS檔案系統上,主要包括上述提出的兩種檔案型別:

HFile, HBase中KeyValue資料的儲存格式,HFile是Hadoop的二進位制格式檔案,實際上StoreFile就是對HFile做了輕量級包裝,即StoreFile底層就是HFile 。

HLog File,HBase中WAL(Write Ahead Log) 的儲存格式,物理上是Hadoop的Sequence File

在開始介紹Hfile和Hlog前,先學習HBASE的資料模型-table。

6.1table格式

1)Table & Column Family

Row Key: 行鍵,Table的主鍵,Table中的記錄按照Row Key排序。與nosql資料庫一樣,row key是用來檢索記錄的主鍵。訪問hbase table中的行,只有三種方式:

    1.通過單個row key訪問
    2.通過row key的range

    3.全表掃描

Row key行鍵 :(Row key)可以是任意字串(最大長度是 64KB,實際應用中長度一般為 10-100bytes),在hbase內部,row key儲存為位元組陣列。儲存時,資料按照Row key的字典序(byte order)排序儲存。設計key時,要充分排序儲存這個特性,將經常一起讀取的行儲存放到一起。(位置相關性)

【注意:字典序對int排序的結果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。要保持整形的自然序,行鍵必須用0作左填充。】

Timestamp: 時間戳,每次資料操作對應的時間戳,可以看作是資料的version number。HBase中通過row和columns確定的為一個存貯單元稱為cell。每個 cell都儲存著同一份資料的多個版本。版本通過時間戳來索引。時間戳的型別是 64位整型。時間戳可以由hbase(在資料寫入時自動 )賦值,此時時間戳是精確到毫秒的當前系統時間。時間戳也可以由客戶顯式賦值。如果應用程式要避免資料版本衝突,就必須自己生成具有唯一性的時間戳。每個 cell中,不同版本的資料按照時間倒序排序,即最新的資料排在最前面。

為了避免資料存在過多版本造成的的管理 (包括存貯和索引)負擔,hbase提供了兩種資料版本回收方式。一是儲存資料的最後n個版本,二是儲存最近一段時間內的版本(比如最近七天)。使用者可以針對每個列族進行設定。

Column Family:列簇,Table在水平方向有一個或者多個Column Family組成,一個Column Family中可以由任意多個Column組成,即Column Family支援動態擴充套件,無需預先定義Column的數量以及型別,所有Column均以二進位制格式儲存,使用者需要自行進行型別轉換。列名都以列族作為字首。例如courses:history,courses:math都屬於courses 這個列族。

訪問控制、磁碟和記憶體的使用統計都是在列族層面進行的。實際應用中,列族上的控制許可權能幫助我們管理不同型別的應用:我們允許一些應用可以新增新的基本資料、一些應用可以讀取基本資料並建立繼承的列族、一些應用則只允許瀏覽資料(甚至可能因為隱私的原因不能瀏覽所有資料)。

Cell:由{row key, column(=<family> + <label>), version} 唯一確定的單元。cell中的資料是沒有型別的,全部是位元組碼形式存貯。

2)Table & Region

當Table隨著記錄數不斷增加而變大後,會逐漸分裂成多份splits,成為regions,一個region由[startkey,endkey)表示,不同的region會被Master分配給相應的RegionServer進行管理。

3)兩個特殊表

-ROOT- 表和.META.表是兩個比較特殊的表,.META.記錄了使用者表的Region資訊,.META.可以有多個regoin。-ROOT-記錄了.META.表的Region資訊,-ROOT-只有一個region,Zookeeper中記錄了-ROOT-表的location。

Client訪問使用者資料之前需要首先訪問zookeeper,然後訪問-ROOT-表,接著訪問.META.表,最後才能找到使用者資料的位置去訪問,中間需要多次網路操作,不過client端會做cache快取。

【注意】
1.root region永遠不會被split,保證了最多需要三次跳轉,就能定位到任意region 。
2.META.表每行儲存一個region的位置資訊,row key 採用表名+表的最後一樣編碼而成。
3.為了加快訪問,.META.表的全部region都儲存在記憶體中。
假設,.META.表的一行在記憶體中大約佔用1KB。並且每個region限制為128MB。
那麼上面的三層結構可以儲存的region數目為:(128MB/1KB) * (128MB/1KB) = = 2^34個region

4.client會將查詢過的位置資訊儲存快取起來,快取不會主動失效,因此如果client上的快取全部失效,則需要進行6次網路來回,才能定位到正確的region(其中三次用來發現快取失效,另外三次用來獲取位置資訊)。

Hbase 0.96之後去掉了-ROOT- 表,因為:

    – 三次請求才能直到使用者Table真正所在的位置也是效能低下的。

    – 即使去掉-ROOT- Table,也還可以支援2^17(131072)個Hregion,對於叢集來說,儲存空間也足夠。

所以目前流程為:

    – 從ZooKeeper(/hbase/meta-region-server)中獲取hbase:meta的位置(HRegionServer的位置),快取該位置資訊。

    – 從HRegionServer中查詢使用者Table對應請求的RowKey所在的HRegionServer,快取該位置資訊。

    – 從查詢到HRegionServer中讀取Row。

6.2Hfile

下圖是HFile的儲存格式:

Hadoop叢集中Hbase的介紹、安裝、使用Hadoop叢集中Hbase的介紹、安裝、使用

首先HFile檔案是不定長的,長度固定的只有其中的兩塊:Trailer和FileInfo。正如圖中所示的,Trailer中有指標指向其他資料塊的起始點。File Info中記錄了檔案的一些Meta資訊,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。Data Index和Meta Index塊記錄了每個Data塊和Meta塊的起始點。

Data Block是HBase I/O的基本單元,為了提高效率,HRegionServer中有基於LRU(Least recently used,快取淘汰演算法)的Block Cache機制。每個Data塊的大小可以在建立一個Table的時候通過引數指定,大號的Block有利於順序Scan,小號Block利於隨機查詢。 每個Data塊除了開頭的Magic以外就是一個個KeyValue對拼接而成,Magic內容就是一些隨機數字,目的是防止資料損壞。HFile裡面的每個KeyValue對就是一個簡單的byte陣列。但是這個byte陣列裡麵包含了很多項,並且有固定的結構。我們來看看裡面的具體結構:

Hadoop叢集中Hbase的介紹、安裝、使用Hadoop叢集中Hbase的介紹、安裝、使用

開始是兩個固定長度的數值,分別表示Key的長度和Value的長度。緊接著是Key,開始是固定長度的數值,表示RowKey的長度,緊接著是 RowKey,然後是固定長度的數值,表示Family的長度,然後是Family,接著是Qualifier,然後是兩個固定長度的數值,表示Time Stamp和Key Type(Put/Delete)。Value部分沒有這麼複雜的結構,就是純粹的二進位制資料了。

6.3Hlog

Hadoop叢集中Hbase的介紹、安裝、使用Hadoop叢集中Hbase的介紹、安裝、使用

上圖中示意了HLog檔案的結構,其實HLog檔案就是一個普通的Hadoop Sequence File,Sequence File 的Key是HLogKey物件,HLogKey中記錄了寫入資料的歸屬資訊,除了table和region名字外,同時還包括 sequence number和timestamp,timestamp是“寫入時間”,sequence number的起始值為0,或者是最近一次存入檔案系統中sequence number。HLog Sequece File的Value是HBase的KeyValue物件,即對應HFile中的KeyValue,可參見上文描述。

LogFlusher

前面提到,資料以KeyValue形式到達HRegionServer,將資料寫入hlog之後,寫入一個SequenceFile。看過去沒問題,但是因為資料流在寫入檔案系統時,經常會快取以提高效能。這樣,有些本以為在日誌檔案中的資料實際在記憶體中。這裡,我們提供了一個LogFlusher的類。它呼叫HLog.optionalSync(),後者根據 hbase.regionserver.optionallogflushinterval (預設是10秒),定期呼叫Hlog.sync()。另外,HLog.doWrite()也會根據hbase.regionserver.flushlogentries (預設100秒)定期呼叫Hlog.sync()。Sync() 本身呼叫HLog.Writer.sync(),它由SequenceFileLogWriter實現。

LogRoller

Log的切換通過$HBASE_HOME/conf/hbase-site.xml的 hbase.regionserver.logroll.period 限制,預設是一個小時。所以每60分鐘,會開啟一個新的log檔案。久而久之,會有一大堆的檔案需要維護。首先,LogRoller呼叫HLog.rollWriter(),定時滾動日誌,之後,利用HLog.cleanOldLogs()可以清除舊的日誌。它首先取得儲存檔案中的最大的sequence number,之後檢查是否存在一個log所有的條目的“sequence number”均低於這個值,如果存在,將刪除這個log。

每個region server維護一個HLog,而不是每一個region一個,這樣不同region(來自不同的table)的日誌會混在一起,這樣做的目的是不斷追加單個檔案相對於同時寫多個檔案而言,可以減少磁碟定址次數,因此可以提高table的寫效能。帶來的問題是,如果一個region server下線,為了恢復其上的region,需要講region server上的log進行拆分,然後分發到其他region server上進行恢復。

7.HBASE容錯

• Master容錯:

      Zookeeper重新選擇一個新的Master。無Master過程中,資料讀取仍照常進行,但是region切分、負載均衡等無法進行。

• Region Server容錯:

    定時向Zookeeper彙報心跳,如果一旦時間內未出現心跳,Master將該RegionServer上的Region重新分配到其他RegionServer上,失效伺服器上“預寫”日誌由主伺服器進行分割並派送給新的RegionServer。

• Zookeeper容錯:

    Zookeeper是一個可靠地服務,一般配置3或5個Zookeeper例項

• WAL(Write-Ahead-Log)預寫日誌是Hbase的RegionServer在處理資料插入和刪除的過程中用來記錄操作內容的一種日誌,在每次Put、 Delete等一條記錄時,首先將其資料寫入到RegionServer對應的HLog檔案的過程。

    客戶端往RegionServer端提交資料的時候,會寫WAL日誌,只有當WAL日誌寫成功以後,客戶端才會被告訴提交資料成功,如果寫WAL失敗會告知客戶端提交失敗。資料落地的過程在一個RegionServer上的所有的Region都共享一個HLog,一次資料的提交是先寫WAL,寫入成功後,再寫memstore。

三、HBase操作

1.安裝和配置

解壓安裝

# tar –zxf hbase-1.2.0-cdh5.7.0 

配置系統變數。

export HBASE_HOME=/usr/local/src/hbase-1.2.0-cdh5.7.0
export PATH=$PATH:$HBASE_HOME/bin

配置hbase-env.sh。

export JAVA_HOME=/usr/local/src/jdk1.8.0_144
export HBASE_MANAGES_ZK=false(注:該屬性讓HBase使用一個已有的不被HBase託管的Zookeepr叢集)

配置hbase-site.xml。

<configuration>
       <property>
               <name>hbase.rootdir</name>
                <value>hdfs://master:9000/hbase</value> 這裡要和hadoop配置的namenode的埠對應起來
        </property>
       <property>
               <name>hbase.cluster.distributed</name>
                <value>true</value>
       </property>
       <property>
               <name>hbase.zookeeper.quorum</name>
               <value>master:2181</value>
       </property>
</configuration>

配置regionservers。

master
slave1
slave2

2.啟動zookeeper

# zkServer.sh start

3.啟動hbase

[root@master bin]# ./start-hbase.sh 檢視程式會發現多了2個:hmaster和hregionserver
# jps
70977 Jps
34946 NameNode
42932 QuorumPeerMain
35476 NodeManager
35045 DataNode
70423 Main
35368 ResourceManager
70875 HRegionServer 
35227 SecondaryNameNode
70733 HMaster

可以在瀏覽器,開啟60010埠檢視。


4.hbase shell

# hbase shell 進入shell模式

1)一般操作

• 檢視資料庫狀態(status、version)

hbase(main):001:0> status
1 active master, 0 backup masters, 1 servers, 0 dead, 3.0000 average load

• 執行help查詢幫助

hbase(main):001:0> help 

    – general:普通命令組

    – ddl:資料定義語言命令組

    – dml:資料操作語言命令組

    – tools:工具組

    – replication:複製命令組

    – SHELL USAGE:shell語法

2)DDL操作

• 命令create / list / describe

hbase(main):027:0> create 'student','member_id','address','info'

    – 表名:music_table

    – 列簇1:meta_data

    – 列簇2: action

hbase(main):028:0> list #列出當前建立的表
hbase(main):029:0> describe 'student' #檢視錶的具體資訊

• 檢視全表內容 scan

hbase(main):006:0> scan 'student'

• 刪除一個列族,alter,disable,enable

    – 凡是要修改表的結構hbase規定,必須先禁用表->修改表->啟用表,直接修改會報錯

hbase(main):032:0> disable 'student'
hbase(main):030:0> alter 'student',{NAME=>'member_id',METHOD=>'delete'} 刪除某個列簇
hbase(main):032:0> enable 'student'

• 命令drop / exists

hbase(main):032:0> disable 'student' 
hbase(main):032:0> drop 'student' #刪除表,在刪除前要先disable該表

hbase(main):032:0> exists 'student' #檢視某個具體表是否存在

• is_enabled/is_disabled

檢視錶格是否是disabled或者是enabled。

is_disabled 'student'

3)DML操作

• 插入命令put

    – 對於hbase來說insert update其實沒有什麼區別,都是插入原理

    – 在hbase中沒有資料型別概念,都是“字元型別”,至於含義在程式中體現

    – 每插入一條記錄都會自動建立一個時間戳,由系統自動生成。也可手動“強行指定”

hbase(main):033:0> put 'student','xll','info:age','24'
0 row(s) in 0.0480 seconds

hbase(main):034:0> put 'student','xll','info:birthday','1992.01.20'
0 row(s) in 0.0110 seconds

hbase(main):035:0> put 'student','xll','address:country','china'
0 row(s) in 0.1120 seconds

hbase(main):036:0> put 'student','xll','address:city','hanzhou'
0 row(s) in 0.0140 seconds

put操作時,需要指定表名、rowkey、列簇:列名、值

• get 檢視記錄

hbase(main):007:0> get 'student','xll' #檢視某rowkey對應的所有資料
COLUMN                              CELL                                                                                                 
 address:city                       timestamp=1527397879925, value=hanzhou                                                               
 address:country                    timestamp=1527397866015, value=china                                                                 
 info:age                           timestamp=1527397813629, value=24                                                                    
 info:birthday                      timestamp=1527397830188, value=1992.01.20                                                            
4 row(s) in 0.0450 seconds
hbase(main):008:0> get 'student','xll','info' #也可以檢視某列簇的資料
COLUMN                              CELL                                                                                                 
 info:age                           timestamp=1527397813629, value=24                                                                    
 info:birthday                      timestamp=1527397830188, value=1992.01.20                                                            
2 row(s) in 0.0390 seconds
hbase(main):009:0> get 'student','xll','info:age' #檢視某列的具體資料
COLUMN                              CELL                                                                                                 
 info:age                           timestamp=1527397813629, value=24                                                                    
1 row(s) in 0.0530 seconds

• 更改某個資料

hbase(main):011:0> get 'student','xll','info:age','25' #再次檢視時,資料已經變化

• 通過timestamp來獲取兩個版本的資料

hbase(main):012:0> get 'student','xll',{COLUMN=>'info:age',TIMESTAMP=>1527397813629} #修改前
COLUMN                              CELL                                                                                                 
 info:age                           timestamp=1527397813629, value=24                                                                    
1 row(s) in 0.0150 seconds

hbase(main):013:0> get 'student','xll',{COLUMN=>'info:age',TIMESTAMP=>1527399122064} #修改後
COLUMN                              CELL                                                                                                 
 info:age                           timestamp=1527399122064, value=25                                                                    
1 row(s) in 0.0210 seconds

如果不加時間戳,則獲取的是最新的資料

• 修改版本儲存個數:

– alter 'student',{NAME=>'info', VERSIONS=>3} #先修改,使之能夠儲存3個歷史資料
hbase(main):021:0> get 'student','xll',{COLUMN=>'info:age', VERSIONS=>3} #檢視3個版本
COLUMN                              CELL                                                                                                 
 info:age                           timestamp=1527399609125, value=27 
 info:age                           timestamp=1527399516025, value=26 
 info:age                           timestamp=1527399122064, value=25 
3 row(s) in 0.0130 seconds

• 刪除delete

    – 刪除指定列簇

hbase(main):026:0> delete 'student','xll','address:country'
0 row(s) in 0.0290 seconds

hbase(main):027:0> get 'student','xll'
COLUMN                              CELL                                                                                                 
 address:city                       timestamp=1527397879925, value=hanzhou                                                  
 info:age                           timestamp=1527399618078, value=28                                                       
 info:birthday                      timestamp=1527397830188, value=1992.01.20                                               
3 row(s) in 0.0580 seconds

    – 刪除整行

hbase(main):030:0> deleteall 'student','lh'
0 row(s) in 0.0110 seconds

• 檢視有多少條記錄count

count 'student'

• 給某id增加欄位,並使用counter實現遞增

hbase(main):034:0> incr 'student','xll','address:country' #給xll增加country欄位
COUNTER VALUE = 1
0 row(s) in 0.0640 seconds

• 清空表truncate

    – 注意:truncate表的處理過程:由於Hadoop的HDFS檔案系統不允許直接修改,所以只能先刪除表,再重新建立以達到清空表的目的

hbase(main):039:0> truncate 'student'
Truncating 'student' table (it may take a while):
 - Disabling table...
 - Truncating table...
0 row(s) in 4.1070 seconds

四、HBASE目錄結構

自0.96版本之後,hbase 原始碼結構上做了很大的優化,目錄結構也發生了變化,做了精簡和優化,這裡以0.98.8為例介紹,目錄如下:

/hbase/.tmp、/hbase/WALs、/hbase/archive、/hbase/corrupt、/hbase/data

/hbase/hbase.id、/hbase/hbase.version、/hbase/oldWALs

1/hbase/.tmp

當對錶做建立或者刪除操作的時候,會將表move 到該 tmp 目錄下,然後再去做處理操作。

2/hbase/WALs

HBase 是支援 WAL(Write AheadLog) 的,HBase 在第一次啟動之初會給每一臺RegionServer 在. WALs下建立一個目錄,若客戶端如果開啟WAL 模式,會先將資料寫入一份到WALs下,當 RegionServer crash 或者目錄達到一定大小,會開啟 replay 模式,類似 MySQL 的 binlog。

WAL最重要的作用是災難恢復,一旦伺服器崩潰,通過重放log,我們可以恢復崩潰之前的資料。如果寫入WAL失敗,整個操作也將認為失敗。

HLog是實現WAL的類。一個HRegionServer對應一個HLog例項。當HRegion初始化時,HLog將作為一個引數傳給HRegion的建構函式。

3/hbase/archive

HBase 在做Split或者 compact 操作完成之後,會將HFile 移到archive 目錄中,然後將之前的 hfile 刪除掉,該目錄由 HMaster 上的一個定時任務定期去清理。

4/hbase/corrupt

儲存HBas損壞的日誌檔案,一般都是為空的。

5/hbase/data

這個才是 hbase 的核心目錄,0.98版本里支援 namespace 的概念模型,系統會預置兩個 namespace 即:hbase和default

5.1 /hbase/data/default

     這個預設的namespace即沒有指定namespace的表都將會flush 到該目錄下面。

5.2 /hbase/data/hbase

    這個namespace 下面儲存了 HBase 的 namespace、meta 和acl 三個表,這裡的 meta 表跟0.94版本的.META.是一樣的,自0.96之後就已經將 ROOT 表去掉了,直接從Zookeeper 中找到meta 表的位置,然後通過 meta 表定位到 region。 namespace 中儲存了 HBase 中的所有 namespace 資訊,包括預置的hbase 和 default。acl 則是表的使用者許可權控制。

     如果自定義一些namespace 的話,就會再/hbase/data 目錄下新建一個 namespace 資料夾,該 namespace 下的表都將 flush 到該目錄下。

6/hbase/hbase.id

     它是一個檔案,儲存叢集唯一的 cluster id 號,是一個 uuid。

7/hbase/hbase.version

     同樣也是一個檔案,儲存叢集的版本號,貌似是加密的,看不到,只能通過web-ui 才能正確顯示出來。

8/hbase/oldWALs

當.logs 資料夾中的 HLog 沒用之後會 move 到.oldlogs 中,HMaster 會定期去清理。

五、Hbase深入

1.HBase的讀資料流程

1)HRegionServer儲存著meta表以及表資料,要訪問表資料,首先Client先去訪問zookeeper,從zookeeper裡面獲取meta表所在的位置資訊,即找到這個meta表在哪個HRegionServer上儲存著。

2)接著Client通過剛才獲取到的HRegionServer的IP來訪問Meta表所在的HRegionServer,從而讀取到Meta,進而獲取到Meta表中存放的後設資料。

3)Client通過後設資料中儲存的資訊,訪問對應的HRegionServer,然後掃描所在HRegionServer的Memstore和Storefile來查詢資料。

4)最後HRegionServer把查詢到的資料響應給Client。

2.HBase的寫資料流程

1)Client也是先訪問zookeeper,找到Meta表,並獲取Meta表後設資料。

2)確定當前將要寫入的資料所對應的HRegion和HRegionServer伺服器。

3)Client向該HRegionServer伺服器發起寫入資料請求,然後HRegionServer收到請求並響應。

4)CLient先把資料寫入到HLog,以防止資料丟失。

5)然後將資料寫入到Memstore

6)如果HLog和Memstore均寫入成功,則這條資料寫入成功

7)如果Memstore達到閾值(注意,不存在“閥值”這麼一說,屬於長期的誤用,在此提醒),會把Memstore中的資料flush到Storefile中。

8)當Storefile越來越多,會觸發Compact合併操作,把過多的Storefile合併成一個大的Storefile。

9)當Storefile越來越大,Region也會越來越大,達到閾值後,會觸發Split操作,將Region一分為二。

3.三個機制

1)Flush機制

當MemStore達到閾值,將Memstore中的資料Flush進Storefile。

涉及屬性:hbase.hregion.memstore.flush.size:134217728,即:128M就是Memstore的預設閾值

hbase.regionserver.global.memstore.upperLimit:0.4,即:這個引數的作用是當單個HRegion內所有的Memstore大小總和超過指定值時,flush該HRegion的所有memstore。RegionServer的flush是通過將請求新增一個佇列,模擬生產消費模式來非同步處理的。那這裡就有一個問題,當佇列來不及消費,產生大量積壓請求時,可能會導致記憶體陡增,最壞的情況是觸發OOM。

hbase.regionserver.global.memstore.lowerLimit:0.38,即:當MemStore使用記憶體總量達到hbase.regionserver. global.memstore.upperLimit指定值時,將會有多個MemStoresflush到檔案中,MemStore flush 順序是按照大小降序執行的,直到重新整理到MemStore使用記憶體略小於hbase.regionserver.global.memstore.lowerLimit。

2)Compact機制:

把小的Memstore檔案合併成大的Storefile檔案。

在HBase中,每當memstore的資料flush到磁碟後,就形成一個storefile,當storefile的數量越來越大時,會嚴重影響HBase的讀效能 ,所以必須將過多的storefile檔案進行合併操作。Compaction是Buffer-flush-merge的LSM-Tree模型的關鍵操作,主要起到如下幾個作用:

(1)合併檔案
(2)清除刪除、過期、多餘版本的資料
(3)提高讀寫資料的效率

hbase(main):043:0> merge_region '759a217c34ad520380b6b209','939affd9185022367a0a4a59a', true #後面跟的是region的id
hbase(main):043:0> major_compact 'student'

3)Split機制

當Region達到閾值,會把過大的Region一分為二。

在Hbase中split是一個很重要的功能,Hbase是通過把資料分配到一定數量的region來達到負載均衡的。一個table會被分配到一個或多個region中,這些region會被分配到一個或者多個regionServer中。在自動split策略中,當一個region達到一定的大小就會自動split成兩個region。table在region中是按照row key來排序的,並且一個row key所對應的行只會儲存在一個region中,這一點保證了Hbase的強一致性 。

在一個region中有一個或多個stroe,每個stroe對應一個column families(列族)。一個store中包含一個memstore 和 0 或 多個store files。每個column family 是分開存放和分開訪問的。

Pre-splitting

當一個table剛被建立的時候,Hbase預設的分配一個region給table。也就是說這個時候,所有的讀寫請求都會訪問到同一個regionServer的同一個region中,這個時候就達不到負載均衡的效果了,叢集中的其他regionServer就可能會處於比較空閒的狀態。解決這個問題可以用pre-splitting,在建立table的時候就配置好,生成多個region。

在table初始化的時候如果不配置的話,Hbase是不知道如何去split region的,因為Hbase不知道哪個row key可以作為split的開始點。如果我們可以大概預測到row key的分佈,我們可以使用pre-spliting來幫助我們提前split region。不過如果我們預測得不準確的話,還是可能導致某個region過熱,被集中訪問,不過還好我們還有auto-split。最好的辦法就是首先預測split的切分點,做pre-splitting,然後後面讓auto-split來處理後面的負載均衡。

Hbase自帶了兩種pre-split的演算法,分別是HexStringSplit 和UniformSplit 。如果我們的row key是十六進位制的字串作為字首的,就比較適合用HexStringSplit,作為pre-split的演算法。例如,我們使用HexHash(prefix)作為row key的字首,其中Hexhash為最終得到十六進位制字串的hash演算法。我們也可以用我們自己的split演算法。

在hbase shell 下:

hbase org.apache.hadoop.hbase.util.RegionSplitter pre_split_table HexStringSplit -c 10 -f f1

【 -c 10 的意思為,最終的region數目為10個;-f  f1為建立一個名稱為f1的 column family。】

我們也可以自定義切分點,例如在hbase shell下使用如下命令:

hbase(main):043:0> create 'student', 'f1', SPLITS=> ['a', 'b', 'c']

自動splitting

當一個reion達到一定的大小,他會自動split稱兩個region。如果我們的Hbase版本是0.94 +,那麼預設的有三種自動split的策略,ConstantSizeRegionSplitPolicy,IncreasingToUpperBoundRegionSplitPolicy還有 KeyPrefixRegionSplitPolicy。

在0.94版本之前ConstantSizeRegionSplitPolicy 是預設和唯一的split策略。當某個store(對應一個column family)的大小大於配置值 ‘hbase.hregion.max.filesize’的時候(預設10G)region就會自動分裂。

而0.94版本中,IncreasingToUpperBoundRegionSplitPolicy 是預設的split策略。這個策略中,最小的分裂大小和table的某個region server的region 個數有關,當store file的大小大於如下公式得出的值的時候就會split,公式如下。

Min (R^2 * “hbase.hregion.memstore.flush.size”, “hbase.hregion.max.filesize”)  R為同一個table中在同一個region server中region的個數。

例如:

hbase.hregion.memstore.flush.size 預設值 128MB。
hbase.hregion.max.filesize預設值為10GB 。
如果初始時R=1,那麼Min(128MB,10GB)=128MB,也就是說在第一個flush的時候就會觸發分裂操作。
當R=2的時候Min(2*2*128MB,10GB)=512MB,當某個store file大小達到512MB的時候,就會觸發分裂。
如此類推,當R=9的時候,store file 達到10GB的時候就會分裂,也就是說當R>=9的時候,store file 達到10GB的時候就會分裂。

split 點都位於region中row key的中間點。KeyPrefixRegionSplitPolicy可以保證相同的字首的row儲存在同一個region中。

指定rowkey字首位數劃分region,通過讀取 KeyPrefixRegionSplitPolicy.prefix_length屬性【該屬性為數字型別,表示字首長度】,在進行split時,按此長度對splitPoint進行擷取。此種策略比較適合固定字首的rowkey。當table中沒有設定該屬性,指定此策略效果等同與使用IncreasingToUpperBoundRegionSplitPolicy。可以通過配置 hbase.regionserver.region.split.policy 來指定split策略,我們也可以寫我們自己的split策略。

強制split

Hbase 允許客戶端強制執行split,在hbase shell中執行以下命令:

hbase(main):042:0> split 'student','b'  //其中student為要split的table , ‘b’ 為split 點

region splits 執行過程

參考:https://www.cnblogs.com/niurougan/p/3976519.html

region server處理寫請求的時候,會先寫入memstore,當memstore 達到一定大小的時候,會寫入磁碟成為一個store file。這個過程叫做 memstore flush。當store files 堆積到一定大小的時候,region server 會 執行‘compact’操作,把他們合成一個大的檔案。 當每次執行完flush 或者compact操作,都會判斷是否需要split。當發生split的時候,會生成兩個region A 和 region B但是parent region資料file並不會發生複製等操作,而是region A 和region B 會有這些file的引用。這些引用檔案會在下次發生compact操作的時候清理掉,並且當region中有引用檔案的時候是不會再進行split操作的。這個地方需要注意一下,如果當region中存在引用檔案的時候,而且寫操作很頻繁和集中,可能會出現region變得很大,但是卻不split。因為寫操作比較頻繁和集中,但是沒有均勻到每個引用檔案上去,所以region一直存在引用檔案,不能進行分裂,這篇文章講到了這個情況,總結得挺好的。http://koven2049.iteye.com/blog/1199519 。
雖然split region操作是region server單獨確定的,但是split過程必須和很多其他部件合作。region server 在split開始前和結束前通知master,並且需要更新.META.表,這樣,客戶端就能知道有新的region。在hdfs中重新排列目錄結構和資料檔案。split是一個複雜的操作。在split region的時候會記錄當前執行的狀態,當出錯的時候,會根據狀態進行回滾。下圖表示split中,執行的過程。(紅色線表示region server 或者master的操作,綠色線表示client的操作。)

1.region server決定split region,第一步,region server在zookeeper的/hbase/region-in-transition/region-name 目錄下,建立一個znode,狀態為SPLITTING。
2.因為master有對region-in-transition的znode做監聽,所以mater得知parent region需要split。
3.region server在hdfs的parent region的目錄下建立一個名為“.splits”的子目錄。
4.region server關閉parent region。強制flush快取,並且在本地資料結構中標記region為下線狀態。如果這個時候客戶端剛好請求到parent region,會丟擲NotServingRegionException。這時客戶端會進行補償性重試。
5.region server在.split目錄下分別為兩個daughter region建立目錄和必要的資料結構。然後建立兩個引用檔案指向parent regions的檔案。
6.region server在HDFS中,建立真正的region目錄,並且把引用檔案移到對應的目錄下。
7.region server送一個put的請求到.META.表中,並且在.META.表中設定parent region為下線狀態,並且在parent region對應的row中兩個daughter region的資訊。但是這個時候在.META.表中daughter region 還不是獨立的row。這個時候如果client scan .META.表,會發現parent region正在split,但是client還看不到daughter region的資訊。當這個put 成功之後,parent region split會被正在的執行。如果在 RPC 成功之前 region server 就失敗了,master和下次開啟parent region的region server 會清除關於這次split的髒狀態。但是當RPC返回結果給到parent region ,即.META.成功更新之後,region split的流程還會繼續進行下去。相當於是個補償機制,下次在開啟這個parent region的時候會進行相應的清理操作。
8.region server開啟兩個daughter region接受寫操作。
9.region server在.META.表中增加daughters A 和 B  region的相關資訊,在這以後,client就能發現這兩個新的regions並且能傳送請求到這兩個新的region了。client本地具體有.META.表的快取,當他們訪問到parent region的時候,發現parent region下線了,就會重新訪問.META.表獲取最新的資訊,並且更新本地快取。
10.region server更新znode的狀態為SPLIT。master就能知道狀態更新了,master的平衡機制會判斷是否需要把daughter regions 分配到其他region server 中。
11.在split之後,meta和HDFS依然會有引用指向parent region。當compact操作發生在daughter regions中,會重寫資料file,這個時候引用就會被逐漸的去掉。垃圾回收任務會定時檢測daughter regions是否還有引用指向parent files,如果沒有引用指向parent files的話,parent region 就會被刪除。

4.region分配

任何時刻,一個region只能分配給一個region server。master記錄了當前有哪些可用的region server。以及當前哪些region分配給了哪些region server,哪些region還沒有分配。當存在未分配的region,並且有一個region server上有可用空間時,master就給這個region server傳送一個裝載請求,把region分配給這個region server。region server得到請求後,就開始對此region提供服務。

5.region server上線

master使用zookeeper來跟蹤region server狀態。當某個region server啟動時,會首先在zookeeper上的server目錄下建立代表自己的檔案,並獲得該檔案的獨佔鎖。由於master訂閱了server目錄上的變更訊息,當server目錄下的檔案出現新增或刪除操作時,master可以得到來自zookeeper的實時通知。因此一旦region server上線,master能馬上得到訊息。

6.region server下線

當region server下線時,它和zookeeper的會話斷開,zookeeper而自動釋放代表這臺server的檔案上的獨佔鎖。而master不斷輪詢server目錄下檔案的鎖狀態。如果master發現某個region server丟失了它自己的獨佔鎖,(或者master連續幾次和region server通訊都無法成功),master就是嘗試去獲取代表這個region server的讀寫鎖,一旦獲取成功,就可以確定:
1)region server和zookeeper之間的網路斷開了。
2)region server掛了。
的其中一種情況發生了,無論哪種情況,region server都無法繼續為它的region提供服務了,此時master會刪除server目錄下代表這臺region server的檔案,並將這臺region server的region分配給其它還活著的regionserver。
如果網路短暫出現問題導致region server丟失了它的鎖,那麼region server重新連線到zookeeper之後,只要代表它的檔案還在,它就會不斷嘗試獲取這個檔案上的鎖,一旦獲取到了,就可以繼續提供服務。

7.master上線

master啟動進行以下步驟:
1)從zookeeper上獲取唯一一個程式碼master的鎖,用來阻止其它master成為master。
2)掃描zookeeper上的server目錄,獲得當前可用的region server列表。
3)和2中的每個region server通訊,獲得當前已分配的region和region server的對應關係。
4)掃描.META.region的集合,計算得到當前還未分配的region,將他們放入待分配region列表。

8.master下線

由於master只維護表和region的後設資料,而不參與表資料IO的過程,master下線僅導致所有後設資料的修改被凍結(無法建立刪除表,無法修改表的schema,無法進行region的負載均衡,無法處理region上下線,無法進行region的合併,唯一例外的是region的split可以正常進行,因為只有region server參與),表的資料讀寫還可以正常進行。因此master下線短時間內對整個hbase叢集沒有影響。從上線過程可以看到,master儲存的資訊全是可以冗餘資訊(都可以從系統其它地方收集到或者計算出來),因此,一般hbase叢集中總是有一個master在提供服務,還有一個以上的master在等待時機搶佔它的位置。

六、Hbase的Python操作

1.安裝Thrift:

– ]# wget http://archive.apache.org/dist/thrift/0.8.0/thrift-0.8.0.tar.gz

– ]# tar xzf thrift-0.8.0.tar.gz

– ]# yum install automake libtool flex bison pkgconfig gcc-c++ boost-devel libeventdevel zlib-devel python-devel ruby-devel openssl-devel 
– ]# yum install boost-devel.x86_64 – ]# yum install libevent-devel.x86_64

進入thrift目錄。

– ]# ./configure --with-cpp=no --with-ruby=no

– ]# make

– ]# make install

• 產生針對Python的Hbase的API:

–下載hbase原始碼:

– ]# wget http://mirrors.hust.edu.cn/apache/hbase/0.98.24/hbase-0.98.24-src.tar.gz
– [root@master hbase-0.98.24]# find . -name Hbase.thrift
./hbase-thrift/src/main/resources/org/apache/hadoop/hbase/thrift/Hbase.thrift

– [root@master hbase-0.98.24]# cd hbase-thrift/src/main/resources/org/apache/hadoop/hbase/thrift

– ]# thrift -gen py Hbase.thrift

– ]# cp -raf gen-py/hbase/ /home/hbase_test #拷貝出來

2.啟動Thrift服務

– ]# hbase-daemon.sh start thrift

3.Python操作

1)建立表

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

from hbase import Hbase
from hbase.ttypes import *

transport = TSocket.TSocket('masteractive', 9090)
transport = TTransport.TBufferedTransport(transport)

protocol = TBinaryProtocol.TBinaryProtocol(transport)

client = Hbase.Client(protocol)

transport.open()


#==============================

base_info_contents = ColumnDescriptor(name='meta-data:', maxVersions=1)
other_info_contents = ColumnDescriptor(name='flags:', maxVersions=1)

client.createTable('new_music_table', [base_info_contents, other_info_contents])

print client.getTableNames()

2)插入資料

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

from hbase import Hbase
from hbase.ttypes import *

transport = TSocket.TSocket('masteractive', 9090)
transport = TTransport.TBufferedTransport(transport)

protocol = TBinaryProtocol.TBinaryProtocol(transport)

client = Hbase.Client(protocol)

transport.open()

tableName = 'new_music_table'
rowKey = '1100'

mutations = [Mutation(column="meta-data:name", value="wangqingshui"), \
        Mutation(column="meta-data:tag", value="pop"), \
        Mutation(column="flags:is_valid", value="TRUE")]

client.mutateRow(tableName, rowKey, mutations, None)

3)讀取

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol

from hbase import Hbase
from hbase.ttypes import *

transport = TSocket.TSocket('masteractive', 9090)
transport = TTransport.TBufferedTransport(transport)

protocol = TBinaryProtocol.TBinaryProtocol(transport)

client = Hbase.Client(protocol)

transport.open()

tableName = 'new_music_table'
rowKey = '1100'

result = client.getRow(tableName, rowKey, None)

for r in result:
    print 'the row is ' , r.row
    print 'the name is ' , r.columns.get('meta-data:name').value
    print 'the flag is ' , r.columns.get('flags:is_valid').value

相關文章