赞
踩
HBase由于其存储和读写的高性能,在OLAP即时分析中越来越发挥重要的作用。作为Nosql数据库的一员,HBase查询只能通过其Rowkey来查询(Rowkey用来表示唯一一行记录),Rowkey设计的优劣直接影响读写性能。
由于HBase是通过Rowkey查询的,一般Rowkey上都会存一些比较关键的检索信息,我们需要提前想好数据具体需要如何查询,根据查询方式进行数据存储格式的设计,要避免做全表扫描,因为效率特别低。
此外易观方舟也使用HBase做用户画像的标签存储方案,存储每个app的用户的人口学属性和商业属性等标签信息。
HBase中设计有MemStore和BlockCache,分别对应列族/Store级别的写入缓存,和RegionServer级别的读取缓存。如果RowKey过长,缓存中存储数据的密度就会降低,影响数据落地或查询效率。
HBase表在刚刚被创建时,只有1个分区(region),当一个region过大(达到hbase.hregion.max.filesize属性中定义的阈值,默认10GB)时,
表将会进行split,分裂为2个分区。表在进行split的时候,会耗费大量的资源,频繁的分区对HBase的性能有巨大的影响。
HBase提供了预分区功能,即用户可以在创建表的时候对表按照一定的规则创建分区。
减少由于region split带来的资源消耗。从而提高HBase的性能。
避免数据倾斜,热点等问题
默认情况下,当我们通过hbaseAdmin指定TableDescriptor来创建一张表时,只有一个region正处于混沌时期,start-end key无边界,可谓海纳百川。所有的rowkey都写入到这个region里,然后数据越来越多,region的size越来越大时,大到一定的阀值,hbase就会将region一分为二,成为2个region,这个过程称为分裂(region-split)。
如果我们就这样默认建表,表里不断的put数据,更严重的是我们的rowkey还是顺序增大的,是比较可怕的。存在的缺点比较明显:
首先是热点写,我们总是会往最大的start-key所在的region写东西,因为我们的rowkey总是会比之前的大,并且hbase的是按升序方式排序的。所以写操作总是被定位到无上界的那个region中。
其次,由于写热点,我们总是往最大start-key的region写记录,之前分裂出来的region不会再被写数据,有点被打进冷宫的赶脚,它们都处于半满状态,这样的分布也是不利的。
如果在写频率高的场景下,数据增长快,split的次数也会增多,由于split是比较耗时耗资源的,所以我们并不希望这种事情经常发生。
预分区与随机散列二者结合起来,是比较完美的。
预分区一开始就预建好了一部分region,这些region都维护着自己的start-end keys,在配合上随机散列,写数据能均衡的命中这些预建的region,就能解决上面的那些缺点,大大提供性能。
https://www.cnblogs.com/cxzdy/p/5521308.html rowkey设计
https://blog.csdn.net/w1014074794/article/details/73140489 rowkey的设计
hash就是rowkey前面由一串随机字符串组成,随机字符串生成方式可以由SHA或者MD5方式生成,只要region所管理的start-end keys范围比较随机,那么就可以解决写热点问题。
1.rowkey的生成策略:
rowkey原本是自增长的long型,可以将rowkey转为hash再转为bytes,加上本身id转为bytes。rowkey=hash(id)+id;这样就生成随便的rowkey。
2.预分区:对于这种方式的rowkey设计,如何去进行预分区呢?
2.1取样,先随机生成一定数量的rowkey,将取样数据按升序排序放到一个集合里。
2.2根据预分区的region个数,对整个集合平均分割,即是相关的splitkeys。
2.3 HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定预分区的splitkey,即指定region间的rowkey临界值。
3. 以上就是按照hash方式,预建好分区,以后再插入数据的时候,也是按照此rowkeyGenerator的方式生成rowkey
- public interface SplitKeysCalculator {
- }
- public interface RowKeyGenerator {
- byte [] nextId();
- }
- public class HashRowKeyGenerator implements RowKeyGenerator {
- private long currentId = 1;
- private long currentTime = System.currentTimeMillis();
- private Random random = new Random();
- public byte[] nextId() {
- try {
- currentTime += random.nextInt(1000);
- byte[] lowT = Bytes.copy(Bytes.toBytes(currentTime), 4, 4);
- byte[] lowU = Bytes.copy(Bytes.toBytes(currentId), 4, 4);
- return Bytes.add(MD5Hash.getMD5AsHex(Bytes.add(lowU, lowT)).substring(0, 8).getBytes(),
- Bytes.toBytes(currentId));
- } finally {
- currentId++;
- }
- }
- }
- //
- public class HashMethodCreatePartition implements SplitKeysCalculator {
- //随机取机数目
- private int baseRecord;
- //rowkey生成器
- private RowKeyGenerator rkGen;
- //取样时,由取样数目及region数相除所得的数量.
- private int splitKeysBase;
- //splitkeys个数
- private int splitKeysNumber;
- //由抽样计算出来的splitkeys结果
- private byte[][] splitKeys;
-
- public HashMethodCreatePartition(int baseRecord, int prepareRegions) {
- this.baseRecord = baseRecord;
- //实例化rowkey生成器
- rkGen = new HashRowKeyGenerator();
- splitKeysNumber = prepareRegions - 1;
- splitKeysBase = baseRecord / prepareRegions;
- }
-
- public byte[][] calcSplitKeys() {
- splitKeys = new byte[splitKeysNumber][];
- //使用treeset保存抽样数据,已排序过
- TreeSet<byte[]> rows = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
- for (int i = 0; i < baseRecord; i++) {
- rows.add(rkGen.nextId());//关键步骤
- }
- int pointer = 0;
- Iterator<byte[]> rowKeyIter = rows.iterator();
- int index = 0;
- while (rowKeyIter.hasNext()) {
- byte[] tempRow = rowKeyIter.next();
- rowKeyIter.remove();
- if ((pointer != 0) && (pointer % splitKeysBase == 0)) {
- if (index < splitKeysNumber) {
- splitKeys[index] = tempRow;
- index ++;
- }
- }
- pointer ++;
- }
- rows.clear();
- rows = null;
- return splitKeys;
- }
- //测试类:
- public class TestHashPartition {
- private final static Logger log= Logger.getLogger(String.valueOf(TestHashPartition.class));
- public static void main(String args[])throws Exception{
- HashMethodCreatePartition worker = new HashMethodCreatePartition(1000000,10);
- byte [][] splitKeys = worker.calcSplitKeys();
- String tableName="hash_split_table";//用户画像hbase中的表
- TableName tn=TableName.valueOf(Constants.HBASE_TABLE_NAME.value);
- String familyArray[]={"mboth"};//定义列族
- initUserTable(tableName,familyArray,true,splitKeys);
-
-
- }
- public static void initUserTable(String tableName,String familyArray[],boolean partionFlag,byte[][] splitKeys){
- List<String> list=new ArrayList<String>();
- try {
- Admin hadmin = HbaseConnectionUtils.getInstance().getConnection().getAdmin();
- TableName tm = TableName.valueOf(tableName);
- if (!hadmin.tableExists(TableName.valueOf(tableName))) {
- HTableDescriptor hTableDescriptor = new HTableDescriptor(tm);
- for(String colFa:familyArray){
- HColumnDescriptor family = new HColumnDescriptor(colFa);
- family.setMaxVersions(1);
- hTableDescriptor.addFamily(family);
- }
- if(partionFlag){
- hadmin.createTable(hTableDescriptor, splitKeys);
- }
- else {
- hadmin.createTable(hTableDescriptor);//不分区
- }
- hadmin.close();
- }
- else {
- log.info("................新建表:"+tableName+"已存在..........................");
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- log.info("................................................create hbase table "+tableName+" successful..........");
- }
- }
#结果:
rowkey:将ID取模后,然后拼上ID整体作为rowkey,rowkey=(id)%regionNum +id;
分区:用长整数作为分区号,每个region管理着相应的区域数据,splitkeys也非常简单,直接是分区号即可。
- public interface RowKeyGenerator {
- byte [] nextId();
- }
- public interface SplitKeysCalculator {
- }
- public class ModPartitionRowKeyManager implements RowKeyGenerator, SplitKeysCalculator {
- public static final int DEFAULT_PARTITION_AMOUNT = 20;
- private long currentId = 1;
- private int partition = DEFAULT_PARTITION_AMOUNT;
- public void setPartition(int partition) {
- this.partition = partition;
- }
-
- public byte[] nextId() {
- try {
- long partitionId = currentId % partition;
- return Bytes.add(Bytes.toBytes(partitionId+""),
- Bytes.toBytes(currentId+""));
- } finally {
- currentId++;
- }
- }
-
- public byte[][] calcSplitKeys() {
- byte[][] splitKeys = new byte[partition - 1][];
- for(int i = 1; i < partition ; i ++) {
- splitKeys[i-1] = Bytes.toBytes((long)i);
- }
- return splitKeys;
- }
- }
- //测试
- public class TestMod {
- private final static Logger log= Logger.getLogger(String.valueOf(TestMod.class));
- public static void main(String args[])throws Exception{
- ModPartitionRowKeyManager rkManager = new ModPartitionRowKeyManager();
- //只预建10个分区
- rkManager.setPartition(10);
- byte [][] splitKeys = rkManager.calcSplitKeys();
- String tableName="hash_split_table2";//用户画像hbase中的表
- TableName tn=TableName.valueOf(Constants.HBASE_TABLE_NAME.value);
- String familyArray[]={"mboth2"};//定义列族
- initUserTable(tableName,familyArray,true,splitKeys);
- //插入1亿条记录,看数据分布
- List<Put> listp = new ArrayList<Put>();
- for(int i = 0; i < 10000; i ++) {
- Put put = new Put(rkManager.nextId());
- put.addColumn("mboth2".getBytes(), "email".getBytes(), Bytes.toBytes("test@123.com"));
- listp.add(put);
- }
- HbaseConnectionUtils.getInstance().getTable("hash_split_table2").put(listp);
- }
- public static void initUserTable(String tableName,String familyArray[],boolean partionFlag,byte[][] splitKeys){
- List<String> list=new ArrayList<String>();
- try {
- Admin hadmin = HbaseConnectionUtils.getInstance().getConnection().getAdmin();
- TableName tm = TableName.valueOf(tableName);
- if (!hadmin.tableExists(TableName.valueOf(tableName))) {
- HTableDescriptor hTableDescriptor = new HTableDescriptor(tm);
- for(String colFa:familyArray){
- HColumnDescriptor family = new HColumnDescriptor(colFa);
- family.setMaxVersions(1);
- hTableDescriptor.addFamily(family);
- }
- if(partionFlag){
- hadmin.createTable(hTableDescriptor, splitKeys);
- }
- else {
- hadmin.createTable(hTableDescriptor);//不分区
- }
- hadmin.close();
- }
- else {
- log.info("................新建表:"+tableName+"已存在..........................");
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- log.info("................................................create hbase table "+tableName+" successful..........");
- }
- }
#结果:
Rowkey的生成策略为=url的md5值;
Rowkey=md5(url)
/**
* 当list的size小于baseReocrd,分区为n,最终的结果可能要小于n
* 如list的size为6,baseReord=100,分区20,理论每个分区5个,所以最终结果为2个分区
所以要尽量设置baseReocrd,regionNum,要合理
#代码:
public class CreateHbaseTable {
private final static Logger log= Logger.getLogger(String.valueOf(HbaseUtil.class));
public static void main(String args[]){
String tableName="hash_split_table3";//用户画像hbase中的表hash_split_table3 hb_user_profile_t
TableName tn=TableName.valueOf(Constants.HBASE_TABLE_NAME.value);
String familyArray[]={"user"};//定义列族
initUserTable(tableName,familyArray,true);
Table table= HbaseConnectionUtils.getInstance().getTable("hash_split_table3");
putTable(table, familyArray);
}
/**
* 当list的size小于baseReocrd,分区n,最终的结果可能要小于n
* 如list的size为6,baseReord=100,分区20,理论每个分区5个,所以最终结果为2个分区
* @param tableName
* @param familyArray
* @param partionFlag
*/
public static void initUserTable(String tableName,String familyArray[],boolean partionFlag){
List<String> list=new ArrayList<String>();
list.add("http://guangzhou.anjuke.com/community/view/756975");
list.add("http://chengdu.anjuke.com/community/view/141695");
list.add("http://beijing.anjuke.com/community/view/80542");
list.add("http://guangzhou.anjuke.com/community/view/111235");
list.add("http://lijingfu.fang.com");
list.add("http://tianjin.anjuke.com/community/view/202811");
try {
Admin hadmin = HbaseConnectionUtils.getInstance().getConnection().getAdmin();
TableName tm = TableName.valueOf(tableName);
if (!hadmin.tableExists(TableName.valueOf(tableName))) {
HTableDescriptor hTableDescriptor = new HTableDescriptor(tm);
for(String colFa:familyArray){
HColumnDescriptor family = new HColumnDescriptor(colFa);
family.setMaxVersions(1);
hTableDescriptor.addFamily(family);
}
if(partionFlag){
hadmin.createTable(hTableDescriptor,HbaseUtil.calcSplitKeys(list,100,20));
}
else {
hadmin.createTable(hTableDescriptor);//不分区
}
hadmin.close();
}
else {
log.info("................新建表:"+tableName+"已存在..........................");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
log.info("................................................create hbase table "+tableName+" successful..........");
}
public static void putTable(Table hTable, String familyArray[]) {
List<Put> listp = new ArrayList<Put>();
List<String> dataList=new ArrayList<String>();
dataList.add("http://guangzhou.anjuke.com/community/view/112825");
dataList.add("http://lijingfu.fang.com/sdfsd/234");
for (String key:dataList) {
String rowKey= HbaseUtil.createMd5(key);
Put put = new Put(Bytes.toBytes(rowKey));
put.addColumn(familyArray[0].getBytes(), "email".getBytes(),
Bytes.toBytes("ddd"));
listp.add(put);
}
try {
hTable.put(listp);
} catch (IOException e) {
e.printStackTrace();
}
listp.clear();
log.info("添加数据成功..........................");
}
}
#splitkeys
/**
* 预分区,根据预分区的region个数,对整个集合平均分割,即是相关的splitkeys。
* @param rkGen
* @param baseRecord
* @param prepareRegions
* @return
*/
public static byte[][] calcSplitKeys(List<String> rkGen, int baseRecord, int prepareRegions) {
int splitKeysNumber = prepareRegions - 1;
int splitKeysBase = baseRecord / prepareRegions;
byte[][] splitKeys = new byte[splitKeysNumber][];
TreeSet<byte[]> rows = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
for (String rk : rkGen) {
rows.add(createMd5(rk).getBytes());
}
int pointer = 0;
Iterator<byte[]> rowKeyIter = rows.iterator();
int index = 0;
while (rowKeyIter.hasNext()) {
byte[] tempRow = rowKeyIter.next();
if ((pointer != 0) && (pointer % splitKeysBase == 0)) {
if (index < splitKeysNumber) {
splitKeys[index] = tempRow;
index ++;
}
}
rowKeyIter.remove();
pointer ++;
}
rows.clear();
rows = null;
//防止分区多,数据少,有些分区没有数据
byte[][] splitKeysRegion = new byte[index][];
for(int m=0;m<splitKeys.length;m++){
byte[] tempRow=splitKeys[m];
if(tempRow==null){
continue;
}
splitKeysRegion[m]=tempRow;
}
return splitKeysRegion;
}
/**
* md5随机散列,通过SHA或者md5生成随机散列的字符串。
* @param plainText
* @return
*/
public static String createMd5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有md5这个算法!");
}
String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字
// 如果生成数字未满32位,需要前面补0
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
#结果:
- public static void getRegionInfo(){
- RegionLocator r= null;
- try {
- r = connection.getRegionLocator(TableName.valueOf("hash_split_table3"));
- HRegionLocation location = r.getRegionLocation(Bytes.toBytes("00582ad1ea63c52490c1c8fd135b67"));
- HRegionInfo rg = location.getRegionInfo();
- String regionname = Bytes.toString(rg.getRegionName());
- String strkey = Bytes.toString(rg.getStartKey());
- String endkey = Bytes.toString(rg.getEndKey());
- System.out.println(regionname);
- System.out.println("START:"+strkey);
- System.out.println("END:"+endkey);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
#结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。