二、 HBase深入使用
1.HBase数据检索流程讲解
图中有瑕疵,实际上一个regionserver一个Hlog。
一个列簇一个store。
HFile:hdfs file。
hbase表的检索流程:
通过regionserver找到region。
命名空间(数据库):
user表时自己创建的用户表。
每个表的region信息存在meta表中:
meta表也需要存储在region中,被某个regionserver管理。可在zookeeper中查看:
其他reginserver的位置:
一个regionserver一个目录,后面的数字是启动的时间。
2. 深入HBase数据存储讲解
(一个列簇一个store)
(这个文件是二进制字节数组)
HBase数据存储:
HregionServer:
StoreFile合并:
小文件合并,如果文件超过设定的大小就分割。
用户写入数据的流程:
Hlog文件结构:
3. HBase Java API使用讲解
将配置文件拷贝到resources:
HbaseTest.java(对HBase表的创建、删除;对表中数据的添加、获取)
publicclass HbaseTest {
public Connection connection; // 用hbaseconfiguration初始化配置信息时会自动加载当前应用classpath下的core-site.xml、hdfs-site.xml和hbase-site.xml // 其实创建connection时,查看源码可发现已加载了上述配置文件,所以这一步可省略 publicstatic Configuration configuration = HBaseConfiguration.create(); public Table table; public Admin admin;
public HbaseTest() throws IOException { // 手动设置加载的配置信息 // configuration.addResource("hbaseaaa.xml"); // 对connection初始化 connection = ConnectionFactory.createConnection(); admin = connection.getAdmin(); }
publicvoid createTable(String tablename, String... cf1) throws IOException { // 创建HTableDescriptor对象,描述表信息 // 创建tablename对象描述表的名称 TableName tname = TableName.valueOf(tablename); HTableDescriptor tDescriptor = new HTableDescriptor(tname); // 判断是否表已存在 if (admin.tableExists(tname)) { System.out.println("表" + tablename + "已存在"); return; } // 添加表列簇信息 for (String cf : cf1) { HColumnDescriptor family = new HColumnDescriptor(cf); tDescriptor.addFamily(family); }
// 调用admin的createTable方法创建表 admin.createTable(tDescriptor); System.out.println("表" + tablename + "创建成功"); }
publicvoid deleteTable(String tablename) throws IOException { Admin admin = connection.getAdmin(); TableName tname = TableName.valueOf(tablename); if (!admin.tableExists(tname)) { System.out.println("表" + tablename + "不存在"); return; } // 停止使用该表 admin.disableTable(tname); admin.deleteTable(tname); System.out.println("表" + tablename + "删除成功"); }
// 新增数据到表里面 put publicvoid putData() throws IOException { TableName tableName = TableName.valueOf("bd18:fromjava"); Table table = connection.getTable(tableName); Random random = new Random(); List<Put> batPut = new ArrayList(); for (inti = 0; i < 10; i++) { // 构建put的参数是rowkey rowkey_i(Bytes是工具类,各种java基础数据类型和字节数组之间的转换) Put put = new Put(Bytes.toBytes("rowkey_" + i)); put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("username"), Bytes.toBytes("un_" + i)); put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("age"), Bytes.toBytes(random.nextInt(50) + 1)); put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("birthday"), Bytes.toBytes("20170" + i + "01")); put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("phone"), Bytes.toBytes("电话_" + i)); put.addColumn(Bytes.toBytes("i"), Bytes.toBytes("email"), Bytes.toBytes("email_" + i)); // 单记录 // table.put(put); batPut.add(put); } table.put(batPut); System.out.println("表插入数据成功");
}
//get数据 publicvoid getData() throws Exception{ TableName tableName = TableName.valueOf("bd18:fromjava"); table = connection.getTable(tableName); //构建get对象 List<Get> gets = new ArrayList<Get>(); for(inti=0;i<5;i++){ Get get = new Get(Bytes.toBytes("rowkey_"+i)); gets.add(get); } Result[] results = table.get(gets); for (Result result : results) { //一行一行读取数据 第一种方法 /*NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> maps = result.getMap(); for(byte[] cf : maps.keySet()){ NavigableMap<byte[], NavigableMap<Long,byte[]>> valueWritableColumnQualify = maps.get(cf); for (byte[] ColumnQualify : valueWritableColumnQualify.keySet()) { NavigableMap<Long, byte[]> valueWritableTimeStamp = valueWritableColumnQualify.get(ColumnQualify); for (long ts : valueWritableTimeStamp.keySet()) { byte[] value = valueWritableTimeStamp.get(ts); System.out.println("rowkey: "+Bytes.toString(result.getRow())+",columnFamliy: "+ Bytes.toString(cf)+",columnQualify: "+Bytes.toString(ColumnQualify)+",timestamp: "+ new Date(ts)+",value: "+Bytes.toString(value)); } } }*/ /* //使用字段名和列簇名来获取value值 第二种方法 System.out.println("rowkey: "+Bytes.toString(result.getRow())+",columfamliy:i,columnqualify:username,value: "+ Bytes.toString(result.getValue(Bytes.toBytes("i"), Bytes.toBytes("username"))));
System.out.println("rowkey: "+Bytes.toString(result.getRow())+",columfamliy:i,columnqualify:username,value: "+ Bytes.toInt(result.getValue(Bytes.toBytes("i"), Bytes.toBytes("age"))));*/
// 使用cell获取result里面的数据 CellScanner cellScanner = result.cellScanner(); while (cellScanner.advance()) { Cell cell = cellScanner.current(); // 从单元格cell中把数据获取并输出 // 实用CellUtil工具类,从cell中把数据或群出来 String famliy = Bytes.toString(CellUtil.cloneFamily(cell)); String qualify = Bytes.toString(CellUtil.cloneQualifier(cell)); String rowkey = Bytes.toString(CellUtil.cloneRow(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); System.out.println( "rowkey: " + rowkey + ",columnqualify:" + famliy + ",qualify: " + qualify + ",value: " + value); } } }
// 关闭连接 publicvoid cleanUp() throws IOException { connection.close(); }
publicstaticvoid main(String[] args) throws Exception { HbaseTest hbaseTest = new HbaseTest(); //hbaseTest.createTable("bd18:fromjava", "i", "j"); //hbaseTest.deleteTable("bd18:fromjava"); //hbaseTest.putData(); hbaseTest.getData(); hbaseTest.cleanUp(); } } |
publicclass HBaseScanTest {
publicstatic Configuration configuration=HBaseConfiguration.create(); private Connection connection; private Table bd18Test;
public HBaseScanTest() throws IOException { connection=ConnectionFactory.createConnection(configuration); bd18Test=connection.getTable(TableName.valueOf("bd18:fromjava")); }
publicvoid scanData() throws IOException {
Scan scan=new Scan(); //添加扫描列簇约束,减少扫描列簇数据文件 scan.addFamily(Bytes.toBytes("i")); //[start,stop),如何使stop变成闭区间:转换成二进制,+1 scan.setStartRow(Bytes.toBytes("rowkey_6")); scan.setStopRow(Bytes.toBytes("rowkey_8")); scan.addColumn(Bytes.toBytes("i"), Bytes.toBytes("username")); //scan.setTimeRange(minStamp, maxStamp); ResultScanner rs=bd18Test.getScanner(scan); showResult(rs); }
//专有过滤器,行健前缀过滤器:PrefixFilter //取出 publicvoid getByPrefixFilter() throws IOException { Filter prefixFilter=new PrefixFilter(Bytes.toBytes("Row")); Scan scan=new Scan(); scan.setFilter(prefixFilter); ResultScanner rs=bd18Test.getScanner(scan); showResult(rs); }
//ColumnPrefixFilter,列前缀过滤器,用法同上 //MultipleColumnPrefixFilter,多子列前缀过滤器 //DependentColumnFilter,列过滤器,既指定列簇又指定列名称的过滤器
//按照姓名查找用户信息 publicvoid getBySEeachValue() throws IOException { Filter valueFilter=new ValueFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator("_")); Scan scan=new Scan(); scan.setFilter(valueFilter); ResultScanner rs=bd18Test.getScanner(scan); showResult(rs); }
publicvoid getByColumRangeFilter() throws IOException { Filter cRangeFilter=new ColumnRangeFilter(Bytes.toBytes("o_name1"), true, Bytes.toBytes("o_name2"), true); Scan scan=new Scan(); //只扫描j列簇 scan.addFamily(Bytes.toBytes("j")); scan.setFilter(cRangeFilter); ResultScanner rs=bd18Test.getScanner(scan); showResult(rs); }
//qualifyFilter 列名过滤器,只返回能匹配的列名称的单元格数据 publicvoid getByQualifyFilter() throws IOException { Filter qualifyFilter=new QualifierFilter(CompareFilter.CompareOp.EQUAL,new BinaryPrefixComparator(Bytes.toBytes("phone_"))); Scan scan=new Scan(); scan.setFilter(qualifyFilter); ResultScanner rs=bd18Test.getScanner(scan); showResult(rs); }
//familyfilter 返回的是设置列簇下面的数据,这个不常用,因为列簇通常只有1-2个。列簇可以 //设置多个除非HBase优化,在缓冲区将数据刷新到HFile时,不连带其他HStore的缓冲区 publicvoid getByFamilyFilter() throws Exception{ FamilyFilter familyFilter = new FamilyFilter(CompareFilter.CompareOp.LESS, new BinaryComparator(Bytes.toBytes("j"))); Scan scan = new Scan(); scan.setFilter(familyFilter); ResultScanner rs = bd18Test.getScanner(scan); showResult(rs); }
//rowfilter //获取rowkey中包含rowkey中包含"row"的数据 publicvoid getByRowFilter() throws IOException { Scan scan=new Scan(); // //rowkey中包含row这个字符串的的都会获取到 // RowFilter rowFilter=new RowFilter(CompareFilter.CompareOp.EQUAL,new RegexStringComparator("row")); // //把过滤器加到scan中 // scan.setFilter(rowFilter); // //把scan加入到table上进行扫描获取resultscanner结果 // ResultScanner rs=bd18Test.getScanner(scan); // showResult(rs);
//获取rowKey<=rowkey_5并包含rowkey_5 RowFilter ItRowFilter=new RowFilter(CompareFilter.CompareOp.LESS_OR_EQUAL,new BinaryComparator(Bytes.toBytes("rowkey_5"))); scan.setFilter(ItRowFilter); ResultScanner rs=bd18Test.getScanner(scan); showResult(rs); }
//FilterList,组合过滤器 //查询rowkey包含n_6,且电话是以135开头的数据 publicvoid getByFilterList() throws IOException { List<Filter> flist=new ArrayList(); //第一个条件:rowkey包含n_6 Filter filter1=new RowFilter(CompareOp.EQUAL,new RegexStringComparator("_6")); flist.add(filter1); //第二个条件:电话是以155开头 Filter filter2=new ColumnPrefixFilter(Bytes.toBytes("phone_155")); flist.add(filter2); //组装两个filter,关系为'或' Filter filter=new FilterList(Operator.MUST_PASS_ONE,flist); Scan scan=new Scan(); scan.setFilter(filter); ResultScanner rs=bd18Test.getScanner(scan); showResult(rs); }
publicvoid showResult(ResultScanner rs) throws IOException { Result result=rs.next(); while(result!=null) { //对result中的数据进行展示 CellScanner cs=result.cellScanner(); System.out.println("rowkey:"+Bytes.toString(result.getRow())); while(cs.advance()) { Cell cell=cs.current(); String family=Bytes.toString(CellUtil.cloneFamily(cell)); String qualify=Bytes.toString(CellUtil.cloneQualifier(cell)); String value=Bytes.toString(CellUtil.cloneValue(cell)); System.out.println(" cf:"+family+",qualify:"+qualify+",value:"+value);
} result=rs.next(); } }
publicstaticvoid main(String[] args) throws Exception { HBaseScanTest hBaseScanTest=new HBaseScanTest(); //hBaseScanTest.scanData(); //hBaseScanTest.getByRowFilter(); //hBaseScanTest.getByFamilyFilter(); //hBaseScanTest.getByQualifyFilter(); //hBaseScanTest.getByColumRangeFilter(); //hBaseScanTest.getBySEeachValue(); //hBaseScanTest.getByPrefixFilter(); hBaseScanTest.getByFilterList(); } } |
配置文件的加载:
将内存中的数据flush到storefile中:
这时候数据还在内存中,即memstore中,还没溢写到stroefile中,这时候可以手动将内存中的数据写到storefile中:
删除操作:
publicvoid deleteData(String tableName) throws IOException { Table table = getHTableByTableName(tableName);
// hBaseOperation.getData(tableName);
Delete delete=new Delete(Bytes.toBytes("10004"));
// delete.addColumns删除该列的所有版本 // 删除该列的最新版本 delete.addColumn( Bytes.toBytes("info"), Bytes.toBytes("address") ); // delete.addFamily(Bytes.toBytes("info")); //删除一个rowkey的一个列簇 table.delete(delete); table.close(); } |
删除,其实并未删除,只是打上标签,在storefile合并时才会删除。
合并前:
合并后:
compact文件的选择首先要判断是major还是minor,如果是major,则整个HStore的所有HFile都被选中,否则就选择部分文件进行minor compact。考虑到compact操作都会耗费大量的IO,因此minorcompact操作的目标就是以最少的IO代价换取最大的读性能提高。目前在新版本里,HStore的compact文件选择策略能够充分考虑了整体情况去选择最佳的方案。
查看所有的Filter:
比较重要的就是prefixFilter和PageFilter。
MR中Hbase的Scan使用技巧:
Hadoop的MR运算中,Hbase可以作为输入数据源参与运算,其中作为HTable的迭代器Scan有几个使用技巧。
涉及的方法如下:
public void setBatch(int batch)
public void setCaching(int caching)
public void setCacheBlocks(boolean cacheBlocks)
public void setBatch(int batch) :
为设置获取记录的列个数,默认无限制,也就是返回所有的列
public void setCaching(int caching):
每次从服务器端读取的行数,默认为配置文件中设置的值
public void setCacheBlocks(boolean cacheBlocks):
为是否缓存块,默认缓存,我们分内存,缓存和磁盘,三种方式,一般数据的读取为内存->缓存->磁盘,当MR的时候为非热点数据,因此不需要缓存。
因此在MR的时候最好设置如下:
scan.setCacheBlocks(false);
scan.setCaching(200);//大了占内存,但是rpc少
scan.setBatch(6);//你需要的列
4. HBase 架构深入剖析讲解
hadoop底层就是rpc协议。
没有单节点故障,就是因为zookeeper。
hbase表的元数据可在zkCli.sh中查看。
ROOT表是老版本的概念,新版本的是meta表。
启动多个master:
查看hbase-daemons.sh脚本:
查看master-backup.sh脚本:
创建backup-masters文件:
执行脚本:
[root@hadoop-seniorhbase-1.2.0-cdh5.13.0]# bin/hbase-daemons.sh start master-backup
管理HregionServer的负载均衡等:
tools工作组都是Hmaster的工作
图中HMaster到HDFS的箭头:
这些region信息都是Hmaster写到HDFS上的。
默认情况下,HBase管理Zookeeper实例,实际生产环境不让他管理,通过以下配置:
hbase-env.sh:
设置为false,并在hbase-site.xml中添加自己安装的zookeeper的信息。
谷歌的三篇论文(三驾马车):
【GFS,MapReduce,BigTable】
被java开源出来的:
【HDFS,MapReduce,HBase】
其中BigTable中有个协作框架,开源出来就是Zookeeper
5. HBase集成MapReduce时, 运行的classpath以及自带mr程序的功能讲解
hbase和mapreduce的集成:
1. 输入表是hbase表
2. 输出表是hbase表
3. 输入输出都是hbase表
java代码打成jar包是不打包依赖的,如下图:
但是bin/yarn jar这个命令会自动帮我们加载依赖。
运行hbase代码打成的jar包时,需要加载hbase的依赖:
查看jar包的相关命令:
······
执行hbase jar包中的rowcounter程序:
(这个jar包中封装了很多工具类)
[root@hadoop-senior hbase-1.2.0-cdh5.13.0]#/opt/cdh5.13.0/hadoop-2.6.0-cdh5.13.0/bin/hadoop jarlib/hbase-server-1.2.0-cdh5.13.0.jar
(提示找不到类,所以要加载hbase jar包)
加载hbase所需的依赖,并查看jar包包含的工具类:
export HBASE_HOME=/opt/cdh5.13.0/hbase-1.2.0-cdh5.13.0 export HADOOP_HOME=/opt/cdh5.13.0/hadoop-2.6.0-cdh5.13.0 HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp` $HADOOP_HOME/bin/yarn jar $HBASE_HOME/lib/hbase-server-1.2.0-cdh5.13.0.jar |
如果运行失败,将后两句放在一起执行:HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp` $HADOOP_HOME/bin/yarn jar$HBASE_HOME/lib/hbase-server-1.2.0-cdh5.13.0.jar
completebulkload:
默认的方式:每条数据要经过:预写日志,memstore,flush storeFile,compact,region。最终形成hfile。
采用completebulkload:
TSV和CSV的区别:
查看工具类的可加参数:
使用rowcounter计算user表的行数:
6. 依据实际需求编写MapReduce程序, 集成HBase对表进行读取和写入数据
参考:
将user表中的name和age列导入到basic表中:
User2BasicMapReduce.java
准备工作:
publicclass User2BasicMapReduce extends Configured implements Tool{
// Mapper Class publicstaticclass ReadUserMapper extends TableMapper<Text, Put>{
private Text mapOutPutKey=new Text();
@Override publicvoid map(ImmutableBytesWritable key, Result value, Mapper<ImmutableBytesWritable, Result, Text, Put>.Context context) throws IOException, InterruptedException { // get rowkey String rowkey=Bytes.toString(key.get()); // set mapOutPutKey.set(rowkey); //--------------------------------------------------------- Put put = new Put(key.get()); // iterator for(Cell cell : value.rawCells()) { // add family : info if("info".equals(Bytes.toString(CellUtil.cloneFamily(cell)))){ // add column : name if("name".equals(Bytes.toString(CellUtil.cloneQualifier(cell)))) { put.add(cell); } } // add family : info if("info".equals(Bytes.toString(CellUtil.cloneFamily(cell)))){ // add column : age if("age".equals(Bytes.toString(CellUtil.cloneQualifier(cell)))) { put.add(cell); } } } context.write(mapOutPutKey, put); }
}
// Rudecer class publicstaticclass writeBasicReducer extends TableReducer<Text, Put, NullWritable>{
@Override publicvoid reduce(Text key, Iterable<Put> values, Reducer<Text, Put, NullWritable, Mutation>.Context context) throws IOException, InterruptedException { for(Put put : values) { context.write(NullWritable.get(), put); }
}
}
// Driver publicint run(String[] args) throws Exception { // create job // Job.getInstance(conf, jobName); Job job=Job.getInstance(this.getConf(),this.getClass().getName());
// set run job class job.setJarByClass(this.getClass());
// set job Scan scan = new Scan(); // 版本太低的话,执行以下操作会出现没有该方法的异常 //scan.setCaching(500); // 1 is the default in Scan, which will be bad for MapReduce jobs //scan.setCacheBlocks(false); // don't set to true for MR jobs // set other scan attrs
// set input and set mapper TableMapReduceUtil.initTableMapperJob( "user", // input table scan, // Scan instance to control CF and attribute selection ReadUserMapper.class, // mapper class Text.class, // mapper output key Put.class, // mapper output value job );
// set output and reducer TableMapReduceUtil.initTableReducerJob( "basic", // output table writeBasicReducer.class, // reducer class job ); job.setNumReduceTasks(1); // at least one, adjust as required
// submit job booleanisSuccess = job.waitForCompletion(true);
returnisSuccess ? 0:1; }
publicstaticvoid main(String[] args) throws Exception { // get configuration Configuration configuration=HBaseConfiguration.create();
// submit job intstatus = ToolRunner.run(configuration, new User2BasicMapReduce(), args);
// exit program System.out.println(status);
} } |
把配置文件然后打成jar包,上传到linux上,执行:
export HBASE_HOME=/opt/cdh5.13.0/hbase-1.2.0-cdh5.13.0
export HADOOP_HOME=/opt/cdh5.13.0/hadoop-2.6.0-cdh5.13.0
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp`
$HADOOP_HOME/bin/yarn jar /opt/datas/hbase-mr-user2basic.jar
查看basic表:
查看ToolRunner.run(configuration, new User2BasicMapReduce(), args) 源码:
细节补充:
7. HBase 的数据迁移常见方式及importTsv功能演示讲解
数据迁移的三种方式:
hbase bulk load tool:把要加载到hbase表中的数据变成hbase底层的hfile文件,然后把它加载到hbase。
通用:通过jdbc从关系型数据库抽数据,然后put往hbase中插入数据。
创建样本数据:
student.tsv
10001 zhangsan 35 male beijing 0108978342 10002 lisi 22 male shanghai 0108213342 10003 wangwu 28 female guangzhou 0111178342 10004 zhaoliu 32 male zhengzhou 0108972222 10005 zengqi 26 female shijiazhuang 0103538342 10006 gouba 35 male hangzhou 0109889842 |
上传到hdfs:
在hbase中创建student表:
执行数据迁移:
export HBASE_HOME=/opt/cdh5.13.0/hbase-1.2.0-cdh5.13.0 export HADOOP_HOME=/opt/cdh5.13.0/hadoop-2.6.0-cdh5.13.0 HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp`
${HADOOP_HOME}/bin/yarn jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.13.0.jar importtsv \ -Dimporttsv.columns=HBASE_ROW_KEY,\ info:name,info:age,info:sex,info:address,info:phone \ student \ hdfs://hadoop-senior:8020/user/zhuyu/hbase/importtsv |
-Dimporttsv.columns字段之间不能有空格
(数据还在memStore中,没有溢写到storeFile)
命令使用过程:
8. 如何使用BulkLoad加载数据到HBase表及剖析原理
使用默认的importtsv加载缺点:
数据还在内存中。
如果我们处理的数据量很大非常耗资源。
引出使用BulkLoad加载数据:
GC:垃圾回收机制
实例:
创建hbase表student2:
执行BulkLoad加载数据成HFile:
export HBASE_HOME=/opt/cdh5.13.0/hbase-1.2.0-cdh5.13.0 export HADOOP_HOME=/opt/cdh5.13.0/hadoop-2.6.0-cdh5.13.0 HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp` {HADOOP_HOME}/bin/yarn jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.13.0.jar importtsv \ -Dimporttsv.columns=HBASE_ROW_KEY,\ info:name,info:age,info:sex,info:address,info:phone \ -Dimporttsv.bulk.output=/user/zhuyu/hbase/hfileoutput \ student2 \ hdfs://hadoop-senior:8020/user/zhuyu/hbase/importtsv |
将HFile加载到hbase表:
export HBASE_HOME=/opt/cdh5.13.0/hbase-1.2.0-cdh5.13.0 export HADOOP_HOME=/opt/cdh5.13.0/hadoop-2.6.0-cdh5.13.0 HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp` ${HADOOP_HOME}/bin/yarn jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.13.0.jar \ completebulkload \ hdfs://hadoop-senior:8020/user/zhuyu/hbase/hfileoutput \ user2 |
查看:
再看生成hfile的文件夹:
文件已经move到了student2表目录下:
查看LoadIncrementalHFiles如何使用:
查看源码:
查看ImportTsv源码:
ctrl+shift+T
(是mapreduce)
查看LoadIncrementalHFiles源码:
(不是mapreduce)
使用mapreduce生成HFile:
作业:
(csv)