Spark读取Hbase数据转换为Dataset
- 前言
- 方案的选择
- 方案一
- 方案二
- 方案三
- 总结
前言
在公司遇到一个业务场景需要spark同时读取hive和hbase的数据进行关联数据分析。起初开发完在测试系统测试的时候,能够稳定运行,但是用到真实数据的时候很快就暴露了问题,报NullException空指针异常。根本原因是需求要关系型数据和非关系型数据进行关联,而hbase本身是列式存储,列信息是可动态扩展的,在spark转换hbase数据的时候没有提前获知完整的表字段schema,导致读取的时候某些字段丢失,以至于不能成功与关系型数据关联。
故,对于此次遇到的问题特此记录!!!
方案的选择
以下分别介绍spark读取hbase数据进行转换Dataset并创建临时关系表结构的三个方案,第一个方案是最不稳定的,不建议;后两者视情况而定。
方案一
此方案的思想是:获取hbase列信息的rdd—>获取hbase数据rdd(Row)—>取列rdd的第一条列信息构建schema
弊端:不难理解,取第一条列信息不一定是完整的schema
public static void getHbaseDataset(SparkSession sparkSession, JavaSparkContext jsc, String hbaseTableName){
Configuration hbaseConfiguration = new SparkHbaseUtils().getConfiguration();
hbaseConfiguration.set(TableInputFormat.INPUT_TABLE,hbaseTableName);
JavaPairRDD<ImmutableBytesWritable, Result> hbaseRDD = jsc.newAPIHadoopRDD(hbaseConfiguration, TableInputFormat.class, ImmutableBytesWritable.class, Result.class);
//获取列信息
JavaRDD<List<String>> hbaseColumnRDD = hbaseRDD.map(new Function<Tuple2<ImmutableBytesWritable, Result>, List<String>>() {
@Override
public List<String> call(Tuple2<ImmutableBytesWritable, Result> tuple) throws Exception {
List<String> recordColumnList = new ArrayList();
Result result = tuple._2;
Cell[] cells = result.rawCells();
for (Cell cell :
cells) {
recordColumnList.add(new String(CellUtil.cloneQualifier(cell)));
}
return recordColumnList;
}
});
//获取数据
JavaRDD<Row> dataRDD = hbaseRDD.map(new Function<Tuple2<ImmutableBytesWritable, Result>, Row>() {
@Override
public Row call(Tuple2<ImmutableBytesWritable, Result> tuple) throws Exception {
List<String> recordList = new ArrayList();
Result result = tuple._2;
Cell[] cells = result.rawCells();
for (Cell cell :
cells) {
recordList.add(new String(CellUtil.cloneValue(cell)));
}
return (Row) RowFactory.create(recordList.toArray());
}
});
//设置即将创建表的字段信息
ArrayList<StructField> structFields = new ArrayList<>();
List<String> fieldsList = hbaseColumnRDD.first();
for (String field :
fieldsList) {
structFields.add(DataTypes.createStructField(field, DataTypes.StringType, true));
}
//新建列schema
StructType schema = DataTypes.createStructType(structFields);
Dataset hbaseDataset = sparkSession.createDataFrame(dataRDD,schema);
//注册关系型表
hbaseDataset.createOrReplaceTempView(hbaseTableName);
//打印表视图信息
hbaseDataset.printSchema();
}
方案二
此方案的前提是大数据环境hive与hbase进行了整合,具体如何整合这里不做赘述。
思想:spark以读取hive的方法读取hbase,然后直接进行转化Dataset就可以,和操作hive一样
public static void getHiveDataset(SparkSession sparkSession, String hiveTableName){
String handleSql;
StringBuilder sqlSb = new StringBuilder();
sqlSb.append("select * from ").append("default.").append(hiveTableName);
handleSql = sqlSb.toString();
Dataset<Row> sql = sparkSession.sql(handleSql);
sql.createOrReplaceTempView(hiveTableName);
sql.printSchema();
}
方案三
此方案是在hive和hbase没有整合的环境下采用的一种非关系数据转换为关系数据的方案。
思想:提前告知所需字段信息,spark读取hbase数据进行转换的时候以此为转换的schema
弊端:不通用,需要提前知晓数据
public static void getHbaseDataset(SparkSession sparkSession, JavaSparkContext jsc, String hbaseTableName, TreeSet<String> useFields) throws IOException {
//获取hbase连接
Configuration hbaseConfiguration = new SparkHbaseUtils().getConfiguration();
Scan scan = new Scan();
//把要查询的表视图化
hbaseConfiguration.set(TableInputFormat.INPUT_TABLE,hbaseTableName);
ClientProtos.Scan proto = ProtobufUtil.toScan(scan);
String convertString = Base64.encodeBytes(proto.toByteArray());
hbaseConfiguration.set(TableInputFormat.SCAN, convertString);
JavaPairRDD<ImmutableBytesWritable, Result> hbaseRDD = jsc.newAPIHadoopRDD(hbaseConfiguration,TableInputFormat.class, ImmutableBytesWritable.class, Result.class);
JavaRDD<Row> dataRDD = hbaseRDD.map(new Function<Tuple2<ImmutableBytesWritable,Result>,Row>() {
@Override
public Row call(Tuple2<ImmutableBytesWritable, Result> tuple) throws Exception {
Result result = tuple._2();
List<String> rowList = new ArrayList<>();
for (String field :
useFields) {
rowList.add(Bytes.toString(result.getValue(Bytes.toBytes("familyName"), Bytes.toBytes(field))));
}
Object[] rowArray = rowList.toArray();
return (Row) RowFactory.create(rowArray);
}
});
List<StructField> structFields=new ArrayList<StructField>();
for (String field :
useFields) {
structFields.add(DataTypes.createStructField(field, DataTypes.StringType, true));
}
StructType schema=DataTypes.createStructType(structFields);
Dataset<Row> hbaseDataset=sparkSession.createDataFrame(dataRDD, schema);
hbaseDataset.createOrReplaceTempView(hbaseTableName);
hbaseDataset.printSchema();
}
总结
基于以上方案三暂时还没有探索通用的方法,会在后续更新。同时也希望广大博友谏言交流,谢谢!!!