在對HBase進(jìn)行操作之前帽氓,首先學(xué)習(xí)一下HBase的基礎(chǔ)架構(gòu)和運行原理。這里講解了
- HBase 在大數(shù)據(jù)生態(tài)圈中的位置
- HBase 與傳統(tǒng)關(guān)系數(shù)據(jù)庫的區(qū)別
- HBase 相關(guān)的模塊以及 HBase 表格的特性
- row-key,hfile,cloumn-family,Master,Region Server相關(guān)概念
- zookeeper在其中的作用
- HBase存數(shù)據(jù)的原理
- row-key設(shè)計方案
1鱼响、Row Key
與nosql數(shù)據(jù)庫們一樣,row key是用來檢索記錄的主鍵玷室。訪問hbase table中的行嗽交,只有三種方式:
- 通過單個row key訪問
- 通過row key的range
- 全表掃描
Row key行鍵 (Row key)可以是任意字符串(最大長度是 64KB醋拧,實際應(yīng)用中長度一般為 10-100bytes)慷嗜,在hbase內(nèi)部淀弹,row key保存為字節(jié)數(shù)組。
存儲時庆械,數(shù)據(jù)按照Row key的字典序(byte order)排序存儲薇溃。設(shè)計key時,要充分排序存儲這個特性干奢,將經(jīng)常一起讀取的行存儲放到一起痊焊。(位置相關(guān)性)
字典序?qū)nt排序的結(jié)果是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作左填充。
行的一次讀寫是原子操作 (不論一次讀寫多少列)辕羽。這個設(shè)計決策能夠使用戶很容易的理解程序在對同一個行進(jìn)行并發(fā)更新操作時的行為逛尚。
2、列族 column family
hbase表中的每個列刁愿,都?xì)w屬與某個列族绰寞。列族是表的chema的一部分(而列不是),必須在使用表之前定義铣口。列名都以列族作為前綴滤钱。例如courses:history,courses:math都屬于courses這個列族脑题。
訪問控制件缸、磁盤和內(nèi)存的使用統(tǒng)計都是在列族層面進(jìn)行的。實際應(yīng)用中叔遂,列族上的控制權(quán)限能幫助我們管理不同類型的應(yīng)用:我們允許一些應(yīng)用可以添加新的基本數(shù)據(jù)他炊、一些應(yīng)用可以讀取基本數(shù)據(jù)并創(chuàng)建繼承的列族、一些應(yīng)用則只允許瀏覽數(shù)據(jù)(甚至可能因為隱私的原因不能瀏覽所有數(shù)據(jù))已艰。
3痊末、單元 Cell
HBase中通過row和columns確定的為一個存貯單元稱為cell。由{row key, column( =<family> + <label>), version} 唯一確定的單元哩掺。cell中的數(shù)據(jù)是沒有類型的凿叠,全部是字節(jié)碼形式存貯。
4嚼吞、時間戳 timestamp
每個cell都保存著同一份數(shù)據(jù)的多個版本盒件。版本通過時間戳來索引。時間戳的類型是 64位整型誊薄。時間戳可以由hbase(在數(shù)據(jù)寫入時自動 )賦值履恩,此時時間戳是精確到毫秒的當(dāng)前系統(tǒng)時間。時間戳也可以由客戶顯式賦值呢蔫。如果應(yīng)用程序要避免數(shù)據(jù)版本沖突切心,就必須自己生成具有唯一性的時間戳飒筑。每個cell中,不同版本的數(shù)據(jù)按照時間倒序排序绽昏,即最新的數(shù)據(jù)排在最前面协屡。
為了避免數(shù)據(jù)存在過多版本造成的的管理 (包括存貯和索引)負(fù)擔(dān),hbase提供了兩種數(shù)據(jù)版本回收方式全谤。一是保存數(shù)據(jù)的最后n個版本肤晓,二是保存最近一段時間內(nèi)的版本(比如最近七天)。用戶可以針對每個列族進(jìn)行設(shè)置认然。
安裝HBase
HBase是Google Bigtable的開源實現(xiàn)补憾,它利用Hadoop HDFS作為其文件存儲系統(tǒng),利用Hadoop MapReduce來處理HBase中的海量數(shù)據(jù)卷员,利用Zookeeper作為協(xié)同服務(wù)盈匾。所以安裝HBase之前還需要安裝zookeeper和hdfs。
HBase shell命令使用
HBase shell是HBase的一套命令行工具,類似傳統(tǒng)數(shù)據(jù)中的sql概念未巫,可以使用shell命令來查詢HBase中數(shù)據(jù)的詳細(xì)情況窿撬。安裝完HBase之后,如果配置了HBase的環(huán)境變量叙凡,只要在shell中執(zhí)行hbase shell
就可以進(jìn)入命令行界面劈伴。需要注意的是HBase的交互界面刪除鍵改為了Ctrl + Backspace組合
查看HBase版本
-
創(chuàng)建數(shù)據(jù)庫(命名空間)
在HBase中,namespace命名空間指對一組表的邏輯分組狭姨,類似RDBMS中的database宰啦,方便對表在業(yè)務(wù)上劃分。HBase系統(tǒng)默認(rèn)定義了兩個缺省的namespace
1.hbase:系統(tǒng)內(nèi)建表饼拍,包括namespace和meta表
2.default:用戶建表時未指定namespace的表都創(chuàng)建在此
使用名list_namespace
列出所有的namespace
使用命令create_namespace 'my_test'
創(chuàng)建數(shù)據(jù)庫(命名空間)
-
刪除數(shù)據(jù)庫
使用命令drop_namespace 'my_test'
創(chuàng)建數(shù)據(jù)庫(命名空間)
-
創(chuàng)建學(xué)生信息表
rowkey要求是學(xué)號赡模,一個列簇,包含姓名师抄、性別漓柑、年齡、地址叨吮。
語法:create ‘<table name>’,’<column family>’
通過list命令查看所有表辆布。
-
刪除表
首先使用命令disable '表名'
禁用表,禁用表之后茶鉴,仍然可以通過 list 和exists命令查看到锋玲。
然后使用命令drop '表名'
刪除表
-
插入記錄
命令put 'student','2016211883','info:name','stu1'
表示插入表名為student,rowkey為2016211880(沒有則創(chuàng)建)的行涵叮,列簇info下面的列name的值為stu1.
按照此格式依次添加10個學(xué)生惭蹂,rowkey依次為2016211880-2016211889伞插,并同時添加姓名、性別盾碗、年齡媚污、地址。
刪除記錄
刪除某個屬性的記錄delete ‘<table name>’, ‘<row>’, ‘<column name >’, ‘<time stamp>’
刪除行deleteall 'table_name','row'
-
修改記錄
命令put ‘table name’,’row ’,'Column family:column name',’new value’
將rowkey為2016211880的info簇的name屬性修改為stu1
-
查詢記錄
使用命令get 'student','2016211883'
查看是否成功插入記錄廷雅。
其中第一個字段填寫查詢記錄的表耗美,第二字段填寫記錄的rowkey
也可以使用命令get 'student','2016211883','info'
獲取指定rowkey和列簇的信息
-
掃描
scan 命令用于查看HTable數(shù)據(jù)。使用 scan 命令可以得到表中的數(shù)據(jù)航缀。它的語法如下:
scan ‘<table name>’
-
統(tǒng)計表的行數(shù)
count 'table_name'
清空表
truncate 'table_name'
Python對HBase的操作
啟動thrift
thrift是用于可伸縮的跨語言服務(wù)開發(fā)框架商架。
首先需要安裝thrift,而安裝thrift之前還需要安裝thrift的很多依賴芥玉,詳見這里甸私。
但是很多情況下已經(jīng)幫你安裝好了thrift(可能是安裝操作系統(tǒng)或者安裝hadoop的時候),所以你只需要去開啟就是了飞傀,不能開啟再去安裝。
第一次開啟報了個這個錯诬烹,原因是JAVA_HOME沒有配置或者低于1.7砸烦,所以去/etc/profile或者h(yuǎn)base的conf下hbase-env.sh加上JAVA_HOME就可以了。
就成功啟動了thrift绞吁。
編寫python程序
這里需要用pip導(dǎo)入需要的包
pip install thrift
pip install hbase-thrift
編寫好后運行說語法錯誤幢痘,但是自己的語法并沒有錯,跟著錯誤點進(jìn)了hbase包的源碼家破,發(fā)現(xiàn)是python2版本的語法颜说,在網(wǎng)上搜了一下,說是把hbase里的Hbase.py和ttypes.py換成python3的即可汰聋,于是去python存放第三方包的地方換了门粪,隨后就可以了。
from thrift.transport import TSocket,TTransport
from thrift.protocol import TBinaryProtocol
from hbase import Hbase
# thrift默認(rèn)端口是9090
socket = TSocket.TSocket('192.168.233.100',9090)
socket.setTimeout(5000)
transport = TTransport.TBufferedTransport(socket)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = Hbase.Client(protocol)
socket.open()
print(client.getTableNames())
print(client.get('student','2016211880','info:name'))
其他的操作和HBase shell差不多烹困,只要拿到了
client = Hbase.Client(protocol)
中的client玄妈,其它的操作只是換成了函數(shù)調(diào)用形式而已。
-
獲取所有表
getTableNames()
可以獲取所有表
創(chuàng)建表
createTable(tbaleName,columnFamilies):
創(chuàng)建表髓梅,無返回值
tableName:表名
columnFamilies:列族信息拟蜻,為一個ColumnDescriptor列表
column = ColumnDescriptor(name='row1')
client.createTable('tb1',[column])
這里需要注意,不能直接像HBase shell一樣傳入字符串作為列簇枯饿,而是要先用函數(shù)ColumnDescriptor(需要導(dǎo)入from hbase.ttypes import ColumnDescriptor
)生成列簇對象酝锅,然后作為參數(shù)傳入進(jìn)去。
- 刪除表
disableTable(tbaleName)
首先禁用表
deleteTable(tbaleName)
刪除指定表
print('before',client.getTableNames())
client.disableTable('tb1')
client.deleteTable('tb1')
print('after',client.getTableNames())
- 插入記錄
mutateRow(tableName,row,mutations):在表中指定行執(zhí)行一系列的變化操作奢方。如果拋出異常搔扁,則事務(wù)被中止爸舒。使用默認(rèn)的當(dāng)前時間戳,所有條目將具有相同的時間戳阁谆。無返回值
tableName:表名
row:行
mutations:變化,list
print('before',client.get('student','2016000','info:name'))
mutation = Mutation(column='info:name',value='inset_name') 這里需要注意碳抄,以前的參數(shù)是name和value,現(xiàn)在改為了column和value
client.mutateRow('student','2016000',[mutation])
print('after',client.get('student','2016000','info:name'))
先把需要插入的列簇信息封裝為Mutation對象(需要導(dǎo)入from hbase.ttypes import Mutation
)场绿,再插入表student剖效,2016000行。
- 修改記錄
修改記錄和剛剛上面的插入記錄一樣焰盗,只是把需要修改的信息封裝在Mutation即可璧尸。
print('before',client.get('student','2016000','info:name'))
mutation = Mutation(column='info:name',value='update_name')
client.mutateRow('student','2016000',[mutation])
print('after',client.get('student','2016000','info:name'))
- 查詢記錄
getRow(tableName,row):獲取表中指定行在最新時間戳上的數(shù)據(jù)。返回一個hbase.ttypes.TRowResult對象列表熬拒,如果行號不存在返回一個空列表
tableName:表名
row:行
print(client.getRow('student','2016211880'))
[TRowResult(row='2016211880', columns={'info:add': TCell(value='xxx', timestamp=1534856603503), 'info:age': TCell(value='20', timestamp=1534856521857), 'info:gender': TCell(value='m', timestamp=1534856178334), 'info:name': TCell(value='stu0', timestamp=1534860070457)})]
這樣可以獲取一個列的所有信息爷光,也可以獲取指定信息。
print(client.get('student','2016211880','info:name'))
[TCell(value='stu0', timestamp=1534860070457)]
還可以獲取所有信息后一個一個取出來
# 行
row = '2016211880'
# 查詢結(jié)果
result = client.getRow('student',row) # result為一個列表
for item in result: # item為hbase.ttypes.TRowResult對象
print(item.row)
print(item.columns.get('info:name').value ) # 獲取值澎粟。item.columns.get('cf:a')為一個hbase.ttypes.TCell對象
print(item.columns.get('info:name').timestamp ) # 獲取時間戳蛀序。item.columns.get('cf:a')為一個hbase.ttypes.TCell對象
- 掃描
scannerOpen(tableName,startRow,columns):在指定表中,從指定行開始掃描活烙,到表中最后一行結(jié)束徐裸,掃描指定列的數(shù)據(jù)。返回一個ScannerID啸盏,int類型
tableName:表名
startRow:起始行
columns:列名列表,list類型
scannerId = client.scannerOpen('student','2016211880',["info:name","info:age"])
獲取ScannerID后重贺,根據(jù)ScannerID來獲取結(jié)果,scannerGet(id):返回一個hbase.ttypes.TRowResult對象列表
scannerId = client.scannerOpen('student','2016211880',["info:name","info:age"])
while True:
result = client.scannerGet(scannerId)
if not result:
break
print(result)
結(jié)果
[TRowResult(row='2016211880', columns={'info:age': TCell(value='20', timestamp=1534856521857), 'info:name': TCell(value='stu0', timestamp=1534860070457)})]
[TRowResult(row='2016211881', columns={'info:age': TCell(value='18', timestamp=1534856521887), 'info:name': TCell(value='stu1', timestamp=1534855473518)})]
[TRowResult(row='2016211882', columns={'info:age': TCell(value='18', timestamp=1534856521912), 'info:name': TCell(value='stu2', timestamp=1534855311597)})]
[TRowResult(row='2016211883', columns={'info:age': TCell(value='18', timestamp=1534856521940), 'info:name': TCell(value='stu3', timestamp=1534855469567)})]
[TRowResult(row='2016211884', columns={'info:age': TCell(value='18', timestamp=1534856521969), 'info:name': TCell(value='stu4', timestamp=1534855469670)})]
[TRowResult(row='2016211885', columns={'info:age': TCell(value='18', timestamp=1534856521993), 'info:name': TCell(value='stu5', timestamp=1534855469700)})]
[TRowResult(row='2016211886', columns={'info:age': TCell(value='19', timestamp=1534856522018), 'info:name': TCell(value='stu6', timestamp=1534855469732)})]
[TRowResult(row='2016211887', columns={'info:age': TCell(value='21', timestamp=1534856522042), 'info:name': TCell(value='stu7', timestamp=1534855469761)})]
[TRowResult(row='2016211888', columns={'info:age': TCell(value='22', timestamp=1534856522191), 'info:name': TCell(value='stu8', timestamp=1534855469791)})]
[TRowResult(row='2016211889', columns={'info:age': TCell(value='21', timestamp=1534856523296), 'info:name': TCell(value='stu9', timestamp=1534855469828)})]
其它的操作都和shell差不多回懦,只是換成了函數(shù)形式气笙,使用時可以查閱API文檔。
HBase預(yù)分區(qū)
HBase中怯晕,表會被劃分為1…n個Region潜圃,被托管在RegionServer中。Region二個重要的屬性:StartKey與 EndKey表示這個Region維護(hù)的rowKey范圍贫贝,當(dāng)我們要讀/寫數(shù)據(jù)時秉犹,如果rowKey落在某個start-end key范圍內(nèi),那么就會定位到目標(biāo)region并且讀/寫到相關(guān)的數(shù)據(jù)稚晚。
HBase默認(rèn)建表時有一個region崇堵,這個region的rowkey是沒有邊界的,即沒有startkey和endkey客燕,在數(shù)據(jù)寫入時鸳劳,所有數(shù)據(jù)都會寫入這個默認(rèn)的region,隨著數(shù)據(jù)量的不斷 增加也搓,此region已經(jīng)不能承受不斷增長的數(shù)據(jù)量赏廓,會進(jìn)行split涵紊,分成2個region。在此過程中幔摸,會產(chǎn)生兩個問題:1.數(shù)據(jù)往一個region上寫,會有寫熱點問題摸柄。2.region split會消耗寶貴的集群I/O資源〖纫洌基于此我們可以控制在建表的時候驱负,創(chuàng)建多個空region,并確定每個region的起始和終止rowky患雇,這樣只要我們的rowkey設(shè)計能均勻的命中各個region跃脊,就不會存在寫熱點問題。自然split的幾率也會大大降低苛吱。當(dāng)然隨著數(shù)據(jù)量的不斷增長酪术,該split的還是要進(jìn)行split。像這樣預(yù)先創(chuàng)建hbase表分區(qū)的方式翠储,稱之為預(yù)分區(qū)绘雁。
預(yù)分區(qū)步驟
- 規(guī)劃hbase預(yù)分區(qū)
首先就是要想明白數(shù)據(jù)的key是如何分布的,然后規(guī)劃一下要分成多少region援所,每個region的startkey和endkey是多少咧七,然后將規(guī)劃的key寫到一個文件中。比如任斋,key的前幾位字符串都是從2016211880~2016211889的數(shù)字,這樣可以分成4個region耻涛,劃分key的文件如下:
#只是在分區(qū)比較多的時候需要這么做废酷。分區(qū)少的情況下可以直接在生成表的時候直接用SPLITS分區(qū)
2016211882|
2016211884|
2016211886|
2016211888|
因為在ASCII碼中,"|"的值是124抹缕,大于所有的數(shù)字和字母等符號澈蟆,當(dāng)然也可以用“~”(ASCII-126)。分隔文件的第一行為第一個region的stopkey卓研,每行依次類推趴俘,最后一行不僅是倒數(shù)第二個region的stopkey,同時也是最后一個region的startkey奏赘。也就是說分區(qū)文件中填的都是key取值范圍的分隔點寥闪。
- HBase中建分區(qū)表,指定分區(qū)文件
和以前創(chuàng)建普通表一樣磨淌,只是創(chuàng)建的時候多了一個參數(shù)疲憋,指明了分區(qū)的信息。
create 'sort_table','info',SPLITS=>['2016211882','2016211884','2016211886','2016211888']
這里的分區(qū)表示方法也可以換成文件替代梁只。
create 'split_table_test', 'cf', {SPLITS_FILE => 'region_split_info.txt'}
去主節(jié)點60010端口下查看分區(qū)成功缚柳。
接著插入一條數(shù)據(jù)試一試
put 'sort_table','2016211885','info:add','xxx'
數(shù)據(jù)插入到了指定的分區(qū)當(dāng)中埃脏。