關係數據庫裏有表(table),分區,hive裏也有這些東西,這些東西在hive技術裏稱爲hive的數據模型。今天本文介紹hive的數據類型,數據模型以及文件存儲格式。這些知識大家可以類比關係數據庫的相關知識。
首先我要講講hive的數據類型。
Hive支持兩種數據類型,一類叫原子數據類型,一類叫複雜數據類型。
原子數據類型包括數值型、布爾型和字符串類型,具體如下表所示:
基本數據類型 | ||
類型 | 描述 | 示例 |
TINYINT | 1個字節(8位)有符號整數 | 1 |
SMALLINT | 2字節(16位)有符號整數 | 1 |
INT | 4字節(32位)有符號整數 | 1 |
BIGINT | 8字節(64位)有符號整數 | 1 |
FLOAT | 4字節(32位)單精度浮點數 | 1.0 |
DOUBLE | 8字節(64位)雙精度浮點數 | 1.0 |
BOOLEAN | true/false | true |
STRING | 字符串 | ‘xia’,”xia” |
由上表我們看到hive不支持日期類型,在hive裏日期都是用字符串來表示的,而常用的日期格式轉化操作則是通過自定義函數進行操作。
hive是用java開發的,hive裏的基本數據類型和java的基本數據類型也是一一對應的,除了string類型。有符號的整數類型:TINYINT、SMALLINT、INT和BIGINT分別等價於java的byte、short、int和long原子類型,它們分別爲1字節、2字節、4字節和8字節有符號整數。Hive的浮點數據類型FLOAT和DOUBLE,對應於java的基本類型float和double類型。而hive的BOOLEAN類型相當於java的基本數據類型boolean。
對於hive的String類型相當於數據庫的varchar類型,該類型是一個可變的字符串,不過它不能聲明其中最多能存儲多少個字符,理論上它可以存儲2GB的字符數。
Hive支持基本類型的轉換,低字節的基本類型可以轉化爲高字節的類型,例如TINYINT、SMALLINT、INT可以轉化爲FLOAT,而所有的整數類型、FLOAT以及STRING類型可以轉化爲DOUBLE類型,這些轉化可以從java語言的類型轉化考慮,因爲hive就是用java編寫的。當然也支持高字節類型轉化爲低字節類型,這就需要使用hive的自定義函數CAST了。
複雜數據類型包括數組(ARRAY)、映射(MAP)和結構體(STRUCT),具體如下表所示:
複雜數據類型 | ||
類型 | 描述 | 示例 |
ARRAY | 一組有序字段。字段的類型必須相同 | Array(1,2) |
MAP | 一組無序的鍵/值對。鍵的類型必須是原子的,值可以是任何類型,同一個映射的鍵的類型必須相同,值得類型也必須相同 | Map(‘a’,1,’b’,2) |
STRUCT | 一組命名的字段。字段類型可以不同 | Struct(‘a’,1,1,0) |
下面我們看看hive使用複雜數據類型的實例,建表:
Create table complex(col1 ARRAY< INT >, Col2 MAP<STRING, INT >, Col3 STRUCT<a:STRING,b : INT ,c: DOUBLE >); |
查詢語句:
Select col1[0],col2[‘b’],col3.c from complex; |
接下來我們來看看hive的數據模型,hive的數據模型包括:database、table、partition和bucket。下面我將一一論述這四種數據模型。
1.Database:相當於關係數據庫裏的命名空間(namespace),它的作用是將用戶和數據庫的應用隔離到不同的數據庫或模式中,該模型在hive 0.6.0之後的版本支持,hive提供了create database dbname、use dbname以及drop database dbname這樣的語句。
2.表(table):hive的表邏輯上由存儲的數據和描述表格中的數據形式的相關元數據組成。表存儲的數據存放在分佈式文件系統裏,例如HDFS,元數據存儲在關係數據庫裏,當我們創建一張hive的表,還沒有爲表加載數據的時候,該表在分佈式文件系統,例如hdfs上就是一個文件夾(文件目錄)。Hive裏的表友兩種類型一種叫託管表,這種表的數據文件存儲在hive的數據倉庫裏,一種叫外部表,這種表的數據文件可以存放在hive數據倉庫外部的分佈式文件系統上,也可以放到hive數據倉庫裏(注意:hive的數據倉庫也就是hdfs上的一個目錄,這個目錄是hive數據文件存儲的默認路徑,它可以在hive的配置文件裏進行配置,最終也會存放到元數據庫裏)。
下面是創建託管表的實例語句:
Create table tuoguan_tbl (flied string); Load data local inpath ‘home/hadoop/test.txt’ into table tuoguan_tbl; |
外部表創建的實例:
Create external table external_tbl (flied string) Location ‘/home/hadoop/external_table’; Load data local inpath ‘home/hadoop/test.txt’ into table external_tbl; |
大家看到了創建外部表時候table之前要加關鍵字external,同時還要用location命令指定文件存儲的路徑,如果不使用locaction數據文件也會放置到hive的數據倉庫裏。
這兩種表在使用的區別主drop命令上,drop是hive刪除表的命令,託管表執行drop命令的時候,會刪除元數據和存儲的數據,而外部表執行drop命令時候只刪除元數據庫裏的數據,而不會刪除存儲的數據。另外我還要談談表的load命令,hive加載數據時候不會對元數據進行任何檢查,只是簡單的移動文件的位置,如果源文件格式不正確,也只有在做查詢操作時候才能發現,那個時候錯誤格式的字段會以NULL來顯示。
3.分區(partition):hive裏分區的概念是根據“分區列”的值對錶的數據進行粗略劃分的機制,在hive存儲上就體現在表的主目錄(hive的表實際顯示就是一個文件夾)下的一個子目錄,這個文件夾的名字就是我們定義的分區列的名字,沒有實際操作經驗的人可能會認爲分區列是表的某個字段,其實不是這樣,分區列不是表裏的某個字段,而是獨立的列,我們根據這個列存儲表的裏的數據文件。使用分區是爲了加快數據分區的查詢速度而設計的,我們在查詢某個具體分區列裏的數據時候沒必要進行全表掃描。下面我就舉一個分區使用的實例:
創建分區:
Create table logs(ts bigint ,line string) Partitioned by (dt string,country string); |
加載數據:
Local data local inpath ‘/home/hadoop/par/file01.txt’ into table logs partition (dt=’2012-06-02’,country=’cn’); |
在hive數據倉庫裏實際存儲的路徑如下所示:
/ user /hive/warehouse/logs/dt=2013-06-02/country=cn/file1.txt / user /hive/warehouse/logs/dt=2013-06-02/country=cn/file2.txt / user /hive/warehouse/logs/dt=2013-06-02/country=us/file3.txt / user /hive/warehouse/logs/dt=2013-06-02/country=us/file4.txt |
我們看到在表logs的目錄下有了兩層子目錄dt=2013-06-02和country=cn
查詢操作:
Select ts,dt,line from logs where country=’cn’, |
這個時候我們的查詢操作只會掃描file1.txt和file2.txt文件。
4.桶(bucket):上面的table和partition都是目錄級別的拆分數據,bucket則是對數據源數據文件本身來拆分數據。使用桶的表會將源數據文件按一定規律拆分成多個文件,要使用bucket,我們首先要打開hive對桶的控制,命令如下:
set hive.enforce.bucketing = true |
下面這段文字是我引用博客園裏風生水起的博文:
示例: 建臨時表student_tmp,並導入數據: hive> desc student_tmp; OK id int age int name string stat_date string Time taken: 0.106 seconds hive> select * from student_tmp; OK 1 20 zxm 20120801 2 21 ljz 20120801 3 19 cds 20120801 4 18 mac 20120801 5 22 android 20120801 6 23 symbian 20120801 7 25 wp 20120801 Time taken: 0.123 seconds 建student表: hive> create table student(id INT , age INT , name STRING) >partitioned by (stat_date STRING) >clustered by (id) sorted by (age) into 2 bucket >row format delimited fields terminated by ',' ; 設置環境變量: > set hive.enforce.bucketing = true ; 插入數據: > from student_tmp > insert overwrite table student partition(stat_date= "20120802" ) > select id,age, name where stat_date= "20120801" sort by age; 查看文件目錄: $ hadoop fs -ls / user /hive/warehouse/studentstat_date=20120802/ Found 2 items -rw-r --r-- 1 work supergroup 31 2012-07-31 19:52 /user/hive/warehouse/student/stat_date=20120802/000000_0 -rw-r --r-- 1 work supergroup 39 2012-07-31 19:52 /user/hive/warehouse/student/stat_date=20120802/000001_0 |
物理上,每個桶就是表(或分區)目錄裏的一個文件,桶文件是按指定字段值進行hash,然後除以桶的個數例如上面例子2,最後去結果餘數,因爲整數的hash值就是整數本身,上面例子裏,字段hash後的值還是字段本身,所以2的餘數只有兩個0和1,所以我們看到產生文件的後綴是*0_0和*1_0,文件裏存儲對應計算出來的元數據。
Hive的桶,我個人認爲沒有特別的場景或者是特別的查詢,我們可以沒有必要使用,也就是不用開啓hive的桶的配置。因爲桶運用的場景有限,一個是做map連接的運算,我在後面的文章裏會講到,一個就是取樣操作了,下面還是引用風生水起博文裏的例子:
查看sampling數據: hive> select * from student tablesample(bucket 1 out of 2 on id); Total MapReduce jobs = 1 Launching Job 1 out of 1 ....... OK 4 18 mac 20120802 2 21 ljz 20120802 6 23 symbian 20120802 Time taken: 20.608 seconds tablesample是抽樣語句,語法:TABLESAMPLE(BUCKET x OUT OF y) y必須是 table 總bucket數的倍數或者因子。hive根據y的大小,決定抽樣的比例。例如, table 總共分了64份,當y=32時,抽取 (64/32=)2個bucket的數據,當y=128時,抽取(64/128=)1/2個bucket的數據。x表示從哪個bucket開始抽取。例 如, table 總bucket數爲32,tablesample(bucket 3 out of 16),表示總共抽取(32/16=)2個bucket的數據,分別爲第3個bucket和第(3+16=)19個bucket的數據。 |
好了,今天就寫到這裏了,明天要上班不能在加班寫文章了。這篇博文的內容並沒有寫完(hive存儲格式沒有寫),因爲這個章節的知識非常重要是理解hive的關鍵,所以要講的細點,明天我爭取寫完hive存儲格式的文章,後天也就是本週二,我將爲我們技術部門介紹hive的相關技術,寫博文算是我的預演了。
最後我要講一下自己對大數據技術的看法,我覺得大數據技術是一個跨時代的技術,是互聯網技術的未來,也是雲計算的未來,它的深入發展不僅僅是數據處理上,也會改變整個互聯網技術的生態鏈,包括我們使用的技術和開發語言,很慶幸親身經歷着整個偉大時代的變革,我也要展開雙臂迎接這個大時代的到來。
Hive 的數據存儲
首先,Hive 沒有專門的數據存儲格式,也沒有爲數據建立索引,用戶可以非常自由的組織 Hive 中的表,只需要在創建表的時候告訴 Hive 數據中的列分隔符和行分隔符,Hive 就可以解析數據。
其次,Hive 中所有的數據都存儲在 HDFS 中,Hive 中包含以下數據模型:Table,External Table,Partition,Bucket。
Hive 中的 Table 和數據庫中的 Table 在概念上是類似的,每一個 Table 在 Hive 中都有一個相應的目錄存儲數據。例如,一個表 pvs,它在 HDFS 中的路徑爲:/wh/pvs,其中,wh 是在 hive-site.xml 中由 ${hive.metastore.warehouse.dir} 指定的數據倉庫的目錄,所有的 Table 數據(不包括 External Table)都保存在這個目錄中。
Partition 對應於數據庫中的 Partition 列的密集索引,但是 Hive 中 Partition 的組織方式和數據庫中的很不相同。在 Hive 中,表中的一個 Partition 對應於表下的一個目錄,所有的 Partition 的數據都存儲在對應的目錄中。例如:pvs 表中包含 ds 和 city 兩個 Partition,則對應於 ds = 20090801, ctry = US 的 HDFS 子目錄爲:/wh/pvs/ds=20090801/ctry=US;對應於 ds = 20090801, ctry = CA 的 HDFS 子目錄爲;/wh/pvs/ds=20090801/ctry=CA
Buckets 對指定列計算 hash,根據 hash 值切分數據,目的是爲了並行,每一個 Bucket 對應一個文件。將 user 列分散至 32 個 bucket,首先對 user 列的值計算 hash,對應 hash 值爲 0 的 HDFS 目錄爲:/wh/pvs/ds=20090801/ctry=US/part-00000;hash 值爲 20 的 HDFS 目錄爲:/wh/pvs/ds=20090801/ctry=US/part-00020
External Table 指向已經在 HDFS 中存在的數據,可以創建 Partition。它和 Table 在元數據的組織上是相同的,而實際數據的存儲則有較大的差異。
Table 的創建過程和數據加載過程(這兩個過程可以在同一個語句中完成),在加載數據的過程中,實際數據會被移動到數據倉庫目錄中;之後對數據對訪問將會直接在數據倉庫目錄中完成。刪除表時,表中的數據和元數據將會被同時刪除。
External Table 只有一個過程,加載數據和創建表同時完成(CREATE EXTERNAL TABLE ……LOCATION),實際數據是存儲在 LOCATION 後面指定的 HDFS 路徑中,並不會移動到數據倉庫目錄中。
////////////////////////////////////////////////////////底層文件格式
hive有textFile,SequenceFile,RCFile三種文件格式。
其中textfile爲默認格式,建表時不指定默認爲這個格式,導入數據時會直接把數據文件拷貝到hdfs上不進行處理。
SequenceFile,RCFile格式的表不能直接從本地文件導入數據,數據要先導入到textfile格式的表中,然後再從textfile表中用insert導入到SequenceFile,RCFile表中。
create table zone0000rc(ra int, dec int, mag int) row format delimited fields terminated by '|' stored as rcfile;
load data local inpath '/home/cq/usnoa/zone0000.asc ' into table zone0000tf;
insert overwrite table zone0000rc select * from zone0000tf;(begin a job)
File Format
TextFile | SequenceFIle | RCFFile | |
Data type | Text Only | Text/Binary | Text/Binary |
Internal Storage Order | Row-based | Row-based | Column-based |
Compression | File Based | Block Based | Block Based |
Splitable | YES | YES | YES |
Splitable After Compression | No | YES | YES |
源數據放在test1表中,大小 26413896039 Byte。
創建sequencefile 壓縮表test2,使用insert overwrite table test2 select ...語句將test1數據導入 test2 ,設置配置項:
set hive.exec.compress.output=true;
set mapred.output.compress=true;
set mapred.output.compression.codec=com.hadoop.compression.lzo.LzoCodec;
SET io.seqfile.compression.type=BLOCK;
set io.compression.codecs=com.hadoop.compression.lzo.LzoCodec;
導入耗時:98.528s。另壓縮類型使用默認的record,耗時爲418.936s。
創建rcfile 表test3 ,同樣方式導入test3。
set hive.exec.compress.output=true;
set mapred.output.compress=true;
set mapred.output.compression.codec=com.hadoop.compression.lzo.LzoCodec;
set io.compression.codecs=com.hadoop.compression.lzo.LzoCodec;
導入耗時 253.876s。
以下爲其他統計數據對比:
rows | 類型 | 合併耗時 | 文件數 | 總數據大小 | count(1) | 基於domain、referer求點擊的top100 |
238610458 | 原始數據 | 1134 | 26413896039 | 66.297s | ||
238610458 | seq | 98.528(block) 418.936(record) | 1134 | 32252973826 | 41.578 | 394.949s(讀入數據:32,253,519,280,讀入行數:238610458) |
238610458 | rcfile | 253.876 s | 15 | 3765481781 | 29.318 | 286.588s(讀入數據:1,358,993,讀入行數:238610458 |
因爲原始數據中均是小文件,所以合併後文件數大量減少,但是hive實現的seqfile 處理竟然還是原來的數目。rcfile 使用lzo 壓縮效果明顯,7倍的壓縮比率。查詢數據中讀入數據因爲這裏這涉及小部分數據,所以rcfile的表讀入數據僅是seqfile的4%.而讀入行數一致。
SequeceFile是Hadoop API提供的一種二進制文件支持。這種二進制文件直接將<key, value>對序列化到文件中。一般對小文件可以使用這種文件合併,即將文件名作爲key,文件內容作爲value序列化到大文件中。這種文件格式有以下好處:
1)支持壓縮,且可定製爲基於Record或Block壓縮(Block級壓縮性能較優)
2)本地化任務支持:因爲文件可以被切分,因此MapReduce任務時數據的本地化情況應該是非常好的。
3)難度低:因爲是Hadoop框架提供的API,業務邏輯側的修改比較簡單。
壞處是需要一個合併文件的過程,且合併後的文件將不方便查看。
SequenceFile 是一個由二進制序列化過的key/value的字節流組成的文本存儲文件,它可以在map/reduce過程中的input/output 的format時被使用。在map/reduce過程中,map處理文件的臨時輸出就是使用SequenceFile處理過的。
SequenceFile分別提供了讀、寫、排序的操作類。
SequenceFile的操作中有三種處理方式:
1) 不壓縮數據直接存儲。 //enum.NONE
2) 壓縮value值不壓縮key值存儲的存儲方式。//enum.RECORD
3)key/value值都壓縮的方式存儲。//enum.BLOCK
工作中用到了RcFile來存儲和讀取RcFile格式的文件,記錄下。
RcFile是FaceBook開發的一個集行存儲和列存儲的優點於一身,壓縮比更高,讀取列更快,它在MapReduce環境中大規模數據處理中扮演着重要的角色。
讀取操作:
- job信息:
- Job job = new Job();
- job.setJarByClass(類.class);
- //設定輸入文件爲RcFile格式
- job.setInputFormatClass(RCFileInputFormat.class);
- //普通輸出
- job.setOutputFormatClass(TextOutputFormat.class);
- //設置輸入路徑
- RCFileInputFormat.addInputPath(job, new Path(srcpath));
- //MultipleInputs.addInputPath(job, new Path(srcpath), RCFileInputFormat.class);
- // 輸出
- TextOutputFormat.setOutputPath(job, new Path(respath));
- // 輸出key格式
- job.setOutputKeyClass(Text.class);
- //輸出value格式
- job.setOutputValueClass(NullWritable.class);
- //設置mapper類
- job.setMapperClass(ReadTestMapper.class);
- //這裏沒設置reduce,reduce的操作就是讀Text類型文件,因爲mapper已經給轉換了。
- code = (job.waitForCompletion(true)) ? 0 : 1;
- // mapper 類
- pulic class ReadTestMapper extends Mapper<LongWritable, BytesRefArrayWritable, Text, NullWritable> {
- @Override
- protected void map(LongWritable key, BytesRefArrayWritable value, Context context) throws IOException, InterruptedException {
- // TODO Auto-generated method stub
- Text txt = new Text();
- //因爲RcFile行存儲和列存儲,所以每次進來的一行數據,Value是個列簇,遍歷,輸出。
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < value.size(); i++) {
- BytesRefWritable v = value.get(i);
- txt.set(v.getData(), v.getStart(), v.getLength());
- if(i==value.size()-1){
- sb.append(txt.toString());
- }else{
- sb.append(txt.toString()+"\t");
- }
- }
- context.write(new Text(sb.toString()),NullWritable.get());
- }
- }
- job信息:
- Job job = new Job();
- job.setJarByClass(類.class);
- //設定輸入文件爲RcFile格式
- job.setInputFormatClass(RCFileInputFormat.class);
- //普通輸出
- job.setOutputFormatClass(TextOutputFormat.class);
- //設置輸入路徑
- RCFileInputFormat.addInputPath(job, new Path(srcpath));
- //MultipleInputs.addInputPath(job, new Path(srcpath), RCFileInputFormat.class);
- // 輸出
- TextOutputFormat.setOutputPath(job, new Path(respath));
- // 輸出key格式
- job.setOutputKeyClass(Text.class);
- //輸出value格式
- job.setOutputValueClass(NullWritable.class);
- //設置mapper類
- job.setMapperClass(ReadTestMapper.class);
- //這裏沒設置reduce,reduce的操作就是讀Text類型文件,因爲mapper已經給轉換了。
- code = (job.waitForCompletion(true)) ? 0 : 1;
- // mapper 類
- pulic class ReadTestMapper extends Mapper<LongWritable, BytesRefArrayWritable, Text, NullWritable> {
- @Override
- protected void map(LongWritable key, BytesRefArrayWritable value, Context context) throws IOException, InterruptedException {
- // TODO Auto-generated method stub
- Text txt = new Text();
- //因爲RcFile行存儲和列存儲,所以每次進來的一行數據,Value是個列簇,遍歷,輸出。
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < value.size(); i++) {
- BytesRefWritable v = value.get(i);
- txt.set(v.getData(), v.getStart(), v.getLength());
- if(i==value.size()-1){
- sb.append(txt.toString());
- }else{
- sb.append(txt.toString()+"\t");
- }
- }
- context.write(new Text(sb.toString()),NullWritable.get());
- }
- }
輸出壓縮爲RcFile格式:
- job信息:
- Job job = new Job();
- Configuration conf = job.getConfiguration();
- //設置每行的列簇數
- RCFileOutputFormat.setColumnNumber(conf, 4);
- job.setJarByClass(類.class);
- FileInputFormat.setInputPaths(job, new Path(srcpath));
- RCFileOutputFormat.setOutputPath(job, new Path(respath));
- job.setInputFormatClass(TextInputFormat.class);
- job.setOutputFormatClass(RCFileOutputFormat.class);
- job.setMapOutputKeyClass(LongWritable.class);
- job.setMapOutputValueClass(BytesRefArrayWritable.class);
- job.setMapperClass(OutPutTestMapper.class);
- conf.set("date", line.getOptionValue(DATE));
- //設置壓縮參數
- conf.setBoolean("mapred.output.compress", true);
- conf.set("mapred.output.compression.codec", "org.apache.hadoop.io.compress.GzipCodec");
- code = (job.waitForCompletion(true)) ? 0 : 1;
- mapper類:
- public class OutPutTestMapper extends Mapper<LongWritable, Text, LongWritable, BytesRefArrayWritable> {
- @Override
- public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
- String line = value.toString();
- String day = context.getConfiguration().get("date");
- if (!line.equals("")) {
- String[] lines = line.split(" ", -1);
- if (lines.length > 3) {
- String time_temp = lines[1];
- String times = timeStampDate(time_temp);
- String d = times.substring(0, 10);
- if (day.equals(d)) {
- byte[][] record = {lines[0].getBytes("UTF-8"), lines[1].getBytes("UTF-8"),lines[2].getBytes("UTF-8"), lines[3].getBytes("UTF-8")};
- BytesRefArrayWritable bytes = new BytesRefArrayWritable(record.length);
- for (int i = 0; i < record.length; i++) {
- BytesRefWritable cu = new BytesRefWritable(record[i], 0, record[i].length);
- bytes.set(i, cu);
- }
- context.write(key, bytes);
- }
- }
- }
- }
SequenceFile提供了若干Writer的構造靜態獲取。
//SequenceFile.createWriter();
SequenceFile.Reader使用了橋接模式,可以讀取SequenceFile.Writer中的任何方式的壓縮數據。
三種不同的壓縮方式是共用一個數據頭,流方式的讀取會先讀取頭字節去判斷是哪種方式的壓縮,然後根據壓縮方式去解壓縮並反序列化字節流數據,得到可識別的數據。
流的存儲頭字節格式:
Header:
*字節頭”SEQ”, 後跟一個字節表示版本”SEQ4”,”SEQ6”.//這裏有點忘了 不記得是怎麼處理的了,回頭補上做詳細解釋
*keyClass name
*valueClass name
*compression boolean型的存儲標示壓縮值是否轉變爲keys/values值了
*blockcompression boolean型的存儲標示是否全壓縮的方式轉變爲keys/values值了
*compressor 壓縮處理的類型,比如我用Gzip壓縮的Hadoop提供的是GzipCodec什麼的..
*元數據 這個大家可看可不看的
所有的String類型的寫操作被封裝爲Hadoop的IO API,Text類型writeString()搞定。
未壓縮的和只壓縮values值的方式的字節流頭部是類似的:
*Header
*RecordLength記錄長度
*key Length key值長度
*key 值
*是否壓縮標誌 boolean
*values
剩下的大家可看可不看的,並非這個類中主要的。
///////////////////////文件存儲格式圖
整理了一下網上的幾種Hive文件存儲格式的性能與Hadoop的文件存儲格式。
Hive的三種文件格式:TEXTFILE、SEQUENCEFILE、RCFILE中,TEXTFILE和SEQUENCEFILE的存儲格式都是基於行存儲的,RCFILE是基於行列混合的思想,先按行把數據劃分成N個row group,在row group中對每個列分別進行存儲。另:Hive能支持自定義格式,詳情見:Hive文件存儲格式
下面對這幾種幾個作一個簡單的介紹:
TextFile:
Hive默認格式,數據不做壓縮,磁盤開銷大,數據解析開銷大。
可結合Gzip、Bzip2、Snappy等使用(系統自動檢查,執行查詢時自動解壓),但使用這種方式,hive不會對數據進行切分,從而無法對數據進行並行操作。
SequenceFile:
SequenceFile是Hadoop API 提供的一種二進制文件,它將數據以<key,value>的形式序列化到文件中。這種二進制文件內部使用Hadoop 的標準的Writable 接口實現序列化和反序列化。它與Hadoop API中的MapFile 是互相兼容的。Hive 中的SequenceFile 繼承自Hadoop API 的SequenceFile,不過它的key爲空,使用value 存放實際的值, 這樣是爲了避免MR 在運行map 階段的排序過程。
SequenceFile的文件結構圖:
Header通用頭文件格式:
SEQ | 3BYTE |
Nun | 1byte數字 |
keyClassName | |
ValueClassName | |
compression | (boolean)指明瞭在文件中是否啓用壓縮 |
blockCompression | (boolean,指明是否是block壓縮) |
compression | codec |
Metadata | 文件元數據 |
Sync | 頭文件結束標誌 |
Block-Compressed SequenceFile格式
RCFile
RCFile是Hive推出的一種專門面向列的數據格式。 它遵循“先按列劃分,再垂直劃分”的設計理念。當查詢過程中,針對它並不關心的列時,它會在IO上跳過這些列。需要說明的是,RCFile在map階段從 遠端拷貝仍然是拷貝整個數據塊,並且拷貝到本地目錄後RCFile並不是真正直接跳過不需要的列,並跳到需要讀取的列, 而是通過掃描每一個row group的頭部定義來實現的,但是在整個HDFS Block 級別的頭部並沒有定義每個列從哪個row group起始到哪個row group結束。所以在讀取所有列的情況下,RCFile的性能反而沒有SequenceFile高。
下面介紹行存儲、列存儲(詳細參照:Facebook數據倉庫揭祕:RCFile高效存儲結構)
行存儲
HDFS塊內行存儲的例子:
基於Hadoop系統行存儲結構的優點在於快速數據加載和動態負載的高適應能力,這是因爲行存儲保證了相同記錄的所有域都在同一個集羣節點,即同一個 HDFS塊。不過,行存儲的缺點也是顯而易見的,例如它不能支持快速查詢處理,因爲當查詢僅僅針對多列表中的少數幾列時,它不能跳過不必要的列讀取;此 外,由於混合着不同數據值的列,行存儲不易獲得一個極高的壓縮比,即空間利用率不易大幅提高。
列存儲
HDFS塊內列存儲的例子
在HDFS上按照列組存儲表格的例子。在這個例子中,列A和列B存儲在同一列組,而列C和列D分別存儲在單獨的列組。查詢時列存儲能夠避免讀不必要的列, 並且壓縮一個列中的相似數據能夠達到較高的壓縮比。然而,由於元組重構的較高開銷,它並不能提供基於Hadoop系統的快速查詢處理。列存儲不能保證同一 記錄的所有域都存儲在同一集羣節點,行存儲的例子中,記錄的4個域存儲在位於不同節點的3個HDFS塊中。因此,記錄的重構將導致通過集羣節點網絡的大 量數據傳輸。儘管預先分組後,多個列在一起能夠減少開銷,但是對於高度動態的負載模式,它並不具備很好的適應性。
RCFile結合行存儲查詢的快速和列存儲節省空間的特點:首先,RCFile保證同一行的數據位於同一節點,因此元組重構的開銷很低;其次,像列存儲一樣,RCFile能夠利用列維度的數據壓縮,並且能跳過不必要的列讀取。
HDFS塊內RCFile方式存儲的例子:
數據測試
第一步:創建三種文件類型的表,建表語法參考Hive文件存儲格式
- --TextFile
- set hive.exec.compress.output=true;
- set mapred.output.compress=true;
- set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec;
- set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec;
- INSERT OVERWRITE table hzr_test_text_table PARTITION(product='xxx',dt='2013-04-22')
- SELECT xxx,xxx.... FROM xxxtable WHERE product='xxx' AND dt='2013-04-22';
- --SquenceFile
- set hive.exec.compress.output=true;
- set mapred.output.compress=true;
- set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec;
- set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec;
- set io.seqfile.compression.type=BLOCK;
- INSERT OVERWRITE table hzr_test_sequence_table PARTITION(product='xxx',dt='2013-04-22')
- SELECT xxx,xxx.... FROM xxxtable WHERE product='xxx' AND dt='2013-04-22';
- --RCFile
- set hive.exec.compress.output=true;
- set mapred.output.compress=true;
- set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec;
- set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec;
- INSERT OVERWRITE table hzr_test_rcfile_table PARTITION(product='xxx',dt='2013-04-22')
- SELECT xxx,xxx.... FROM xxxtable WHERE product='xxx' AND dt='2013-04-22';
第二步:測試insert overwrite table tablename select.... 耗時,存儲空間
類型 | insert耗時(S) | 存儲空間(G) |
Sequence | 97.291 | 7.13G |
RCFile | 120.901 | 5.73G |
TextFile | 290.517 | 6.80G |
insert耗時、count(1)耗時比較:
第三步:查詢響應時間
測試一
- 方案一,測試整行記錄的查詢效率:
- select * from hzr_test_sequence_table where game='XXX' ;
- select * from hzr_test_rcfile_table where game='XXX' ;
- select * from hzr_test_text_table where game='XXX' ;
- 方案二,測試特定列的查詢效率:
- select game,game_server from hzr_test_sequence_table where game ='XXX';
- select game,game_server from hzr_test_rcfile_table where game ='XXX';
- select game,game_server from hzr_test_text_table where game ='XXX';
文件格式 | 查詢整行記錄耗時(S) | 查詢特定列記錄耗時(S) |
sequence | 42.241 | 39.918 |
rcfile | 37.395 | 36.248 |
text | 43.164 | 41.632 |
方案耗時對比:
測試二:
本測試目的是驗證RCFILE的數據讀取方式和Lazy解壓方式是否有性能優勢。數據讀取方式只讀取元數據和相關的列,節省IO;Lazy解壓方式只解壓相關的列數據,對不滿足where條件的查詢數據不進行解壓,IO和效率都有優勢。
方案一:
記錄數:698020
- insert overwrite local directory 'XXX/XXXX' select game,game_server from hzr_test_xxx_table where game ='XXX';
方案二:
記錄數:67236221
- insert overwrite local directory 'xxx/xxxx' select game,game_server from hzr_test_xxx_table;
方案三:
記錄數:
- insert overwrite local directory 'xxx/xxx'
- select game from hzr_xxx_rcfile_table;
文件類型 | 方案一 | 方案二 | 方案三 |
TextFile | 54.895 | 69.428 | 167.667 |
SequenceFile | 137.096 | 77.03 | 123.667 |
RCFile | 44.28 | 57.037 | 89.9 |
上圖表現反應在大小數據集上,RCFILE的查詢效率高於SEQUENCEFILE,在特定字段數據讀取時,RCFILE的查詢效率依然優於SEQUENCEFILE。