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();
}

总结

基于以上方案三暂时还没有探索通用的方法,会在后续更新。同时也希望广大博友谏言交流,谢谢!!!