序列與反序列的元件以及常用方法
序列與反序列
例如:TCP/IP協議是一個四層協議,而OSI模型卻是七層協議模型。在OSI七層協議模型中展現層 (Presentation Layer)的主要功能是把應用層的物件轉換成一段連續的二進位制串,或者反過來,把二進位制串轉換成應用層的物件--這兩個功能就是序列化和反序列化。一般 而言,TCP/IP協議的應用層對應與OSI七層協議模型的應用層,展示層和會話層,所以序列化協議屬於TCP/IP協議應用層的一部分。本文對序列化協 議的講解主要基於OSI七層協議模型。
· 序列化: 將資料結構或物件轉換成二進位制串的過程
· 反序列化:將在序列化過程中所生成的二進位制串轉換成資料結構或者物件的過程
三、序列化和反序列化的元件
典型的序列化和反序列化過程往往需要如下元件:
· IDL(Interface description language)檔案:參與通訊的各方需要對通訊的內容需要做相關的約定(Specifications)。為了建立一個與語言和平臺無關的約定,這個 約定需要採用與具體開發語言、平臺無關的語言來進行描述。這種語言被稱為介面描述語言(IDL),採用IDL撰寫的協議約定稱之為IDL檔案。
· IDL Compiler:IDL檔案中約定的內容為了在各語言和平臺可見,需要有一個編譯器,將IDL檔案轉換成各語言對應的動態庫。
· Stub/Skeleton Lib:負責序列化和反序列化的工作程式碼。
· Client/Server:指的是應用層程式程式碼,他們面對的是IDL所生存的特定語言的class或struct。
· 底層協議棧和網際網路:序列化之後的資料通過底層的傳輸層、網路層、鏈路層以及物理層協議轉換成數字訊號在網際網路中傳遞。
序列化元件與資料庫訪問元件的對比
資料庫訪問對於很多工程師來說相對熟悉,所用到的元件也相對容易理解。下表類比了序列化過程中用到的部分元件和資料庫訪問元件的對應關係,以便於大家更好的把握序列化相關元件的概念。
序列化元件 |
資料庫元件 |
說明 |
IDL |
DDL |
用於建表或者模型的語言 |
DL file |
DB Schema |
表建立檔案或模型檔案 |
Stub/Skeleton lib |
O/R mapping |
將class和Table或者資料模型進行對映 |
四、幾種常見的序列化和反序列化協議
網際網路早期的序列化協議主要有COM和CORBA。
COM主要用於Windows平臺,並沒有真正實現跨平臺,另外COM的序列化的原理利用了編譯器中虛表,使得其學習成本巨大(想一下這個場景, 工程師需要是簡單的序列化協議,但卻要先掌握語言編譯器)。由於序列化的資料與編譯器緊耦合,擴充套件屬性非常麻煩。
CORBA是早期比較好的實現了跨平臺,跨語言的序列化協議。COBRA的主要問題是參與方過多帶來的版本過多,版本之間相容性較差,以及使用 複雜晦澀。這些政治經濟,技術實現以及早期設計不成熟的問題,最終導致COBRA的漸漸消亡。J2SE 1.3之後的版本提供了基於CORBA協議的RMI-IIOP技術,這使得Java開發者可以採用純粹的Java語言進行CORBA的開發。
這裡主要介紹和對比幾種當下比較流行的序列化協議,包括XML、JSON、Protobuf、Thrift和Avro。
一個例子
如前所述,序列化和反序列化的出現往往晦澀而隱蔽,與其他概念之間往往相互包容。為了更好了讓大家理解序列化和反序列化的相關概念在每種協議裡 面的具體實現,我們將一個例子穿插在各種序列化協議講解中。在該例子中,我們希望將一個使用者資訊在多個系統裡面進行傳遞;在應用層,如果採用Java語 言,所面對的類物件如下所示:
class Address
{
private String city;
private String postcode;
private String street;
}public class UserInfo
{
private Integer userid;
private String name;
private List<Address> address;
}
XML&SOAP
XML是一種常用的序列化和反序列化協議,具有跨機器,跨語言等優點。 XML的最初產生目標是對網際網路文件(Document)進行標記,所以它的 設計理念中就包含了對於人和機器都具備可讀性。 但是,當這種標記文件的設計被用來序列化物件的時候,就顯得冗長而複雜(Verbose and Complex)。 XML本質上是一種描述語言,並且具有自我描述(Self-describing)的屬性,所以XML自身就被用於XML序列化的IDL。 標準的XML描述格式有兩種:DTD(Document Type Definition)和XSD(XML Schema Definition)。
SOAP(Simple Object Access protocol) 是一種被廣泛應用的,基於XML為序列化和反序列化協議的結構化訊息傳遞協議。即Web service。SOAP最常見的使用方式是XML+HTTP。SOAP協議的主要介面描述語言(IDL)是 WSDL(Web Service Description Language)。SOAP具有安全、可擴充套件、跨語言、跨平臺並支援多種傳輸層協議。
自我描述與遞迴
IDL檔案舉例
採用WSDL描述上述使用者基本資訊的例子如下:
<xsd:complexType name='Address'>
<xsd:attribute name='city' type='xsd:string' />
<xsd:attribute name='postcode' type='xsd:string' />
<xsd:attribute name='street' type='xsd:string' />
</xsd:complexType>
<xsd:complexType name='UserInfo'>
<xsd:sequence>
<xsd:element name='address' type='tns:Address'/>
<xsd:element name='address1' type='tns:Address'/>
</xsd:sequence>
<xsd:attribute name='userid' type='xsd:int' />
<xsd:attribute name='name' type='xsd:string' />
</xsd:complexType>
典型應用場景和非應用場景
SOAP協議具有廣泛的群眾基礎,基於HTTP的傳輸協議使得其在穿越防火牆時具有良好安全特性,XML所具有的人眼可讀(Human- readable)特性使得其具有出眾的可除錯性,網際網路頻寬的日益劇增也大大彌補了其空間開銷大(Verbose)的缺點。對於在公司之間傳輸資料量相 對小或者實時性要求相對低(例如秒級別)的服務是一個好的選擇。
JSON(Javascript Object Notation)
JSON起源於弱型別語言Javascript, 它的產生來自於一種稱之為"Associative array"的概念,其本質是就是採用"Attribute-value"的方式來描述物件。實際上在Javascript和PHP等弱型別語言中,類的 描述方式就是Associative array。JSON的如下優點,使得它快速成為最廣泛使用的序列化協議之一:
看到了嗎?區分大小寫,需要實現申明型別外,一個重要的區別是,弱型別的語言的東西沒有明顯的型別,他能隨著環境的不同,自動變換型別而強型別則沒這樣的規定,不同型別間的操作有嚴格定義,只有相同型別的變數才能操作,雖然系統也有一定的預設轉換,當絕沒有弱型別那麼隨便
XML所產生序列化之後檔案的大小接近JSON的兩倍。
它具備Javascript的先天性支援,所以被廣泛應用於Web browser的應用常景中,是Ajax的事實標準協議。
5、與XML相比,其協議比較簡單,解析速度比較快。
6、鬆散的Associative array使得其具有良好的可擴充套件性和相容性。
IDL悖論
JSON實在是太簡單了,或者說太像各種語言裡面的類了,所以採用JSON進行序列化不需要IDL。這實在是太神奇了,存在一種天然的序列化協議,自身就實現了跨語言和跨平臺。然而事實沒有那麼神奇,之所以產生這種假象,來自於兩個原因:
第一、Associative array在弱型別語言裡面就是類的概念,在PHP和Javascript裡面Associative
array就是其class的實際實現方式,所以在這些弱型別語言裡面,JSON得到了非常良好的支援。
第二、IDL的目的是撰寫IDL檔案,而IDL檔案被IDL
Compiler編譯後能夠產生一些程式碼(Stub/Skeleton),而這些程式碼是真正負責相應的序列化和反序列化工作的元件。 但是由於Associative array和一般語言裡面的class太像了,他們之間形成了一一對應關係,這就使得我們可以採用一套標準的程式碼進行相應的轉化。對於自身支援
Associative array的弱型別語言,語言自身就具備操作JSON序列化後的資料的能力;對於Java這強型別語言,可以採用反射的方式統一解決,例如Google提
供的Gson。
典型應用場景和非應用場景
JSON在很多應用場景中可以替代XML,更簡潔並且解析速度更快。典型應用場景包括:
1、公司之間傳輸資料量相對小,實時性要求相對低(例如秒級別)的服務。
2、基於Web browser的Ajax請求。
3、由於JSON具有非常強的前後相容性,對於介面經常發生變化,並對可調式性要求高的場景,例如Mobile app與服務端的通訊。
4、由於JSON的典型應用場景是JSON+HTTP,適合跨防火牆訪問。
總的來說,採用JSON進行序列化的額外空間開銷比較大,對於大資料量服務或持久化,這意味著巨大的記憶體和磁碟開銷,這種場景不適合。沒有統一 可用的IDL降低了對參與方的約束,實際操作中往往只能採用文件方式來進行約定,這可能會給除錯帶來一些不便,延長開發週期。 由於JSON在一些語言中的序列化和反序列化需要採用反射機制,所以在效能要求為ms級別,不建議使用。
IDL檔案舉例
以下是UserInfo序列化之後的一個例子:
{"userid":1,"name":"messi","address":[{"city":"北京","postcode":"1000000","street":"wangjingdonglu"}]}
Thrift
Thrift是Facebook開源提供的一個高效能,輕量級RPC服務框架,其產生正是為了滿足當前大資料量、分散式、跨語言、跨平臺資料通 訊的需求。 但是,Thrift並不僅僅是序列化協議,而是一個RPC框架。相對於JSON和XML而言,Thrift在空間開銷和解析效能上有了比較大的提升,對於 對效能要求比較高的分散式系統,它是一個優秀的RPC解決方案;但是由於Thrift的序列化被嵌入到Thrift框架裡面,Thrift框架本身並沒有 透出序列化和反序列化介面,這導致其很難和其他傳輸層協議共同使用(例如HTTP)。
典型應用場景和非應用場景
對於需求為高效能,分散式的RPC服務,Thrift是一個優秀的解決方案。它支援眾多語言和豐富的資料型別,並對於資料欄位的增刪具有較強的相容性。所以非常適用於作為公司內部的面向服務構建(SOA)的標準RPC框架。
不過Thrift的文件相對比較缺乏,目前使用的群眾基礎相對較少。另外由於其Server是基於自身的Socket服務,所以在跨防火牆訪問 時,安全是一個顧慮,所以在公司間進行通訊時需要謹慎。 另外Thrift序列化之後的資料是Binary陣列,不具有可讀性,除錯程式碼時相對困難。最後,由於Thrift的序列化和框架緊耦合,無法支援向持久 層直接讀寫資料,所以不適合做資料持久化序列化協議。
IDL檔案舉例
struct Address
{
1: required string city;
2: optional string postcode;
3: optional string street;
}
struct UserInfo
{
1: required string userid;
2: required i32 name;
3: optional list<Address> address;
}
Protobuf
Protobuf具備了優秀的序列化協議的所需的眾多典型特徵:
1、標準的IDL和IDL編譯器,這使得其對工程師非常友好。
2、序列化資料非常簡潔,緊湊,與XML相比,其序列化之後的資料量約為1/3到1/10。
3、解析速度非常快,比對應的XML快約20-100倍。
4、提供了非常友好的動態庫,使用非常簡介,反序列化只需要一行程式碼。
Protobuf是一個純粹的展示層協議,可以和各種傳輸層協議一起使用;Protobuf的文件也非常完善。 但是由於Protobuf產生於Google,所以目前其僅僅支援Java、C++、Python三種語言。另外Protobuf支援的資料型別相對較 少,不支援常量型別。由於其設計的理念是純粹的展現層協議(Presentation Layer),目前並沒有一個專門支援Protobuf的RPC框架。
典型應用場景和非應用場景
Protobuf具有廣泛的使用者基礎,空間開銷小以及高解析效能是其亮點,非常適合於公司內部的對效能要求高的RPC呼叫。由於 Protobuf提供了標準的IDL以及對應的編譯器,其IDL檔案是參與各方的非常強的業務約束,另外,Protobuf與傳輸層無關,採用HTTP具 有良好的跨防火牆的訪問屬性,所以Protobuf也適用於公司間對效能要求比較高的場景。由於其解析效能高,序列化後資料量相對少,非常適合應用層物件 的持久化場景。
它的主要問題在於其所支援的語言相對較少,另外由於沒有繫結的標準底層傳輸層協議,在公司間進行傳輸層協議的除錯工作相對麻煩。
IDL檔案舉例
message Address
{
required string city=1;
optional string postcode=2;
optional string street=3;
}
message UserInfo
{
required string userid=1;
required string name=2;
repeated Address address=3;
}
Avro
Avro的產生解決了JSON的冗長和沒有IDL的問題,Avro屬於Apache Hadoop的一個子專案。 Avro提供兩種序列化格式:JSON格式或者Binary格式。Binary格式在空間開銷和解析效能方面可以和Protobuf媲美,JSON格式方 便測試階段的除錯。 Avro支援的資料型別非常豐富,包括C++語言裡面的union型別。Avro支援JSON格式的IDL和類似於Thrift和Protobuf的 IDL(實驗階段),這兩者之間可以互轉。Schema可以在傳輸資料的同時傳送,加上JSON的自我描述屬性,這使得Avro非常適合動態型別語言。 Avro在做檔案持久化的時候,一般會和Schema一起儲存,所以Avro序列化檔案自身具有自我描述屬性,所以非常適合於做Hive、Pig和 MapReduce的持久化資料格式。對於不同版本的Schema,在進行RPC呼叫的時候,服務端和客戶端可以在握手階段對Schema進行互相確認, 大大提高了最終的資料解析速度。
典型應用場景和非應用場景
Avro解析效能高並且序列化之後的資料非常簡潔,比較適合於高效能的序列化服務。
由於Avro目前非JSON格式的IDL處於實驗階段,而JSON格式的IDL對於習慣於靜態型別語言的工程師來說不直觀。
IDL檔案舉例
protocol Userservice {
record Address {
string city;
string postcode;
string street;
}
record UserInfo {
string name;
int userid;
array<Address> address = [];
}
}
所對應的JSON Schema格式如下:
{
"protocol" : "Userservice",
"namespace" : "org.apache.avro.ipc.specific",
"version" : "1.0.5",
"types" : [ {
"type" : "record",
"name" : "Address",
"fields" : [ {
"name" : "city",
"type" : "string"
}, {
"name" : "postcode",
"type" : "string"
}, {
"name" : "street",
"type" : "string"
} ]
}, {
"type" : "record",
"name" : "UserInfo",
"fields" : [ {
"name" : "name",
"type" : "string"
}, {
"name" : "userid",
"type" : "int"
}, {
"name" : "address",
"type" : {
"type" : "array",
"items" : "Address"
},
"default" : [ ]
} ]
} ],
"messages" : { }
}
五、Benchmark以及選型建議
Benchmark
解析效能
序列化之空間開銷
從上圖可得出如下結論:
1、XML序列化(Xstream)無論在效能和簡潔性上比較差。
2、Thrift與Protobuf相比在時空開銷方面都有一定的劣勢。
3、Protobuf和Avro在兩方面表現都非常優越。
選型建議
以上描述的五種序列化和反序列化協議都各自具有相應的特點,適用於不同的場景:
1、對於公司間的系統呼叫,如果效能要求在100ms以上的服務,基於XML的SOAP協議是一個值得考慮的方案。 2、基於Web browser的Ajax,以及Mobile
app與服務端之間的通訊,JSON協議是首選。對於效能要求不太高,或者以動態型別語言為主,或者傳輸資料載荷很小的的運用場景,JSON也是非常不錯的選擇。 3、對於除錯環境比較惡劣的場景,採用JSON或XML能夠極大的提高除錯效率,降低系統開發成本。 4、當對效能和簡潔性有極高要求的場景,Protobuf,Thrift,Avro之間具有一定的競爭關係。 5、對於T級別的資料的持久化應用場景,Protobuf和Avro是首要選擇。如果持久化後的資料儲存在Hadoop子專案裡,Avro會是更好的選擇。 6、由於Avro的設計理念偏向於動態型別語言,對於動態語言為主的應用場景,Avro是更好的選擇。 7、對於持久層非Hadoop專案,以靜態型別語言為主的應用場景,Protobuf會更符合靜態型別語言工程師的開發習慣。 8、如果需要提供一個完整的RPC解決方案,Thrift是一個好的選擇。 9、如果序列化之後需要支援不同的傳輸層協議,或者需要跨防火牆訪問的高效能場景,Protobuf可以優先考慮。