hadoop中的序列化

weixin_34234823發表於2018-10-29

此文已由作者肖凡授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。



最近在學習hadoop,發現hadoop的序列化過程和jdk的序列化有很大的區別,下面就來說說這兩者的區別都有哪些。


1、先簡單回顧下JAVA的序列化

JDK的序列化只要實現serializable介面OK了,但是有時需要加上序列化版本ID serialVersionUID ,這是為了:在某些場合,希望類的不同版本對序列化相容,因此需要確保類的不同版本具有相同的serialVersionUID;而在另外一些場合,不希望類的不同版本對序列化相容。

java的序列化演算法的過程主要如下:

1) 將物件例項相關的類後設資料輸出。

2) 遞迴地輸出類的超類描述直到不再有超類。

3) 類後設資料完了以後,開始從最頂層的超類開始輸出物件例項的實際資料值。

4) 從上至下遞迴輸出例項的資料

java的序列化很強大,對於複雜的情形,JAVA序列化機制也能應付自如,所以反序列化就so easy。 但是,Java的序列化機制的缺點也是很明顯的,就是計算量開銷大,且序列化的結果體積大太,有時能達到物件大小的數倍乃至十倍。它的引用機制也會導致大檔案不能分割的問題,比如在一個很大的檔案中反序列化某個物件時,需要訪問檔案中前面的某一個後設資料,這將導致整個檔案不能被切割,故而不能通過MapReduce來處理。同時,Java序列化會不斷的建立新的物件,對於MapReduce應用來說,這將會帶來大量的系統開銷。這些缺點使得Java的序列化機制對Hadoop來說是不合適的。於是Hadoop設計了自己的序列化機制。


2、hadoop的序列化過程

對於處理大規模資料的Hadoop平臺,其序列化機制需要具有如下特徵:

1、緊湊:由於頻寬是叢集中資訊傳遞的最寶貴的資源,所以我們必須想法設法縮小傳遞資訊的大小。

2、快速:在程式間通訊時會大量使用序列化機制,因此必須儘量減少序列化和反序列化的開銷。

3、物件可重用:JDK的反序列化會不斷地建立物件,這肯定會造成一定的系統開銷,但是在hadoop的反序列化中,能重複的利用一個物件的readField方法來重新產生不同的物件。     為了支援以上這些特性,hadoo引入了Writeable的介面,作為所有可序列化物件必須實現的介面。Writable機制緊湊、快速,和serializable介面不同,Writable不是一個說明性的介面,它包含兩個方法:

public interface Writable {  /** 
   * 輸出(序列化)物件到流中
   * 
   * @param out DataOuput 流,序列化的結果儲存在流中
   * @throws IOException
   */
  void write(DataOutput out) throws IOException;  /** 
   * 從流中讀取(反序列化)物件
   * 為了效率,請儘可能複用現有的物件
   * @param in DataInput流,從該流中讀取資料
   * @throws IOException
   */
  void readFields(DataInput in) throws IOException;
}

Writable.write()方法用於將物件狀態寫入二進位制的DataOutput中,反序列化的過程由readFields()從DataInput流中讀取狀態完成。下面是一個例子:

public class MyWritable implements Writable {	private Text id;	private Text name;	public MyWritable(Text id, Text name) {		super();		this.id = id;		this.name = name;
	}	public synchronized Text getId() {		return id;
	}	public synchronized void setId(Text id) {		this.id = id;
	}	public synchronized Text getName() {		return name;
	}	public synchronized void setName(Text name) {		this.name = name;
	}	@Override
	public void write(DataOutput out) throws IOException {
		id.write(out);
		name.write(out);
	}	@Override
	public void readFields(DataInput in) throws IOException {
		id.readFields(in);
		name.readFields(in);
	}
}

從上面的例子可以看出,write()和readFields()這兩個方法的實現都很簡單:MyWritable有兩個成員變數,write()方法簡單地把這兩個成員變數寫入流中,而readFields()則從流中依次讀入這些資料。目前Java基本型別對應的Writable封裝如下表所示:              

Java基本型別

Writable

序列化後的長度

boolean

BooleanWritable

1

byte

ByteWritable

1

int

IntWritable

VIntWritable

4

1~5

float

FloatWritable

4

long

LongWritable

VLongWritable

8

1~9

double

DoubleWritable

8

從上表可以看出,對整形(int和long)進行編碼的時候,有固定長度格式(IntWritable和LongWritable)和可變長度格式(VIntWritable和VLongWritable)兩種選擇。固定長度格式的整型,序列化後的資料時定長的,而可變長度格式則使用一種比較靈活的編碼方式,對於數值比較小的整型,它們往往比較節省空間,這對於hadoop尤為重要。


3.Hadoop序列化框架

我們知道,大部分的MapReduce程式都使用Writable鍵-值對作為輸入和輸出,但這並不是Hadoop的API指定的,其它序列化機制也能夠和Hadoop配合使用,目前除了前面提到的JAVA序列化機制和Hadoop使用的Writable機制,還流行其它的序列化框架,如Hadoop Avro、Apache Thrift和Google Protocol Buffer,有興趣的同學可以去了解下。

Hadoop提供了一個簡單的序列化框架API,用於整合各種序列化實現,該框架由Serialization介面實現。通過Serialization可以獲得Serializer和Deserializer,分別用來將一個物件轉換為一個位元組流和將一個位元組流轉化為一個物件,相關程式碼如下:

public interface Serialization {  /**
   * 客戶端用於判斷序列化實現是否支援該類物件
   */
  boolean accept(Class c);  /**
   * 獲得用於序列化物件的Serializer實現
   */
  Serializer getSerializer(Class c);  /**
   * 獲得用於反序列化物件的Deserializer實現
   */
  Deserializer getDeserializer(Class c);
}

如果需要使用Serializer來執行序列化,一般通過open()方法開啟Serializer,open()方法傳入一個流物件,然後就可以使用serialize()方法序列化物件到流中,最後序列化結束後,通過close()方法關閉Serializer,相關程式碼如下:

public interface Serializer {  /**
   * 為輸出物件做準備
   */
  void open(OutputStream out) throws IOException; 
  /**
   * 將物件序列化到底層的流中
   */
  void serialize(T t) throws IOException;  /**
   * 序列化結束,清理
   */  
  void close() throws IOException;
}

Hadoop目前支援兩個Serialization實現,分別是支援Writable機制的WritableSerialization和支援Java序列化的JavaSerialization。通過JavaSerialization可以再MapReduce程式中使用標準的Java型別,但是這種序列化不如Hadoop的序列化機制有效,非特殊情況不要輕易嘗試。


網易雲免費體驗館,0成本體驗20+款雲產品! 

更多網易技術、產品、運營經驗分享請點選




相關文章:
【推薦】 ThreeJs基礎入門

相關文章