Hbase RowKey 設(shè)計
使用Spark或通過REST/API 方式存取Hbase烛卧,性能影響最大的因素在于Hbase 的結(jié)構(gòu)設(shè)計咪啡。Hbase 結(jié)構(gòu)設(shè)計包括兩個方面
- rowKey 的設(shè)計
- rowKey 和Hbase 表預(yù)分區(qū)
rowKey 數(shù)據(jù)盡量保持短小精悍缀辩,同時還要能與業(yè)務(wù)數(shù)據(jù)的主鍵有關(guān)聯(lián)荆残。
同時盡量散列芽唇。這樣才能保證數(shù)據(jù)均勻的存儲到Hbase 的Region里弛车。數(shù)據(jù)均勻分不到Hbase Region 中蛉鹿,檢索的速度才夠快滨砍。
rowKey 設(shè)計有以下幾種思路
- 拼接業(yè)務(wù)主鍵,轉(zhuǎn)換為md5
- 拼接業(yè)務(wù)主鍵妖异,按照一個Hash 算法截取前綴再拼接業(yè)務(wù)主鍵
第一種方法:數(shù)據(jù)分布比較平均惋戏,處理簡單,當(dāng)無法從rowKey 中抽取業(yè)務(wù)主鍵他膳,因此需在Hbase 數(shù)據(jù)列中額外花費額外的空間存儲响逢。
第二種方法:數(shù)據(jù)分布也比較平均,需專門實現(xiàn)Hash 算法和抽取業(yè)務(wù)主鍵的方法棕孙,當(dāng)節(jié)省了數(shù)據(jù)存成空間舔亭。
幾種構(gòu)造RowKey 的方法
// 對指定的列構(gòu)造rowKey,采用Hash前綴拼接業(yè)務(wù)主鍵的方法
def rowKeyWithHashPrefix(column: String*): Array[Byte] = {
val rkString = column.mkString("")
val hash_prefix = getHashCode(rkString)
val rowKey = Bytes.add(Bytes.toBytes(hash_prefix), Bytes.toBytes(rkString))
rowKey
}
// 對指定的列構(gòu)造rowKey, 采用Md5 前綴拼接業(yè)務(wù)主鍵方法,主要目的是建表時采用MD5 前綴進(jìn)行預(yù)分區(qū)
def rowKeyWithMD5Prefix(separator:String,length: Int,column: String*): Array[Byte] = {
val columns = column.mkString(separator)
var md5_prefix = MD5Hash.getMD5AsHex(Bytes.toBytes(columns))
if (length < 8){
md5_prefix = md5_prefix.substring(0, 8)
}else if (length >= 8 || length <= 32){
md5_prefix = md5_prefix.substring(0, length)
}
val row = Array(md5_prefix,columns)
val rowKey = Bytes.toBytes(row.mkString(separator))
rowKey
}
// 對指定的列構(gòu)造RowKey,采用MD5方法
def rowKeyByMD5(column: String*): Array[Byte] = {
val rkString = column.mkString("")
val md5 = MD5Hash.getMD5AsHex(Bytes.toBytes(rkString))
val rowKey = Bytes.toBytes(md5)
rowKey
}
// 直接拼接業(yè)務(wù)主鍵構(gòu)造rowKey
def rowKey(column:String*):Array[Byte] = Bytes.toBytes(column.mkString(""))
// Hash 前綴的方法:指定列拼接之后與最大的Short值做 & 運算
// 目的是預(yù)分區(qū)蟀俊,盡量保證數(shù)據(jù)均勻分布
private def getHashCode(field: String): Short ={
(field.hashCode() & 0x7FFF).toShort
}
Hbase RowKey 設(shè)計和Hbase 建表
為了提高Hbase 寫入速度钦铺,預(yù)分區(qū)是一種非常重要的技術(shù)手段。預(yù)分區(qū)之后肢预,數(shù)據(jù)會被均勻分散到不同的region 中职抡,這樣不會出現(xiàn)寫熱點,從而提高Hbase寫入速度误甚。
/**
* Hbase自帶了兩種pre-split的算法,分別是 HexStringSplit 和 UniformSplit
* 如果我們的row key是十六進(jìn)制的字符串作為前綴的,就比較適合用HexStringSplit
* @param tablename 表名
* @param regionNum 預(yù)分區(qū)數(shù)量
* @param columns 列簇數(shù)組
*/
def createHTable(connection: Connection, tablename: String,regionNum: Int, columns: Array[String]): Unit = {
val hexsplit: HexStringSplit = new HexStringSplit()
// 預(yù)先構(gòu)建分區(qū)缚甩,指定分區(qū)的start key
val splitkeys: Array[Array[Byte]] = hexsplit.split(regionNum)
val admin = connection.getAdmin
val tableName = TableName.valueOf(nameSpace + ":" + tablename)
if (!admin.tableExists(tableName)) {
if(!admin.getNamespaceDescriptor(nameSpace).getName.equals(nameSpace))
admin.createNamespace(NamespaceDescriptor.create(nameSpace).build())
val tableDescriptor = new HTableDescriptor(tableName)
if (columns != null) {
columns.foreach(c => {
val hcd = new HColumnDescriptor(c.getBytes()) //設(shè)置列簇
hcd.setMaxVersions(1)
hcd.setCompressionType(Algorithm.GZ) //設(shè)定數(shù)據(jù)存儲的壓縮類型.默認(rèn)無壓縮(NONE)
tableDescriptor.addFamily(hcd)
})
}
admin.createTable(tableDescriptor,splitkeys)
}
}
/**
* short預(yù)分區(qū)建表:0X0000~0X7FFF
* @param connection
* @param tablename 表名
* @param regionNum 預(yù)分區(qū)數(shù)量
* @param columns 列簇數(shù)組
*/
def createHTable(connection: Connection, tablename: String,regionNum: Short, columns: Array[String]): Unit = {
val admin = connection.getAdmin
val tableName = TableName.valueOf(nameSpace+ ":" + tablename)
if (!admin.tableExists(tableName)) {
if(!admin.getNamespaceDescriptor(nameSpace).getName.equals(nameSpace))
admin.createNamespace(NamespaceDescriptor.create(nameSpace).build())
val tableDescriptor = new HTableDescriptor(tableName)
if (columns != null) {
columns.foreach(c => {
val hcd = new HColumnDescriptor(c.getBytes()) //設(shè)置列簇
hcd.setMaxVersions(1)
hcd.setCompressionType(Algorithm.GZ) //設(shè)定數(shù)據(jù)存儲的壓縮類型.默認(rèn)無壓縮(NONE)
tableDescriptor.addFamily(hcd)
})
}
val start = (0x7FFF / regionNum).toShort
val end = (0x7FFF - start).toShort
admin.createTable(tableDescriptor,Bytes.toBytes(start),Bytes.toBytes(end),regionNum)
}
}
第一種建表方式,需要在存取數(shù)據(jù)時采用MD5 算法構(gòu)造rowKey, 第二種需要構(gòu)造Hash前綴的rowKey.
通過以上方式建表和查詢能大幅提高Hbase 寫入和讀取速度窑邦,并且不會出現(xiàn)熱點region擅威。
可參考我在Github 上實現(xiàn):https://github.com/Smallhi/example