由于spark本身是不支持jdbc写入hive的,我们这里通过byzer做了实现,byzer自带了HiveJdbcDialect,有了这个功能后,我们通过save是可以正常创建表结构的,然后就拷贝hdfs数据,并通过原生的hive load data关联hdfs文件数据。
需要注意的是,直接通过spark jdbc写hive会报如下错误:
java.sql.SQLFeatureNotSupportedException: Method not supported
at org.apache.hive.jdbc.HivePreparedStatement.addBatch(HivePreparedStatement.java:78)
查看源码后发现此方法不支持
public void addBatch() throws SQLException {
// TODO Auto-generated method stub
throw new SQLFeatureNotSupportedException("Method not supported");
}
源码改造
我们可以简单处理一下SQLFeatureNotSupportedException异常,或者单独实现一个createTable spark数据源,去掉数据源中的saveTable即可,代码位置如下:
保存到Hive,使用overwrite模式
在byzer中使用方式如下:
注意,我们在createTableOptions中设置了 STORED AS PARQUET ,是为了生成形如下面的建表语句:
CREATE TABLE parquet_test (
id int,
str string,
mp MAP<STRING,STRING>,
lst ARRAY<STRING>,
strct STRUCT<A:STRING,B:STRING>)
PARTITIONED BY (part string)
STORED AS PARQUET;
保存到Hive,使用ignore模式
由于我们全程使用sql完成保存操作,读者可能会发现,这个是不是没有办法支持 ignore模式了?在第二步中save操作已经创建好了表结构,后续的sql如果使用 `load data inpath ... ignore into table` 句式,不是会导致应该写入数据,但是由于先创建了表,导致忽略写入吗?
上面的理解是正确的,确实会导致数据无法写入,同时在 SaveMode 的选择上,我们会陷入3个困境:
- 使用 ignore into table ,会导致表存在或者不存在,都无法写入,因为第二步总是会创建表结构。
- 使用 overwrite into table ,会导致本来表存在我们在业务中是需要跳过不写入(第二步使用了save ignore),而在最后一步我们强制做了 overwrite ,导致原有数据被覆盖。
- 分成几段sql来处理,在处理保存操作之前,我们去发起请求查询一下表是否存在,一旦发现存在,我们可以在业务代码中跳过表创建的执行,否则就执行overwrite 句式。但这样也会导致一个问题,需要多次的交互才能完成一个保存操作,sql层面ignore 语义是缺失的,同时多阶段操作也会增加数据不一致的风险。
解法:Byzer支持在sql中写if ... else 逻辑,借此功能我们可以在sql中完成表是否存在的检测逻辑。
if 语法为命令式语法,详情参考 分支/If|Else ,下面是一个简单示例:
set status= `select status from raw_cnodejs_articles` where type="sql" and mode="runtime";
-- 如果状态不是200,则模拟不带数据的新表
!if ''' :status != 200 ''';
!then;
run command as EmptyTableWithSchema.`` where schema='''st(field(content,binary),field(status,integer))''' as raw_cnodejs_articles;
!fi;
完成的save ignore操作示例代码如下:
- 首先我们加载数据源,通过save语法创建表结构,执行了一个简单的数据处理算子(列增加唯一标识符),并读取目标表计算表数据行数:
- 根据行数我们判断是否执行HDFS数据写入,如果行数(tabVal)大于0,则不做数据写入,直接执行可以空指令(!emptyTable;):
读者会注意到load FS操作并没有指定路径,这里实际上是做hdfs Nameservice 配置初始化,为后续方便我们做save操作,不会做实际的文件读取,详情参考: https://github.com/byzer-org/byzer-lang/issues/1868
- 如果是空表,我们对写入的数据做hive元数据关联:
通过 !fi; 命令,结束if语句。