Parquet是一種新型列存儲(chǔ)格式,它可以兼容Hadoop生態(tài)圈中大多數(shù)計(jì)算框架(Hadoop乍钻、Spark等)肛循,被多種查詢引擎支持(Hive、Impala银择、Drill等)多糠,并且它是語(yǔ)言和平臺(tái)無關(guān)的。Parquet最初是由Twitter和Cloudera(由于Impala的緣故)合作開發(fā)完成并開源浩考,2015年5月從Apache的孵化器里畢業(yè)成為Apache頂級(jí)項(xiàng)目[1][2]夹孔。
用Java讀寫Parquet格式文件需要以下maven依賴:
<dependency>
<groupId>org.apache.parquet</groupId>
<artifactId>parquet-column</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.parquet</groupId>
<artifactId>parquet-common</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.parquet</groupId>
<artifactId>parquet-encoding</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.parquet</groupId>
<artifactId>parquet-hadoop</artifactId>
<version>1.8.2</version>
</dependency>
下面是寫入parquet文件說明和關(guān)鍵代碼:
Date、Timestamp類型需要保存為int96析孽,否則impala讀取出錯(cuò)搭伤。
int96為12字節(jié),前8字節(jié)表示時(shí)間戳對(duì)應(yīng)當(dāng)天已過去的納秒數(shù)袜瞬,后4字節(jié)表示時(shí)間戳當(dāng)天距離儒略歷起始日已過去的天數(shù)怜俐。
注意前8字節(jié)和后4字節(jié)都是小端字節(jié)序,如果寫入時(shí)使用大端序?qū)?dǎo)致讀取失敗[3][4]邓尤。
// schema定義
...
required int96 timestamp_field;
...
public static byte[] getBytes(int i) {
byte[] bytes=new byte[4];
bytes[0]=(byte)((i >> 24) & 0xFF);
bytes[1]=(byte)((i >> 16) & 0xFF);
bytes[2]=(byte)((i >> 8) & 0xFF);
bytes[3]=(byte)(i & 0xFF);
return bytes;
}
public static byte[] getBytes(long i) {
byte[] bytes=new byte[8];
bytes[0]=(byte)((i >> 56) & 0xFF);
bytes[1]=(byte)((i >> 48) & 0xFF);
bytes[2]=(byte)((i >> 40) & 0xFF);
bytes[3]=(byte)((i >> 32) & 0xFF);
bytes[4]=(byte)((i >> 24) & 0xFF);
bytes[5]=(byte)((i >> 16) & 0xFF);
bytes[6]=(byte)((i >> 8) & 0xFF);
bytes[7]=(byte)(i & 0xFF);
return bytes;
}
// 調(diào)轉(zhuǎn)字節(jié)數(shù)組
public static void flip(byte[] bytes) {
for(int i=0,j=bytes.length-1;i<j;i++,j--) {
byte t=bytes[i];
bytes[i]=bytes[j];
bytes[j]=t;
}
}
// 每天的納秒數(shù)
private static final long NANO_SECONDS_PER_DAY = 86400_000_000_000L;
// 儒略歷起始日(儒略歷的公元前4713年1月1日中午12點(diǎn)拍鲤,在格里歷是公元前4714年11月24日)距離1970-01-01的天數(shù)
private static final long JULIAN_EPOCH_OFFSET_DAYS = 2440588;
// 寫入數(shù)據(jù)贴谎,此處預(yù)先存在一個(gè)Date對(duì)象(可由時(shí)間戳轉(zhuǎn)換得到)
Date date = ...
// 轉(zhuǎn)換成距1970-01-01 00:00:00的納秒數(shù)
long nano = date.getTime() * 1000_000;
// 轉(zhuǎn)換成距儒略歷起始日的天數(shù)
int julianDays = (int) ((nano / NANO_SECONDS_PER_DAY) + JULIAN_EPOCH_OFFSET_DAYS);
byte[] julianDaysBytes = getBytes(julianDays);
flip(julianDaysBytes);
// 當(dāng)前時(shí)間戳距離當(dāng)天已過去的納秒數(shù)
long lastDayNanos = nano % NANO_SECONDS_PER_DAY;
byte[] lastDayNanosBytes = getBytes(lastDayNanos);
flip(lastDayNanosBytes);
byte[] dst = new byte[12];
// 前8字節(jié)表示時(shí)間戳對(duì)應(yīng)當(dāng)天已過去的納秒數(shù)
System.arraycopy(lastDayNanosBytes, 0, dst, 0, 8);
// 后4字節(jié)表示時(shí)間戳當(dāng)天距離儒略歷起始日已過去的天數(shù)
System.arraycopy(julianDaysBytes, 0, dst, 8, 4);
// Group group = factory.newGroup();
group.append("timestamp_field", Binary.fromConstantByteArray(dst));
這樣寫入parquet文件后,Impala將可以正確讀取對(duì)應(yīng)字段的內(nèi)容季稳。
參考:
[1] https://parquet.apache.org/
[2] Parquet格式詳解:https://blog.csdn.net/yu616568/article/details/50993491
[3] NanosecondsToImpalaTimestamp函數(shù):https://github.com/apache/parquet-cpp/blob/master/src/parquet/arrow/writer.h
[4] Github關(guān)于INT96的討論:https://github.com/apache/parquet-format/pull/49