一、问题背景
主要的源码思路还是按照这篇HdfsWriter如何实现支持decimal类型数据写入来,这里不再介绍。上篇说binary类型读取会有坑,因此这篇主要解释一下坑的地方以及解决的办法。
PS:虽说binary类型的字段读写Hive,本人没有想到对于数据仓库建设使用上有什么用处,但是本着好奇、学习的心态,就去研究了一下并集成到datax内。
开头附上github地址。
二. 环境准备
Datax版本:3.0
Hadoop版本:2.7.3
Hive版本:2.3.2
三. 源码
在之前一篇文章中可以看到,hive底层源码对于binary类型字段是支持的,也提供了给定的class对象。
binaryTypeEntry = new PrimitiveObjectInspectorUtils.PrimitiveTypeEntry(PrimitiveCategory.BINARY, "binary", byte[].class, byte[].class, BytesWritable.class);
因此我直接就使用了byte[].class作为了hdfs写入的序列化器。
HdfsHelper:
// orc需要获取对应的序列化器
public List<ObjectInspector> getColumnTypeInspectors(List<Configuration> columns){
...
case BINARY:
objectInspector = ObjectInspectorFactory.getReflectionObjectInspector(byte[].class, ObjectInspectorFactory.ObjectInspectorOptions.JAVA);
break;
...
}
// 字段从channel中读取转换真正类型
public static MutablePair<List<Object>, Boolean> transportOneRecord(
Record record,List<Configuration> columnsConfiguration,
TaskPluginCollector taskPluginCollector){
String rowData = column.getRawData().toString();
case BINARY:
// 这里如果使用 rowData.getBytes() ==> 得到的是 byte数组字符串地址的字节数组
// 即 "[B@74ad1f1f".getBytes(),所以得使用column.asBytes()
recordList.add(column.asBytes());
break;
}
在写入orc类型文件时,在hive中查询没有任何问题
而在text类型的文件查询中时,显示的是byte数组的内存地址
说明在hdfs在写入text文件时存在问题,接下来首先定位问题所在,在接着看如何改造。
HdfsHelper:
// text类型文件最终写入要转换为string类型进行字符串拼接
public static MutablePair<Text, Boolean> transportOneRecord(
Record record, char fieldDelimiter, List<Configuration> columnsConfiguration, TaskPluginCollector taskPluginCollector) {
...
if(null != transportResultList){
// 调用column对象toString进行拼接
Text recordResult = new Text(StringUtils.join(transportResultList.getLeft(), fieldDelimiter));
transportResult.setRight(transportResultList.getRight());
transportResult.setLeft(recordResult);
}
...
}
所以很明显的是,byte[]数组的toString方法肯定是取得内存地址。因此在做值转换的时候我们,不能采用直接用byte[]对象。观察到开头hive源码提供的BytesWritable对象,查看该对象,发现这个对象已经重写了toString方法,所以我们就采用这个对象去做类型转换。
public List<ObjectInspector> getColumnTypeInspectors(List<Configuration> columns){
...
case BINARY:
objectInspector = ObjectInspectorFactory.getReflectionObjectInspector(BytesWritable.class, ObjectInspectorFactory.ObjectInspectorOptions.JAVA);
break;
...
}
public static MutablePair<List<Object>, Boolean> transportOneRecord(
Record record,List<Configuration> columnsConfiguration,
TaskPluginCollector taskPluginCollector){
...
case BINARY:
recordList.add(new BytesWritable(column.asBytes()));
break;
...
}
经过测试,text类型以及orc类型的文件全部写入正常。
到这里肯定会有人和我有同样想法,那既然我已经写入了,我用datax去做读取数据的时候,重写写入Mysql还和原先的值是否能保持一致。因此直接看了下hdfsreader配置,发现不支持binary,统一用string方法去接数据,最终入库的数据,是一串数字+空格组成的数据。
因为hdfs读取string时是不做处理的,直接将值拿过来,说明在hdfs里存的值就是这个,但是hive查出来又是正确的,就很神奇,这里就回想起之前BytesWritable对象的toString方法。
BytesWritable:
/**
* Generate the stream of bytes as hex pairs separated by ' '.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(3*size);
for (int idx = 0; idx < size; idx++) {
// if not the first, put a blank separator in
if (idx != 0) {
sb.append(' ');
}
String num = Integer.toHexString(0xff & bytes[idx]);
// if it is only one digit, add a leading 0.
if (num.length() < 2) {
sb.append('0');
}
sb.append(num);
}
return sb.toString();
}
这就能解释为啥是数字加空格了,他将byte转为16进制数,在通过+空格的方式做转换了。看到这,那就马上想到,hdfsreader中我只要加上binary类型,然后重新将这个字符串转为byte数组问题不就解决了么。
orc类型文件需要改DFSUtil类:
private Record transportOneRecord(List<ColumnEntry> columnConfigs, List<Object> recordFields
, RecordSender recordSender, TaskPluginCollector taskPluginCollector, boolean isReadAllColumns, String nullFormat) {
...
case BINARY:
String [] array = columnValue.split("\\s");
byte[] bytes = new byte[array.length];
for (int i = 0; i<array.length;i++){
bytes[i] = (byte) Integer.parseInt(array[i], 16);
}
columnGenerated = new BytesColumn(bytes);
break;
...
}
text类型文件需要改UnstructuredStorageReaderUtil:
public static Record transportOneRecord(RecordSender recordSender,
List<ColumnEntry> columnConfigs, String[] sourceLine,
String nullFormat, TaskPluginCollector taskPluginCollector) {
...
case BINARY:
String [] array = columnValue.split("\\s");
byte[] bytes = new byte[array.length];
for (int i = 0; i<array.length;i++){
bytes[i] = (byte) Integer.parseInt(array[i], 16);
}
columnGenerated = new BytesColumn(bytes);
break;
...
}
接着,在自己的job文件里将binary字段加上,再进行数据从hive同步到Mysql看结果,无论是orc文件还是text类型文件,结果显示都是正常的: