太多column family的影响
每个 MemoryStore分配到的内存较少,进而导致过多的合并,影响性能
几个column family比较合适呢
推荐是:1-3个
划分column family的原则:
1、是否具有相似的数据格式
2、是否具有相似的访问类型
例子一: 相同的rowkey,有一个很大的text数据需要存储,又有一个picture数据需要存储
对于很大的text数据我们肯定是想让它Compress后再存储
而picture的数据呢,我们并不想让他压缩后存储,因为对于这种二进制的数据压缩并不能节省空间
所以,我们可以将这两个数据分成两个column family来存储
create 'table',{NAME => 't', COMPRESSION => 'SNAPPY'},
{NAME => 'p'}
几个column family比较合适呢
例子二: 有一张hbase表,需要存储每个用户的信息(比如名字、年龄等)和这个用户每天访问网站的信息
对于用户的信息,不经常变,而且量少
对于用户每天访问网站的信息是经常变化且数据量很大的
如果将这两种信息放在同一个column family中的话,用户每天访问网站的信息数据的增大导致会出现memory store的flush,然后会导致compaction,因为compaction是column family级别的,所以会将每个用户的信息(比如名字、年龄等)和这个用户每天访问网站的信息都合并到文件中
其实用户的信息不大,且不经常变,没必要每次compaction都要将用户的信息写到磁盘中,导致资源的浪费
所以可以将用户的信息和用户每天访问网站的信息分成两个column family来存储
Table Schema的设计
1、每一个region的大小在10到50G
2、每一个table控制在50-100个regions
3、每一个table控制在1到3个column family
4、每一个column family的命名最好要短,因为column family是会存储在数据文件中的
RowKey的设计一
长度原则:
rowkey的长度一般被建议在10-100个字节,不过建议是越短越好
1、数据持久化文件HFile是按照keyvalue存储的,如果rowkey过长,比如100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率
2、MemStore将缓存部分数据到内存,如果Rowkey字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey的字节长度越短越好。
3、目前操作系统是都是64位系统,内存8字节对齐。如果rowkey是8字节的整数倍的话,则利用了操作系统的最佳特性。
RowKey的设计二
特性: rowkey是按照字典顺序进行存储的
相似的rowkey会存储在同一个Region中
比如,我们的rowkey是网站的域名,如下:
www.apache.org
mail.apache.org
jira.apache.org
将域名反转作为rowkey的话更好点,如下:
org.apache.www
org.apache.mail
org.apache.jira
RowKey的设计三
因为rowkey是按照字典顺序存储的,所以如果rowkey没有设计好的话,还会引发:
Hotspotting:大量的请求只发往到一个Region中
解决Hotspotting的三个方法:
1、Salting((撒盐似的)散布、加盐)
create 'test_salt', 'f',SPLITS => ['b','c','d']
原始的rowkey:
boo0001
boo0002
boo0003
boo0004
boo0005
boo0003
salting rowkey:
a-boo0001
b-boo0002
c-boo0003
d-boo0004
a-boo0005
d-boo0003
import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class KeySalter { private AtomicInteger index = new AtomicInteger(0); private String[] prefixes = {"a", "b", "c", "d"}; public String getRowKey(String originalKey) { StringBuilder sb = new StringBuilder(prefixes[index.incrementAndGet() % 4]); sb.append("-").append(originalKey); return sb.toString(); } public List<String> getAllRowKeys(String originalKey) { List<String> allKeys = new ArrayList<>(); for (String prefix : prefixes) { StringBuilder sb = new StringBuilder(prefix); sb.append("-").append(originalKey); allKeys.add(sb.toString()); } //a-boo0001 //b-boo0001 //c-boo0001 //d-boo0001 return allKeys; } }
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class SaltingTest { public static void main(String[] args) throws IOException { Configuration config = HBaseConfiguration.create(); try (Connection connection = ConnectionFactory.createConnection(config); Table table = connection.getTable(TableName.valueOf("test_salt"))) { KeySalter keySalter = new KeySalter(); List<String> rowkeys = Arrays.asList("boo0001", "boo0002", "boo0003", "boo0004"); List<Put> puts = new ArrayList<>(); for (String key : rowkeys) { Put put = new Put(Bytes.toBytes(keySalter.getRowKey(key))); put.addColumn(Bytes.toBytes("f"), null, Bytes.toBytes("value" + key)); puts.add(put); } table.put(puts); } } }
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class SaltingGetter { public static void main(String[] args) throws IOException { Configuration config = HBaseConfiguration.create(); try (Connection connection = ConnectionFactory.createConnection(config); Table table = connection.getTable(TableName.valueOf("test_salt"))) { KeySalter keySalter = new KeySalter(); List<String> allKeys = keySalter.getAllRowKeys("boo0001"); //读取boo001 List<Get> gets = new ArrayList<>(); for (String key : allKeys) { Get get = new Get(Bytes.toBytes(key)); gets.add(get); } Result[] results = table.get(gets); for (Result result : results) { if (result != null) { //do something } } } } }
RowKey的设计三
2、Hashing
create 'test_hash', 'f', { NUMREGIONS => 4, SPLITALGO => 'HexStringSplit' }
原始的rowkey:
boo0001
boo0002
boo0003
boo0004
md5 hash rowkey:
4b5cdf065e1ada3dbc8fb7a65f6850c4
b31e7da79decd47f0372a59dd6418ba4
d88bf133cf242e30e1b1ae69335d5812
f6f6457b333c93ed1e260dc5e22d8afa
import org.apache.hadoop.hbase.util.MD5Hash; public class KeyHasher { public static String getRowKey(String originalKey) { return MD5Hash.getMD5AsHex(originalKey.getBytes()); } }
package com.twq.hbase.rowkey.hash; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class HashingTest { public static void main(String[] args) throws IOException { Configuration config = HBaseConfiguration.create(); try (Connection connection = ConnectionFactory.createConnection(config); Table table = connection.getTable(TableName.valueOf("test_hash"))) { List<String> rowkeys = Arrays.asList("boo0001", "boo0002", "boo0003", "boo0004"); List<Put> puts = new ArrayList<>(); for (String key : rowkeys) { Put put = new Put(Bytes.toBytes(KeyHasher.getRowKey(key))); put.addColumn(Bytes.toBytes("f"), null, Bytes.toBytes("value" + key)); puts.add(put); } table.put(puts); } } }
import com.twq.hbase.rowkey.salt.KeySalter; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class HashingGetter { public static void main(String[] args) throws IOException { Configuration config = HBaseConfiguration.create(); try (Connection connection = ConnectionFactory.createConnection(config); Table table = connection.getTable(TableName.valueOf("test_hash"))) { Get get = new Get(Bytes.toBytes(KeyHasher.getRowKey("boo0001"))); Result results = table.get(get); // process result... for (Cell cell : results.listCells()) { System.out.println(Bytes.toString(CellUtil.cloneRow(cell)) + "===> " + Bytes.toString(CellUtil.cloneFamily(cell)) + ":" + Bytes.toString(CellUtil.cloneQualifier(cell)) + "{" + Bytes.toString(CellUtil.cloneValue(cell)) + "}"); } } } }
RowKey的设计三
3、反转rowkey
create 'test_reverse', 'f',SPLITS => ['0','1','2','3','4','5','6','7','8','9']
时间戳类型的rowkey:
1524536830360
1524536830362
1524536830376
反转rowkey:
0630386354251
2630386354251
6730386354251
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.filter.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; public class DataFilter { public static void main(String[] args) throws IOException { Configuration config = HBaseConfiguration.create(); //Add any necessary configuration files (hbase-site.xml, core-site.xml) config.addResource(new Path("src/main/resources/hbase-site.xml")); config.addResource(new Path("src/main/resources/core-site.xml")); try(Connection connection = ConnectionFactory.createConnection(config)) { Table table = connection.getTable(TableName.valueOf("sound")); Scan scan = new Scan(); scan.setStartRow(Bytes.toBytes("00000120120901")); scan.setStopRow(Bytes.toBytes("00000120121001")); SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(Bytes.toBytes("f"), Bytes.toBytes("n"), CompareFilter.CompareOp.EQUAL, new SubstringComparator("中国好声音")); SingleColumnValueFilter categoryFilter = new SingleColumnValueFilter(Bytes.toBytes("f"), Bytes.toBytes("c"), CompareFilter.CompareOp.EQUAL, new SubstringComparator("综艺")); FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL); filterList.addFilter(nameFilter); filterList.addFilter(categoryFilter); scan.setFilter(filterList); ResultScanner rs = table.getScanner(scan); try { for (Result r = rs.next(); r != null; r = rs.next()) { // process result... for (Cell cell : r.listCells()) { System.out.println(Bytes.toString(CellUtil.cloneRow(cell)) + "===> " + Bytes.toString(CellUtil.cloneFamily(cell)) + ":" + Bytes.toString(CellUtil.cloneQualifier(cell)) + "{" + Bytes.toString(CellUtil.cloneValue(cell)) + "}"); } } } finally { rs.close(); // always close the ResultScanner! } } } }
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.util.Bytes; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; /** * create 'sound', */ public class DataPrepare { public static void main(String[] args) throws IOException { InputStream ins = DataPrepare.class.getClassLoader().getResourceAsStream("sound.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(ins)); List<SoundInfo> soundInfos = new ArrayList<>(); String line = null; while ((line = br.readLine()) != null) { SoundInfo soundInfo = new SoundInfo(); String[] arr = line.split("\|"); String rowkey = format(arr[4], 6) + arr[1] + format(arr[0], 6); soundInfo.setRowkey(rowkey); soundInfo.setName(arr[2]); soundInfo.setCategory(arr[3]); soundInfos.add(soundInfo); } Configuration config = HBaseConfiguration.create(); //Add any necessary configuration files (hbase-site.xml, core-site.xml) config.addResource(new Path("src/main/resources/hbase-site.xml")); config.addResource(new Path("src/main/resources/core-site.xml")); try (Connection connection = ConnectionFactory.createConnection(config)) { Table table = connection.getTable(TableName.valueOf("sound")); List<Put> puts = new ArrayList<>(); for (SoundInfo soundInfo : soundInfos) { Put put = new Put(Bytes.toBytes(soundInfo.getRowkey())); put.addColumn(Bytes.toBytes("f"), Bytes.toBytes("n"), Bytes.toBytes(soundInfo.getName())); put.addColumn(Bytes.toBytes("f"), Bytes.toBytes("c"), Bytes.toBytes(soundInfo.getCategory())); puts.add(put); } table.put(puts); } } public static String format(String str, int num) { return String.format("%0" + num + "d", Integer.parseInt(str)); } }
在建立一个scan对象后,我们setStartRow(00000120120901),setStopRow(00000120120914)。
这样,scan时只扫描userID=1的数据,且时间范围限定在这个指定的时间段内,满足了按用户以及按时间范围对结果的筛选。并且由于记录集中存储,性能很好。
然后使用 SingleColumnValueFilter(org.apache.hadoop.hbase.filter.SingleColumnValueFilter),共4个,分别约束name的上下限,与category的上下限。满足按同时按文件名以及分类名的前缀匹配。
(注意:使用SingleColumnValueFilter会影响查询性能,在真正处理海量数据时会消耗很大的资源,且需要较长的时间)
如果需要分页还可以再加一个PageFilter限制返回记录的个数。